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