1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Driver for EHCI UHP on Atmel chips |
4 | * |
5 | * Copyright (C) 2009 Atmel Corporation, |
6 | * Nicolas Ferre <nicolas.ferre@atmel.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/kernel.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/of_platform.h> |
18 | #include <linux/platform_device.h> |
19 | #include <linux/usb.h> |
20 | #include <linux/usb/hcd.h> |
21 | #include <linux/usb/phy.h> |
22 | #include <linux/usb/of.h> |
23 | |
24 | #include "ehci.h" |
25 | |
26 | #define DRIVER_DESC "EHCI Atmel driver" |
27 | |
28 | #define EHCI_INSNREG(index) ((index) * 4 + 0x90) |
29 | #define EHCI_INSNREG08_HSIC_EN BIT(2) |
30 | |
31 | /* interface and function clocks */ |
32 | #define hcd_to_atmel_ehci_priv(h) \ |
33 | ((struct atmel_ehci_priv *)hcd_to_ehci(h)->priv) |
34 | |
35 | struct atmel_ehci_priv { |
36 | struct clk *iclk; |
37 | struct clk *uclk; |
38 | bool clocked; |
39 | }; |
40 | |
41 | static struct hc_driver __read_mostly ehci_atmel_hc_driver; |
42 | |
43 | static const struct ehci_driver_overrides ehci_atmel_drv_overrides __initconst = { |
44 | .extra_priv_size = sizeof(struct atmel_ehci_priv), |
45 | }; |
46 | |
47 | /*-------------------------------------------------------------------------*/ |
48 | |
49 | static void atmel_start_clock(struct atmel_ehci_priv *atmel_ehci) |
50 | { |
51 | if (atmel_ehci->clocked) |
52 | return; |
53 | |
54 | clk_prepare_enable(clk: atmel_ehci->uclk); |
55 | clk_prepare_enable(clk: atmel_ehci->iclk); |
56 | atmel_ehci->clocked = true; |
57 | } |
58 | |
59 | static void atmel_stop_clock(struct atmel_ehci_priv *atmel_ehci) |
60 | { |
61 | if (!atmel_ehci->clocked) |
62 | return; |
63 | |
64 | clk_disable_unprepare(clk: atmel_ehci->iclk); |
65 | clk_disable_unprepare(clk: atmel_ehci->uclk); |
66 | atmel_ehci->clocked = false; |
67 | } |
68 | |
69 | static void atmel_start_ehci(struct platform_device *pdev) |
70 | { |
71 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
72 | struct atmel_ehci_priv *atmel_ehci = hcd_to_atmel_ehci_priv(hcd); |
73 | |
74 | dev_dbg(&pdev->dev, "start\n" ); |
75 | atmel_start_clock(atmel_ehci); |
76 | } |
77 | |
78 | static void atmel_stop_ehci(struct platform_device *pdev) |
79 | { |
80 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
81 | struct atmel_ehci_priv *atmel_ehci = hcd_to_atmel_ehci_priv(hcd); |
82 | |
83 | dev_dbg(&pdev->dev, "stop\n" ); |
84 | atmel_stop_clock(atmel_ehci); |
85 | } |
86 | |
87 | /*-------------------------------------------------------------------------*/ |
88 | |
89 | static int ehci_atmel_drv_probe(struct platform_device *pdev) |
90 | { |
91 | struct usb_hcd *hcd; |
92 | const struct hc_driver *driver = &ehci_atmel_hc_driver; |
93 | struct resource *res; |
94 | struct ehci_hcd *ehci; |
95 | struct atmel_ehci_priv *atmel_ehci; |
96 | int irq; |
97 | int retval; |
98 | |
99 | if (usb_disabled()) |
100 | return -ENODEV; |
101 | |
102 | pr_debug("Initializing Atmel-SoC USB Host Controller\n" ); |
103 | |
104 | irq = platform_get_irq(pdev, 0); |
105 | if (irq < 0) { |
106 | retval = irq; |
107 | goto fail_create_hcd; |
108 | } |
109 | |
110 | /* Right now device-tree probed devices don't get dma_mask set. |
111 | * Since shared usb code relies on it, set it here for now. |
112 | * Once we have dma capability bindings this can go away. |
113 | */ |
114 | retval = dma_coerce_mask_and_coherent(dev: &pdev->dev, DMA_BIT_MASK(32)); |
115 | if (retval) |
116 | goto fail_create_hcd; |
117 | |
118 | hcd = usb_create_hcd(driver, dev: &pdev->dev, bus_name: dev_name(dev: &pdev->dev)); |
119 | if (!hcd) { |
120 | retval = -ENOMEM; |
121 | goto fail_create_hcd; |
122 | } |
123 | atmel_ehci = hcd_to_atmel_ehci_priv(hcd); |
124 | |
125 | hcd->regs = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
126 | if (IS_ERR(ptr: hcd->regs)) { |
127 | retval = PTR_ERR(ptr: hcd->regs); |
128 | goto fail_request_resource; |
129 | } |
130 | |
131 | hcd->rsrc_start = res->start; |
132 | hcd->rsrc_len = resource_size(res); |
133 | |
134 | atmel_ehci->iclk = devm_clk_get(dev: &pdev->dev, id: "ehci_clk" ); |
135 | if (IS_ERR(ptr: atmel_ehci->iclk)) { |
136 | dev_err(&pdev->dev, "Error getting interface clock\n" ); |
137 | retval = -ENOENT; |
138 | goto fail_request_resource; |
139 | } |
140 | |
141 | atmel_ehci->uclk = devm_clk_get(dev: &pdev->dev, id: "usb_clk" ); |
142 | if (IS_ERR(ptr: atmel_ehci->uclk)) { |
143 | dev_err(&pdev->dev, "failed to get uclk\n" ); |
144 | retval = PTR_ERR(ptr: atmel_ehci->uclk); |
145 | goto fail_request_resource; |
146 | } |
147 | |
148 | ehci = hcd_to_ehci(hcd); |
149 | /* registers start at offset 0x0 */ |
150 | ehci->caps = hcd->regs; |
151 | |
152 | atmel_start_ehci(pdev); |
153 | |
154 | retval = usb_add_hcd(hcd, irqnum: irq, IRQF_SHARED); |
155 | if (retval) |
156 | goto fail_add_hcd; |
157 | device_wakeup_enable(dev: hcd->self.controller); |
158 | |
159 | if (of_usb_get_phy_mode(np: pdev->dev.of_node) == USBPHY_INTERFACE_MODE_HSIC) |
160 | writel(EHCI_INSNREG08_HSIC_EN, addr: hcd->regs + EHCI_INSNREG(8)); |
161 | |
162 | return retval; |
163 | |
164 | fail_add_hcd: |
165 | atmel_stop_ehci(pdev); |
166 | fail_request_resource: |
167 | usb_put_hcd(hcd); |
168 | fail_create_hcd: |
169 | dev_err(&pdev->dev, "init %s fail, %d\n" , |
170 | dev_name(&pdev->dev), retval); |
171 | |
172 | return retval; |
173 | } |
174 | |
175 | static void ehci_atmel_drv_remove(struct platform_device *pdev) |
176 | { |
177 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
178 | |
179 | usb_remove_hcd(hcd); |
180 | usb_put_hcd(hcd); |
181 | |
182 | atmel_stop_ehci(pdev); |
183 | } |
184 | |
185 | static int __maybe_unused ehci_atmel_drv_suspend(struct device *dev) |
186 | { |
187 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
188 | struct atmel_ehci_priv *atmel_ehci = hcd_to_atmel_ehci_priv(hcd); |
189 | int ret; |
190 | |
191 | ret = ehci_suspend(hcd, do_wakeup: false); |
192 | if (ret) |
193 | return ret; |
194 | |
195 | atmel_stop_clock(atmel_ehci); |
196 | return 0; |
197 | } |
198 | |
199 | static int __maybe_unused ehci_atmel_drv_resume(struct device *dev) |
200 | { |
201 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
202 | struct atmel_ehci_priv *atmel_ehci = hcd_to_atmel_ehci_priv(hcd); |
203 | |
204 | atmel_start_clock(atmel_ehci); |
205 | ehci_resume(hcd, force_reset: false); |
206 | return 0; |
207 | } |
208 | |
209 | #ifdef CONFIG_OF |
210 | static const struct of_device_id atmel_ehci_dt_ids[] = { |
211 | { .compatible = "atmel,at91sam9g45-ehci" }, |
212 | { /* sentinel */ } |
213 | }; |
214 | |
215 | MODULE_DEVICE_TABLE(of, atmel_ehci_dt_ids); |
216 | #endif |
217 | |
218 | static SIMPLE_DEV_PM_OPS(ehci_atmel_pm_ops, ehci_atmel_drv_suspend, |
219 | ehci_atmel_drv_resume); |
220 | |
221 | static struct platform_driver ehci_atmel_driver = { |
222 | .probe = ehci_atmel_drv_probe, |
223 | .remove_new = ehci_atmel_drv_remove, |
224 | .shutdown = usb_hcd_platform_shutdown, |
225 | .driver = { |
226 | .name = "atmel-ehci" , |
227 | .pm = &ehci_atmel_pm_ops, |
228 | .of_match_table = of_match_ptr(atmel_ehci_dt_ids), |
229 | }, |
230 | }; |
231 | |
232 | static int __init ehci_atmel_init(void) |
233 | { |
234 | if (usb_disabled()) |
235 | return -ENODEV; |
236 | |
237 | ehci_init_driver(drv: &ehci_atmel_hc_driver, over: &ehci_atmel_drv_overrides); |
238 | return platform_driver_register(&ehci_atmel_driver); |
239 | } |
240 | module_init(ehci_atmel_init); |
241 | |
242 | static void __exit ehci_atmel_cleanup(void) |
243 | { |
244 | platform_driver_unregister(&ehci_atmel_driver); |
245 | } |
246 | module_exit(ehci_atmel_cleanup); |
247 | |
248 | MODULE_DESCRIPTION(DRIVER_DESC); |
249 | MODULE_ALIAS("platform:atmel-ehci" ); |
250 | MODULE_AUTHOR("Nicolas Ferre" ); |
251 | MODULE_LICENSE("GPL" ); |
252 | |