]>
Commit | Line | Data |
---|---|---|
5fd54ace | 1 | // SPDX-License-Identifier: GPL-2.0 |
c8c38de9 | 2 | /* |
7675d6ba | 3 | * Driver for EHCI HCD on SPEAr SOC |
c8c38de9 DS |
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 | * This file is subject to the terms and conditions of the GNU General Public | |
11 | * License. See the file COPYING in the main directory of this archive for | |
12 | * more details. | |
13 | */ | |
14 | ||
c8c38de9 | 15 | #include <linux/clk.h> |
7675d6ba MG |
16 | #include <linux/dma-mapping.h> |
17 | #include <linux/io.h> | |
8c1b3693 | 18 | #include <linux/jiffies.h> |
7675d6ba MG |
19 | #include <linux/kernel.h> |
20 | #include <linux/module.h> | |
56fafb94 | 21 | #include <linux/of.h> |
8c1b3693 DS |
22 | #include <linux/platform_device.h> |
23 | #include <linux/pm.h> | |
7675d6ba MG |
24 | #include <linux/usb.h> |
25 | #include <linux/usb/hcd.h> | |
c8c38de9 | 26 | |
7675d6ba | 27 | #include "ehci.h" |
c8c38de9 | 28 | |
7675d6ba | 29 | #define DRIVER_DESC "EHCI SPEAr driver" |
c8c38de9 | 30 | |
7675d6ba | 31 | static const char hcd_name[] = "SPEAr-ehci"; |
c8c38de9 | 32 | |
7675d6ba MG |
33 | struct spear_ehci { |
34 | struct clk *clk; | |
35 | }; | |
c8c38de9 | 36 | |
7675d6ba | 37 | #define to_spear_ehci(hcd) (struct spear_ehci *)(hcd_to_ehci(hcd)->priv) |
c8c38de9 | 38 | |
7675d6ba | 39 | static struct hc_driver __read_mostly ehci_spear_hc_driver; |
c8c38de9 | 40 | |
ab1f046a | 41 | #ifdef CONFIG_PM_SLEEP |
8c1b3693 DS |
42 | static int ehci_spear_drv_suspend(struct device *dev) |
43 | { | |
44 | struct usb_hcd *hcd = dev_get_drvdata(dev); | |
c5cf9212 AS |
45 | bool do_wakeup = device_may_wakeup(dev); |
46 | ||
47 | return ehci_suspend(hcd, do_wakeup); | |
8c1b3693 DS |
48 | } |
49 | ||
50 | static int ehci_spear_drv_resume(struct device *dev) | |
51 | { | |
52 | struct usb_hcd *hcd = dev_get_drvdata(dev); | |
8c1b3693 | 53 | |
c5cf9212 | 54 | ehci_resume(hcd, false); |
8c1b3693 DS |
55 | return 0; |
56 | } | |
ab1f046a | 57 | #endif /* CONFIG_PM_SLEEP */ |
8c1b3693 DS |
58 | |
59 | static SIMPLE_DEV_PM_OPS(ehci_spear_pm_ops, ehci_spear_drv_suspend, | |
60 | ehci_spear_drv_resume); | |
61 | ||
c8c38de9 DS |
62 | static int spear_ehci_hcd_drv_probe(struct platform_device *pdev) |
63 | { | |
64 | struct usb_hcd *hcd ; | |
7675d6ba | 65 | struct spear_ehci *sehci; |
c8c38de9 DS |
66 | struct resource *res; |
67 | struct clk *usbh_clk; | |
68 | const struct hc_driver *driver = &ehci_spear_hc_driver; | |
c8c38de9 | 69 | int irq, retval; |
c8c38de9 DS |
70 | |
71 | if (usb_disabled()) | |
72 | return -ENODEV; | |
73 | ||
74 | irq = platform_get_irq(pdev, 0); | |
75 | if (irq < 0) { | |
76 | retval = irq; | |
98515e59 | 77 | goto fail; |
c8c38de9 DS |
78 | } |
79 | ||
56fafb94 SR |
80 | /* |
81 | * Right now device-tree probed devices don't get dma_mask set. | |
82 | * Since shared usb code relies on it, set it here for now. | |
83 | * Once we have dma capability bindings this can go away. | |
84 | */ | |
e1fd7341 | 85 | retval = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); |
22d9d8e8 RK |
86 | if (retval) |
87 | goto fail; | |
56fafb94 | 88 | |
98515e59 | 89 | usbh_clk = devm_clk_get(&pdev->dev, NULL); |
c8c38de9 DS |
90 | if (IS_ERR(usbh_clk)) { |
91 | dev_err(&pdev->dev, "Error getting interface clock\n"); | |
92 | retval = PTR_ERR(usbh_clk); | |
98515e59 | 93 | goto fail; |
c8c38de9 DS |
94 | } |
95 | ||
96 | hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); | |
97 | if (!hcd) { | |
98 | retval = -ENOMEM; | |
98515e59 | 99 | goto fail; |
c8c38de9 DS |
100 | } |
101 | ||
102 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
e0f77a91 VG |
103 | hcd->regs = devm_ioremap_resource(&pdev->dev, res); |
104 | if (IS_ERR(hcd->regs)) { | |
105 | retval = PTR_ERR(hcd->regs); | |
98515e59 | 106 | goto err_put_hcd; |
c8c38de9 | 107 | } |
3b59d31d VB |
108 | hcd->rsrc_start = res->start; |
109 | hcd->rsrc_len = resource_size(res); | |
c8c38de9 | 110 | |
7675d6ba MG |
111 | sehci = to_spear_ehci(hcd); |
112 | sehci->clk = usbh_clk; | |
113 | ||
114 | /* registers start at offset 0x0 */ | |
115 | hcd_to_ehci(hcd)->caps = hcd->regs; | |
c8c38de9 | 116 | |
7675d6ba | 117 | clk_prepare_enable(sehci->clk); |
b5dd18d8 | 118 | retval = usb_add_hcd(hcd, irq, IRQF_SHARED); |
c8c38de9 | 119 | if (retval) |
98515e59 | 120 | goto err_stop_ehci; |
c8c38de9 | 121 | |
3c9740a1 | 122 | device_wakeup_enable(hcd->self.controller); |
c8c38de9 DS |
123 | return retval; |
124 | ||
98515e59 | 125 | err_stop_ehci: |
7675d6ba | 126 | clk_disable_unprepare(sehci->clk); |
98515e59 | 127 | err_put_hcd: |
c8c38de9 | 128 | usb_put_hcd(hcd); |
98515e59 | 129 | fail: |
c8c38de9 DS |
130 | dev_err(&pdev->dev, "init fail, %d\n", retval); |
131 | ||
132 | return retval ; | |
133 | } | |
134 | ||
135 | static int spear_ehci_hcd_drv_remove(struct platform_device *pdev) | |
136 | { | |
137 | struct usb_hcd *hcd = platform_get_drvdata(pdev); | |
7675d6ba | 138 | struct spear_ehci *sehci = to_spear_ehci(hcd); |
c8c38de9 | 139 | |
c8c38de9 DS |
140 | usb_remove_hcd(hcd); |
141 | ||
7675d6ba MG |
142 | if (sehci->clk) |
143 | clk_disable_unprepare(sehci->clk); | |
c8c38de9 DS |
144 | usb_put_hcd(hcd); |
145 | ||
c8c38de9 DS |
146 | return 0; |
147 | } | |
148 | ||
3632eba5 | 149 | static const struct of_device_id spear_ehci_id_table[] = { |
56fafb94 SR |
150 | { .compatible = "st,spear600-ehci", }, |
151 | { }, | |
152 | }; | |
e76eaefd | 153 | MODULE_DEVICE_TABLE(of, spear_ehci_id_table); |
56fafb94 | 154 | |
c8c38de9 DS |
155 | static struct platform_driver spear_ehci_hcd_driver = { |
156 | .probe = spear_ehci_hcd_drv_probe, | |
157 | .remove = spear_ehci_hcd_drv_remove, | |
158 | .shutdown = usb_hcd_platform_shutdown, | |
159 | .driver = { | |
160 | .name = "spear-ehci", | |
8c1b3693 DS |
161 | .bus = &platform_bus_type, |
162 | .pm = &ehci_spear_pm_ops, | |
2c398f3e | 163 | .of_match_table = spear_ehci_id_table, |
c8c38de9 DS |
164 | } |
165 | }; | |
166 | ||
edc8c54b | 167 | static const struct ehci_driver_overrides spear_overrides __initconst = { |
7675d6ba MG |
168 | .extra_priv_size = sizeof(struct spear_ehci), |
169 | }; | |
170 | ||
171 | static int __init ehci_spear_init(void) | |
172 | { | |
173 | if (usb_disabled()) | |
174 | return -ENODEV; | |
175 | ||
176 | pr_info("%s: " DRIVER_DESC "\n", hcd_name); | |
177 | ||
178 | ehci_init_driver(&ehci_spear_hc_driver, &spear_overrides); | |
179 | return platform_driver_register(&spear_ehci_hcd_driver); | |
180 | } | |
181 | module_init(ehci_spear_init); | |
182 | ||
183 | static void __exit ehci_spear_cleanup(void) | |
184 | { | |
185 | platform_driver_unregister(&spear_ehci_hcd_driver); | |
186 | } | |
187 | module_exit(ehci_spear_cleanup); | |
188 | ||
189 | MODULE_DESCRIPTION(DRIVER_DESC); | |
c8c38de9 | 190 | MODULE_ALIAS("platform:spear-ehci"); |
7675d6ba MG |
191 | MODULE_AUTHOR("Deepak Sikri"); |
192 | MODULE_LICENSE("GPL"); |