Commit | Line | Data |
---|---|---|
c8c38de9 DS |
1 | /* |
2 | * OHCI HCD (Host Controller Driver) for USB. | |
3 | * | |
4 | * Copyright (C) 2010 ST Microelectronics. | |
5 | * Deepak Sikri<deepak.sikri@st.com> | |
6 | * | |
7 | * Based on various ohci-*.c drivers | |
8 | * | |
9 | * This file is licensed under the terms of the GNU General Public | |
10 | * License version 2. This program is licensed "as is" without any | |
11 | * warranty of any kind, whether express or implied. | |
12 | */ | |
13 | ||
c8c38de9 | 14 | #include <linux/clk.h> |
1cc6ac59 MG |
15 | #include <linux/dma-mapping.h> |
16 | #include <linux/io.h> | |
17 | #include <linux/kernel.h> | |
18 | #include <linux/module.h> | |
56fafb94 | 19 | #include <linux/of.h> |
1cc6ac59 MG |
20 | #include <linux/platform_device.h> |
21 | #include <linux/signal.h> | |
22 | #include <linux/usb.h> | |
23 | #include <linux/usb/hcd.h> | |
24 | ||
25 | #include "ohci.h" | |
c8c38de9 | 26 | |
1cc6ac59 MG |
27 | #define DRIVER_DESC "OHCI SPEAr driver" |
28 | ||
29 | static const char hcd_name[] = "SPEAr-ohci"; | |
c8c38de9 | 30 | struct spear_ohci { |
c8c38de9 DS |
31 | struct clk *clk; |
32 | }; | |
33 | ||
1cc6ac59 | 34 | #define to_spear_ohci(hcd) (struct spear_ohci *)(hcd_to_ohci(hcd)->priv) |
c8c38de9 | 35 | |
1cc6ac59 | 36 | static struct hc_driver __read_mostly ohci_spear_hc_driver; |
c8c38de9 DS |
37 | |
38 | static int spear_ohci_hcd_drv_probe(struct platform_device *pdev) | |
39 | { | |
40 | const struct hc_driver *driver = &ohci_spear_hc_driver; | |
1cc6ac59 | 41 | struct ohci_hcd *ohci; |
c8c38de9 DS |
42 | struct usb_hcd *hcd = NULL; |
43 | struct clk *usbh_clk; | |
1cc6ac59 | 44 | struct spear_ohci *sohci_p; |
c8c38de9 DS |
45 | struct resource *res; |
46 | int retval, irq; | |
c8c38de9 DS |
47 | |
48 | irq = platform_get_irq(pdev, 0); | |
49 | if (irq < 0) { | |
50 | retval = irq; | |
98515e59 | 51 | goto fail; |
c8c38de9 DS |
52 | } |
53 | ||
56fafb94 SR |
54 | /* |
55 | * Right now device-tree probed devices don't get dma_mask set. | |
56 | * Since shared usb code relies on it, set it here for now. | |
57 | * Once we have dma capability bindings this can go away. | |
58 | */ | |
e1fd7341 | 59 | retval = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); |
22d9d8e8 RK |
60 | if (retval) |
61 | goto fail; | |
56fafb94 | 62 | |
98515e59 | 63 | usbh_clk = devm_clk_get(&pdev->dev, NULL); |
c8c38de9 DS |
64 | if (IS_ERR(usbh_clk)) { |
65 | dev_err(&pdev->dev, "Error getting interface clock\n"); | |
66 | retval = PTR_ERR(usbh_clk); | |
98515e59 | 67 | goto fail; |
c8c38de9 DS |
68 | } |
69 | ||
70 | hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); | |
71 | if (!hcd) { | |
72 | retval = -ENOMEM; | |
98515e59 | 73 | goto fail; |
c8c38de9 DS |
74 | } |
75 | ||
76 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
09796be1 JH |
77 | hcd->regs = devm_ioremap_resource(&pdev->dev, res); |
78 | if (IS_ERR(hcd->regs)) { | |
79 | retval = PTR_ERR(hcd->regs); | |
98515e59 | 80 | goto err_put_hcd; |
c8c38de9 DS |
81 | } |
82 | ||
0bf80cbf VB |
83 | hcd->rsrc_start = pdev->resource[0].start; |
84 | hcd->rsrc_len = resource_size(res); | |
85 | ||
1cc6ac59 MG |
86 | sohci_p = to_spear_ohci(hcd); |
87 | sohci_p->clk = usbh_clk; | |
88 | ||
89 | clk_prepare_enable(sohci_p->clk); | |
90 | ||
91 | ohci = hcd_to_ohci(hcd); | |
c8c38de9 | 92 | |
b5dd18d8 | 93 | retval = usb_add_hcd(hcd, platform_get_irq(pdev, 0), 0); |
3c9740a1 PC |
94 | if (retval == 0) { |
95 | device_wakeup_enable(hcd->self.controller); | |
c8c38de9 | 96 | return retval; |
3c9740a1 | 97 | } |
c8c38de9 | 98 | |
1cc6ac59 | 99 | clk_disable_unprepare(sohci_p->clk); |
98515e59 | 100 | err_put_hcd: |
c8c38de9 | 101 | usb_put_hcd(hcd); |
98515e59 | 102 | fail: |
c8c38de9 DS |
103 | dev_err(&pdev->dev, "init fail, %d\n", retval); |
104 | ||
105 | return retval; | |
106 | } | |
107 | ||
108 | static int spear_ohci_hcd_drv_remove(struct platform_device *pdev) | |
109 | { | |
110 | struct usb_hcd *hcd = platform_get_drvdata(pdev); | |
1cc6ac59 | 111 | struct spear_ohci *sohci_p = to_spear_ohci(hcd); |
c8c38de9 DS |
112 | |
113 | usb_remove_hcd(hcd); | |
1cc6ac59 MG |
114 | if (sohci_p->clk) |
115 | clk_disable_unprepare(sohci_p->clk); | |
c8c38de9 | 116 | |
c8c38de9 | 117 | usb_put_hcd(hcd); |
c8c38de9 DS |
118 | return 0; |
119 | } | |
120 | ||
121 | #if defined(CONFIG_PM) | |
d2e3d2b3 | 122 | static int spear_ohci_hcd_drv_suspend(struct platform_device *pdev, |
c8c38de9 DS |
123 | pm_message_t message) |
124 | { | |
d2e3d2b3 | 125 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
c8c38de9 | 126 | struct ohci_hcd *ohci = hcd_to_ohci(hcd); |
1cc6ac59 | 127 | struct spear_ohci *sohci_p = to_spear_ohci(hcd); |
d2e3d2b3 MG |
128 | bool do_wakeup = device_may_wakeup(&pdev->dev); |
129 | int ret; | |
c8c38de9 DS |
130 | |
131 | if (time_before(jiffies, ohci->next_statechange)) | |
132 | msleep(5); | |
133 | ohci->next_statechange = jiffies; | |
134 | ||
d2e3d2b3 MG |
135 | ret = ohci_suspend(hcd, do_wakeup); |
136 | if (ret) | |
137 | return ret; | |
138 | ||
1cc6ac59 MG |
139 | clk_disable_unprepare(sohci_p->clk); |
140 | ||
d2e3d2b3 | 141 | return ret; |
c8c38de9 DS |
142 | } |
143 | ||
144 | static int spear_ohci_hcd_drv_resume(struct platform_device *dev) | |
145 | { | |
146 | struct usb_hcd *hcd = platform_get_drvdata(dev); | |
147 | struct ohci_hcd *ohci = hcd_to_ohci(hcd); | |
1cc6ac59 | 148 | struct spear_ohci *sohci_p = to_spear_ohci(hcd); |
c8c38de9 DS |
149 | |
150 | if (time_before(jiffies, ohci->next_statechange)) | |
151 | msleep(5); | |
152 | ohci->next_statechange = jiffies; | |
153 | ||
1cc6ac59 | 154 | clk_prepare_enable(sohci_p->clk); |
cfa49b4b | 155 | ohci_resume(hcd, false); |
c8c38de9 DS |
156 | return 0; |
157 | } | |
158 | #endif | |
159 | ||
10e15d6d | 160 | static const struct of_device_id spear_ohci_id_table[] = { |
56fafb94 SR |
161 | { .compatible = "st,spear600-ohci", }, |
162 | { }, | |
163 | }; | |
04c0b766 | 164 | MODULE_DEVICE_TABLE(of, spear_ohci_id_table); |
56fafb94 | 165 | |
c8c38de9 DS |
166 | /* Driver definition to register with the platform bus */ |
167 | static struct platform_driver spear_ohci_hcd_driver = { | |
168 | .probe = spear_ohci_hcd_drv_probe, | |
169 | .remove = spear_ohci_hcd_drv_remove, | |
170 | #ifdef CONFIG_PM | |
171 | .suspend = spear_ohci_hcd_drv_suspend, | |
172 | .resume = spear_ohci_hcd_drv_resume, | |
173 | #endif | |
174 | .driver = { | |
c8c38de9 | 175 | .name = "spear-ohci", |
c0d6f0b4 | 176 | .of_match_table = spear_ohci_id_table, |
c8c38de9 DS |
177 | }, |
178 | }; | |
179 | ||
1cc6ac59 MG |
180 | static const struct ohci_driver_overrides spear_overrides __initconst = { |
181 | .extra_priv_size = sizeof(struct spear_ohci), | |
182 | }; | |
183 | static int __init ohci_spear_init(void) | |
184 | { | |
185 | if (usb_disabled()) | |
186 | return -ENODEV; | |
187 | ||
188 | pr_info("%s: " DRIVER_DESC "\n", hcd_name); | |
189 | ||
190 | ohci_init_driver(&ohci_spear_hc_driver, &spear_overrides); | |
191 | return platform_driver_register(&spear_ohci_hcd_driver); | |
192 | } | |
193 | module_init(ohci_spear_init); | |
194 | ||
195 | static void __exit ohci_spear_cleanup(void) | |
196 | { | |
197 | platform_driver_unregister(&spear_ohci_hcd_driver); | |
198 | } | |
199 | module_exit(ohci_spear_cleanup); | |
200 | ||
201 | MODULE_DESCRIPTION(DRIVER_DESC); | |
202 | MODULE_AUTHOR("Deepak Sikri"); | |
203 | MODULE_LICENSE("GPL v2"); | |
c8c38de9 | 204 | MODULE_ALIAS("platform:spear-ohci"); |