1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (c) 2018, The Linux Foundation. All rights reserved. |
3 | * |
4 | * Inspired by dwc3-of-simple.c |
5 | */ |
6 | |
7 | #include <linux/io.h> |
8 | #include <linux/of.h> |
9 | #include <linux/clk.h> |
10 | #include <linux/irq.h> |
11 | #include <linux/of_clk.h> |
12 | #include <linux/module.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/extcon.h> |
15 | #include <linux/interconnect.h> |
16 | #include <linux/of_platform.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/phy/phy.h> |
19 | #include <linux/usb/of.h> |
20 | #include <linux/reset.h> |
21 | #include <linux/iopoll.h> |
22 | #include <linux/usb/hcd.h> |
23 | #include <linux/usb.h> |
24 | #include "core.h" |
25 | |
26 | /* USB QSCRATCH Hardware registers */ |
27 | #define QSCRATCH_HS_PHY_CTRL 0x10 |
28 | #define UTMI_OTG_VBUS_VALID BIT(20) |
29 | #define SW_SESSVLD_SEL BIT(28) |
30 | |
31 | #define QSCRATCH_SS_PHY_CTRL 0x30 |
32 | #define LANE0_PWR_PRESENT BIT(24) |
33 | |
34 | #define QSCRATCH_GENERAL_CFG 0x08 |
35 | #define PIPE_UTMI_CLK_SEL BIT(0) |
36 | #define PIPE3_PHYSTATUS_SW BIT(3) |
37 | #define PIPE_UTMI_CLK_DIS BIT(8) |
38 | |
39 | #define PWR_EVNT_IRQ_STAT_REG 0x58 |
40 | #define PWR_EVNT_LPM_IN_L2_MASK BIT(4) |
41 | #define PWR_EVNT_LPM_OUT_L2_MASK BIT(5) |
42 | |
43 | #define SDM845_QSCRATCH_BASE_OFFSET 0xf8800 |
44 | #define SDM845_QSCRATCH_SIZE 0x400 |
45 | #define SDM845_DWC3_CORE_SIZE 0xcd00 |
46 | |
47 | /* Interconnect path bandwidths in MBps */ |
48 | #define USB_MEMORY_AVG_HS_BW MBps_to_icc(240) |
49 | #define USB_MEMORY_PEAK_HS_BW MBps_to_icc(700) |
50 | #define USB_MEMORY_AVG_SS_BW MBps_to_icc(1000) |
51 | #define USB_MEMORY_PEAK_SS_BW MBps_to_icc(2500) |
52 | #define APPS_USB_AVG_BW 0 |
53 | #define APPS_USB_PEAK_BW MBps_to_icc(40) |
54 | |
55 | struct dwc3_qcom { |
56 | struct device *dev; |
57 | void __iomem *qscratch_base; |
58 | struct platform_device *dwc3; |
59 | struct clk **clks; |
60 | int num_clocks; |
61 | struct reset_control *resets; |
62 | |
63 | int qusb2_phy_irq; |
64 | int dp_hs_phy_irq; |
65 | int dm_hs_phy_irq; |
66 | int ss_phy_irq; |
67 | enum usb_device_speed usb2_speed; |
68 | |
69 | struct extcon_dev *edev; |
70 | struct extcon_dev *host_edev; |
71 | struct notifier_block vbus_nb; |
72 | struct notifier_block host_nb; |
73 | |
74 | enum usb_dr_mode mode; |
75 | bool is_suspended; |
76 | bool pm_suspended; |
77 | struct icc_path *icc_path_ddr; |
78 | struct icc_path *icc_path_apps; |
79 | }; |
80 | |
81 | static inline void dwc3_qcom_setbits(void __iomem *base, u32 offset, u32 val) |
82 | { |
83 | u32 reg; |
84 | |
85 | reg = readl(addr: base + offset); |
86 | reg |= val; |
87 | writel(val: reg, addr: base + offset); |
88 | |
89 | /* ensure that above write is through */ |
90 | readl(addr: base + offset); |
91 | } |
92 | |
93 | static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val) |
94 | { |
95 | u32 reg; |
96 | |
97 | reg = readl(addr: base + offset); |
98 | reg &= ~val; |
99 | writel(val: reg, addr: base + offset); |
100 | |
101 | /* ensure that above write is through */ |
102 | readl(addr: base + offset); |
103 | } |
104 | |
105 | static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable) |
106 | { |
107 | if (enable) { |
108 | dwc3_qcom_setbits(base: qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL, |
109 | LANE0_PWR_PRESENT); |
110 | dwc3_qcom_setbits(base: qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL, |
111 | UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL); |
112 | } else { |
113 | dwc3_qcom_clrbits(base: qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL, |
114 | LANE0_PWR_PRESENT); |
115 | dwc3_qcom_clrbits(base: qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL, |
116 | UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL); |
117 | } |
118 | } |
119 | |
120 | static int dwc3_qcom_vbus_notifier(struct notifier_block *nb, |
121 | unsigned long event, void *ptr) |
122 | { |
123 | struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, vbus_nb); |
124 | |
125 | /* enable vbus override for device mode */ |
126 | dwc3_qcom_vbus_override_enable(qcom, enable: event); |
127 | qcom->mode = event ? USB_DR_MODE_PERIPHERAL : USB_DR_MODE_HOST; |
128 | |
129 | return NOTIFY_DONE; |
130 | } |
131 | |
132 | static int dwc3_qcom_host_notifier(struct notifier_block *nb, |
133 | unsigned long event, void *ptr) |
134 | { |
135 | struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, host_nb); |
136 | |
137 | /* disable vbus override in host mode */ |
138 | dwc3_qcom_vbus_override_enable(qcom, enable: !event); |
139 | qcom->mode = event ? USB_DR_MODE_HOST : USB_DR_MODE_PERIPHERAL; |
140 | |
141 | return NOTIFY_DONE; |
142 | } |
143 | |
144 | static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom) |
145 | { |
146 | struct device *dev = qcom->dev; |
147 | struct extcon_dev *host_edev; |
148 | int ret; |
149 | |
150 | if (!of_property_read_bool(np: dev->of_node, propname: "extcon" )) |
151 | return 0; |
152 | |
153 | qcom->edev = extcon_get_edev_by_phandle(dev, index: 0); |
154 | if (IS_ERR(ptr: qcom->edev)) |
155 | return dev_err_probe(dev, err: PTR_ERR(ptr: qcom->edev), |
156 | fmt: "Failed to get extcon\n" ); |
157 | |
158 | qcom->vbus_nb.notifier_call = dwc3_qcom_vbus_notifier; |
159 | |
160 | qcom->host_edev = extcon_get_edev_by_phandle(dev, index: 1); |
161 | if (IS_ERR(ptr: qcom->host_edev)) |
162 | qcom->host_edev = NULL; |
163 | |
164 | ret = devm_extcon_register_notifier(dev, edev: qcom->edev, EXTCON_USB, |
165 | nb: &qcom->vbus_nb); |
166 | if (ret < 0) { |
167 | dev_err(dev, "VBUS notifier register failed\n" ); |
168 | return ret; |
169 | } |
170 | |
171 | if (qcom->host_edev) |
172 | host_edev = qcom->host_edev; |
173 | else |
174 | host_edev = qcom->edev; |
175 | |
176 | qcom->host_nb.notifier_call = dwc3_qcom_host_notifier; |
177 | ret = devm_extcon_register_notifier(dev, edev: host_edev, EXTCON_USB_HOST, |
178 | nb: &qcom->host_nb); |
179 | if (ret < 0) { |
180 | dev_err(dev, "Host notifier register failed\n" ); |
181 | return ret; |
182 | } |
183 | |
184 | /* Update initial VBUS override based on extcon state */ |
185 | if (extcon_get_state(edev: qcom->edev, EXTCON_USB) || |
186 | !extcon_get_state(edev: host_edev, EXTCON_USB_HOST)) |
187 | dwc3_qcom_vbus_notifier(nb: &qcom->vbus_nb, event: true, ptr: qcom->edev); |
188 | else |
189 | dwc3_qcom_vbus_notifier(nb: &qcom->vbus_nb, event: false, ptr: qcom->edev); |
190 | |
191 | return 0; |
192 | } |
193 | |
194 | static int dwc3_qcom_interconnect_enable(struct dwc3_qcom *qcom) |
195 | { |
196 | int ret; |
197 | |
198 | ret = icc_enable(path: qcom->icc_path_ddr); |
199 | if (ret) |
200 | return ret; |
201 | |
202 | ret = icc_enable(path: qcom->icc_path_apps); |
203 | if (ret) |
204 | icc_disable(path: qcom->icc_path_ddr); |
205 | |
206 | return ret; |
207 | } |
208 | |
209 | static int dwc3_qcom_interconnect_disable(struct dwc3_qcom *qcom) |
210 | { |
211 | int ret; |
212 | |
213 | ret = icc_disable(path: qcom->icc_path_ddr); |
214 | if (ret) |
215 | return ret; |
216 | |
217 | ret = icc_disable(path: qcom->icc_path_apps); |
218 | if (ret) |
219 | icc_enable(path: qcom->icc_path_ddr); |
220 | |
221 | return ret; |
222 | } |
223 | |
224 | /** |
225 | * dwc3_qcom_interconnect_init() - Get interconnect path handles |
226 | * and set bandwidth. |
227 | * @qcom: Pointer to the concerned usb core. |
228 | * |
229 | */ |
230 | static int dwc3_qcom_interconnect_init(struct dwc3_qcom *qcom) |
231 | { |
232 | enum usb_device_speed max_speed; |
233 | struct device *dev = qcom->dev; |
234 | int ret; |
235 | |
236 | qcom->icc_path_ddr = of_icc_get(dev, name: "usb-ddr" ); |
237 | if (IS_ERR(ptr: qcom->icc_path_ddr)) { |
238 | return dev_err_probe(dev, err: PTR_ERR(ptr: qcom->icc_path_ddr), |
239 | fmt: "failed to get usb-ddr path\n" ); |
240 | } |
241 | |
242 | qcom->icc_path_apps = of_icc_get(dev, name: "apps-usb" ); |
243 | if (IS_ERR(ptr: qcom->icc_path_apps)) { |
244 | ret = dev_err_probe(dev, err: PTR_ERR(ptr: qcom->icc_path_apps), |
245 | fmt: "failed to get apps-usb path\n" ); |
246 | goto put_path_ddr; |
247 | } |
248 | |
249 | max_speed = usb_get_maximum_speed(dev: &qcom->dwc3->dev); |
250 | if (max_speed >= USB_SPEED_SUPER || max_speed == USB_SPEED_UNKNOWN) { |
251 | ret = icc_set_bw(path: qcom->icc_path_ddr, |
252 | USB_MEMORY_AVG_SS_BW, USB_MEMORY_PEAK_SS_BW); |
253 | } else { |
254 | ret = icc_set_bw(path: qcom->icc_path_ddr, |
255 | USB_MEMORY_AVG_HS_BW, USB_MEMORY_PEAK_HS_BW); |
256 | } |
257 | if (ret) { |
258 | dev_err(dev, "failed to set bandwidth for usb-ddr path: %d\n" , ret); |
259 | goto put_path_apps; |
260 | } |
261 | |
262 | ret = icc_set_bw(path: qcom->icc_path_apps, APPS_USB_AVG_BW, APPS_USB_PEAK_BW); |
263 | if (ret) { |
264 | dev_err(dev, "failed to set bandwidth for apps-usb path: %d\n" , ret); |
265 | goto put_path_apps; |
266 | } |
267 | |
268 | return 0; |
269 | |
270 | put_path_apps: |
271 | icc_put(path: qcom->icc_path_apps); |
272 | put_path_ddr: |
273 | icc_put(path: qcom->icc_path_ddr); |
274 | return ret; |
275 | } |
276 | |
277 | /** |
278 | * dwc3_qcom_interconnect_exit() - Release interconnect path handles |
279 | * @qcom: Pointer to the concerned usb core. |
280 | * |
281 | * This function is used to release interconnect path handle. |
282 | */ |
283 | static void dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom) |
284 | { |
285 | icc_put(path: qcom->icc_path_ddr); |
286 | icc_put(path: qcom->icc_path_apps); |
287 | } |
288 | |
289 | /* Only usable in contexts where the role can not change. */ |
290 | static bool dwc3_qcom_is_host(struct dwc3_qcom *qcom) |
291 | { |
292 | struct dwc3 *dwc; |
293 | |
294 | /* |
295 | * FIXME: Fix this layering violation. |
296 | */ |
297 | dwc = platform_get_drvdata(pdev: qcom->dwc3); |
298 | |
299 | /* Core driver may not have probed yet. */ |
300 | if (!dwc) |
301 | return false; |
302 | |
303 | return dwc->xhci; |
304 | } |
305 | |
306 | static enum usb_device_speed dwc3_qcom_read_usb2_speed(struct dwc3_qcom *qcom) |
307 | { |
308 | struct dwc3 *dwc = platform_get_drvdata(pdev: qcom->dwc3); |
309 | struct usb_device *udev; |
310 | struct usb_hcd __maybe_unused *hcd; |
311 | |
312 | /* |
313 | * FIXME: Fix this layering violation. |
314 | */ |
315 | hcd = platform_get_drvdata(pdev: dwc->xhci); |
316 | |
317 | /* |
318 | * It is possible to query the speed of all children of |
319 | * USB2.0 root hub via usb_hub_for_each_child(). DWC3 code |
320 | * currently supports only 1 port per controller. So |
321 | * this is sufficient. |
322 | */ |
323 | #ifdef CONFIG_USB |
324 | udev = usb_hub_find_child(hdev: hcd->self.root_hub, port1: 1); |
325 | #else |
326 | udev = NULL; |
327 | #endif |
328 | if (!udev) |
329 | return USB_SPEED_UNKNOWN; |
330 | |
331 | return udev->speed; |
332 | } |
333 | |
334 | static void dwc3_qcom_enable_wakeup_irq(int irq, unsigned int polarity) |
335 | { |
336 | if (!irq) |
337 | return; |
338 | |
339 | if (polarity) |
340 | irq_set_irq_type(irq, type: polarity); |
341 | |
342 | enable_irq(irq); |
343 | enable_irq_wake(irq); |
344 | } |
345 | |
346 | static void dwc3_qcom_disable_wakeup_irq(int irq) |
347 | { |
348 | if (!irq) |
349 | return; |
350 | |
351 | disable_irq_wake(irq); |
352 | disable_irq_nosync(irq); |
353 | } |
354 | |
355 | static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom) |
356 | { |
357 | dwc3_qcom_disable_wakeup_irq(irq: qcom->qusb2_phy_irq); |
358 | |
359 | if (qcom->usb2_speed == USB_SPEED_LOW) { |
360 | dwc3_qcom_disable_wakeup_irq(irq: qcom->dm_hs_phy_irq); |
361 | } else if ((qcom->usb2_speed == USB_SPEED_HIGH) || |
362 | (qcom->usb2_speed == USB_SPEED_FULL)) { |
363 | dwc3_qcom_disable_wakeup_irq(irq: qcom->dp_hs_phy_irq); |
364 | } else { |
365 | dwc3_qcom_disable_wakeup_irq(irq: qcom->dp_hs_phy_irq); |
366 | dwc3_qcom_disable_wakeup_irq(irq: qcom->dm_hs_phy_irq); |
367 | } |
368 | |
369 | dwc3_qcom_disable_wakeup_irq(irq: qcom->ss_phy_irq); |
370 | } |
371 | |
372 | static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom) |
373 | { |
374 | dwc3_qcom_enable_wakeup_irq(irq: qcom->qusb2_phy_irq, polarity: 0); |
375 | |
376 | /* |
377 | * Configure DP/DM line interrupts based on the USB2 device attached to |
378 | * the root hub port. When HS/FS device is connected, configure the DP line |
379 | * as falling edge to detect both disconnect and remote wakeup scenarios. When |
380 | * LS device is connected, configure DM line as falling edge to detect both |
381 | * disconnect and remote wakeup. When no device is connected, configure both |
382 | * DP and DM lines as rising edge to detect HS/HS/LS device connect scenario. |
383 | */ |
384 | |
385 | if (qcom->usb2_speed == USB_SPEED_LOW) { |
386 | dwc3_qcom_enable_wakeup_irq(irq: qcom->dm_hs_phy_irq, |
387 | polarity: IRQ_TYPE_EDGE_FALLING); |
388 | } else if ((qcom->usb2_speed == USB_SPEED_HIGH) || |
389 | (qcom->usb2_speed == USB_SPEED_FULL)) { |
390 | dwc3_qcom_enable_wakeup_irq(irq: qcom->dp_hs_phy_irq, |
391 | polarity: IRQ_TYPE_EDGE_FALLING); |
392 | } else { |
393 | dwc3_qcom_enable_wakeup_irq(irq: qcom->dp_hs_phy_irq, |
394 | polarity: IRQ_TYPE_EDGE_RISING); |
395 | dwc3_qcom_enable_wakeup_irq(irq: qcom->dm_hs_phy_irq, |
396 | polarity: IRQ_TYPE_EDGE_RISING); |
397 | } |
398 | |
399 | dwc3_qcom_enable_wakeup_irq(irq: qcom->ss_phy_irq, polarity: 0); |
400 | } |
401 | |
402 | static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup) |
403 | { |
404 | u32 val; |
405 | int i, ret; |
406 | |
407 | if (qcom->is_suspended) |
408 | return 0; |
409 | |
410 | val = readl(addr: qcom->qscratch_base + PWR_EVNT_IRQ_STAT_REG); |
411 | if (!(val & PWR_EVNT_LPM_IN_L2_MASK)) |
412 | dev_err(qcom->dev, "HS-PHY not in L2\n" ); |
413 | |
414 | for (i = qcom->num_clocks - 1; i >= 0; i--) |
415 | clk_disable_unprepare(clk: qcom->clks[i]); |
416 | |
417 | ret = dwc3_qcom_interconnect_disable(qcom); |
418 | if (ret) |
419 | dev_warn(qcom->dev, "failed to disable interconnect: %d\n" , ret); |
420 | |
421 | /* |
422 | * The role is stable during suspend as role switching is done from a |
423 | * freezable workqueue. |
424 | */ |
425 | if (dwc3_qcom_is_host(qcom) && wakeup) { |
426 | qcom->usb2_speed = dwc3_qcom_read_usb2_speed(qcom); |
427 | dwc3_qcom_enable_interrupts(qcom); |
428 | } |
429 | |
430 | qcom->is_suspended = true; |
431 | |
432 | return 0; |
433 | } |
434 | |
435 | static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup) |
436 | { |
437 | int ret; |
438 | int i; |
439 | |
440 | if (!qcom->is_suspended) |
441 | return 0; |
442 | |
443 | if (dwc3_qcom_is_host(qcom) && wakeup) |
444 | dwc3_qcom_disable_interrupts(qcom); |
445 | |
446 | for (i = 0; i < qcom->num_clocks; i++) { |
447 | ret = clk_prepare_enable(clk: qcom->clks[i]); |
448 | if (ret < 0) { |
449 | while (--i >= 0) |
450 | clk_disable_unprepare(clk: qcom->clks[i]); |
451 | return ret; |
452 | } |
453 | } |
454 | |
455 | ret = dwc3_qcom_interconnect_enable(qcom); |
456 | if (ret) |
457 | dev_warn(qcom->dev, "failed to enable interconnect: %d\n" , ret); |
458 | |
459 | /* Clear existing events from PHY related to L2 in/out */ |
460 | dwc3_qcom_setbits(base: qcom->qscratch_base, PWR_EVNT_IRQ_STAT_REG, |
461 | PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK); |
462 | |
463 | qcom->is_suspended = false; |
464 | |
465 | return 0; |
466 | } |
467 | |
468 | static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data) |
469 | { |
470 | struct dwc3_qcom *qcom = data; |
471 | struct dwc3 *dwc = platform_get_drvdata(pdev: qcom->dwc3); |
472 | |
473 | /* If pm_suspended then let pm_resume take care of resuming h/w */ |
474 | if (qcom->pm_suspended) |
475 | return IRQ_HANDLED; |
476 | |
477 | /* |
478 | * This is safe as role switching is done from a freezable workqueue |
479 | * and the wakeup interrupts are disabled as part of resume. |
480 | */ |
481 | if (dwc3_qcom_is_host(qcom)) |
482 | pm_runtime_resume(dev: &dwc->xhci->dev); |
483 | |
484 | return IRQ_HANDLED; |
485 | } |
486 | |
487 | static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom) |
488 | { |
489 | /* Configure dwc3 to use UTMI clock as PIPE clock not present */ |
490 | dwc3_qcom_setbits(base: qcom->qscratch_base, QSCRATCH_GENERAL_CFG, |
491 | PIPE_UTMI_CLK_DIS); |
492 | |
493 | usleep_range(min: 100, max: 1000); |
494 | |
495 | dwc3_qcom_setbits(base: qcom->qscratch_base, QSCRATCH_GENERAL_CFG, |
496 | PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW); |
497 | |
498 | usleep_range(min: 100, max: 1000); |
499 | |
500 | dwc3_qcom_clrbits(base: qcom->qscratch_base, QSCRATCH_GENERAL_CFG, |
501 | PIPE_UTMI_CLK_DIS); |
502 | } |
503 | |
504 | static int dwc3_qcom_setup_irq(struct platform_device *pdev) |
505 | { |
506 | struct dwc3_qcom *qcom = platform_get_drvdata(pdev); |
507 | int irq; |
508 | int ret; |
509 | |
510 | irq = platform_get_irq_byname_optional(dev: pdev, name: "qusb2_phy" ); |
511 | if (irq > 0) { |
512 | /* Keep wakeup interrupts disabled until suspend */ |
513 | ret = devm_request_threaded_irq(dev: qcom->dev, irq, NULL, |
514 | thread_fn: qcom_dwc3_resume_irq, |
515 | IRQF_ONESHOT | IRQF_NO_AUTOEN, |
516 | devname: "qcom_dwc3 QUSB2" , dev_id: qcom); |
517 | if (ret) { |
518 | dev_err(qcom->dev, "qusb2_phy_irq failed: %d\n" , ret); |
519 | return ret; |
520 | } |
521 | qcom->qusb2_phy_irq = irq; |
522 | } |
523 | |
524 | irq = platform_get_irq_byname_optional(dev: pdev, name: "dp_hs_phy_irq" ); |
525 | if (irq > 0) { |
526 | ret = devm_request_threaded_irq(dev: qcom->dev, irq, NULL, |
527 | thread_fn: qcom_dwc3_resume_irq, |
528 | IRQF_ONESHOT | IRQF_NO_AUTOEN, |
529 | devname: "qcom_dwc3 DP_HS" , dev_id: qcom); |
530 | if (ret) { |
531 | dev_err(qcom->dev, "dp_hs_phy_irq failed: %d\n" , ret); |
532 | return ret; |
533 | } |
534 | qcom->dp_hs_phy_irq = irq; |
535 | } |
536 | |
537 | irq = platform_get_irq_byname_optional(dev: pdev, name: "dm_hs_phy_irq" ); |
538 | if (irq > 0) { |
539 | ret = devm_request_threaded_irq(dev: qcom->dev, irq, NULL, |
540 | thread_fn: qcom_dwc3_resume_irq, |
541 | IRQF_ONESHOT | IRQF_NO_AUTOEN, |
542 | devname: "qcom_dwc3 DM_HS" , dev_id: qcom); |
543 | if (ret) { |
544 | dev_err(qcom->dev, "dm_hs_phy_irq failed: %d\n" , ret); |
545 | return ret; |
546 | } |
547 | qcom->dm_hs_phy_irq = irq; |
548 | } |
549 | |
550 | irq = platform_get_irq_byname_optional(dev: pdev, name: "ss_phy_irq" ); |
551 | if (irq > 0) { |
552 | ret = devm_request_threaded_irq(dev: qcom->dev, irq, NULL, |
553 | thread_fn: qcom_dwc3_resume_irq, |
554 | IRQF_ONESHOT | IRQF_NO_AUTOEN, |
555 | devname: "qcom_dwc3 SS" , dev_id: qcom); |
556 | if (ret) { |
557 | dev_err(qcom->dev, "ss_phy_irq failed: %d\n" , ret); |
558 | return ret; |
559 | } |
560 | qcom->ss_phy_irq = irq; |
561 | } |
562 | |
563 | return 0; |
564 | } |
565 | |
566 | static int dwc3_qcom_clk_init(struct dwc3_qcom *qcom, int count) |
567 | { |
568 | struct device *dev = qcom->dev; |
569 | struct device_node *np = dev->of_node; |
570 | int i; |
571 | |
572 | if (!np || !count) |
573 | return 0; |
574 | |
575 | if (count < 0) |
576 | return count; |
577 | |
578 | qcom->num_clocks = count; |
579 | |
580 | qcom->clks = devm_kcalloc(dev, n: qcom->num_clocks, |
581 | size: sizeof(struct clk *), GFP_KERNEL); |
582 | if (!qcom->clks) |
583 | return -ENOMEM; |
584 | |
585 | for (i = 0; i < qcom->num_clocks; i++) { |
586 | struct clk *clk; |
587 | int ret; |
588 | |
589 | clk = of_clk_get(np, index: i); |
590 | if (IS_ERR(ptr: clk)) { |
591 | while (--i >= 0) |
592 | clk_put(clk: qcom->clks[i]); |
593 | return PTR_ERR(ptr: clk); |
594 | } |
595 | |
596 | ret = clk_prepare_enable(clk); |
597 | if (ret < 0) { |
598 | while (--i >= 0) { |
599 | clk_disable_unprepare(clk: qcom->clks[i]); |
600 | clk_put(clk: qcom->clks[i]); |
601 | } |
602 | clk_put(clk); |
603 | |
604 | return ret; |
605 | } |
606 | |
607 | qcom->clks[i] = clk; |
608 | } |
609 | |
610 | return 0; |
611 | } |
612 | |
613 | static int dwc3_qcom_of_register_core(struct platform_device *pdev) |
614 | { |
615 | struct dwc3_qcom *qcom = platform_get_drvdata(pdev); |
616 | struct device_node *np = pdev->dev.of_node, *dwc3_np; |
617 | struct device *dev = &pdev->dev; |
618 | int ret; |
619 | |
620 | dwc3_np = of_get_compatible_child(parent: np, compatible: "snps,dwc3" ); |
621 | if (!dwc3_np) { |
622 | dev_err(dev, "failed to find dwc3 core child\n" ); |
623 | return -ENODEV; |
624 | } |
625 | |
626 | ret = of_platform_populate(root: np, NULL, NULL, parent: dev); |
627 | if (ret) { |
628 | dev_err(dev, "failed to register dwc3 core - %d\n" , ret); |
629 | goto node_put; |
630 | } |
631 | |
632 | qcom->dwc3 = of_find_device_by_node(np: dwc3_np); |
633 | if (!qcom->dwc3) { |
634 | ret = -ENODEV; |
635 | dev_err(dev, "failed to get dwc3 platform device\n" ); |
636 | of_platform_depopulate(parent: dev); |
637 | } |
638 | |
639 | node_put: |
640 | of_node_put(node: dwc3_np); |
641 | |
642 | return ret; |
643 | } |
644 | |
645 | static int dwc3_qcom_probe(struct platform_device *pdev) |
646 | { |
647 | struct device_node *np = pdev->dev.of_node; |
648 | struct device *dev = &pdev->dev; |
649 | struct dwc3_qcom *qcom; |
650 | struct resource *res; |
651 | int ret, i; |
652 | bool ignore_pipe_clk; |
653 | bool wakeup_source; |
654 | |
655 | qcom = devm_kzalloc(dev: &pdev->dev, size: sizeof(*qcom), GFP_KERNEL); |
656 | if (!qcom) |
657 | return -ENOMEM; |
658 | |
659 | platform_set_drvdata(pdev, data: qcom); |
660 | qcom->dev = &pdev->dev; |
661 | |
662 | qcom->resets = devm_reset_control_array_get_optional_exclusive(dev); |
663 | if (IS_ERR(ptr: qcom->resets)) { |
664 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: qcom->resets), |
665 | fmt: "failed to get resets\n" ); |
666 | } |
667 | |
668 | ret = reset_control_assert(rstc: qcom->resets); |
669 | if (ret) { |
670 | dev_err(&pdev->dev, "failed to assert resets, err=%d\n" , ret); |
671 | return ret; |
672 | } |
673 | |
674 | usleep_range(min: 10, max: 1000); |
675 | |
676 | ret = reset_control_deassert(rstc: qcom->resets); |
677 | if (ret) { |
678 | dev_err(&pdev->dev, "failed to deassert resets, err=%d\n" , ret); |
679 | goto reset_assert; |
680 | } |
681 | |
682 | ret = dwc3_qcom_clk_init(qcom, count: of_clk_get_parent_count(np)); |
683 | if (ret) { |
684 | dev_err_probe(dev, err: ret, fmt: "failed to get clocks\n" ); |
685 | goto reset_assert; |
686 | } |
687 | |
688 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
689 | |
690 | qcom->qscratch_base = devm_ioremap_resource(dev, res); |
691 | if (IS_ERR(ptr: qcom->qscratch_base)) { |
692 | ret = PTR_ERR(ptr: qcom->qscratch_base); |
693 | goto clk_disable; |
694 | } |
695 | |
696 | ret = dwc3_qcom_setup_irq(pdev); |
697 | if (ret) { |
698 | dev_err(dev, "failed to setup IRQs, err=%d\n" , ret); |
699 | goto clk_disable; |
700 | } |
701 | |
702 | /* |
703 | * Disable pipe_clk requirement if specified. Used when dwc3 |
704 | * operates without SSPHY and only HS/FS/LS modes are supported. |
705 | */ |
706 | ignore_pipe_clk = device_property_read_bool(dev, |
707 | propname: "qcom,select-utmi-as-pipe-clk" ); |
708 | if (ignore_pipe_clk) |
709 | dwc3_qcom_select_utmi_clk(qcom); |
710 | |
711 | ret = dwc3_qcom_of_register_core(pdev); |
712 | if (ret) { |
713 | dev_err(dev, "failed to register DWC3 Core, err=%d\n" , ret); |
714 | goto clk_disable; |
715 | } |
716 | |
717 | ret = dwc3_qcom_interconnect_init(qcom); |
718 | if (ret) |
719 | goto depopulate; |
720 | |
721 | qcom->mode = usb_get_dr_mode(dev: &qcom->dwc3->dev); |
722 | |
723 | /* enable vbus override for device mode */ |
724 | if (qcom->mode != USB_DR_MODE_HOST) |
725 | dwc3_qcom_vbus_override_enable(qcom, enable: true); |
726 | |
727 | /* register extcon to override sw_vbus on Vbus change later */ |
728 | ret = dwc3_qcom_register_extcon(qcom); |
729 | if (ret) |
730 | goto interconnect_exit; |
731 | |
732 | wakeup_source = of_property_read_bool(np: dev->of_node, propname: "wakeup-source" ); |
733 | device_init_wakeup(dev: &pdev->dev, enable: wakeup_source); |
734 | device_init_wakeup(dev: &qcom->dwc3->dev, enable: wakeup_source); |
735 | |
736 | qcom->is_suspended = false; |
737 | pm_runtime_set_active(dev); |
738 | pm_runtime_enable(dev); |
739 | pm_runtime_forbid(dev); |
740 | |
741 | return 0; |
742 | |
743 | interconnect_exit: |
744 | dwc3_qcom_interconnect_exit(qcom); |
745 | depopulate: |
746 | of_platform_depopulate(parent: &pdev->dev); |
747 | platform_device_put(pdev: qcom->dwc3); |
748 | clk_disable: |
749 | for (i = qcom->num_clocks - 1; i >= 0; i--) { |
750 | clk_disable_unprepare(clk: qcom->clks[i]); |
751 | clk_put(clk: qcom->clks[i]); |
752 | } |
753 | reset_assert: |
754 | reset_control_assert(rstc: qcom->resets); |
755 | |
756 | return ret; |
757 | } |
758 | |
759 | static void dwc3_qcom_remove(struct platform_device *pdev) |
760 | { |
761 | struct dwc3_qcom *qcom = platform_get_drvdata(pdev); |
762 | struct device *dev = &pdev->dev; |
763 | int i; |
764 | |
765 | of_platform_depopulate(parent: &pdev->dev); |
766 | platform_device_put(pdev: qcom->dwc3); |
767 | |
768 | for (i = qcom->num_clocks - 1; i >= 0; i--) { |
769 | clk_disable_unprepare(clk: qcom->clks[i]); |
770 | clk_put(clk: qcom->clks[i]); |
771 | } |
772 | qcom->num_clocks = 0; |
773 | |
774 | dwc3_qcom_interconnect_exit(qcom); |
775 | reset_control_assert(rstc: qcom->resets); |
776 | |
777 | pm_runtime_allow(dev); |
778 | pm_runtime_disable(dev); |
779 | } |
780 | |
781 | static int __maybe_unused dwc3_qcom_pm_suspend(struct device *dev) |
782 | { |
783 | struct dwc3_qcom *qcom = dev_get_drvdata(dev); |
784 | bool wakeup = device_may_wakeup(dev); |
785 | int ret; |
786 | |
787 | ret = dwc3_qcom_suspend(qcom, wakeup); |
788 | if (ret) |
789 | return ret; |
790 | |
791 | qcom->pm_suspended = true; |
792 | |
793 | return 0; |
794 | } |
795 | |
796 | static int __maybe_unused dwc3_qcom_pm_resume(struct device *dev) |
797 | { |
798 | struct dwc3_qcom *qcom = dev_get_drvdata(dev); |
799 | bool wakeup = device_may_wakeup(dev); |
800 | int ret; |
801 | |
802 | ret = dwc3_qcom_resume(qcom, wakeup); |
803 | if (ret) |
804 | return ret; |
805 | |
806 | qcom->pm_suspended = false; |
807 | |
808 | return 0; |
809 | } |
810 | |
811 | static int __maybe_unused dwc3_qcom_runtime_suspend(struct device *dev) |
812 | { |
813 | struct dwc3_qcom *qcom = dev_get_drvdata(dev); |
814 | |
815 | return dwc3_qcom_suspend(qcom, wakeup: true); |
816 | } |
817 | |
818 | static int __maybe_unused dwc3_qcom_runtime_resume(struct device *dev) |
819 | { |
820 | struct dwc3_qcom *qcom = dev_get_drvdata(dev); |
821 | |
822 | return dwc3_qcom_resume(qcom, wakeup: true); |
823 | } |
824 | |
825 | static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = { |
826 | SET_SYSTEM_SLEEP_PM_OPS(dwc3_qcom_pm_suspend, dwc3_qcom_pm_resume) |
827 | SET_RUNTIME_PM_OPS(dwc3_qcom_runtime_suspend, dwc3_qcom_runtime_resume, |
828 | NULL) |
829 | }; |
830 | |
831 | static const struct of_device_id dwc3_qcom_of_match[] = { |
832 | { .compatible = "qcom,dwc3" }, |
833 | { } |
834 | }; |
835 | MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match); |
836 | |
837 | static struct platform_driver dwc3_qcom_driver = { |
838 | .probe = dwc3_qcom_probe, |
839 | .remove_new = dwc3_qcom_remove, |
840 | .driver = { |
841 | .name = "dwc3-qcom" , |
842 | .pm = &dwc3_qcom_dev_pm_ops, |
843 | .of_match_table = dwc3_qcom_of_match, |
844 | }, |
845 | }; |
846 | |
847 | module_platform_driver(dwc3_qcom_driver); |
848 | |
849 | MODULE_LICENSE("GPL v2" ); |
850 | MODULE_DESCRIPTION("DesignWare DWC3 QCOM Glue Driver" ); |
851 | |