1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (C) 2011 Marvell International Ltd. All rights reserved. |
4 | * Author: Chao Xie <chao.xie@marvell.com> |
5 | * Neil Zhang <zhangwm@marvell.com> |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/clk.h> |
12 | #include <linux/err.h> |
13 | #include <linux/usb/otg.h> |
14 | #include <linux/usb/of.h> |
15 | #include <linux/platform_data/mv_usb.h> |
16 | #include <linux/io.h> |
17 | |
18 | #include <linux/usb/hcd.h> |
19 | |
20 | #include "ehci.h" |
21 | |
22 | /* registers */ |
23 | #define U2x_CAPREGS_OFFSET 0x100 |
24 | |
25 | #define CAPLENGTH_MASK (0xff) |
26 | |
27 | #define hcd_to_ehci_hcd_mv(h) ((struct ehci_hcd_mv *)hcd_to_ehci(h)->priv) |
28 | |
29 | struct ehci_hcd_mv { |
30 | /* Which mode does this ehci running OTG/Host ? */ |
31 | int mode; |
32 | |
33 | void __iomem *base; |
34 | void __iomem *cap_regs; |
35 | void __iomem *op_regs; |
36 | |
37 | struct usb_phy *otg; |
38 | struct clk *clk; |
39 | |
40 | struct phy *phy; |
41 | |
42 | int (*set_vbus)(unsigned int vbus); |
43 | }; |
44 | |
45 | static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv) |
46 | { |
47 | int retval; |
48 | |
49 | retval = clk_prepare_enable(clk: ehci_mv->clk); |
50 | if (retval) |
51 | return retval; |
52 | |
53 | retval = phy_init(phy: ehci_mv->phy); |
54 | if (retval) |
55 | clk_disable_unprepare(clk: ehci_mv->clk); |
56 | |
57 | return retval; |
58 | } |
59 | |
60 | static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv) |
61 | { |
62 | phy_exit(phy: ehci_mv->phy); |
63 | clk_disable_unprepare(clk: ehci_mv->clk); |
64 | } |
65 | |
66 | static int mv_ehci_reset(struct usb_hcd *hcd) |
67 | { |
68 | struct device *dev = hcd->self.controller; |
69 | struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd); |
70 | struct ehci_hcd *ehci = hcd_to_ehci(hcd); |
71 | u32 status; |
72 | int retval; |
73 | |
74 | if (ehci_mv == NULL) { |
75 | dev_err(dev, "Can not find private ehci data\n" ); |
76 | return -ENODEV; |
77 | } |
78 | |
79 | hcd->has_tt = 1; |
80 | |
81 | retval = ehci_setup(hcd); |
82 | if (retval) |
83 | dev_err(dev, "ehci_setup failed %d\n" , retval); |
84 | |
85 | if (of_usb_get_phy_mode(np: dev->of_node) == USBPHY_INTERFACE_MODE_HSIC) { |
86 | status = ehci_readl(ehci, regs: &ehci->regs->port_status[0]); |
87 | status |= PORT_TEST_FORCE; |
88 | ehci_writel(ehci, val: status, regs: &ehci->regs->port_status[0]); |
89 | status &= ~PORT_TEST_FORCE; |
90 | ehci_writel(ehci, val: status, regs: &ehci->regs->port_status[0]); |
91 | } |
92 | |
93 | return retval; |
94 | } |
95 | |
96 | static struct hc_driver __read_mostly ehci_platform_hc_driver; |
97 | |
98 | static const struct ehci_driver_overrides platform_overrides __initconst = { |
99 | .reset = mv_ehci_reset, |
100 | .extra_priv_size = sizeof(struct ehci_hcd_mv), |
101 | }; |
102 | |
103 | static int mv_ehci_probe(struct platform_device *pdev) |
104 | { |
105 | struct mv_usb_platform_data *pdata = dev_get_platdata(dev: &pdev->dev); |
106 | struct usb_hcd *hcd; |
107 | struct ehci_hcd *ehci; |
108 | struct ehci_hcd_mv *ehci_mv; |
109 | struct resource *r; |
110 | int retval; |
111 | u32 offset; |
112 | u32 status; |
113 | |
114 | if (usb_disabled()) |
115 | return -ENODEV; |
116 | |
117 | hcd = usb_create_hcd(driver: &ehci_platform_hc_driver, dev: &pdev->dev, bus_name: dev_name(dev: &pdev->dev)); |
118 | if (!hcd) |
119 | return -ENOMEM; |
120 | |
121 | platform_set_drvdata(pdev, data: hcd); |
122 | ehci_mv = hcd_to_ehci_hcd_mv(hcd); |
123 | |
124 | ehci_mv->mode = MV_USB_MODE_HOST; |
125 | if (pdata) { |
126 | ehci_mv->mode = pdata->mode; |
127 | ehci_mv->set_vbus = pdata->set_vbus; |
128 | } |
129 | |
130 | ehci_mv->phy = devm_phy_optional_get(dev: &pdev->dev, string: "usb" ); |
131 | if (IS_ERR(ptr: ehci_mv->phy)) { |
132 | retval = PTR_ERR(ptr: ehci_mv->phy); |
133 | if (retval != -EPROBE_DEFER) |
134 | dev_err(&pdev->dev, "Failed to get phy.\n" ); |
135 | goto err_put_hcd; |
136 | } |
137 | |
138 | ehci_mv->clk = devm_clk_get(dev: &pdev->dev, NULL); |
139 | if (IS_ERR(ptr: ehci_mv->clk)) { |
140 | dev_err(&pdev->dev, "error getting clock\n" ); |
141 | retval = PTR_ERR(ptr: ehci_mv->clk); |
142 | goto err_put_hcd; |
143 | } |
144 | |
145 | ehci_mv->base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &r); |
146 | if (IS_ERR(ptr: ehci_mv->base)) { |
147 | retval = PTR_ERR(ptr: ehci_mv->base); |
148 | goto err_put_hcd; |
149 | } |
150 | |
151 | retval = mv_ehci_enable(ehci_mv); |
152 | if (retval) { |
153 | dev_err(&pdev->dev, "init phy error %d\n" , retval); |
154 | goto err_put_hcd; |
155 | } |
156 | |
157 | ehci_mv->cap_regs = |
158 | (void __iomem *) ((unsigned long) ehci_mv->base + U2x_CAPREGS_OFFSET); |
159 | offset = readl(addr: ehci_mv->cap_regs) & CAPLENGTH_MASK; |
160 | ehci_mv->op_regs = |
161 | (void __iomem *) ((unsigned long) ehci_mv->cap_regs + offset); |
162 | |
163 | hcd->rsrc_start = r->start; |
164 | hcd->rsrc_len = resource_size(res: r); |
165 | hcd->regs = ehci_mv->op_regs; |
166 | |
167 | retval = platform_get_irq(pdev, 0); |
168 | if (retval < 0) |
169 | goto err_disable_clk; |
170 | hcd->irq = retval; |
171 | |
172 | ehci = hcd_to_ehci(hcd); |
173 | ehci->caps = (struct ehci_caps __iomem *) ehci_mv->cap_regs; |
174 | |
175 | if (ehci_mv->mode == MV_USB_MODE_OTG) { |
176 | ehci_mv->otg = devm_usb_get_phy(dev: &pdev->dev, type: USB_PHY_TYPE_USB2); |
177 | if (IS_ERR(ptr: ehci_mv->otg)) { |
178 | retval = PTR_ERR(ptr: ehci_mv->otg); |
179 | |
180 | if (retval == -ENXIO) |
181 | dev_info(&pdev->dev, "MV_USB_MODE_OTG " |
182 | "must have CONFIG_USB_PHY enabled\n" ); |
183 | else |
184 | dev_err(&pdev->dev, |
185 | "unable to find transceiver\n" ); |
186 | goto err_disable_clk; |
187 | } |
188 | |
189 | retval = otg_set_host(otg: ehci_mv->otg->otg, host: &hcd->self); |
190 | if (retval < 0) { |
191 | dev_err(&pdev->dev, |
192 | "unable to register with transceiver\n" ); |
193 | retval = -ENODEV; |
194 | goto err_disable_clk; |
195 | } |
196 | /* otg will enable clock before use as host */ |
197 | mv_ehci_disable(ehci_mv); |
198 | } else { |
199 | if (ehci_mv->set_vbus) |
200 | ehci_mv->set_vbus(1); |
201 | |
202 | retval = usb_add_hcd(hcd, irqnum: hcd->irq, IRQF_SHARED); |
203 | if (retval) { |
204 | dev_err(&pdev->dev, |
205 | "failed to add hcd with err %d\n" , retval); |
206 | goto err_set_vbus; |
207 | } |
208 | device_wakeup_enable(dev: hcd->self.controller); |
209 | } |
210 | |
211 | if (of_usb_get_phy_mode(np: pdev->dev.of_node) == USBPHY_INTERFACE_MODE_HSIC) { |
212 | status = ehci_readl(ehci, regs: &ehci->regs->port_status[0]); |
213 | /* These "reserved" bits actually enable HSIC mode. */ |
214 | status |= BIT(25); |
215 | status &= ~GENMASK(31, 30); |
216 | ehci_writel(ehci, val: status, regs: &ehci->regs->port_status[0]); |
217 | } |
218 | |
219 | dev_info(&pdev->dev, |
220 | "successful find EHCI device with regs 0x%p irq %d" |
221 | " working in %s mode\n" , hcd->regs, hcd->irq, |
222 | ehci_mv->mode == MV_USB_MODE_OTG ? "OTG" : "Host" ); |
223 | |
224 | return 0; |
225 | |
226 | err_set_vbus: |
227 | if (ehci_mv->set_vbus) |
228 | ehci_mv->set_vbus(0); |
229 | err_disable_clk: |
230 | mv_ehci_disable(ehci_mv); |
231 | err_put_hcd: |
232 | usb_put_hcd(hcd); |
233 | |
234 | return retval; |
235 | } |
236 | |
237 | static void mv_ehci_remove(struct platform_device *pdev) |
238 | { |
239 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
240 | struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd); |
241 | |
242 | if (hcd->rh_registered) |
243 | usb_remove_hcd(hcd); |
244 | |
245 | if (!IS_ERR_OR_NULL(ptr: ehci_mv->otg)) |
246 | otg_set_host(otg: ehci_mv->otg->otg, NULL); |
247 | |
248 | if (ehci_mv->mode == MV_USB_MODE_HOST) { |
249 | if (ehci_mv->set_vbus) |
250 | ehci_mv->set_vbus(0); |
251 | |
252 | mv_ehci_disable(ehci_mv); |
253 | } |
254 | |
255 | usb_put_hcd(hcd); |
256 | } |
257 | |
258 | static const struct platform_device_id ehci_id_table[] = { |
259 | {"pxa-u2oehci" , 0}, |
260 | {"pxa-sph" , 0}, |
261 | {}, |
262 | }; |
263 | |
264 | static void mv_ehci_shutdown(struct platform_device *pdev) |
265 | { |
266 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
267 | |
268 | if (!hcd->rh_registered) |
269 | return; |
270 | |
271 | if (hcd->driver->shutdown) |
272 | hcd->driver->shutdown(hcd); |
273 | } |
274 | |
275 | static const struct of_device_id ehci_mv_dt_ids[] = { |
276 | { .compatible = "marvell,pxau2o-ehci" , }, |
277 | {}, |
278 | }; |
279 | |
280 | static struct platform_driver ehci_mv_driver = { |
281 | .probe = mv_ehci_probe, |
282 | .remove_new = mv_ehci_remove, |
283 | .shutdown = mv_ehci_shutdown, |
284 | .driver = { |
285 | .name = "mv-ehci" , |
286 | .bus = &platform_bus_type, |
287 | .of_match_table = ehci_mv_dt_ids, |
288 | }, |
289 | .id_table = ehci_id_table, |
290 | }; |
291 | |
292 | static int __init ehci_platform_init(void) |
293 | { |
294 | if (usb_disabled()) |
295 | return -ENODEV; |
296 | |
297 | ehci_init_driver(drv: &ehci_platform_hc_driver, over: &platform_overrides); |
298 | return platform_driver_register(&ehci_mv_driver); |
299 | } |
300 | module_init(ehci_platform_init); |
301 | |
302 | static void __exit ehci_platform_cleanup(void) |
303 | { |
304 | platform_driver_unregister(&ehci_mv_driver); |
305 | } |
306 | module_exit(ehci_platform_cleanup); |
307 | |
308 | MODULE_DESCRIPTION("Marvell EHCI driver" ); |
309 | MODULE_AUTHOR("Chao Xie <chao.xie@marvell.com>" ); |
310 | MODULE_AUTHOR("Neil Zhang <zhangwm@marvell.com>" ); |
311 | MODULE_ALIAS("mv-ehci" ); |
312 | MODULE_LICENSE("GPL" ); |
313 | MODULE_DEVICE_TABLE(of, ehci_mv_dt_ids); |
314 | |