-- Add OF based platform device registration -- Modify debug prints to be compatible with both pci and platform devices -- Add members to 'struct udc' for extcon and phy support -- Add checks to not process repeated calls to udc connect and disconnect routines -- Kconfig changes Signed-off-by: Raviteja Garimella <raviteja.garimella@xxxxxxxxxxxx> --- drivers/usb/gadget/udc/Kconfig | 6 +- drivers/usb/gadget/udc/amd5536udc.c | 397 +++++++++++++++++++++++++++++++++--- drivers/usb/gadget/udc/amd5536udc.h | 17 +- 3 files changed, 387 insertions(+), 33 deletions(-) diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 658b8da..7728fec 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -263,7 +263,7 @@ source "drivers/usb/gadget/udc/bdc/Kconfig" config USB_AMD5536UDC tristate "AMD5536 UDC" - depends on PCI + depends on PCI || OF help The AMD5536 UDC is part of the AMD Geode CS5536, an x86 southbridge. It is a USB Highspeed DMA capable USB device controller. Beside ep0 @@ -271,6 +271,10 @@ config USB_AMD5536UDC The UDC port supports OTG operation, and may be used as a host port if it's not being used to implement peripheral or OTG roles. + This UDC is based on Synopsis Designware core USB Device controller + IP. The driver also supports UDCs integrated to ARM based SoC's like + Broadcom's Northstar2 and Cygnus platforms. + Say "y" to link the driver statically, or "m" to build a dynamically linked module called "amd5536udc" and force all gadget drivers to also be dynamically linked. diff --git a/drivers/usb/gadget/udc/amd5536udc.c b/drivers/usb/gadget/udc/amd5536udc.c index ea03ca7..b086cf1 100644 --- a/drivers/usb/gadget/udc/amd5536udc.c +++ b/drivers/usb/gadget/udc/amd5536udc.c @@ -22,16 +22,25 @@ * UDC DMA requires 32-bit aligned buffers so DMA with gadget ether does not * work without updating NET_IP_ALIGN. Or PIO mode (module param "use_dma=0") * can be used with gadget ether. + * + * This driver is compatible for Broadcom's UDC for Northstar2 and Cygnus + * SOCs. The UDC is based on Synopsys designware core USB device controller IP. */ /* debug control */ /* #define UDC_VERBOSE */ /* Driver strings */ -#define UDC_MOD_DESCRIPTION "AMD 5536 UDC - USB Device Controller" +#define UDC_MOD_DESCRIPTION "AMD 5536 UDC - Synopsys USB Device Controller" #define UDC_DRIVER_VERSION_STRING "01.00.0206" /* system */ +#include <linux/extcon.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/kernel.h> @@ -251,18 +260,18 @@ static void print_regs(struct udc *dev) if (use_dma && use_dma_ppb && !use_dma_ppb_du) { DBG(dev, "DMA mode = PPBNDU (packet per buffer " "WITHOUT desc. update)\n"); - dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBNDU"); + dev_info(dev->dev, "DMA mode (%s)\n", "PPBNDU"); } else if (use_dma && use_dma_ppb && use_dma_ppb_du) { DBG(dev, "DMA mode = PPBDU (packet per buffer " "WITH desc. update)\n"); - dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBDU"); + dev_info(dev->dev, "DMA mode (%s)\n", "PPBDU"); } if (use_dma && use_dma_bufferfill_mode) { DBG(dev, "DMA mode = BF (buffer fill mode)\n"); - dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "BF"); + dev_info(dev->dev, "DMA mode (%s)\n", "BF"); } if (!use_dma) - dev_info(&dev->pdev->dev, "FIFO mode\n"); + dev_info(dev->dev, "FIFO mode\n"); DBG(dev, "-------------------------------------------------------\n"); } @@ -1666,8 +1675,11 @@ static void udc_setup_endpoints(struct udc *dev) /* Bringup after Connect event, initial bringup to be ready for ep0 events */ static void usb_connect(struct udc *dev) { + /* Return if already connected */ + if (dev->connected) + return; - dev_info(&dev->pdev->dev, "USB Connect\n"); + dev_info(dev->dev, "USB Connect\n"); dev->connected = 1; @@ -1684,8 +1696,11 @@ static void usb_connect(struct udc *dev) */ static void usb_disconnect(struct udc *dev) { + /* Return if already disconnected */ + if (!dev->connected) + return; - dev_info(&dev->pdev->dev, "USB Disconnect\n"); + dev_info(dev->dev, "USB Disconnect\n"); dev->connected = 0; @@ -1758,11 +1773,15 @@ static void udc_soft_reset(struct udc *dev) /* device int. status reset */ writel(UDC_DEV_MSK_DISABLE, &dev->regs->irqsts); - spin_lock_irqsave(&udc_irq_spinlock, flags); - writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); - readl(&dev->regs->cfg); - spin_unlock_irqrestore(&udc_irq_spinlock, flags); - + /* Don't do this for Broadcom UDC since this is a reserved + * bit. + */ + if (dev->chiprev != UDC_BCM_REV) { + spin_lock_irqsave(&udc_irq_spinlock, flags); + writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); + readl(&dev->regs->cfg); + spin_unlock_irqrestore(&udc_irq_spinlock, flags); + } } /* RDE timer callback to set RDE bit */ @@ -2149,7 +2168,7 @@ static irqreturn_t udc_data_out_isr(struct udc *dev, int ep_ix) } /* HE event ? */ if (tmp & AMD_BIT(UDC_EPSTS_HE)) { - dev_err(&dev->pdev->dev, "HE ep%dout occurred\n", ep->num); + dev_err(dev->dev, "HE ep%dout occurred\n", ep->num); /* clear HE */ writel(tmp | AMD_BIT(UDC_EPSTS_HE), &ep->regs->sts); @@ -2348,7 +2367,7 @@ static irqreturn_t udc_data_in_isr(struct udc *dev, int ep_ix) if (use_dma) { /* BNA ? */ if (epsts & AMD_BIT(UDC_EPSTS_BNA)) { - dev_err(&dev->pdev->dev, + dev_err(dev->dev, "BNA ep%din occurred - DESPTR = %08lx\n", ep->num, (unsigned long) readl(&ep->regs->desptr)); @@ -2361,7 +2380,7 @@ static irqreturn_t udc_data_in_isr(struct udc *dev, int ep_ix) } /* HE event ? */ if (epsts & AMD_BIT(UDC_EPSTS_HE)) { - dev_err(&dev->pdev->dev, + dev_err(dev->dev, "HE ep%dn occurred - DESPTR = %08lx\n", ep->num, (unsigned long) readl(&ep->regs->desptr)); @@ -2999,7 +3018,7 @@ __acquires(dev->lock) /* link up all endpoints */ udc_setup_endpoints(dev); - dev_info(&dev->pdev->dev, "Connect: %s\n", + dev_info(dev->dev, "Connect: %s\n", usb_speed_string(dev->gadget.speed)); /* init ep 0 */ @@ -3162,7 +3181,7 @@ static int init_dma_pools(struct udc *dev) } /* DMA setup */ - dev->data_requests = dma_pool_create("data_requests", NULL, + dev->data_requests = dma_pool_create("data_requests", dev->dev, sizeof(struct udc_data_dma), 0, 0); if (!dev->data_requests) { DBG(dev, "can't get request data pool\n"); @@ -3173,7 +3192,7 @@ static int init_dma_pools(struct udc *dev) dev->ep[UDC_EP0IN_IX].dma = &dev->regs->ctl; /* dma desc for setup data */ - dev->stp_requests = dma_pool_create("setup requests", NULL, + dev->stp_requests = dma_pool_create("setup requests", dev->dev, sizeof(struct udc_stp_dma), 0, 0); if (!dev->stp_requests) { DBG(dev, "can't get stp request pool\n"); @@ -3232,24 +3251,29 @@ static int udc_probe(struct udc *dev) /* init registers, interrupts, ... */ startup_registers(dev); - dev_info(&dev->pdev->dev, "%s\n", mod_desc); + dev_info(dev->dev, "%s\n", mod_desc); snprintf(tmp, sizeof(tmp), "%d", dev->irq); - dev_info(&dev->pdev->dev, - "irq %s, pci mem %08lx, chip rev %02x(Geode5536 %s)\n", - tmp, dev->phys_addr, dev->chiprev, - (dev->chiprev == UDC_HSA0_REV) ? "A0" : "B1"); - strcpy(tmp, UDC_DRIVER_VERSION_STRING); - if (dev->chiprev == UDC_HSA0_REV) { - dev_err(&dev->pdev->dev, "chip revision is A0; too old\n"); - retval = -ENODEV; - goto finished; + + /* Print this device info for AMD chips only */ + if (dev->chiprev == UDC_HSA0_REV || + dev->chiprev == UDC_HSB1_REV) { + dev_info(dev->dev, "irq %s, pci mem %08lx, chip rev %02x(Geode5536 %s)\n", + tmp, dev->phys_addr, dev->chiprev, + (dev->chiprev == UDC_HSA0_REV) ? + "A0" : "B1"); + strcpy(tmp, UDC_DRIVER_VERSION_STRING); + if (dev->chiprev == UDC_HSA0_REV) { + dev_err(dev->dev, "chip revision is A0; too old\n"); + retval = -ENODEV; + goto finished; + } + dev_info(dev->dev, + "driver version: %s(for Geode5536 B1)\n", tmp); } - dev_info(&dev->pdev->dev, - "driver version: %s(for Geode5536 B1)\n", tmp); udc = dev; - retval = usb_add_gadget_udc_release(&udc->pdev->dev, &dev->gadget, + retval = usb_add_gadget_udc_release(udc->dev, &dev->gadget, gadget_release); if (retval) goto finished; @@ -3363,6 +3387,7 @@ static int udc_pci_probe( dev->phys_addr = resource; dev->irq = pdev->irq; dev->pdev = pdev; + dev->dev = &pdev->dev; /* general probing */ if (udc_probe(dev)) { @@ -3408,6 +3433,316 @@ static struct pci_driver udc_pci_driver = { module_pci_driver(udc_pci_driver); +void start_udc(struct udc *udc) +{ + if (udc->driver) { + dev_info(udc->dev, "Connecting...\n"); + udc_enable_dev_setup_interrupts(udc); + udc_basic_init(udc); + udc->connected = 1; + } +} + +void stop_udc(struct udc *udc) +{ + int num; + u32 reg; + + spin_lock(&udc->lock); + + /* Flush the receieve fifo */ + reg = readl(&udc->regs->ctl); + reg |= AMD_BIT(UDC_DEVCTL_SRX_FLUSH); + writel(reg, &udc->regs->ctl); + + reg = readl(&udc->regs->ctl); + reg &= ~(AMD_BIT(UDC_DEVCTL_SRX_FLUSH)); + writel(reg, &udc->regs->ctl); + dev_dbg(udc->dev, "ep rx queue flushed\n"); + + /* Mask interrupts. Required more so when the + * UDC is connected to a DRD phy. + */ + udc_mask_unused_interrupts(udc); + + /* Disconnect gadget driver */ + if (udc->driver) { + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + + /* empty queues */ + for (num = 0; num < UDC_EP_NUM; num++) + empty_req_queue(&udc->ep[num]); + } + udc->connected = 0; + + spin_unlock(&udc->lock); + dev_info(udc->dev, "Device disconnected\n"); +} + +void udc_drd_work(struct work_struct *work) +{ + struct udc *udc; + + udc = container_of(to_delayed_work(work), + struct udc, drd_work); + + if (udc->conn_type) { + dev_dbg(udc->dev, "idle -> device\n"); + start_udc(udc); + } else { + dev_dbg(udc->dev, "device -> idle\n"); + stop_udc(udc); + } +} + +static int usbd_connect_notify(struct notifier_block *self, + unsigned long event, void *ptr) +{ + struct udc *udc = container_of(self, struct udc, nb); + + dev_dbg(udc->dev, "%s: event: %lu\n", __func__, event); + + udc->conn_type = event; + + schedule_delayed_work(&udc->drd_work, 0); + + return NOTIFY_OK; +} + +static int udc_plat_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct udc *udc; + int ret; + + udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + spin_lock_init(&udc->lock); + udc->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + udc->virt_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(udc->regs)) + return PTR_ERR(udc->regs); + + /* udc csr registers base */ + udc->csr = udc->virt_addr + UDC_CSR_ADDR; + + /* dev registers base */ + udc->regs = udc->virt_addr + UDC_DEVCFG_ADDR; + + /* ep registers base */ + udc->ep_regs = udc->virt_addr + UDC_EPREGS_ADDR; + + /* fifo's base */ + udc->rxfifo = (u32 __iomem *)(udc->virt_addr + UDC_RXFIFO_ADDR); + udc->txfifo = (u32 __iomem *)(udc->virt_addr + UDC_TXFIFO_ADDR); + + udc->phys_addr = (unsigned long)res->start; + + udc->irq = irq_of_parse_and_map(dev->of_node, 0); + if (udc->irq <= 0) { + dev_err(dev, "Can't parse and map interrupt\n"); + return -EINVAL; + } + + udc->udc_phy = devm_of_phy_get_by_index(dev, dev->of_node, 0); + if (IS_ERR(udc->udc_phy)) { + dev_err(dev, "Failed to obtain phy from device tree\n"); + return PTR_ERR(udc->udc_phy); + } + + ret = phy_init(udc->udc_phy); + if (ret) { + dev_err(dev, "UDC phy init failed"); + return ret; + } + + ret = phy_power_on(udc->udc_phy); + if (ret) { + dev_err(dev, "UDC phy power on failed"); + phy_exit(udc->udc_phy); + return ret; + } + + /* Register for extcon if supported */ + if (of_get_property(dev->of_node, "extcon", NULL)) { + udc->edev = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(udc->edev)) { + if (PTR_ERR(udc->edev) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_err(dev, "Invalid or missing extcon\n"); + ret = PTR_ERR(udc->edev); + goto exit_phy; + } + + udc->nb.notifier_call = usbd_connect_notify; + ret = extcon_register_notifier(udc->edev, EXTCON_USB, + &udc->nb); + if (ret < 0) { + dev_err(dev, "Can't register extcon device\n"); + goto exit_phy; + } + + ret = extcon_get_cable_state_(udc->edev, EXTCON_USB); + if (ret < 0) { + dev_err(dev, "Can't get cable state\n"); + goto exit_extcon; + } else if (ret) { + udc->conn_type = ret; + } + INIT_DELAYED_WORK(&udc->drd_work, udc_drd_work); + } + + /* init dma pools */ + if (use_dma) { + ret = init_dma_pools(udc); + if (ret != 0) + goto exit_extcon; + } + + ret = devm_request_irq(dev, udc->irq, udc_irq, IRQF_SHARED, + "snps-udc", udc); + if (ret < 0) { + dev_err(dev, "Request irq %d failed for UDC\n", udc->irq); + goto exit_dma; + } + + platform_set_drvdata(pdev, udc); + udc->chiprev = UDC_BCM_REV; + + if (udc_probe(udc)) { + ret = -ENODEV; + goto exit_dma; + } + dev_info(dev, "Synopsys UDC platform driver probe successful\n"); + + return 0; + +exit_dma: + if (use_dma) + free_dma_pools(udc); +exit_extcon: + if (udc->edev) + extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb); +exit_phy: + if (udc->udc_phy) { + phy_power_off(udc->udc_phy); + phy_exit(udc->udc_phy); + } + return ret; +} + +static int udc_plat_remove(struct platform_device *pdev) +{ + struct udc *dev; + + dev = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + /* gadget driver must not be registered */ + if (WARN_ON(dev->driver)) + return 0; + + /* dma pool cleanup */ + free_dma_pools(dev); + + udc_remove(dev); + + platform_set_drvdata(pdev, NULL); + + if (udc->drd_wq) { + flush_workqueue(udc->drd_wq); + destroy_workqueue(udc->drd_wq); + } + + /* free_udc_dma(pdev, udc); */ + phy_power_off(udc->udc_phy); + phy_exit(udc->udc_phy); + extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb); + + dev_info(&pdev->dev, "Synopsys UDC driver removed\n"); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int udc_plat_suspend(struct device *dev) +{ + struct udc *udc; + + udc = dev_get_drvdata(dev); + usb_disconnect(udc); + + if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) { + dev_dbg(udc->dev, "device -> idle\n"); + stop_udc(udc); + } + phy_power_off(udc->udc_phy); + phy_exit(udc->udc_phy); + + return 0; +} + +static int udc_plat_resume(struct device *dev) +{ + struct udc *udc; + int ret; + + udc = dev_get_drvdata(dev); + + ret = phy_init(udc->udc_phy); + if (ret) { + dev_err(udc->dev, "UDC phy init failure"); + return ret; + } + + ret = phy_power_on(udc->udc_phy); + if (ret) { + dev_err(udc->dev, "UDC phy power on failure"); + phy_exit(udc->udc_phy); + return ret; + } + + if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) { + dev_dbg(udc->dev, "idle -> device\n"); + start_udc(udc); + } + + return 0; +} +static const struct dev_pm_ops udc_plat_pm_ops = { + .suspend = udc_plat_suspend, + .resume = udc_plat_resume, +}; +#endif + +#if defined(CONFIG_OF) +static const struct of_device_id of_udc_match[] = { + { .compatible = "snps,bcm-ahb-udc", }, + { } +}; +MODULE_DEVICE_TABLE(of, of_udc_match); +#endif + +static struct platform_driver udc_plat_driver = { + .probe = udc_plat_probe, + .remove = udc_plat_remove, + .driver = { + .name = "snps-udc", + .of_match_table = of_match_ptr(of_udc_match), +#ifdef CONFIG_PM_SLEEP + .pm = &udc_plat_pm_ops, +#endif + }, +}; +module_platform_driver(udc_plat_driver); + MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION); MODULE_AUTHOR("Thomas Dahlmann"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/amd5536udc.h b/drivers/usb/gadget/udc/amd5536udc.h index 4638d70..88e1b75 100644 --- a/drivers/usb/gadget/udc/amd5536udc.h +++ b/drivers/usb/gadget/udc/amd5536udc.h @@ -22,6 +22,9 @@ #define UDC_HSA0_REV 1 #define UDC_HSB1_REV 2 +/* Broadcom chip rev. */ +#define UDC_BCM_REV 10 + /* * SETUP usb commands * needed, because some SETUP's are handled in hw, but must be passed to @@ -106,6 +109,7 @@ #define UDC_DEVCTL_BRLEN_MASK 0x00ff0000 #define UDC_DEVCTL_BRLEN_OFS 16 +#define UDC_DEVCTL_SRX_FLUSH 14 #define UDC_DEVCTL_CSR_DONE 13 #define UDC_DEVCTL_DEVNAK 12 #define UDC_DEVCTL_SD 10 @@ -518,6 +522,7 @@ struct udc_ep { in : 1; }; +#define USBD_WQ_DELAY_MS msecs_to_jiffies(100) /* device struct */ struct udc { struct usb_gadget gadget; @@ -557,6 +562,16 @@ struct udc { u16 cur_config; u16 cur_intf; u16 cur_alt; + + /* for platform device and extcon support */ + struct device *dev; + struct phy *udc_phy; + struct extcon_dev *edev; + struct extcon_specific_cable_nb extcon_nb; + struct notifier_block nb; + struct delayed_work drd_work; + struct workqueue_struct *drd_wq; + u32 conn_type; }; #define to_amd5536_udc(g) (container_of((g), struct udc, gadget)) @@ -603,7 +618,7 @@ union udc_setup_data { /* debug macros ------------------------------------------------------------*/ -#define DBG(udc , args...) dev_dbg(&(udc)->pdev->dev, args) +#define DBG(udc , args...) dev_dbg(udc->dev, args) #ifdef UDC_VERBOSE #define VDBG DBG -- 2.1.0 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html