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
22static 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
32static 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
70static 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
86static 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
100static 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
212exit_dma:
213 if (use_dma)
214 free_dma_pools(dev: udc);
215exit_extcon:
216 if (udc->edev)
217 extcon_unregister_notifier(edev: udc->edev, EXTCON_USB, nb: &udc->nb);
218exit_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
226static 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
252static 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
269static 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}
296static const struct dev_pm_ops udc_plat_pm_ops = {
297 .suspend = udc_plat_suspend,
298 .resume = udc_plat_resume,
299};
300#endif
301
302static 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};
308MODULE_DEVICE_TABLE(of, of_udc_match);
309
310static 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};
321module_platform_driver(udc_plat_driver);
322
323MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION);
324MODULE_AUTHOR("Broadcom");
325MODULE_LICENSE("GPL v2");
326

source code of linux/drivers/usb/gadget/udc/snps_udc_plat.c