1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * omap-usb2.c - USB PHY, talking to USB controller on TI SoCs. |
4 | * |
5 | * Copyright (C) 2012-2020 Texas Instruments Incorporated - http://www.ti.com |
6 | * Author: Kishon Vijay Abraham I <kishon@ti.com> |
7 | */ |
8 | |
9 | #include <linux/clk.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/err.h> |
12 | #include <linux/io.h> |
13 | #include <linux/mfd/syscon.h> |
14 | #include <linux/module.h> |
15 | #include <linux/of.h> |
16 | #include <linux/of_platform.h> |
17 | #include <linux/phy/omap_control_phy.h> |
18 | #include <linux/phy/omap_usb.h> |
19 | #include <linux/phy/phy.h> |
20 | #include <linux/platform_device.h> |
21 | #include <linux/pm_runtime.h> |
22 | #include <linux/property.h> |
23 | #include <linux/regmap.h> |
24 | #include <linux/slab.h> |
25 | #include <linux/sys_soc.h> |
26 | #include <linux/usb/phy_companion.h> |
27 | |
28 | #define USB2PHY_ANA_CONFIG1 0x4c |
29 | #define USB2PHY_DISCON_BYP_LATCH BIT(31) |
30 | |
31 | #define USB2PHY_CHRG_DET 0x14 |
32 | #define USB2PHY_CHRG_DET_USE_CHG_DET_REG BIT(29) |
33 | #define USB2PHY_CHRG_DET_DIS_CHG_DET BIT(28) |
34 | |
35 | /* SoC Specific USB2_OTG register definitions */ |
36 | #define AM654_USB2_OTG_PD BIT(8) |
37 | #define AM654_USB2_VBUS_DET_EN BIT(5) |
38 | #define AM654_USB2_VBUSVALID_DET_EN BIT(4) |
39 | |
40 | #define OMAP_DEV_PHY_PD BIT(0) |
41 | #define OMAP_USB2_PHY_PD BIT(28) |
42 | |
43 | #define AM437X_USB2_PHY_PD BIT(0) |
44 | #define AM437X_USB2_OTG_PD BIT(1) |
45 | #define AM437X_USB2_OTGVDET_EN BIT(19) |
46 | #define AM437X_USB2_OTGSESSEND_EN BIT(20) |
47 | |
48 | /* Driver Flags */ |
49 | #define OMAP_USB2_HAS_START_SRP BIT(0) |
50 | #define OMAP_USB2_HAS_SET_VBUS BIT(1) |
51 | #define OMAP_USB2_CALIBRATE_FALSE_DISCONNECT BIT(2) |
52 | #define OMAP_USB2_DISABLE_CHRG_DET BIT(3) |
53 | |
54 | struct omap_usb { |
55 | struct usb_phy phy; |
56 | struct phy_companion *comparator; |
57 | void __iomem *pll_ctrl_base; |
58 | void __iomem *phy_base; |
59 | struct device *dev; |
60 | struct device *control_dev; |
61 | struct clk *wkupclk; |
62 | struct clk *optclk; |
63 | u8 flags; |
64 | struct regmap *syscon_phy_power; /* ctrl. reg. acces */ |
65 | unsigned int power_reg; /* power reg. index within syscon */ |
66 | u32 mask; |
67 | u32 power_on; |
68 | u32 power_off; |
69 | }; |
70 | |
71 | #define phy_to_omapusb(x) container_of((x), struct omap_usb, phy) |
72 | |
73 | struct usb_phy_data { |
74 | const char *label; |
75 | u8 flags; |
76 | u32 mask; |
77 | u32 power_on; |
78 | u32 power_off; |
79 | }; |
80 | |
81 | static inline u32 omap_usb_readl(void __iomem *addr, unsigned int offset) |
82 | { |
83 | return __raw_readl(addr: addr + offset); |
84 | } |
85 | |
86 | static inline void omap_usb_writel(void __iomem *addr, unsigned int offset, |
87 | u32 data) |
88 | { |
89 | __raw_writel(val: data, addr: addr + offset); |
90 | } |
91 | |
92 | /** |
93 | * omap_usb2_set_comparator() - links the comparator present in the system with this phy |
94 | * |
95 | * @comparator: the companion phy(comparator) for this phy |
96 | * |
97 | * The phy companion driver should call this API passing the phy_companion |
98 | * filled with set_vbus and start_srp to be used by usb phy. |
99 | * |
100 | * For use by phy companion driver |
101 | */ |
102 | int omap_usb2_set_comparator(struct phy_companion *comparator) |
103 | { |
104 | struct omap_usb *phy; |
105 | struct usb_phy *x = usb_get_phy(type: USB_PHY_TYPE_USB2); |
106 | |
107 | if (IS_ERR(ptr: x)) |
108 | return -ENODEV; |
109 | |
110 | phy = phy_to_omapusb(x); |
111 | phy->comparator = comparator; |
112 | return 0; |
113 | } |
114 | EXPORT_SYMBOL_GPL(omap_usb2_set_comparator); |
115 | |
116 | static int omap_usb_set_vbus(struct usb_otg *otg, bool enabled) |
117 | { |
118 | struct omap_usb *phy = phy_to_omapusb(otg->usb_phy); |
119 | |
120 | if (!phy->comparator || !phy->comparator->set_vbus) |
121 | return -ENODEV; |
122 | |
123 | return phy->comparator->set_vbus(phy->comparator, enabled); |
124 | } |
125 | |
126 | static int omap_usb_start_srp(struct usb_otg *otg) |
127 | { |
128 | struct omap_usb *phy = phy_to_omapusb(otg->usb_phy); |
129 | |
130 | if (!phy->comparator || !phy->comparator->start_srp) |
131 | return -ENODEV; |
132 | |
133 | return phy->comparator->start_srp(phy->comparator); |
134 | } |
135 | |
136 | static int omap_usb_set_host(struct usb_otg *otg, struct usb_bus *host) |
137 | { |
138 | otg->host = host; |
139 | if (!host) |
140 | otg->state = OTG_STATE_UNDEFINED; |
141 | |
142 | return 0; |
143 | } |
144 | |
145 | static int omap_usb_set_peripheral(struct usb_otg *otg, |
146 | struct usb_gadget *gadget) |
147 | { |
148 | otg->gadget = gadget; |
149 | if (!gadget) |
150 | otg->state = OTG_STATE_UNDEFINED; |
151 | |
152 | return 0; |
153 | } |
154 | |
155 | static int omap_usb_phy_power(struct omap_usb *phy, int on) |
156 | { |
157 | u32 val; |
158 | int ret; |
159 | |
160 | if (!phy->syscon_phy_power) { |
161 | omap_control_phy_power(dev: phy->control_dev, on); |
162 | return 0; |
163 | } |
164 | |
165 | if (on) |
166 | val = phy->power_on; |
167 | else |
168 | val = phy->power_off; |
169 | |
170 | ret = regmap_update_bits(map: phy->syscon_phy_power, reg: phy->power_reg, |
171 | mask: phy->mask, val); |
172 | return ret; |
173 | } |
174 | |
175 | static int omap_usb_power_off(struct phy *x) |
176 | { |
177 | struct omap_usb *phy = phy_get_drvdata(phy: x); |
178 | |
179 | return omap_usb_phy_power(phy, on: false); |
180 | } |
181 | |
182 | static int omap_usb_power_on(struct phy *x) |
183 | { |
184 | struct omap_usb *phy = phy_get_drvdata(phy: x); |
185 | |
186 | return omap_usb_phy_power(phy, on: true); |
187 | } |
188 | |
189 | static int omap_usb2_disable_clocks(struct omap_usb *phy) |
190 | { |
191 | clk_disable_unprepare(clk: phy->wkupclk); |
192 | if (!IS_ERR(ptr: phy->optclk)) |
193 | clk_disable_unprepare(clk: phy->optclk); |
194 | |
195 | return 0; |
196 | } |
197 | |
198 | static int omap_usb2_enable_clocks(struct omap_usb *phy) |
199 | { |
200 | int ret; |
201 | |
202 | ret = clk_prepare_enable(clk: phy->wkupclk); |
203 | if (ret < 0) { |
204 | dev_err(phy->dev, "Failed to enable wkupclk %d\n" , ret); |
205 | goto err0; |
206 | } |
207 | |
208 | if (!IS_ERR(ptr: phy->optclk)) { |
209 | ret = clk_prepare_enable(clk: phy->optclk); |
210 | if (ret < 0) { |
211 | dev_err(phy->dev, "Failed to enable optclk %d\n" , ret); |
212 | goto err1; |
213 | } |
214 | } |
215 | |
216 | return 0; |
217 | |
218 | err1: |
219 | clk_disable_unprepare(clk: phy->wkupclk); |
220 | |
221 | err0: |
222 | return ret; |
223 | } |
224 | |
225 | static int omap_usb_init(struct phy *x) |
226 | { |
227 | struct omap_usb *phy = phy_get_drvdata(phy: x); |
228 | u32 val; |
229 | |
230 | omap_usb2_enable_clocks(phy); |
231 | |
232 | if (phy->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) { |
233 | /* |
234 | * |
235 | * Reduce the sensitivity of internal PHY by enabling the |
236 | * DISCON_BYP_LATCH of the USB2PHY_ANA_CONFIG1 register. This |
237 | * resolves issues with certain devices which can otherwise |
238 | * be prone to false disconnects. |
239 | * |
240 | */ |
241 | val = omap_usb_readl(addr: phy->phy_base, USB2PHY_ANA_CONFIG1); |
242 | val |= USB2PHY_DISCON_BYP_LATCH; |
243 | omap_usb_writel(addr: phy->phy_base, USB2PHY_ANA_CONFIG1, data: val); |
244 | } |
245 | |
246 | if (phy->flags & OMAP_USB2_DISABLE_CHRG_DET) { |
247 | val = omap_usb_readl(addr: phy->phy_base, USB2PHY_CHRG_DET); |
248 | val |= USB2PHY_CHRG_DET_USE_CHG_DET_REG | |
249 | USB2PHY_CHRG_DET_DIS_CHG_DET; |
250 | omap_usb_writel(addr: phy->phy_base, USB2PHY_CHRG_DET, data: val); |
251 | } |
252 | |
253 | return 0; |
254 | } |
255 | |
256 | static int omap_usb_exit(struct phy *x) |
257 | { |
258 | struct omap_usb *phy = phy_get_drvdata(phy: x); |
259 | |
260 | return omap_usb2_disable_clocks(phy); |
261 | } |
262 | |
263 | static const struct phy_ops ops = { |
264 | .init = omap_usb_init, |
265 | .exit = omap_usb_exit, |
266 | .power_on = omap_usb_power_on, |
267 | .power_off = omap_usb_power_off, |
268 | .owner = THIS_MODULE, |
269 | }; |
270 | |
271 | static const struct usb_phy_data omap_usb2_data = { |
272 | .label = "omap_usb2" , |
273 | .flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS, |
274 | .mask = OMAP_DEV_PHY_PD, |
275 | .power_off = OMAP_DEV_PHY_PD, |
276 | }; |
277 | |
278 | static const struct usb_phy_data omap5_usb2_data = { |
279 | .label = "omap5_usb2" , |
280 | .flags = 0, |
281 | .mask = OMAP_DEV_PHY_PD, |
282 | .power_off = OMAP_DEV_PHY_PD, |
283 | }; |
284 | |
285 | static const struct usb_phy_data dra7x_usb2_data = { |
286 | .label = "dra7x_usb2" , |
287 | .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, |
288 | .mask = OMAP_DEV_PHY_PD, |
289 | .power_off = OMAP_DEV_PHY_PD, |
290 | }; |
291 | |
292 | static const struct usb_phy_data dra7x_usb2_phy2_data = { |
293 | .label = "dra7x_usb2_phy2" , |
294 | .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, |
295 | .mask = OMAP_USB2_PHY_PD, |
296 | .power_off = OMAP_USB2_PHY_PD, |
297 | }; |
298 | |
299 | static const struct usb_phy_data am437x_usb2_data = { |
300 | .label = "am437x_usb2" , |
301 | .flags = 0, |
302 | .mask = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD | |
303 | AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN, |
304 | .power_on = AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN, |
305 | .power_off = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD, |
306 | }; |
307 | |
308 | static const struct usb_phy_data am654_usb2_data = { |
309 | .label = "am654_usb2" , |
310 | .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, |
311 | .mask = AM654_USB2_OTG_PD | AM654_USB2_VBUS_DET_EN | |
312 | AM654_USB2_VBUSVALID_DET_EN, |
313 | .power_on = AM654_USB2_VBUS_DET_EN | AM654_USB2_VBUSVALID_DET_EN, |
314 | .power_off = AM654_USB2_OTG_PD, |
315 | }; |
316 | |
317 | static const struct of_device_id omap_usb2_id_table[] = { |
318 | { |
319 | .compatible = "ti,omap-usb2" , |
320 | .data = &omap_usb2_data, |
321 | }, |
322 | { |
323 | .compatible = "ti,omap5-usb2" , |
324 | .data = &omap5_usb2_data, |
325 | }, |
326 | { |
327 | .compatible = "ti,dra7x-usb2" , |
328 | .data = &dra7x_usb2_data, |
329 | }, |
330 | { |
331 | .compatible = "ti,dra7x-usb2-phy2" , |
332 | .data = &dra7x_usb2_phy2_data, |
333 | }, |
334 | { |
335 | .compatible = "ti,am437x-usb2" , |
336 | .data = &am437x_usb2_data, |
337 | }, |
338 | { |
339 | .compatible = "ti,am654-usb2" , |
340 | .data = &am654_usb2_data, |
341 | }, |
342 | {}, |
343 | }; |
344 | MODULE_DEVICE_TABLE(of, omap_usb2_id_table); |
345 | |
346 | static void omap_usb2_init_errata(struct omap_usb *phy) |
347 | { |
348 | static const struct soc_device_attribute am65x_sr10_soc_devices[] = { |
349 | { .family = "AM65X" , .revision = "SR1.0" }, |
350 | { /* sentinel */ } |
351 | }; |
352 | |
353 | /* |
354 | * Errata i2075: USB2PHY: USB2PHY Charger Detect is Enabled by |
355 | * Default Without VBUS Presence. |
356 | * |
357 | * AM654x SR1.0 has a silicon bug due to which D+ is pulled high after |
358 | * POR, which could cause enumeration failure with some USB hubs. |
359 | * Disabling the USB2_PHY Charger Detect function will put D+ |
360 | * into the normal state. |
361 | */ |
362 | if (soc_device_match(matches: am65x_sr10_soc_devices)) |
363 | phy->flags |= OMAP_USB2_DISABLE_CHRG_DET; |
364 | } |
365 | |
366 | static int omap_usb2_probe(struct platform_device *pdev) |
367 | { |
368 | struct omap_usb *phy; |
369 | struct phy *generic_phy; |
370 | struct phy_provider *phy_provider; |
371 | struct usb_otg *otg; |
372 | struct device_node *node = pdev->dev.of_node; |
373 | struct device_node *control_node; |
374 | struct platform_device *control_pdev; |
375 | const struct usb_phy_data *phy_data; |
376 | |
377 | phy_data = device_get_match_data(dev: &pdev->dev); |
378 | if (!phy_data) |
379 | return -EINVAL; |
380 | |
381 | phy = devm_kzalloc(dev: &pdev->dev, size: sizeof(*phy), GFP_KERNEL); |
382 | if (!phy) |
383 | return -ENOMEM; |
384 | |
385 | otg = devm_kzalloc(dev: &pdev->dev, size: sizeof(*otg), GFP_KERNEL); |
386 | if (!otg) |
387 | return -ENOMEM; |
388 | |
389 | phy->dev = &pdev->dev; |
390 | |
391 | phy->phy.dev = phy->dev; |
392 | phy->phy.label = phy_data->label; |
393 | phy->phy.otg = otg; |
394 | phy->phy.type = USB_PHY_TYPE_USB2; |
395 | phy->mask = phy_data->mask; |
396 | phy->power_on = phy_data->power_on; |
397 | phy->power_off = phy_data->power_off; |
398 | phy->flags = phy_data->flags; |
399 | |
400 | omap_usb2_init_errata(phy); |
401 | |
402 | phy->phy_base = devm_platform_ioremap_resource(pdev, index: 0); |
403 | if (IS_ERR(ptr: phy->phy_base)) |
404 | return PTR_ERR(ptr: phy->phy_base); |
405 | |
406 | phy->syscon_phy_power = syscon_regmap_lookup_by_phandle(np: node, |
407 | property: "syscon-phy-power" ); |
408 | if (IS_ERR(ptr: phy->syscon_phy_power)) { |
409 | dev_dbg(&pdev->dev, |
410 | "can't get syscon-phy-power, using control device\n" ); |
411 | phy->syscon_phy_power = NULL; |
412 | |
413 | control_node = of_parse_phandle(np: node, phandle_name: "ctrl-module" , index: 0); |
414 | if (!control_node) { |
415 | dev_err(&pdev->dev, |
416 | "Failed to get control device phandle\n" ); |
417 | return -EINVAL; |
418 | } |
419 | |
420 | control_pdev = of_find_device_by_node(np: control_node); |
421 | if (!control_pdev) { |
422 | dev_err(&pdev->dev, "Failed to get control device\n" ); |
423 | return -EINVAL; |
424 | } |
425 | phy->control_dev = &control_pdev->dev; |
426 | } else { |
427 | if (of_property_read_u32_index(np: node, |
428 | propname: "syscon-phy-power" , index: 1, |
429 | out_value: &phy->power_reg)) { |
430 | dev_err(&pdev->dev, |
431 | "couldn't get power reg. offset\n" ); |
432 | return -EINVAL; |
433 | } |
434 | } |
435 | |
436 | phy->wkupclk = devm_clk_get(dev: phy->dev, id: "wkupclk" ); |
437 | if (IS_ERR(ptr: phy->wkupclk)) { |
438 | if (PTR_ERR(ptr: phy->wkupclk) == -EPROBE_DEFER) |
439 | return -EPROBE_DEFER; |
440 | |
441 | dev_warn(&pdev->dev, "unable to get wkupclk %ld, trying old name\n" , |
442 | PTR_ERR(phy->wkupclk)); |
443 | phy->wkupclk = devm_clk_get(dev: phy->dev, id: "usb_phy_cm_clk32k" ); |
444 | |
445 | if (IS_ERR(ptr: phy->wkupclk)) |
446 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: phy->wkupclk), |
447 | fmt: "unable to get usb_phy_cm_clk32k\n" ); |
448 | |
449 | dev_warn(&pdev->dev, |
450 | "found usb_phy_cm_clk32k, please fix DTS\n" ); |
451 | } |
452 | |
453 | phy->optclk = devm_clk_get(dev: phy->dev, id: "refclk" ); |
454 | if (IS_ERR(ptr: phy->optclk)) { |
455 | if (PTR_ERR(ptr: phy->optclk) == -EPROBE_DEFER) |
456 | return -EPROBE_DEFER; |
457 | |
458 | dev_dbg(&pdev->dev, "unable to get refclk, trying old name\n" ); |
459 | phy->optclk = devm_clk_get(dev: phy->dev, id: "usb_otg_ss_refclk960m" ); |
460 | |
461 | if (IS_ERR(ptr: phy->optclk)) { |
462 | if (PTR_ERR(ptr: phy->optclk) != -EPROBE_DEFER) { |
463 | dev_dbg(&pdev->dev, |
464 | "unable to get usb_otg_ss_refclk960m\n" ); |
465 | } |
466 | } else { |
467 | dev_warn(&pdev->dev, |
468 | "found usb_otg_ss_refclk960m, please fix DTS\n" ); |
469 | } |
470 | } |
471 | |
472 | otg->set_host = omap_usb_set_host; |
473 | otg->set_peripheral = omap_usb_set_peripheral; |
474 | if (phy_data->flags & OMAP_USB2_HAS_SET_VBUS) |
475 | otg->set_vbus = omap_usb_set_vbus; |
476 | if (phy_data->flags & OMAP_USB2_HAS_START_SRP) |
477 | otg->start_srp = omap_usb_start_srp; |
478 | otg->usb_phy = &phy->phy; |
479 | |
480 | platform_set_drvdata(pdev, data: phy); |
481 | pm_runtime_enable(dev: phy->dev); |
482 | |
483 | generic_phy = devm_phy_create(dev: phy->dev, NULL, ops: &ops); |
484 | if (IS_ERR(ptr: generic_phy)) { |
485 | pm_runtime_disable(dev: phy->dev); |
486 | return PTR_ERR(ptr: generic_phy); |
487 | } |
488 | |
489 | phy_set_drvdata(phy: generic_phy, data: phy); |
490 | omap_usb_power_off(x: generic_phy); |
491 | |
492 | phy_provider = devm_of_phy_provider_register(phy->dev, |
493 | of_phy_simple_xlate); |
494 | if (IS_ERR(ptr: phy_provider)) { |
495 | pm_runtime_disable(dev: phy->dev); |
496 | return PTR_ERR(ptr: phy_provider); |
497 | } |
498 | |
499 | usb_add_phy_dev(&phy->phy); |
500 | |
501 | return 0; |
502 | } |
503 | |
504 | static void omap_usb2_remove(struct platform_device *pdev) |
505 | { |
506 | struct omap_usb *phy = platform_get_drvdata(pdev); |
507 | |
508 | usb_remove_phy(&phy->phy); |
509 | pm_runtime_disable(dev: phy->dev); |
510 | } |
511 | |
512 | static struct platform_driver omap_usb2_driver = { |
513 | .probe = omap_usb2_probe, |
514 | .remove_new = omap_usb2_remove, |
515 | .driver = { |
516 | .name = "omap-usb2" , |
517 | .of_match_table = omap_usb2_id_table, |
518 | }, |
519 | }; |
520 | |
521 | module_platform_driver(omap_usb2_driver); |
522 | |
523 | MODULE_ALIAS("platform:omap_usb2" ); |
524 | MODULE_AUTHOR("Texas Instruments Inc." ); |
525 | MODULE_DESCRIPTION("OMAP USB2 phy driver" ); |
526 | MODULE_LICENSE("GPL v2" ); |
527 | |