1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * phy-uniphier-usb3ss.c - SS-PHY driver for Socionext UniPhier USB3 controller |
4 | * Copyright 2015-2018 Socionext Inc. |
5 | * Author: |
6 | * Kunihiko Hayashi <hayashi.kunihiko@socionext.com> |
7 | * Contributors: |
8 | * Motoya Tanigawa <tanigawa.motoya@socionext.com> |
9 | * Masami Hiramatsu <masami.hiramatsu@linaro.org> |
10 | */ |
11 | |
12 | #include <linux/bitfield.h> |
13 | #include <linux/bitops.h> |
14 | #include <linux/clk.h> |
15 | #include <linux/io.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of.h> |
18 | #include <linux/of_platform.h> |
19 | #include <linux/phy/phy.h> |
20 | #include <linux/platform_device.h> |
21 | #include <linux/regulator/consumer.h> |
22 | #include <linux/reset.h> |
23 | |
24 | #define SSPHY_TESTI 0x0 |
25 | #define TESTI_DAT_MASK GENMASK(13, 6) |
26 | #define TESTI_ADR_MASK GENMASK(5, 1) |
27 | #define TESTI_WR_EN BIT(0) |
28 | |
29 | #define SSPHY_TESTO 0x4 |
30 | #define TESTO_DAT_MASK GENMASK(7, 0) |
31 | |
32 | #define PHY_F(regno, msb, lsb) { (regno), (msb), (lsb) } |
33 | |
34 | #define CDR_CPD_TRIM PHY_F(7, 3, 0) /* RxPLL charge pump current */ |
35 | #define CDR_CPF_TRIM PHY_F(8, 3, 0) /* RxPLL charge pump current 2 */ |
36 | #define TX_PLL_TRIM PHY_F(9, 3, 0) /* TxPLL charge pump current */ |
37 | #define BGAP_TRIM PHY_F(11, 3, 0) /* Bandgap voltage */ |
38 | #define CDR_TRIM PHY_F(13, 6, 5) /* Clock Data Recovery setting */ |
39 | #define VCO_CTRL PHY_F(26, 7, 4) /* VCO control */ |
40 | #define VCOPLL_CTRL PHY_F(27, 2, 0) /* TxPLL VCO tuning */ |
41 | #define VCOPLL_CM PHY_F(28, 1, 0) /* TxPLL voltage */ |
42 | |
43 | #define MAX_PHY_PARAMS 7 |
44 | |
45 | struct uniphier_u3ssphy_param { |
46 | struct { |
47 | int reg_no; |
48 | int msb; |
49 | int lsb; |
50 | } field; |
51 | u8 value; |
52 | }; |
53 | |
54 | struct uniphier_u3ssphy_priv { |
55 | struct device *dev; |
56 | void __iomem *base; |
57 | struct clk *clk, *clk_ext, *clk_parent, *clk_parent_gio; |
58 | struct reset_control *rst, *rst_parent, *rst_parent_gio; |
59 | struct regulator *vbus; |
60 | const struct uniphier_u3ssphy_soc_data *data; |
61 | }; |
62 | |
63 | struct uniphier_u3ssphy_soc_data { |
64 | bool is_legacy; |
65 | int nparams; |
66 | const struct uniphier_u3ssphy_param param[MAX_PHY_PARAMS]; |
67 | }; |
68 | |
69 | static void uniphier_u3ssphy_testio_write(struct uniphier_u3ssphy_priv *priv, |
70 | u32 data) |
71 | { |
72 | /* need to read TESTO twice after accessing TESTI */ |
73 | writel(val: data, addr: priv->base + SSPHY_TESTI); |
74 | readl(addr: priv->base + SSPHY_TESTO); |
75 | readl(addr: priv->base + SSPHY_TESTO); |
76 | } |
77 | |
78 | static void uniphier_u3ssphy_set_param(struct uniphier_u3ssphy_priv *priv, |
79 | const struct uniphier_u3ssphy_param *p) |
80 | { |
81 | u32 val; |
82 | u8 field_mask = GENMASK(p->field.msb, p->field.lsb); |
83 | u8 data; |
84 | |
85 | /* read previous data */ |
86 | val = FIELD_PREP(TESTI_DAT_MASK, 1); |
87 | val |= FIELD_PREP(TESTI_ADR_MASK, p->field.reg_no); |
88 | uniphier_u3ssphy_testio_write(priv, data: val); |
89 | val = readl(addr: priv->base + SSPHY_TESTO) & TESTO_DAT_MASK; |
90 | |
91 | /* update value */ |
92 | val &= ~field_mask; |
93 | data = field_mask & (p->value << p->field.lsb); |
94 | val = FIELD_PREP(TESTI_DAT_MASK, data | val); |
95 | val |= FIELD_PREP(TESTI_ADR_MASK, p->field.reg_no); |
96 | uniphier_u3ssphy_testio_write(priv, data: val); |
97 | uniphier_u3ssphy_testio_write(priv, data: val | TESTI_WR_EN); |
98 | uniphier_u3ssphy_testio_write(priv, data: val); |
99 | |
100 | /* read current data as dummy */ |
101 | val = FIELD_PREP(TESTI_DAT_MASK, 1); |
102 | val |= FIELD_PREP(TESTI_ADR_MASK, p->field.reg_no); |
103 | uniphier_u3ssphy_testio_write(priv, data: val); |
104 | readl(addr: priv->base + SSPHY_TESTO); |
105 | } |
106 | |
107 | static int uniphier_u3ssphy_power_on(struct phy *phy) |
108 | { |
109 | struct uniphier_u3ssphy_priv *priv = phy_get_drvdata(phy); |
110 | int ret; |
111 | |
112 | ret = clk_prepare_enable(clk: priv->clk_ext); |
113 | if (ret) |
114 | return ret; |
115 | |
116 | ret = clk_prepare_enable(clk: priv->clk); |
117 | if (ret) |
118 | goto out_clk_ext_disable; |
119 | |
120 | ret = reset_control_deassert(rstc: priv->rst); |
121 | if (ret) |
122 | goto out_clk_disable; |
123 | |
124 | if (priv->vbus) { |
125 | ret = regulator_enable(regulator: priv->vbus); |
126 | if (ret) |
127 | goto out_rst_assert; |
128 | } |
129 | |
130 | return 0; |
131 | |
132 | out_rst_assert: |
133 | reset_control_assert(rstc: priv->rst); |
134 | out_clk_disable: |
135 | clk_disable_unprepare(clk: priv->clk); |
136 | out_clk_ext_disable: |
137 | clk_disable_unprepare(clk: priv->clk_ext); |
138 | |
139 | return ret; |
140 | } |
141 | |
142 | static int uniphier_u3ssphy_power_off(struct phy *phy) |
143 | { |
144 | struct uniphier_u3ssphy_priv *priv = phy_get_drvdata(phy); |
145 | |
146 | if (priv->vbus) |
147 | regulator_disable(regulator: priv->vbus); |
148 | |
149 | reset_control_assert(rstc: priv->rst); |
150 | clk_disable_unprepare(clk: priv->clk); |
151 | clk_disable_unprepare(clk: priv->clk_ext); |
152 | |
153 | return 0; |
154 | } |
155 | |
156 | static int uniphier_u3ssphy_init(struct phy *phy) |
157 | { |
158 | struct uniphier_u3ssphy_priv *priv = phy_get_drvdata(phy); |
159 | int i, ret; |
160 | |
161 | ret = clk_prepare_enable(clk: priv->clk_parent); |
162 | if (ret) |
163 | return ret; |
164 | |
165 | ret = clk_prepare_enable(clk: priv->clk_parent_gio); |
166 | if (ret) |
167 | goto out_clk_disable; |
168 | |
169 | ret = reset_control_deassert(rstc: priv->rst_parent); |
170 | if (ret) |
171 | goto out_clk_gio_disable; |
172 | |
173 | ret = reset_control_deassert(rstc: priv->rst_parent_gio); |
174 | if (ret) |
175 | goto out_rst_assert; |
176 | |
177 | if (priv->data->is_legacy) |
178 | return 0; |
179 | |
180 | for (i = 0; i < priv->data->nparams; i++) |
181 | uniphier_u3ssphy_set_param(priv, p: &priv->data->param[i]); |
182 | |
183 | return 0; |
184 | |
185 | out_rst_assert: |
186 | reset_control_assert(rstc: priv->rst_parent); |
187 | out_clk_gio_disable: |
188 | clk_disable_unprepare(clk: priv->clk_parent_gio); |
189 | out_clk_disable: |
190 | clk_disable_unprepare(clk: priv->clk_parent); |
191 | |
192 | return ret; |
193 | } |
194 | |
195 | static int uniphier_u3ssphy_exit(struct phy *phy) |
196 | { |
197 | struct uniphier_u3ssphy_priv *priv = phy_get_drvdata(phy); |
198 | |
199 | reset_control_assert(rstc: priv->rst_parent_gio); |
200 | reset_control_assert(rstc: priv->rst_parent); |
201 | clk_disable_unprepare(clk: priv->clk_parent_gio); |
202 | clk_disable_unprepare(clk: priv->clk_parent); |
203 | |
204 | return 0; |
205 | } |
206 | |
207 | static const struct phy_ops uniphier_u3ssphy_ops = { |
208 | .init = uniphier_u3ssphy_init, |
209 | .exit = uniphier_u3ssphy_exit, |
210 | .power_on = uniphier_u3ssphy_power_on, |
211 | .power_off = uniphier_u3ssphy_power_off, |
212 | .owner = THIS_MODULE, |
213 | }; |
214 | |
215 | static int uniphier_u3ssphy_probe(struct platform_device *pdev) |
216 | { |
217 | struct device *dev = &pdev->dev; |
218 | struct uniphier_u3ssphy_priv *priv; |
219 | struct phy_provider *phy_provider; |
220 | struct phy *phy; |
221 | |
222 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
223 | if (!priv) |
224 | return -ENOMEM; |
225 | |
226 | priv->dev = dev; |
227 | priv->data = of_device_get_match_data(dev); |
228 | if (WARN_ON(!priv->data || |
229 | priv->data->nparams > MAX_PHY_PARAMS)) |
230 | return -EINVAL; |
231 | |
232 | priv->base = devm_platform_ioremap_resource(pdev, index: 0); |
233 | if (IS_ERR(ptr: priv->base)) |
234 | return PTR_ERR(ptr: priv->base); |
235 | |
236 | if (!priv->data->is_legacy) { |
237 | priv->clk = devm_clk_get(dev, id: "phy" ); |
238 | if (IS_ERR(ptr: priv->clk)) |
239 | return PTR_ERR(ptr: priv->clk); |
240 | |
241 | priv->clk_ext = devm_clk_get_optional(dev, id: "phy-ext" ); |
242 | if (IS_ERR(ptr: priv->clk_ext)) |
243 | return PTR_ERR(ptr: priv->clk_ext); |
244 | |
245 | priv->rst = devm_reset_control_get_shared(dev, id: "phy" ); |
246 | if (IS_ERR(ptr: priv->rst)) |
247 | return PTR_ERR(ptr: priv->rst); |
248 | } else { |
249 | priv->clk_parent_gio = devm_clk_get(dev, id: "gio" ); |
250 | if (IS_ERR(ptr: priv->clk_parent_gio)) |
251 | return PTR_ERR(ptr: priv->clk_parent_gio); |
252 | |
253 | priv->rst_parent_gio = |
254 | devm_reset_control_get_shared(dev, id: "gio" ); |
255 | if (IS_ERR(ptr: priv->rst_parent_gio)) |
256 | return PTR_ERR(ptr: priv->rst_parent_gio); |
257 | } |
258 | |
259 | priv->clk_parent = devm_clk_get(dev, id: "link" ); |
260 | if (IS_ERR(ptr: priv->clk_parent)) |
261 | return PTR_ERR(ptr: priv->clk_parent); |
262 | |
263 | priv->rst_parent = devm_reset_control_get_shared(dev, id: "link" ); |
264 | if (IS_ERR(ptr: priv->rst_parent)) |
265 | return PTR_ERR(ptr: priv->rst_parent); |
266 | |
267 | priv->vbus = devm_regulator_get_optional(dev, id: "vbus" ); |
268 | if (IS_ERR(ptr: priv->vbus)) { |
269 | if (PTR_ERR(ptr: priv->vbus) == -EPROBE_DEFER) |
270 | return PTR_ERR(ptr: priv->vbus); |
271 | priv->vbus = NULL; |
272 | } |
273 | |
274 | phy = devm_phy_create(dev, node: dev->of_node, ops: &uniphier_u3ssphy_ops); |
275 | if (IS_ERR(ptr: phy)) |
276 | return PTR_ERR(ptr: phy); |
277 | |
278 | phy_set_drvdata(phy, data: priv); |
279 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
280 | |
281 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
282 | } |
283 | |
284 | static const struct uniphier_u3ssphy_soc_data uniphier_pro4_data = { |
285 | .is_legacy = true, |
286 | }; |
287 | |
288 | static const struct uniphier_u3ssphy_soc_data uniphier_pxs2_data = { |
289 | .is_legacy = false, |
290 | .nparams = 7, |
291 | .param = { |
292 | { CDR_CPD_TRIM, 10 }, |
293 | { CDR_CPF_TRIM, 3 }, |
294 | { TX_PLL_TRIM, 5 }, |
295 | { BGAP_TRIM, 9 }, |
296 | { CDR_TRIM, 2 }, |
297 | { VCOPLL_CTRL, 7 }, |
298 | { VCOPLL_CM, 1 }, |
299 | }, |
300 | }; |
301 | |
302 | static const struct uniphier_u3ssphy_soc_data uniphier_ld20_data = { |
303 | .is_legacy = false, |
304 | .nparams = 3, |
305 | .param = { |
306 | { CDR_CPD_TRIM, 6 }, |
307 | { CDR_TRIM, 2 }, |
308 | { VCO_CTRL, 5 }, |
309 | }, |
310 | }; |
311 | |
312 | static const struct of_device_id uniphier_u3ssphy_match[] = { |
313 | { |
314 | .compatible = "socionext,uniphier-pro4-usb3-ssphy" , |
315 | .data = &uniphier_pro4_data, |
316 | }, |
317 | { |
318 | .compatible = "socionext,uniphier-pro5-usb3-ssphy" , |
319 | .data = &uniphier_pro4_data, |
320 | }, |
321 | { |
322 | .compatible = "socionext,uniphier-pxs2-usb3-ssphy" , |
323 | .data = &uniphier_pxs2_data, |
324 | }, |
325 | { |
326 | .compatible = "socionext,uniphier-ld20-usb3-ssphy" , |
327 | .data = &uniphier_ld20_data, |
328 | }, |
329 | { |
330 | .compatible = "socionext,uniphier-pxs3-usb3-ssphy" , |
331 | .data = &uniphier_ld20_data, |
332 | }, |
333 | { |
334 | .compatible = "socionext,uniphier-nx1-usb3-ssphy" , |
335 | .data = &uniphier_ld20_data, |
336 | }, |
337 | { /* sentinel */ } |
338 | }; |
339 | MODULE_DEVICE_TABLE(of, uniphier_u3ssphy_match); |
340 | |
341 | static struct platform_driver uniphier_u3ssphy_driver = { |
342 | .probe = uniphier_u3ssphy_probe, |
343 | .driver = { |
344 | .name = "uniphier-usb3-ssphy" , |
345 | .of_match_table = uniphier_u3ssphy_match, |
346 | }, |
347 | }; |
348 | |
349 | module_platform_driver(uniphier_u3ssphy_driver); |
350 | |
351 | MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>" ); |
352 | MODULE_DESCRIPTION("UniPhier SS-PHY driver for USB3 controller" ); |
353 | MODULE_LICENSE("GPL v2" ); |
354 | |