]>
Commit | Line | Data |
---|---|---|
1b9f35ad RG |
1 | /* |
2 | * snps_udc_plat.c - Synopsys UDC Platform Driver | |
3 | * | |
4 | * Copyright (C) 2016 Broadcom | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU General Public License as | |
8 | * published by the Free Software Foundation version 2. | |
9 | * | |
10 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | |
11 | * kind, whether express or implied; without even the implied warranty | |
12 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | */ | |
15 | ||
16 | #include <linux/extcon.h> | |
17 | #include <linux/of_address.h> | |
18 | #include <linux/of_irq.h> | |
19 | #include <linux/of_gpio.h> | |
20 | #include <linux/platform_device.h> | |
21 | #include <linux/phy/phy.h> | |
22 | #include <linux/module.h> | |
23 | #include <linux/dmapool.h> | |
24 | #include <linux/interrupt.h> | |
25 | #include <linux/moduleparam.h> | |
26 | #include "amd5536udc.h" | |
27 | ||
28 | /* description */ | |
29 | #define UDC_MOD_DESCRIPTION "Synopsys UDC platform driver" | |
30 | ||
31 | void start_udc(struct udc *udc) | |
32 | { | |
33 | if (udc->driver) { | |
34 | dev_info(udc->dev, "Connecting...\n"); | |
35 | udc_enable_dev_setup_interrupts(udc); | |
36 | udc_basic_init(udc); | |
37 | udc->connected = 1; | |
38 | } | |
39 | } | |
40 | ||
41 | void stop_udc(struct udc *udc) | |
42 | { | |
43 | int tmp; | |
44 | u32 reg; | |
45 | ||
46 | spin_lock(&udc->lock); | |
47 | ||
48 | /* Flush the receieve fifo */ | |
49 | reg = readl(&udc->regs->ctl); | |
50 | reg |= AMD_BIT(UDC_DEVCTL_SRX_FLUSH); | |
51 | writel(reg, &udc->regs->ctl); | |
52 | ||
53 | reg = readl(&udc->regs->ctl); | |
54 | reg &= ~(AMD_BIT(UDC_DEVCTL_SRX_FLUSH)); | |
55 | writel(reg, &udc->regs->ctl); | |
56 | dev_dbg(udc->dev, "ep rx queue flushed\n"); | |
57 | ||
58 | /* Mask interrupts. Required more so when the | |
59 | * UDC is connected to a DRD phy. | |
60 | */ | |
61 | udc_mask_unused_interrupts(udc); | |
62 | ||
63 | /* Disconnect gadget driver */ | |
64 | if (udc->driver) { | |
65 | spin_unlock(&udc->lock); | |
66 | udc->driver->disconnect(&udc->gadget); | |
67 | spin_lock(&udc->lock); | |
68 | ||
69 | /* empty queues */ | |
70 | for (tmp = 0; tmp < UDC_EP_NUM; tmp++) | |
71 | empty_req_queue(&udc->ep[tmp]); | |
72 | } | |
73 | udc->connected = 0; | |
74 | ||
75 | spin_unlock(&udc->lock); | |
76 | dev_info(udc->dev, "Device disconnected\n"); | |
77 | } | |
78 | ||
79 | void udc_drd_work(struct work_struct *work) | |
80 | { | |
81 | struct udc *udc; | |
82 | ||
83 | udc = container_of(to_delayed_work(work), | |
84 | struct udc, drd_work); | |
85 | ||
86 | if (udc->conn_type) { | |
87 | dev_dbg(udc->dev, "idle -> device\n"); | |
88 | start_udc(udc); | |
89 | } else { | |
90 | dev_dbg(udc->dev, "device -> idle\n"); | |
91 | stop_udc(udc); | |
92 | } | |
93 | } | |
94 | ||
95 | static int usbd_connect_notify(struct notifier_block *self, | |
96 | unsigned long event, void *ptr) | |
97 | { | |
98 | struct udc *udc = container_of(self, struct udc, nb); | |
99 | ||
100 | dev_dbg(udc->dev, "%s: event: %lu\n", __func__, event); | |
101 | ||
102 | udc->conn_type = event; | |
103 | ||
104 | schedule_delayed_work(&udc->drd_work, 0); | |
105 | ||
106 | return NOTIFY_OK; | |
107 | } | |
108 | ||
109 | static int udc_plat_probe(struct platform_device *pdev) | |
110 | { | |
111 | struct device *dev = &pdev->dev; | |
112 | struct resource *res; | |
113 | struct udc *udc; | |
114 | int ret; | |
115 | ||
116 | udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL); | |
117 | if (!udc) | |
118 | return -ENOMEM; | |
119 | ||
120 | spin_lock_init(&udc->lock); | |
121 | udc->dev = dev; | |
122 | ||
123 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
124 | udc->virt_addr = devm_ioremap_resource(dev, res); | |
125 | if (IS_ERR(udc->regs)) | |
126 | return PTR_ERR(udc->regs); | |
127 | ||
128 | /* udc csr registers base */ | |
129 | udc->csr = udc->virt_addr + UDC_CSR_ADDR; | |
130 | ||
131 | /* dev registers base */ | |
132 | udc->regs = udc->virt_addr + UDC_DEVCFG_ADDR; | |
133 | ||
134 | /* ep registers base */ | |
135 | udc->ep_regs = udc->virt_addr + UDC_EPREGS_ADDR; | |
136 | ||
137 | /* fifo's base */ | |
138 | udc->rxfifo = (u32 __iomem *)(udc->virt_addr + UDC_RXFIFO_ADDR); | |
139 | udc->txfifo = (u32 __iomem *)(udc->virt_addr + UDC_TXFIFO_ADDR); | |
140 | ||
141 | udc->phys_addr = (unsigned long)res->start; | |
142 | ||
143 | udc->irq = irq_of_parse_and_map(dev->of_node, 0); | |
144 | if (udc->irq <= 0) { | |
145 | dev_err(dev, "Can't parse and map interrupt\n"); | |
146 | return -EINVAL; | |
147 | } | |
148 | ||
149 | udc->udc_phy = devm_of_phy_get_by_index(dev, dev->of_node, 0); | |
150 | if (IS_ERR(udc->udc_phy)) { | |
151 | dev_err(dev, "Failed to obtain phy from device tree\n"); | |
152 | return PTR_ERR(udc->udc_phy); | |
153 | } | |
154 | ||
155 | ret = phy_init(udc->udc_phy); | |
156 | if (ret) { | |
157 | dev_err(dev, "UDC phy init failed"); | |
158 | return ret; | |
159 | } | |
160 | ||
161 | ret = phy_power_on(udc->udc_phy); | |
162 | if (ret) { | |
163 | dev_err(dev, "UDC phy power on failed"); | |
164 | phy_exit(udc->udc_phy); | |
165 | return ret; | |
166 | } | |
167 | ||
168 | /* Register for extcon if supported */ | |
169 | if (of_get_property(dev->of_node, "extcon", NULL)) { | |
170 | udc->edev = extcon_get_edev_by_phandle(dev, 0); | |
171 | if (IS_ERR(udc->edev)) { | |
172 | if (PTR_ERR(udc->edev) == -EPROBE_DEFER) | |
173 | return -EPROBE_DEFER; | |
174 | dev_err(dev, "Invalid or missing extcon\n"); | |
175 | ret = PTR_ERR(udc->edev); | |
176 | goto exit_phy; | |
177 | } | |
178 | ||
179 | udc->nb.notifier_call = usbd_connect_notify; | |
180 | ret = extcon_register_notifier(udc->edev, EXTCON_USB, | |
181 | &udc->nb); | |
182 | if (ret < 0) { | |
183 | dev_err(dev, "Can't register extcon device\n"); | |
184 | goto exit_phy; | |
185 | } | |
186 | ||
187 | ret = extcon_get_cable_state_(udc->edev, EXTCON_USB); | |
188 | if (ret < 0) { | |
189 | dev_err(dev, "Can't get cable state\n"); | |
190 | goto exit_extcon; | |
191 | } else if (ret) { | |
192 | udc->conn_type = ret; | |
193 | } | |
194 | INIT_DELAYED_WORK(&udc->drd_work, udc_drd_work); | |
195 | } | |
196 | ||
197 | /* init dma pools */ | |
198 | if (use_dma) { | |
199 | ret = init_dma_pools(udc); | |
200 | if (ret != 0) | |
201 | goto exit_extcon; | |
202 | } | |
203 | ||
204 | ret = devm_request_irq(dev, udc->irq, udc_irq, IRQF_SHARED, | |
205 | "snps-udc", udc); | |
206 | if (ret < 0) { | |
207 | dev_err(dev, "Request irq %d failed for UDC\n", udc->irq); | |
208 | goto exit_dma; | |
209 | } | |
210 | ||
211 | platform_set_drvdata(pdev, udc); | |
212 | udc->chiprev = UDC_BCM_REV; | |
213 | ||
214 | if (udc_probe(udc)) { | |
215 | ret = -ENODEV; | |
216 | goto exit_dma; | |
217 | } | |
218 | dev_info(dev, "Synopsys UDC platform driver probe successful\n"); | |
219 | ||
220 | return 0; | |
221 | ||
222 | exit_dma: | |
223 | if (use_dma) | |
224 | free_dma_pools(udc); | |
225 | exit_extcon: | |
226 | if (udc->edev) | |
227 | extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb); | |
228 | exit_phy: | |
229 | if (udc->udc_phy) { | |
230 | phy_power_off(udc->udc_phy); | |
231 | phy_exit(udc->udc_phy); | |
232 | } | |
233 | return ret; | |
234 | } | |
235 | ||
236 | static int udc_plat_remove(struct platform_device *pdev) | |
237 | { | |
238 | struct udc *dev; | |
239 | ||
240 | dev = platform_get_drvdata(pdev); | |
241 | ||
242 | usb_del_gadget_udc(&dev->gadget); | |
243 | /* gadget driver must not be registered */ | |
244 | if (WARN_ON(dev->driver)) | |
245 | return 0; | |
246 | ||
247 | /* dma pool cleanup */ | |
248 | free_dma_pools(dev); | |
249 | ||
250 | udc_remove(dev); | |
251 | ||
252 | platform_set_drvdata(pdev, NULL); | |
253 | ||
254 | if (dev->drd_wq) { | |
255 | flush_workqueue(dev->drd_wq); | |
256 | destroy_workqueue(dev->drd_wq); | |
257 | } | |
258 | ||
259 | phy_power_off(dev->udc_phy); | |
260 | phy_exit(dev->udc_phy); | |
261 | extcon_unregister_notifier(dev->edev, EXTCON_USB, &dev->nb); | |
262 | ||
263 | dev_info(&pdev->dev, "Synopsys UDC platform driver removed\n"); | |
264 | ||
265 | return 0; | |
266 | } | |
267 | ||
268 | #ifdef CONFIG_PM_SLEEP | |
269 | static int udc_plat_suspend(struct device *dev) | |
270 | { | |
271 | struct udc *udc; | |
272 | ||
273 | udc = dev_get_drvdata(dev); | |
274 | stop_udc(udc); | |
275 | ||
276 | if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) { | |
277 | dev_dbg(udc->dev, "device -> idle\n"); | |
278 | stop_udc(udc); | |
279 | } | |
280 | phy_power_off(udc->udc_phy); | |
281 | phy_exit(udc->udc_phy); | |
282 | ||
283 | return 0; | |
284 | } | |
285 | ||
286 | static int udc_plat_resume(struct device *dev) | |
287 | { | |
288 | struct udc *udc; | |
289 | int ret; | |
290 | ||
291 | udc = dev_get_drvdata(dev); | |
292 | ||
293 | ret = phy_init(udc->udc_phy); | |
294 | if (ret) { | |
295 | dev_err(udc->dev, "UDC phy init failure"); | |
296 | return ret; | |
297 | } | |
298 | ||
299 | ret = phy_power_on(udc->udc_phy); | |
300 | if (ret) { | |
301 | dev_err(udc->dev, "UDC phy power on failure"); | |
302 | phy_exit(udc->udc_phy); | |
303 | return ret; | |
304 | } | |
305 | ||
306 | if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) { | |
307 | dev_dbg(udc->dev, "idle -> device\n"); | |
308 | start_udc(udc); | |
309 | } | |
310 | ||
311 | return 0; | |
312 | } | |
313 | static const struct dev_pm_ops udc_plat_pm_ops = { | |
314 | .suspend = udc_plat_suspend, | |
315 | .resume = udc_plat_resume, | |
316 | }; | |
317 | #endif | |
318 | ||
319 | #if defined(CONFIG_OF) | |
320 | static const struct of_device_id of_udc_match[] = { | |
321 | { .compatible = "brcm,ns2-udc", }, | |
322 | { .compatible = "brcm,cygnus-udc", }, | |
323 | { .compatible = "brcm,iproc-udc", }, | |
324 | { } | |
325 | }; | |
326 | MODULE_DEVICE_TABLE(of, of_udc_match); | |
327 | #endif | |
328 | ||
329 | static struct platform_driver udc_plat_driver = { | |
330 | .probe = udc_plat_probe, | |
331 | .remove = udc_plat_remove, | |
332 | .driver = { | |
333 | .name = "snps-udc-plat", | |
334 | .of_match_table = of_match_ptr(of_udc_match), | |
335 | #ifdef CONFIG_PM_SLEEP | |
336 | .pm = &udc_plat_pm_ops, | |
337 | #endif | |
338 | }, | |
339 | }; | |
340 | module_platform_driver(udc_plat_driver); | |
341 | ||
342 | MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION); | |
343 | MODULE_AUTHOR("Broadcom"); | |
344 | MODULE_LICENSE("GPL v2"); |