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