1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Driver for EHCI HCD on SPEAr SOC |
4 | * |
5 | * Copyright (C) 2010 ST Micro Electronics, |
6 | * Deepak Sikri <deepak.sikri@st.com> |
7 | * |
8 | * Based on various ehci-*.c drivers |
9 | */ |
10 | |
11 | #include <linux/clk.h> |
12 | #include <linux/dma-mapping.h> |
13 | #include <linux/io.h> |
14 | #include <linux/jiffies.h> |
15 | #include <linux/kernel.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of.h> |
18 | #include <linux/platform_device.h> |
19 | #include <linux/pm.h> |
20 | #include <linux/usb.h> |
21 | #include <linux/usb/hcd.h> |
22 | |
23 | #include "ehci.h" |
24 | |
25 | #define DRIVER_DESC "EHCI SPEAr driver" |
26 | |
27 | struct spear_ehci { |
28 | struct clk *clk; |
29 | }; |
30 | |
31 | #define to_spear_ehci(hcd) (struct spear_ehci *)(hcd_to_ehci(hcd)->priv) |
32 | |
33 | static struct hc_driver __read_mostly ehci_spear_hc_driver; |
34 | |
35 | static int __maybe_unused ehci_spear_drv_suspend(struct device *dev) |
36 | { |
37 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
38 | bool do_wakeup = device_may_wakeup(dev); |
39 | |
40 | return ehci_suspend(hcd, do_wakeup); |
41 | } |
42 | |
43 | static int __maybe_unused ehci_spear_drv_resume(struct device *dev) |
44 | { |
45 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
46 | |
47 | ehci_resume(hcd, force_reset: false); |
48 | return 0; |
49 | } |
50 | |
51 | static SIMPLE_DEV_PM_OPS(ehci_spear_pm_ops, ehci_spear_drv_suspend, |
52 | ehci_spear_drv_resume); |
53 | |
54 | static int spear_ehci_hcd_drv_probe(struct platform_device *pdev) |
55 | { |
56 | struct usb_hcd *hcd ; |
57 | struct spear_ehci *sehci; |
58 | struct resource *res; |
59 | struct clk *usbh_clk; |
60 | const struct hc_driver *driver = &ehci_spear_hc_driver; |
61 | int irq, retval; |
62 | |
63 | if (usb_disabled()) |
64 | return -ENODEV; |
65 | |
66 | irq = platform_get_irq(pdev, 0); |
67 | if (irq < 0) { |
68 | retval = irq; |
69 | goto fail; |
70 | } |
71 | |
72 | /* |
73 | * Right now device-tree probed devices don't get dma_mask set. |
74 | * Since shared usb code relies on it, set it here for now. |
75 | * Once we have dma capability bindings this can go away. |
76 | */ |
77 | retval = dma_coerce_mask_and_coherent(dev: &pdev->dev, DMA_BIT_MASK(32)); |
78 | if (retval) |
79 | goto fail; |
80 | |
81 | usbh_clk = devm_clk_get(dev: &pdev->dev, NULL); |
82 | if (IS_ERR(ptr: usbh_clk)) { |
83 | dev_err(&pdev->dev, "Error getting interface clock\n" ); |
84 | retval = PTR_ERR(ptr: usbh_clk); |
85 | goto fail; |
86 | } |
87 | |
88 | hcd = usb_create_hcd(driver, dev: &pdev->dev, bus_name: dev_name(dev: &pdev->dev)); |
89 | if (!hcd) { |
90 | retval = -ENOMEM; |
91 | goto fail; |
92 | } |
93 | |
94 | hcd->regs = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
95 | if (IS_ERR(ptr: hcd->regs)) { |
96 | retval = PTR_ERR(ptr: hcd->regs); |
97 | goto err_put_hcd; |
98 | } |
99 | hcd->rsrc_start = res->start; |
100 | hcd->rsrc_len = resource_size(res); |
101 | |
102 | sehci = to_spear_ehci(hcd); |
103 | sehci->clk = usbh_clk; |
104 | |
105 | /* registers start at offset 0x0 */ |
106 | hcd_to_ehci(hcd)->caps = hcd->regs; |
107 | |
108 | clk_prepare_enable(clk: sehci->clk); |
109 | retval = usb_add_hcd(hcd, irqnum: irq, IRQF_SHARED); |
110 | if (retval) |
111 | goto err_stop_ehci; |
112 | |
113 | device_wakeup_enable(dev: hcd->self.controller); |
114 | return retval; |
115 | |
116 | err_stop_ehci: |
117 | clk_disable_unprepare(clk: sehci->clk); |
118 | err_put_hcd: |
119 | usb_put_hcd(hcd); |
120 | fail: |
121 | dev_err(&pdev->dev, "init fail, %d\n" , retval); |
122 | |
123 | return retval ; |
124 | } |
125 | |
126 | static void spear_ehci_hcd_drv_remove(struct platform_device *pdev) |
127 | { |
128 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
129 | struct spear_ehci *sehci = to_spear_ehci(hcd); |
130 | |
131 | usb_remove_hcd(hcd); |
132 | |
133 | if (sehci->clk) |
134 | clk_disable_unprepare(clk: sehci->clk); |
135 | usb_put_hcd(hcd); |
136 | } |
137 | |
138 | static const struct of_device_id spear_ehci_id_table[] = { |
139 | { .compatible = "st,spear600-ehci" , }, |
140 | { }, |
141 | }; |
142 | MODULE_DEVICE_TABLE(of, spear_ehci_id_table); |
143 | |
144 | static struct platform_driver spear_ehci_hcd_driver = { |
145 | .probe = spear_ehci_hcd_drv_probe, |
146 | .remove_new = spear_ehci_hcd_drv_remove, |
147 | .shutdown = usb_hcd_platform_shutdown, |
148 | .driver = { |
149 | .name = "spear-ehci" , |
150 | .bus = &platform_bus_type, |
151 | .pm = pm_ptr(&ehci_spear_pm_ops), |
152 | .of_match_table = spear_ehci_id_table, |
153 | } |
154 | }; |
155 | |
156 | static const struct ehci_driver_overrides spear_overrides __initconst = { |
157 | .extra_priv_size = sizeof(struct spear_ehci), |
158 | }; |
159 | |
160 | static int __init ehci_spear_init(void) |
161 | { |
162 | if (usb_disabled()) |
163 | return -ENODEV; |
164 | |
165 | ehci_init_driver(drv: &ehci_spear_hc_driver, over: &spear_overrides); |
166 | return platform_driver_register(&spear_ehci_hcd_driver); |
167 | } |
168 | module_init(ehci_spear_init); |
169 | |
170 | static void __exit ehci_spear_cleanup(void) |
171 | { |
172 | platform_driver_unregister(&spear_ehci_hcd_driver); |
173 | } |
174 | module_exit(ehci_spear_cleanup); |
175 | |
176 | MODULE_DESCRIPTION(DRIVER_DESC); |
177 | MODULE_ALIAS("platform:spear-ehci" ); |
178 | MODULE_AUTHOR("Deepak Sikri" ); |
179 | MODULE_LICENSE("GPL" ); |
180 | |