diff options
Diffstat (limited to 'drivers/phy/phy-qcom-qusb2.c')
-rw-r--r-- | drivers/phy/phy-qcom-qusb2.c | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/drivers/phy/phy-qcom-qusb2.c b/drivers/phy/phy-qcom-qusb2.c new file mode 100644 index 000000000000..6c575244c0fb --- /dev/null +++ b/drivers/phy/phy-qcom-qusb2.c | |||
@@ -0,0 +1,493 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2017, The Linux Foundation. All rights reserved. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License version 2 and | ||
6 | * only version 2 as published by the Free Software Foundation. | ||
7 | * | ||
8 | * This program is distributed in the hope that it will be useful, | ||
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | * GNU General Public License for more details. | ||
12 | */ | ||
13 | |||
14 | #include <linux/clk.h> | ||
15 | #include <linux/delay.h> | ||
16 | #include <linux/err.h> | ||
17 | #include <linux/io.h> | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/mfd/syscon.h> | ||
20 | #include <linux/module.h> | ||
21 | #include <linux/nvmem-consumer.h> | ||
22 | #include <linux/of.h> | ||
23 | #include <linux/of_device.h> | ||
24 | #include <linux/phy/phy.h> | ||
25 | #include <linux/platform_device.h> | ||
26 | #include <linux/regmap.h> | ||
27 | #include <linux/regulator/consumer.h> | ||
28 | #include <linux/reset.h> | ||
29 | #include <linux/slab.h> | ||
30 | |||
31 | #define QUSB2PHY_PLL_TEST 0x04 | ||
32 | #define CLK_REF_SEL BIT(7) | ||
33 | |||
34 | #define QUSB2PHY_PLL_TUNE 0x08 | ||
35 | #define QUSB2PHY_PLL_USER_CTL1 0x0c | ||
36 | #define QUSB2PHY_PLL_USER_CTL2 0x10 | ||
37 | #define QUSB2PHY_PLL_AUTOPGM_CTL1 0x1c | ||
38 | #define QUSB2PHY_PLL_PWR_CTRL 0x18 | ||
39 | |||
40 | #define QUSB2PHY_PLL_STATUS 0x38 | ||
41 | #define PLL_LOCKED BIT(5) | ||
42 | |||
43 | #define QUSB2PHY_PORT_TUNE1 0x80 | ||
44 | #define QUSB2PHY_PORT_TUNE2 0x84 | ||
45 | #define QUSB2PHY_PORT_TUNE3 0x88 | ||
46 | #define QUSB2PHY_PORT_TUNE4 0x8c | ||
47 | #define QUSB2PHY_PORT_TUNE5 0x90 | ||
48 | #define QUSB2PHY_PORT_TEST2 0x9c | ||
49 | |||
50 | #define QUSB2PHY_PORT_POWERDOWN 0xb4 | ||
51 | #define CLAMP_N_EN BIT(5) | ||
52 | #define FREEZIO_N BIT(1) | ||
53 | #define POWER_DOWN BIT(0) | ||
54 | |||
55 | #define QUSB2PHY_REFCLK_ENABLE BIT(0) | ||
56 | |||
57 | #define PHY_CLK_SCHEME_SEL BIT(0) | ||
58 | |||
59 | struct qusb2_phy_init_tbl { | ||
60 | unsigned int offset; | ||
61 | unsigned int val; | ||
62 | }; | ||
63 | |||
64 | #define QUSB2_PHY_INIT_CFG(o, v) \ | ||
65 | { \ | ||
66 | .offset = o, \ | ||
67 | .val = v, \ | ||
68 | } | ||
69 | |||
70 | static const struct qusb2_phy_init_tbl msm8996_init_tbl[] = { | ||
71 | QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE1, 0xf8), | ||
72 | QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE2, 0xb3), | ||
73 | QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE3, 0x83), | ||
74 | QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE4, 0xc0), | ||
75 | QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_TUNE, 0x30), | ||
76 | QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL1, 0x79), | ||
77 | QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL2, 0x21), | ||
78 | QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TEST2, 0x14), | ||
79 | QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_AUTOPGM_CTL1, 0x9f), | ||
80 | QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_PWR_CTRL, 0x00), | ||
81 | }; | ||
82 | |||
83 | struct qusb2_phy_cfg { | ||
84 | const struct qusb2_phy_init_tbl *tbl; | ||
85 | /* number of entries in the table */ | ||
86 | unsigned int tbl_num; | ||
87 | /* offset to PHY_CLK_SCHEME register in TCSR map */ | ||
88 | unsigned int clk_scheme_offset; | ||
89 | }; | ||
90 | |||
91 | static const struct qusb2_phy_cfg msm8996_phy_cfg = { | ||
92 | .tbl = msm8996_init_tbl, | ||
93 | .tbl_num = ARRAY_SIZE(msm8996_init_tbl), | ||
94 | }; | ||
95 | |||
96 | static const char * const qusb2_phy_vreg_names[] = { | ||
97 | "vdda-pll", "vdda-phy-dpdm", | ||
98 | }; | ||
99 | |||
100 | #define QUSB2_NUM_VREGS ARRAY_SIZE(qusb2_phy_vreg_names) | ||
101 | |||
102 | /** | ||
103 | * struct qusb2_phy - structure holding qusb2 phy attributes | ||
104 | * | ||
105 | * @phy: generic phy | ||
106 | * @base: iomapped memory space for qubs2 phy | ||
107 | * | ||
108 | * @cfg_ahb_clk: AHB2PHY interface clock | ||
109 | * @ref_clk: phy reference clock | ||
110 | * @iface_clk: phy interface clock | ||
111 | * @phy_reset: phy reset control | ||
112 | * @vregs: regulator supplies bulk data | ||
113 | * | ||
114 | * @tcsr: TCSR syscon register map | ||
115 | * @cell: nvmem cell containing phy tuning value | ||
116 | * | ||
117 | * @cfg: phy config data | ||
118 | * @has_se_clk_scheme: indicate if PHY has single-ended ref clock scheme | ||
119 | */ | ||
120 | struct qusb2_phy { | ||
121 | struct phy *phy; | ||
122 | void __iomem *base; | ||
123 | |||
124 | struct clk *cfg_ahb_clk; | ||
125 | struct clk *ref_clk; | ||
126 | struct clk *iface_clk; | ||
127 | struct reset_control *phy_reset; | ||
128 | struct regulator_bulk_data vregs[QUSB2_NUM_VREGS]; | ||
129 | |||
130 | struct regmap *tcsr; | ||
131 | struct nvmem_cell *cell; | ||
132 | |||
133 | const struct qusb2_phy_cfg *cfg; | ||
134 | bool has_se_clk_scheme; | ||
135 | }; | ||
136 | |||
137 | static inline void qusb2_setbits(void __iomem *base, u32 offset, u32 val) | ||
138 | { | ||
139 | u32 reg; | ||
140 | |||
141 | reg = readl(base + offset); | ||
142 | reg |= val; | ||
143 | writel(reg, base + offset); | ||
144 | |||
145 | /* Ensure above write is completed */ | ||
146 | readl(base + offset); | ||
147 | } | ||
148 | |||
149 | static inline void qusb2_clrbits(void __iomem *base, u32 offset, u32 val) | ||
150 | { | ||
151 | u32 reg; | ||
152 | |||
153 | reg = readl(base + offset); | ||
154 | reg &= ~val; | ||
155 | writel(reg, base + offset); | ||
156 | |||
157 | /* Ensure above write is completed */ | ||
158 | readl(base + offset); | ||
159 | } | ||
160 | |||
161 | static inline | ||
162 | void qcom_qusb2_phy_configure(void __iomem *base, | ||
163 | const struct qusb2_phy_init_tbl tbl[], int num) | ||
164 | { | ||
165 | int i; | ||
166 | |||
167 | for (i = 0; i < num; i++) | ||
168 | writel(tbl[i].val, base + tbl[i].offset); | ||
169 | } | ||
170 | |||
171 | /* | ||
172 | * Fetches HS Tx tuning value from nvmem and sets the | ||
173 | * QUSB2PHY_PORT_TUNE2 register. | ||
174 | * For error case, skip setting the value and use the default value. | ||
175 | */ | ||
176 | static void qusb2_phy_set_tune2_param(struct qusb2_phy *qphy) | ||
177 | { | ||
178 | struct device *dev = &qphy->phy->dev; | ||
179 | u8 *val; | ||
180 | |||
181 | /* | ||
182 | * Read efuse register having TUNE2 parameter's high nibble. | ||
183 | * If efuse register shows value as 0x0, or if we fail to find | ||
184 | * a valid efuse register settings, then use default value | ||
185 | * as 0xB for high nibble that we have already set while | ||
186 | * configuring phy. | ||
187 | */ | ||
188 | val = nvmem_cell_read(qphy->cell, NULL); | ||
189 | if (IS_ERR(val) || !val[0]) { | ||
190 | dev_dbg(dev, "failed to read a valid hs-tx trim value\n"); | ||
191 | return; | ||
192 | } | ||
193 | |||
194 | /* Fused TUNE2 value is the higher nibble only */ | ||
195 | qusb2_setbits(qphy->base, QUSB2PHY_PORT_TUNE2, val[0] << 0x4); | ||
196 | } | ||
197 | |||
198 | static int qusb2_phy_poweron(struct phy *phy) | ||
199 | { | ||
200 | struct qusb2_phy *qphy = phy_get_drvdata(phy); | ||
201 | int num = ARRAY_SIZE(qphy->vregs); | ||
202 | int ret; | ||
203 | |||
204 | dev_vdbg(&phy->dev, "%s(): Powering-on QUSB2 phy\n", __func__); | ||
205 | |||
206 | /* turn on regulator supplies */ | ||
207 | ret = regulator_bulk_enable(num, qphy->vregs); | ||
208 | if (ret) | ||
209 | return ret; | ||
210 | |||
211 | ret = clk_prepare_enable(qphy->iface_clk); | ||
212 | if (ret) { | ||
213 | dev_err(&phy->dev, "failed to enable iface_clk, %d\n", ret); | ||
214 | regulator_bulk_disable(num, qphy->vregs); | ||
215 | return ret; | ||
216 | } | ||
217 | |||
218 | return 0; | ||
219 | } | ||
220 | |||
221 | static int qusb2_phy_poweroff(struct phy *phy) | ||
222 | { | ||
223 | struct qusb2_phy *qphy = phy_get_drvdata(phy); | ||
224 | |||
225 | clk_disable_unprepare(qphy->iface_clk); | ||
226 | |||
227 | regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs); | ||
228 | |||
229 | return 0; | ||
230 | } | ||
231 | |||
232 | static int qusb2_phy_init(struct phy *phy) | ||
233 | { | ||
234 | struct qusb2_phy *qphy = phy_get_drvdata(phy); | ||
235 | unsigned int val; | ||
236 | unsigned int clk_scheme; | ||
237 | int ret; | ||
238 | |||
239 | dev_vdbg(&phy->dev, "%s(): Initializing QUSB2 phy\n", __func__); | ||
240 | |||
241 | /* enable ahb interface clock to program phy */ | ||
242 | ret = clk_prepare_enable(qphy->cfg_ahb_clk); | ||
243 | if (ret) { | ||
244 | dev_err(&phy->dev, "failed to enable cfg ahb clock, %d\n", ret); | ||
245 | return ret; | ||
246 | } | ||
247 | |||
248 | /* Perform phy reset */ | ||
249 | ret = reset_control_assert(qphy->phy_reset); | ||
250 | if (ret) { | ||
251 | dev_err(&phy->dev, "failed to assert phy_reset, %d\n", ret); | ||
252 | goto disable_ahb_clk; | ||
253 | } | ||
254 | |||
255 | /* 100 us delay to keep PHY in reset mode */ | ||
256 | usleep_range(100, 150); | ||
257 | |||
258 | ret = reset_control_deassert(qphy->phy_reset); | ||
259 | if (ret) { | ||
260 | dev_err(&phy->dev, "failed to de-assert phy_reset, %d\n", ret); | ||
261 | goto disable_ahb_clk; | ||
262 | } | ||
263 | |||
264 | /* Disable the PHY */ | ||
265 | qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, | ||
266 | CLAMP_N_EN | FREEZIO_N | POWER_DOWN); | ||
267 | |||
268 | /* save reset value to override reference clock scheme later */ | ||
269 | val = readl(qphy->base + QUSB2PHY_PLL_TEST); | ||
270 | |||
271 | qcom_qusb2_phy_configure(qphy->base, qphy->cfg->tbl, | ||
272 | qphy->cfg->tbl_num); | ||
273 | |||
274 | /* Set efuse value for tuning the PHY */ | ||
275 | qusb2_phy_set_tune2_param(qphy); | ||
276 | |||
277 | /* Enable the PHY */ | ||
278 | qusb2_clrbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, POWER_DOWN); | ||
279 | |||
280 | /* Required to get phy pll lock successfully */ | ||
281 | usleep_range(150, 160); | ||
282 | |||
283 | /* Default is single-ended clock on msm8996 */ | ||
284 | qphy->has_se_clk_scheme = true; | ||
285 | /* | ||
286 | * read TCSR_PHY_CLK_SCHEME register to check if single-ended | ||
287 | * clock scheme is selected. If yes, then disable differential | ||
288 | * ref_clk and use single-ended clock, otherwise use differential | ||
289 | * ref_clk only. | ||
290 | */ | ||
291 | if (qphy->tcsr) { | ||
292 | ret = regmap_read(qphy->tcsr, qphy->cfg->clk_scheme_offset, | ||
293 | &clk_scheme); | ||
294 | if (ret) { | ||
295 | dev_err(&phy->dev, "failed to read clk scheme reg\n"); | ||
296 | goto assert_phy_reset; | ||
297 | } | ||
298 | |||
299 | /* is it a differential clock scheme ? */ | ||
300 | if (!(clk_scheme & PHY_CLK_SCHEME_SEL)) { | ||
301 | dev_vdbg(&phy->dev, "%s(): select differential clk\n", | ||
302 | __func__); | ||
303 | qphy->has_se_clk_scheme = false; | ||
304 | } else { | ||
305 | dev_vdbg(&phy->dev, "%s(): select single-ended clk\n", | ||
306 | __func__); | ||
307 | } | ||
308 | } | ||
309 | |||
310 | if (!qphy->has_se_clk_scheme) { | ||
311 | val &= ~CLK_REF_SEL; | ||
312 | ret = clk_prepare_enable(qphy->ref_clk); | ||
313 | if (ret) { | ||
314 | dev_err(&phy->dev, "failed to enable ref clk, %d\n", | ||
315 | ret); | ||
316 | goto assert_phy_reset; | ||
317 | } | ||
318 | } else { | ||
319 | val |= CLK_REF_SEL; | ||
320 | } | ||
321 | |||
322 | writel(val, qphy->base + QUSB2PHY_PLL_TEST); | ||
323 | |||
324 | /* ensure above write is through */ | ||
325 | readl(qphy->base + QUSB2PHY_PLL_TEST); | ||
326 | |||
327 | /* Required to get phy pll lock successfully */ | ||
328 | usleep_range(100, 110); | ||
329 | |||
330 | val = readb(qphy->base + QUSB2PHY_PLL_STATUS); | ||
331 | if (!(val & PLL_LOCKED)) { | ||
332 | dev_err(&phy->dev, | ||
333 | "QUSB2PHY pll lock failed: status reg = %x\n", val); | ||
334 | ret = -EBUSY; | ||
335 | goto disable_ref_clk; | ||
336 | } | ||
337 | |||
338 | return 0; | ||
339 | |||
340 | disable_ref_clk: | ||
341 | if (!qphy->has_se_clk_scheme) | ||
342 | clk_disable_unprepare(qphy->ref_clk); | ||
343 | assert_phy_reset: | ||
344 | reset_control_assert(qphy->phy_reset); | ||
345 | disable_ahb_clk: | ||
346 | clk_disable_unprepare(qphy->cfg_ahb_clk); | ||
347 | return ret; | ||
348 | } | ||
349 | |||
350 | static int qusb2_phy_exit(struct phy *phy) | ||
351 | { | ||
352 | struct qusb2_phy *qphy = phy_get_drvdata(phy); | ||
353 | |||
354 | /* Disable the PHY */ | ||
355 | qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, | ||
356 | CLAMP_N_EN | FREEZIO_N | POWER_DOWN); | ||
357 | |||
358 | if (!qphy->has_se_clk_scheme) | ||
359 | clk_disable_unprepare(qphy->ref_clk); | ||
360 | |||
361 | reset_control_assert(qphy->phy_reset); | ||
362 | |||
363 | clk_disable_unprepare(qphy->cfg_ahb_clk); | ||
364 | |||
365 | return 0; | ||
366 | } | ||
367 | |||
368 | static const struct phy_ops qusb2_phy_gen_ops = { | ||
369 | .init = qusb2_phy_init, | ||
370 | .exit = qusb2_phy_exit, | ||
371 | .power_on = qusb2_phy_poweron, | ||
372 | .power_off = qusb2_phy_poweroff, | ||
373 | .owner = THIS_MODULE, | ||
374 | }; | ||
375 | |||
376 | static const struct of_device_id qusb2_phy_of_match_table[] = { | ||
377 | { | ||
378 | .compatible = "qcom,msm8996-qusb2-phy", | ||
379 | .data = &msm8996_phy_cfg, | ||
380 | }, | ||
381 | { }, | ||
382 | }; | ||
383 | MODULE_DEVICE_TABLE(of, qusb2_phy_of_match_table); | ||
384 | |||
385 | static int qusb2_phy_probe(struct platform_device *pdev) | ||
386 | { | ||
387 | struct device *dev = &pdev->dev; | ||
388 | struct qusb2_phy *qphy; | ||
389 | struct phy_provider *phy_provider; | ||
390 | struct phy *generic_phy; | ||
391 | struct resource *res; | ||
392 | int ret, i; | ||
393 | int num; | ||
394 | |||
395 | qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); | ||
396 | if (!qphy) | ||
397 | return -ENOMEM; | ||
398 | |||
399 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
400 | qphy->base = devm_ioremap_resource(dev, res); | ||
401 | if (IS_ERR(qphy->base)) | ||
402 | return PTR_ERR(qphy->base); | ||
403 | |||
404 | qphy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb"); | ||
405 | if (IS_ERR(qphy->cfg_ahb_clk)) { | ||
406 | ret = PTR_ERR(qphy->cfg_ahb_clk); | ||
407 | if (ret != -EPROBE_DEFER) | ||
408 | dev_err(dev, "failed to get cfg ahb clk, %d\n", ret); | ||
409 | return ret; | ||
410 | } | ||
411 | |||
412 | qphy->ref_clk = devm_clk_get(dev, "ref"); | ||
413 | if (IS_ERR(qphy->ref_clk)) { | ||
414 | ret = PTR_ERR(qphy->ref_clk); | ||
415 | if (ret != -EPROBE_DEFER) | ||
416 | dev_err(dev, "failed to get ref clk, %d\n", ret); | ||
417 | return ret; | ||
418 | } | ||
419 | |||
420 | qphy->iface_clk = devm_clk_get(dev, "iface"); | ||
421 | if (IS_ERR(qphy->iface_clk)) { | ||
422 | ret = PTR_ERR(qphy->iface_clk); | ||
423 | if (ret == -EPROBE_DEFER) | ||
424 | return ret; | ||
425 | qphy->iface_clk = NULL; | ||
426 | dev_dbg(dev, "failed to get iface clk, %d\n", ret); | ||
427 | } | ||
428 | |||
429 | qphy->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0); | ||
430 | if (IS_ERR(qphy->phy_reset)) { | ||
431 | dev_err(dev, "failed to get phy core reset\n"); | ||
432 | return PTR_ERR(qphy->phy_reset); | ||
433 | } | ||
434 | |||
435 | num = ARRAY_SIZE(qphy->vregs); | ||
436 | for (i = 0; i < num; i++) | ||
437 | qphy->vregs[i].supply = qusb2_phy_vreg_names[i]; | ||
438 | |||
439 | ret = devm_regulator_bulk_get(dev, num, qphy->vregs); | ||
440 | if (ret) { | ||
441 | dev_err(dev, "failed to get regulator supplies\n"); | ||
442 | return ret; | ||
443 | } | ||
444 | |||
445 | /* Get the specific init parameters of QMP phy */ | ||
446 | qphy->cfg = of_device_get_match_data(dev); | ||
447 | |||
448 | qphy->tcsr = syscon_regmap_lookup_by_phandle(dev->of_node, | ||
449 | "qcom,tcsr-syscon"); | ||
450 | if (IS_ERR(qphy->tcsr)) { | ||
451 | dev_dbg(dev, "failed to lookup TCSR regmap\n"); | ||
452 | qphy->tcsr = NULL; | ||
453 | } | ||
454 | |||
455 | qphy->cell = devm_nvmem_cell_get(dev, NULL); | ||
456 | if (IS_ERR(qphy->cell)) { | ||
457 | if (PTR_ERR(qphy->cell) == -EPROBE_DEFER) | ||
458 | return -EPROBE_DEFER; | ||
459 | qphy->cell = NULL; | ||
460 | dev_dbg(dev, "failed to lookup tune2 hstx trim value\n"); | ||
461 | } | ||
462 | |||
463 | generic_phy = devm_phy_create(dev, NULL, &qusb2_phy_gen_ops); | ||
464 | if (IS_ERR(generic_phy)) { | ||
465 | ret = PTR_ERR(generic_phy); | ||
466 | dev_err(dev, "failed to create phy, %d\n", ret); | ||
467 | return ret; | ||
468 | } | ||
469 | qphy->phy = generic_phy; | ||
470 | |||
471 | dev_set_drvdata(dev, qphy); | ||
472 | phy_set_drvdata(generic_phy, qphy); | ||
473 | |||
474 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); | ||
475 | if (!IS_ERR(phy_provider)) | ||
476 | dev_info(dev, "Registered Qcom-QUSB2 phy\n"); | ||
477 | |||
478 | return PTR_ERR_OR_ZERO(phy_provider); | ||
479 | } | ||
480 | |||
481 | static struct platform_driver qusb2_phy_driver = { | ||
482 | .probe = qusb2_phy_probe, | ||
483 | .driver = { | ||
484 | .name = "qcom-qusb2-phy", | ||
485 | .of_match_table = qusb2_phy_of_match_table, | ||
486 | }, | ||
487 | }; | ||
488 | |||
489 | module_platform_driver(qusb2_phy_driver); | ||
490 | |||
491 | MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>"); | ||
492 | MODULE_DESCRIPTION("Qualcomm QUSB2 PHY driver"); | ||
493 | MODULE_LICENSE("GPL v2"); | ||