xref: /openbmc/linux/drivers/usb/gadget/udc/snps_udc_plat.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
21b9f35adSRaviteja Garimella /*
31b9f35adSRaviteja Garimella  * snps_udc_plat.c - Synopsys UDC Platform Driver
41b9f35adSRaviteja Garimella  *
51b9f35adSRaviteja Garimella  * Copyright (C) 2016 Broadcom
61b9f35adSRaviteja Garimella  */
71b9f35adSRaviteja Garimella 
81b9f35adSRaviteja Garimella #include <linux/extcon.h>
91b9f35adSRaviteja Garimella #include <linux/of_address.h>
101b9f35adSRaviteja Garimella #include <linux/of_irq.h>
111b9f35adSRaviteja Garimella #include <linux/of_gpio.h>
121b9f35adSRaviteja Garimella #include <linux/platform_device.h>
131b9f35adSRaviteja Garimella #include <linux/phy/phy.h>
141b9f35adSRaviteja Garimella #include <linux/module.h>
151b9f35adSRaviteja Garimella #include <linux/dmapool.h>
161b9f35adSRaviteja Garimella #include <linux/interrupt.h>
171b9f35adSRaviteja Garimella #include <linux/moduleparam.h>
181b9f35adSRaviteja Garimella #include "amd5536udc.h"
191b9f35adSRaviteja Garimella 
201b9f35adSRaviteja Garimella /* description */
211b9f35adSRaviteja Garimella #define UDC_MOD_DESCRIPTION     "Synopsys UDC platform driver"
221b9f35adSRaviteja Garimella 
start_udc(struct udc * udc)23ab1d53d5Skbuild test robot static void start_udc(struct udc *udc)
241b9f35adSRaviteja Garimella {
251b9f35adSRaviteja Garimella 	if (udc->driver) {
261b9f35adSRaviteja Garimella 		dev_info(udc->dev, "Connecting...\n");
271b9f35adSRaviteja Garimella 		udc_enable_dev_setup_interrupts(udc);
281b9f35adSRaviteja Garimella 		udc_basic_init(udc);
291b9f35adSRaviteja Garimella 		udc->connected = 1;
301b9f35adSRaviteja Garimella 	}
311b9f35adSRaviteja Garimella }
321b9f35adSRaviteja Garimella 
stop_udc(struct udc * udc)33ab1d53d5Skbuild test robot static void stop_udc(struct udc *udc)
341b9f35adSRaviteja Garimella {
351b9f35adSRaviteja Garimella 	int tmp;
361b9f35adSRaviteja Garimella 	u32 reg;
371b9f35adSRaviteja Garimella 
381b9f35adSRaviteja Garimella 	spin_lock(&udc->lock);
391b9f35adSRaviteja Garimella 
401b9f35adSRaviteja Garimella 	/* Flush the receieve fifo */
411b9f35adSRaviteja Garimella 	reg = readl(&udc->regs->ctl);
421b9f35adSRaviteja Garimella 	reg |= AMD_BIT(UDC_DEVCTL_SRX_FLUSH);
431b9f35adSRaviteja Garimella 	writel(reg, &udc->regs->ctl);
441b9f35adSRaviteja Garimella 
451b9f35adSRaviteja Garimella 	reg = readl(&udc->regs->ctl);
461b9f35adSRaviteja Garimella 	reg &= ~(AMD_BIT(UDC_DEVCTL_SRX_FLUSH));
471b9f35adSRaviteja Garimella 	writel(reg, &udc->regs->ctl);
481b9f35adSRaviteja Garimella 	dev_dbg(udc->dev, "ep rx queue flushed\n");
491b9f35adSRaviteja Garimella 
501b9f35adSRaviteja Garimella 	/* Mask interrupts. Required more so when the
511b9f35adSRaviteja Garimella 	 * UDC is connected to a DRD phy.
521b9f35adSRaviteja Garimella 	 */
531b9f35adSRaviteja Garimella 	udc_mask_unused_interrupts(udc);
541b9f35adSRaviteja Garimella 
551b9f35adSRaviteja Garimella 	/* Disconnect gadget driver */
561b9f35adSRaviteja Garimella 	if (udc->driver) {
571b9f35adSRaviteja Garimella 		spin_unlock(&udc->lock);
581b9f35adSRaviteja Garimella 		udc->driver->disconnect(&udc->gadget);
591b9f35adSRaviteja Garimella 		spin_lock(&udc->lock);
601b9f35adSRaviteja Garimella 
611b9f35adSRaviteja Garimella 		/* empty queues */
621b9f35adSRaviteja Garimella 		for (tmp = 0; tmp < UDC_EP_NUM; tmp++)
631b9f35adSRaviteja Garimella 			empty_req_queue(&udc->ep[tmp]);
641b9f35adSRaviteja Garimella 	}
651b9f35adSRaviteja Garimella 	udc->connected = 0;
661b9f35adSRaviteja Garimella 
671b9f35adSRaviteja Garimella 	spin_unlock(&udc->lock);
681b9f35adSRaviteja Garimella 	dev_info(udc->dev, "Device disconnected\n");
691b9f35adSRaviteja Garimella }
701b9f35adSRaviteja Garimella 
udc_drd_work(struct work_struct * work)71ab1d53d5Skbuild test robot static void udc_drd_work(struct work_struct *work)
721b9f35adSRaviteja Garimella {
731b9f35adSRaviteja Garimella 	struct udc *udc;
741b9f35adSRaviteja Garimella 
751b9f35adSRaviteja Garimella 	udc = container_of(to_delayed_work(work),
761b9f35adSRaviteja Garimella 			   struct udc, drd_work);
771b9f35adSRaviteja Garimella 
781b9f35adSRaviteja Garimella 	if (udc->conn_type) {
791b9f35adSRaviteja Garimella 		dev_dbg(udc->dev, "idle -> device\n");
801b9f35adSRaviteja Garimella 		start_udc(udc);
811b9f35adSRaviteja Garimella 	} else {
821b9f35adSRaviteja Garimella 		dev_dbg(udc->dev, "device -> idle\n");
831b9f35adSRaviteja Garimella 		stop_udc(udc);
841b9f35adSRaviteja Garimella 	}
851b9f35adSRaviteja Garimella }
861b9f35adSRaviteja Garimella 
usbd_connect_notify(struct notifier_block * self,unsigned long event,void * ptr)871b9f35adSRaviteja Garimella static int usbd_connect_notify(struct notifier_block *self,
881b9f35adSRaviteja Garimella 			       unsigned long event, void *ptr)
891b9f35adSRaviteja Garimella {
901b9f35adSRaviteja Garimella 	struct udc *udc = container_of(self, struct udc, nb);
911b9f35adSRaviteja Garimella 
921b9f35adSRaviteja Garimella 	dev_dbg(udc->dev, "%s: event: %lu\n", __func__, event);
931b9f35adSRaviteja Garimella 
941b9f35adSRaviteja Garimella 	udc->conn_type = event;
951b9f35adSRaviteja Garimella 
961b9f35adSRaviteja Garimella 	schedule_delayed_work(&udc->drd_work, 0);
971b9f35adSRaviteja Garimella 
981b9f35adSRaviteja Garimella 	return NOTIFY_OK;
991b9f35adSRaviteja Garimella }
1001b9f35adSRaviteja Garimella 
udc_plat_probe(struct platform_device * pdev)1011b9f35adSRaviteja Garimella static int udc_plat_probe(struct platform_device *pdev)
1021b9f35adSRaviteja Garimella {
1031b9f35adSRaviteja Garimella 	struct device *dev = &pdev->dev;
1041b9f35adSRaviteja Garimella 	struct resource *res;
1051b9f35adSRaviteja Garimella 	struct udc *udc;
1061b9f35adSRaviteja Garimella 	int ret;
1071b9f35adSRaviteja Garimella 
1081b9f35adSRaviteja Garimella 	udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL);
1091b9f35adSRaviteja Garimella 	if (!udc)
1101b9f35adSRaviteja Garimella 		return -ENOMEM;
1111b9f35adSRaviteja Garimella 
1121b9f35adSRaviteja Garimella 	spin_lock_init(&udc->lock);
1131b9f35adSRaviteja Garimella 	udc->dev = dev;
1141b9f35adSRaviteja Garimella 
115effc9916SYangtao Li 	udc->virt_addr = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
1162e3d055bSYang Yingliang 	if (IS_ERR(udc->virt_addr))
1172e3d055bSYang Yingliang 		return PTR_ERR(udc->virt_addr);
1181b9f35adSRaviteja Garimella 
1191b9f35adSRaviteja Garimella 	/* udc csr registers base */
1201b9f35adSRaviteja Garimella 	udc->csr = udc->virt_addr + UDC_CSR_ADDR;
1211b9f35adSRaviteja Garimella 
1221b9f35adSRaviteja Garimella 	/* dev registers base */
1231b9f35adSRaviteja Garimella 	udc->regs = udc->virt_addr + UDC_DEVCFG_ADDR;
1241b9f35adSRaviteja Garimella 
1251b9f35adSRaviteja Garimella 	/* ep registers base */
1261b9f35adSRaviteja Garimella 	udc->ep_regs = udc->virt_addr + UDC_EPREGS_ADDR;
1271b9f35adSRaviteja Garimella 
1281b9f35adSRaviteja Garimella 	/* fifo's base */
1291b9f35adSRaviteja Garimella 	udc->rxfifo = (u32 __iomem *)(udc->virt_addr + UDC_RXFIFO_ADDR);
1301b9f35adSRaviteja Garimella 	udc->txfifo = (u32 __iomem *)(udc->virt_addr + UDC_TXFIFO_ADDR);
1311b9f35adSRaviteja Garimella 
1321b9f35adSRaviteja Garimella 	udc->phys_addr = (unsigned long)res->start;
1331b9f35adSRaviteja Garimella 
1341b9f35adSRaviteja Garimella 	udc->irq = irq_of_parse_and_map(dev->of_node, 0);
1351b9f35adSRaviteja Garimella 	if (udc->irq <= 0) {
1361b9f35adSRaviteja Garimella 		dev_err(dev, "Can't parse and map interrupt\n");
1371b9f35adSRaviteja Garimella 		return -EINVAL;
1381b9f35adSRaviteja Garimella 	}
1391b9f35adSRaviteja Garimella 
1401b9f35adSRaviteja Garimella 	udc->udc_phy = devm_of_phy_get_by_index(dev, dev->of_node, 0);
1411b9f35adSRaviteja Garimella 	if (IS_ERR(udc->udc_phy)) {
1421b9f35adSRaviteja Garimella 		dev_err(dev, "Failed to obtain phy from device tree\n");
1431b9f35adSRaviteja Garimella 		return PTR_ERR(udc->udc_phy);
1441b9f35adSRaviteja Garimella 	}
1451b9f35adSRaviteja Garimella 
1461b9f35adSRaviteja Garimella 	ret = phy_init(udc->udc_phy);
1471b9f35adSRaviteja Garimella 	if (ret) {
1481b9f35adSRaviteja Garimella 		dev_err(dev, "UDC phy init failed");
1491b9f35adSRaviteja Garimella 		return ret;
1501b9f35adSRaviteja Garimella 	}
1511b9f35adSRaviteja Garimella 
1521b9f35adSRaviteja Garimella 	ret = phy_power_on(udc->udc_phy);
1531b9f35adSRaviteja Garimella 	if (ret) {
1541b9f35adSRaviteja Garimella 		dev_err(dev, "UDC phy power on failed");
1551b9f35adSRaviteja Garimella 		phy_exit(udc->udc_phy);
1561b9f35adSRaviteja Garimella 		return ret;
1571b9f35adSRaviteja Garimella 	}
1581b9f35adSRaviteja Garimella 
1591b9f35adSRaviteja Garimella 	/* Register for extcon if supported */
160a3927e1aSRob Herring 	if (of_property_present(dev->of_node, "extcon")) {
1611b9f35adSRaviteja Garimella 		udc->edev = extcon_get_edev_by_phandle(dev, 0);
1621b9f35adSRaviteja Garimella 		if (IS_ERR(udc->edev)) {
1631b9f35adSRaviteja Garimella 			if (PTR_ERR(udc->edev) == -EPROBE_DEFER)
1641b9f35adSRaviteja Garimella 				return -EPROBE_DEFER;
1651b9f35adSRaviteja Garimella 			dev_err(dev, "Invalid or missing extcon\n");
1661b9f35adSRaviteja Garimella 			ret = PTR_ERR(udc->edev);
1671b9f35adSRaviteja Garimella 			goto exit_phy;
1681b9f35adSRaviteja Garimella 		}
1691b9f35adSRaviteja Garimella 
1701b9f35adSRaviteja Garimella 		udc->nb.notifier_call = usbd_connect_notify;
1711b9f35adSRaviteja Garimella 		ret = extcon_register_notifier(udc->edev, EXTCON_USB,
1721b9f35adSRaviteja Garimella 					       &udc->nb);
1731b9f35adSRaviteja Garimella 		if (ret < 0) {
1741b9f35adSRaviteja Garimella 			dev_err(dev, "Can't register extcon device\n");
1751b9f35adSRaviteja Garimella 			goto exit_phy;
1761b9f35adSRaviteja Garimella 		}
1771b9f35adSRaviteja Garimella 
178ee613711SChanwoo Choi 		ret = extcon_get_state(udc->edev, EXTCON_USB);
1791b9f35adSRaviteja Garimella 		if (ret < 0) {
1801b9f35adSRaviteja Garimella 			dev_err(dev, "Can't get cable state\n");
1811b9f35adSRaviteja Garimella 			goto exit_extcon;
1821b9f35adSRaviteja Garimella 		} else if (ret) {
1831b9f35adSRaviteja Garimella 			udc->conn_type = ret;
1841b9f35adSRaviteja Garimella 		}
1851b9f35adSRaviteja Garimella 		INIT_DELAYED_WORK(&udc->drd_work, udc_drd_work);
1861b9f35adSRaviteja Garimella 	}
1871b9f35adSRaviteja Garimella 
1881b9f35adSRaviteja Garimella 	/* init dma pools */
1891b9f35adSRaviteja Garimella 	if (use_dma) {
1901b9f35adSRaviteja Garimella 		ret = init_dma_pools(udc);
1911b9f35adSRaviteja Garimella 		if (ret != 0)
1921b9f35adSRaviteja Garimella 			goto exit_extcon;
1931b9f35adSRaviteja Garimella 	}
1941b9f35adSRaviteja Garimella 
1951b9f35adSRaviteja Garimella 	ret = devm_request_irq(dev, udc->irq, udc_irq, IRQF_SHARED,
1961b9f35adSRaviteja Garimella 			       "snps-udc", udc);
1971b9f35adSRaviteja Garimella 	if (ret < 0) {
1981b9f35adSRaviteja Garimella 		dev_err(dev, "Request irq %d failed for UDC\n", udc->irq);
1991b9f35adSRaviteja Garimella 		goto exit_dma;
2001b9f35adSRaviteja Garimella 	}
2011b9f35adSRaviteja Garimella 
2021b9f35adSRaviteja Garimella 	platform_set_drvdata(pdev, udc);
2031b9f35adSRaviteja Garimella 	udc->chiprev = UDC_BCM_REV;
2041b9f35adSRaviteja Garimella 
2051b9f35adSRaviteja Garimella 	if (udc_probe(udc)) {
2061b9f35adSRaviteja Garimella 		ret = -ENODEV;
2071b9f35adSRaviteja Garimella 		goto exit_dma;
2081b9f35adSRaviteja Garimella 	}
2091b9f35adSRaviteja Garimella 	dev_info(dev, "Synopsys UDC platform driver probe successful\n");
2101b9f35adSRaviteja Garimella 
2111b9f35adSRaviteja Garimella 	return 0;
2121b9f35adSRaviteja Garimella 
2131b9f35adSRaviteja Garimella exit_dma:
2141b9f35adSRaviteja Garimella 	if (use_dma)
2151b9f35adSRaviteja Garimella 		free_dma_pools(udc);
2161b9f35adSRaviteja Garimella exit_extcon:
2171b9f35adSRaviteja Garimella 	if (udc->edev)
2181b9f35adSRaviteja Garimella 		extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb);
2191b9f35adSRaviteja Garimella exit_phy:
2201b9f35adSRaviteja Garimella 	if (udc->udc_phy) {
2211b9f35adSRaviteja Garimella 		phy_power_off(udc->udc_phy);
2221b9f35adSRaviteja Garimella 		phy_exit(udc->udc_phy);
2231b9f35adSRaviteja Garimella 	}
2241b9f35adSRaviteja Garimella 	return ret;
2251b9f35adSRaviteja Garimella }
2261b9f35adSRaviteja Garimella 
udc_plat_remove(struct platform_device * pdev)227dad23c87SUwe Kleine-König static void udc_plat_remove(struct platform_device *pdev)
2281b9f35adSRaviteja Garimella {
2291b9f35adSRaviteja Garimella 	struct udc *dev;
2301b9f35adSRaviteja Garimella 
2311b9f35adSRaviteja Garimella 	dev = platform_get_drvdata(pdev);
2321b9f35adSRaviteja Garimella 
2331b9f35adSRaviteja Garimella 	usb_del_gadget_udc(&dev->gadget);
2341b9f35adSRaviteja Garimella 	/* gadget driver must not be registered */
2351b9f35adSRaviteja Garimella 	if (WARN_ON(dev->driver))
236dad23c87SUwe Kleine-König 		return;
2371b9f35adSRaviteja Garimella 
2381b9f35adSRaviteja Garimella 	/* dma pool cleanup */
2391b9f35adSRaviteja Garimella 	free_dma_pools(dev);
2401b9f35adSRaviteja Garimella 
2411b9f35adSRaviteja Garimella 	udc_remove(dev);
2421b9f35adSRaviteja Garimella 
2431b9f35adSRaviteja Garimella 	platform_set_drvdata(pdev, NULL);
2441b9f35adSRaviteja Garimella 
2451b9f35adSRaviteja Garimella 	phy_power_off(dev->udc_phy);
2461b9f35adSRaviteja Garimella 	phy_exit(dev->udc_phy);
2471b9f35adSRaviteja Garimella 	extcon_unregister_notifier(dev->edev, EXTCON_USB, &dev->nb);
2481b9f35adSRaviteja Garimella 
2491b9f35adSRaviteja Garimella 	dev_info(&pdev->dev, "Synopsys UDC platform driver removed\n");
2501b9f35adSRaviteja Garimella }
2511b9f35adSRaviteja Garimella 
2521b9f35adSRaviteja Garimella #ifdef CONFIG_PM_SLEEP
udc_plat_suspend(struct device * dev)2531b9f35adSRaviteja Garimella static int udc_plat_suspend(struct device *dev)
2541b9f35adSRaviteja Garimella {
2551b9f35adSRaviteja Garimella 	struct udc *udc;
2561b9f35adSRaviteja Garimella 
2571b9f35adSRaviteja Garimella 	udc = dev_get_drvdata(dev);
2581b9f35adSRaviteja Garimella 	stop_udc(udc);
2591b9f35adSRaviteja Garimella 
260ee613711SChanwoo Choi 	if (extcon_get_state(udc->edev, EXTCON_USB) > 0) {
2611b9f35adSRaviteja Garimella 		dev_dbg(udc->dev, "device -> idle\n");
2621b9f35adSRaviteja Garimella 		stop_udc(udc);
2631b9f35adSRaviteja Garimella 	}
2641b9f35adSRaviteja Garimella 	phy_power_off(udc->udc_phy);
2651b9f35adSRaviteja Garimella 	phy_exit(udc->udc_phy);
2661b9f35adSRaviteja Garimella 
2671b9f35adSRaviteja Garimella 	return 0;
2681b9f35adSRaviteja Garimella }
2691b9f35adSRaviteja Garimella 
udc_plat_resume(struct device * dev)2701b9f35adSRaviteja Garimella static int udc_plat_resume(struct device *dev)
2711b9f35adSRaviteja Garimella {
2721b9f35adSRaviteja Garimella 	struct udc *udc;
2731b9f35adSRaviteja Garimella 	int ret;
2741b9f35adSRaviteja Garimella 
2751b9f35adSRaviteja Garimella 	udc = dev_get_drvdata(dev);
2761b9f35adSRaviteja Garimella 
2771b9f35adSRaviteja Garimella 	ret = phy_init(udc->udc_phy);
2781b9f35adSRaviteja Garimella 	if (ret) {
2791b9f35adSRaviteja Garimella 		dev_err(udc->dev, "UDC phy init failure");
2801b9f35adSRaviteja Garimella 		return ret;
2811b9f35adSRaviteja Garimella 	}
2821b9f35adSRaviteja Garimella 
2831b9f35adSRaviteja Garimella 	ret = phy_power_on(udc->udc_phy);
2841b9f35adSRaviteja Garimella 	if (ret) {
2851b9f35adSRaviteja Garimella 		dev_err(udc->dev, "UDC phy power on failure");
2861b9f35adSRaviteja Garimella 		phy_exit(udc->udc_phy);
2871b9f35adSRaviteja Garimella 		return ret;
2881b9f35adSRaviteja Garimella 	}
2891b9f35adSRaviteja Garimella 
290ee613711SChanwoo Choi 	if (extcon_get_state(udc->edev, EXTCON_USB) > 0) {
2911b9f35adSRaviteja Garimella 		dev_dbg(udc->dev, "idle -> device\n");
2921b9f35adSRaviteja Garimella 		start_udc(udc);
2931b9f35adSRaviteja Garimella 	}
2941b9f35adSRaviteja Garimella 
2951b9f35adSRaviteja Garimella 	return 0;
2961b9f35adSRaviteja Garimella }
2971b9f35adSRaviteja Garimella static const struct dev_pm_ops udc_plat_pm_ops = {
2981b9f35adSRaviteja Garimella 	.suspend	= udc_plat_suspend,
2991b9f35adSRaviteja Garimella 	.resume		= udc_plat_resume,
3001b9f35adSRaviteja Garimella };
3011b9f35adSRaviteja Garimella #endif
3021b9f35adSRaviteja Garimella 
3031b9f35adSRaviteja Garimella static const struct of_device_id of_udc_match[] = {
3041b9f35adSRaviteja Garimella 	{ .compatible = "brcm,ns2-udc", },
3051b9f35adSRaviteja Garimella 	{ .compatible = "brcm,cygnus-udc", },
3061b9f35adSRaviteja Garimella 	{ .compatible = "brcm,iproc-udc", },
3071b9f35adSRaviteja Garimella 	{ }
3081b9f35adSRaviteja Garimella };
3091b9f35adSRaviteja Garimella MODULE_DEVICE_TABLE(of, of_udc_match);
3101b9f35adSRaviteja Garimella 
3111b9f35adSRaviteja Garimella static struct platform_driver udc_plat_driver = {
3121b9f35adSRaviteja Garimella 	.probe		= udc_plat_probe,
313dad23c87SUwe Kleine-König 	.remove_new	= udc_plat_remove,
3141b9f35adSRaviteja Garimella 	.driver		= {
3151b9f35adSRaviteja Garimella 		.name	= "snps-udc-plat",
316*bb8dc3dfSRuan Jinjie 		.of_match_table = of_udc_match,
3171b9f35adSRaviteja Garimella #ifdef CONFIG_PM_SLEEP
3181b9f35adSRaviteja Garimella 		.pm	= &udc_plat_pm_ops,
3191b9f35adSRaviteja Garimella #endif
3201b9f35adSRaviteja Garimella 	},
3211b9f35adSRaviteja Garimella };
3221b9f35adSRaviteja Garimella module_platform_driver(udc_plat_driver);
3231b9f35adSRaviteja Garimella 
3241b9f35adSRaviteja Garimella MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION);
3251b9f35adSRaviteja Garimella MODULE_AUTHOR("Broadcom");
3261b9f35adSRaviteja Garimella MODULE_LICENSE("GPL v2");
327