1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* SpacemiT K1 USB 2.0 PHY driver
*
* Copyright (C) 2025 SpacemiT (Hangzhou) Technology Co. Ltd
* Copyright (C) 2025 Ze Huang <huang.ze@linux.dev>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/iopoll.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/usb/of.h>
#define PHY_RST_MODE_CTRL 0x04
#define PHY_PLL_RDY BIT(0)
#define PHY_CLK_CDR_EN BIT(1)
#define PHY_CLK_PLL_EN BIT(2)
#define PHY_CLK_MAC_EN BIT(3)
#define PHY_MAC_RSTN BIT(5)
#define PHY_CDR_RSTN BIT(6)
#define PHY_PLL_RSTN BIT(7)
/*
* hs line state sel (Bit 13):
* - 1 (Default): Internal HS line state is set to 01 when usb_hs_tx_en is valid.
* - 0: Internal HS line state is always driven by usb_hs_lstate.
*
* fs line state sel (Bit 14):
* - 1 (Default): FS line state is determined by the output data
* (usb_fs_datain/b).
* - 0: FS line state is always determined by the input data (dmo/dpo).
*/
#define PHY_HS_LINE_TX_MODE BIT(13)
#define PHY_FS_LINE_TX_MODE BIT(14)
#define PHY_INIT_MODE_BITS (PHY_FS_LINE_TX_MODE | PHY_HS_LINE_TX_MODE)
#define PHY_CLK_ENABLE_BITS (PHY_CLK_PLL_EN | PHY_CLK_CDR_EN | \
PHY_CLK_MAC_EN)
#define PHY_DEASSERT_RST_BITS (PHY_PLL_RSTN | PHY_CDR_RSTN | \
PHY_MAC_RSTN)
#define PHY_TX_HOST_CTRL 0x10
#define PHY_HST_DISC_AUTO_CLR BIT(2) /* autoclear hs host disc when re-connect */
#define PHY_HSTXP_HW_CTRL 0x34
#define PHY_HSTXP_RSTN BIT(2) /* generate reset for clock hstxp */
#define PHY_CLK_HSTXP_EN BIT(3) /* clock hstxp enable */
#define PHY_HSTXP_MODE BIT(4) /* 0: force en_txp to be 1; 1: no force */
#define PHY_PLL_DIV_CFG 0x98
#define PHY_FDIV_FRACT_8_15 GENMASK(7, 0)
#define PHY_FDIV_FRACT_16_19 GENMASK(11, 8)
#define PHY_FDIV_FRACT_20_21 BIT(12) /* fdiv_reg<21>, <20>, bit21 == bit20 */
/*
* freq_sel<1:0>
* if ref clk freq=24.0MHz-->freq_sel<2:0> == 3b'001, then internal divider value == 80
*/
#define PHY_FDIV_FRACT_0_1 GENMASK(14, 13)
/*
* pll divider value selection
* 1: divider value will choose internal default value ,dependent on freq_sel<1:0>
* 0: divider value will be over ride by fdiv_reg<21:0>
*/
#define PHY_DIV_LOCAL_EN BIT(15)
#define PHY_SEL_FREQ_24MHZ 0x01
#define FDIV_REG_MASK (PHY_FDIV_FRACT_20_21 | PHY_FDIV_FRACT_16_19 | \
PHY_FDIV_FRACT_8_15)
#define FDIV_REG_VAL 0x1ec4 /* 0x100 selects 24MHz, rest are default */
#define K1_USB2PHY_RESET_TIME_MS 50
struct spacemit_usb2phy {
struct phy *phy;
struct clk *clk;
struct regmap *regmap_base;
};
static const struct regmap_config phy_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = 0x200,
};
static int spacemit_usb2phy_init(struct phy *phy)
{
struct spacemit_usb2phy *sphy = phy_get_drvdata(phy);
struct regmap *map = sphy->regmap_base;
u32 val;
int ret;
ret = clk_enable(sphy->clk);
if (ret) {
dev_err(&phy->dev, "failed to enable clock\n");
clk_disable(sphy->clk);
return ret;
}
/*
* make sure the usb controller is not under reset process before
* any configuration
*/
usleep_range(150, 200);
/* 24M ref clk */
val = FIELD_PREP(FDIV_REG_MASK, FDIV_REG_VAL) |
FIELD_PREP(PHY_FDIV_FRACT_0_1, PHY_SEL_FREQ_24MHZ) |
PHY_DIV_LOCAL_EN;
regmap_write(map, PHY_PLL_DIV_CFG, val);
ret = regmap_read_poll_timeout(map, PHY_RST_MODE_CTRL, val,
(val & PHY_PLL_RDY),
500, K1_USB2PHY_RESET_TIME_MS * 1000);
if (ret) {
dev_err(&phy->dev, "wait PLLREADY timeout\n");
clk_disable(sphy->clk);
return ret;
}
/* release usb2 phy internal reset and enable clock gating */
val = (PHY_INIT_MODE_BITS | PHY_CLK_ENABLE_BITS | PHY_DEASSERT_RST_BITS);
regmap_write(map, PHY_RST_MODE_CTRL, val);
val = (PHY_HSTXP_RSTN | PHY_CLK_HSTXP_EN | PHY_HSTXP_MODE);
regmap_write(map, PHY_HSTXP_HW_CTRL, val);
/* auto clear host disc */
regmap_update_bits(map, PHY_TX_HOST_CTRL, PHY_HST_DISC_AUTO_CLR,
PHY_HST_DISC_AUTO_CLR);
return 0;
}
static int spacemit_usb2phy_exit(struct phy *phy)
{
struct spacemit_usb2phy *sphy = phy_get_drvdata(phy);
clk_disable(sphy->clk);
return 0;
}
static const struct phy_ops spacemit_usb2phy_ops = {
.init = spacemit_usb2phy_init,
.exit = spacemit_usb2phy_exit,
.owner = THIS_MODULE,
};
static int spacemit_usb2phy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct spacemit_usb2phy *sphy;
void __iomem *base;
sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL);
if (!sphy)
return -ENOMEM;
sphy->clk = devm_clk_get_prepared(&pdev->dev, NULL);
if (IS_ERR(sphy->clk))
return dev_err_probe(dev, PTR_ERR(sphy->clk), "Failed to get clock\n");
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
sphy->regmap_base = devm_regmap_init_mmio(dev, base, &phy_regmap_config);
if (IS_ERR(sphy->regmap_base))
return dev_err_probe(dev, PTR_ERR(sphy->regmap_base), "Failed to init regmap\n");
sphy->phy = devm_phy_create(dev, NULL, &spacemit_usb2phy_ops);
if (IS_ERR(sphy->phy))
return dev_err_probe(dev, PTR_ERR(sphy->phy), "Failed to create phy\n");
phy_set_drvdata(sphy->phy, sphy);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct of_device_id spacemit_usb2phy_dt_match[] = {
{ .compatible = "spacemit,k1-usb2-phy", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, spacemit_usb2phy_dt_match);
static struct platform_driver spacemit_usb2_phy_driver = {
.probe = spacemit_usb2phy_probe,
.driver = {
.name = "spacemit-usb2-phy",
.of_match_table = spacemit_usb2phy_dt_match,
},
};
module_platform_driver(spacemit_usb2_phy_driver);
MODULE_DESCRIPTION("Spacemit USB 2.0 PHY driver");
MODULE_LICENSE("GPL");
|