]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blame - drivers/usb/gadget/udc/snps_udc_plat.c
Merge tag 'for-linus-20170825' of git://git.infradead.org/linux-mtd
[mirror_ubuntu-artful-kernel.git] / drivers / usb / gadget / udc / snps_udc_plat.c
CommitLineData
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
ab1d53d5 31static void start_udc(struct udc *udc)
1b9f35ad
RG
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
ab1d53d5 41static void stop_udc(struct udc *udc)
1b9f35ad
RG
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
ab1d53d5 79static void udc_drd_work(struct work_struct *work)
1b9f35ad
RG
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
95static 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
109static 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
222exit_dma:
223 if (use_dma)
224 free_dma_pools(udc);
225exit_extcon:
226 if (udc->edev)
227 extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb);
228exit_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
236static 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
269static 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
286static 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}
313static 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)
320static 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};
326MODULE_DEVICE_TABLE(of, of_udc_match);
327#endif
328
329static 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};
340module_platform_driver(udc_plat_driver);
341
342MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION);
343MODULE_AUTHOR("Broadcom");
344MODULE_LICENSE("GPL v2");