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