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