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