| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * amd5536udc_pci.c -- AMD 5536 UDC high/full speed USB device controller |
| * |
| * Copyright (C) 2005-2007 AMD (http://www.amd.com) |
| * Author: Thomas Dahlmann |
| */ |
| |
| /* |
| * The AMD5536 UDC is part of the x86 southbridge AMD Geode CS5536. |
| * It is a USB Highspeed DMA capable USB device controller. Beside ep0 it |
| * provides 4 IN and 4 OUT endpoints (bulk or interrupt type). |
| * |
| * Make sure that UDC is assigned to port 4 by BIOS settings (port can also |
| * be used as host port) and UOC bits PAD_EN and APU are set (should be done |
| * by BIOS init). |
| * |
| * 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 file does pci device registration, and the core driver implementation |
| * is done in amd5536udc.c |
| * |
| * The driver is split so as to use the core UDC driver which is based on |
| * Synopsys device controller IP (different than HS OTG IP) in UDCs |
| * integrated to SoC platforms. |
| * |
| */ |
| |
| /* Driver strings */ |
| #define UDC_MOD_DESCRIPTION "AMD 5536 UDC - USB Device Controller" |
| |
| /* system */ |
| #include <linux/device.h> |
| #include <linux/dmapool.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/irq.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/prefetch.h> |
| #include <linux/pci.h> |
| |
| /* udc specific */ |
| #include "amd5536udc.h" |
| |
| /* pointer to device object */ |
| static struct udc *udc; |
| |
| /* description */ |
| static const char mod_desc[] = UDC_MOD_DESCRIPTION; |
| static const char name[] = "amd5536udc-pci"; |
| |
| /* Reset all pci context */ |
| static void udc_pci_remove(struct pci_dev *pdev) |
| { |
| struct udc *dev; |
| |
| dev = pci_get_drvdata(pdev); |
| |
| usb_del_gadget_udc(&udc->gadget); |
| /* gadget driver must not be registered */ |
| if (WARN_ON(dev->driver)) |
| return; |
| |
| /* dma pool cleanup */ |
| free_dma_pools(dev); |
| |
| /* reset controller */ |
| writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); |
| free_irq(pdev->irq, dev); |
| iounmap(dev->virt_addr); |
| release_mem_region(pci_resource_start(pdev, 0), |
| pci_resource_len(pdev, 0)); |
| pci_disable_device(pdev); |
| |
| udc_remove(dev); |
| } |
| |
| /* Called by pci bus driver to init pci context */ |
| static int udc_pci_probe( |
| struct pci_dev *pdev, |
| const struct pci_device_id *id |
| ) |
| { |
| struct udc *dev; |
| unsigned long resource; |
| unsigned long len; |
| int retval = 0; |
| |
| /* one udc only */ |
| if (udc) { |
| dev_dbg(&pdev->dev, "already probed\n"); |
| return -EBUSY; |
| } |
| |
| /* init */ |
| dev = kzalloc(sizeof(struct udc), GFP_KERNEL); |
| if (!dev) |
| return -ENOMEM; |
| |
| /* pci setup */ |
| if (pci_enable_device(pdev) < 0) { |
| retval = -ENODEV; |
| goto err_pcidev; |
| } |
| |
| /* PCI resource allocation */ |
| resource = pci_resource_start(pdev, 0); |
| len = pci_resource_len(pdev, 0); |
| |
| if (!request_mem_region(resource, len, name)) { |
| dev_dbg(&pdev->dev, "pci device used already\n"); |
| retval = -EBUSY; |
| goto err_memreg; |
| } |
| |
| dev->virt_addr = ioremap_nocache(resource, len); |
| if (!dev->virt_addr) { |
| dev_dbg(&pdev->dev, "start address cannot be mapped\n"); |
| retval = -EFAULT; |
| goto err_ioremap; |
| } |
| |
| if (!pdev->irq) { |
| dev_err(&pdev->dev, "irq not set\n"); |
| retval = -ENODEV; |
| goto err_irq; |
| } |
| |
| spin_lock_init(&dev->lock); |
| /* udc csr registers base */ |
| dev->csr = dev->virt_addr + UDC_CSR_ADDR; |
| /* dev registers base */ |
| dev->regs = dev->virt_addr + UDC_DEVCFG_ADDR; |
| /* ep registers base */ |
| dev->ep_regs = dev->virt_addr + UDC_EPREGS_ADDR; |
| /* fifo's base */ |
| dev->rxfifo = (u32 __iomem *)(dev->virt_addr + UDC_RXFIFO_ADDR); |
| dev->txfifo = (u32 __iomem *)(dev->virt_addr + UDC_TXFIFO_ADDR); |
| |
| if (request_irq(pdev->irq, udc_irq, IRQF_SHARED, name, dev) != 0) { |
| dev_dbg(&pdev->dev, "request_irq(%d) fail\n", pdev->irq); |
| retval = -EBUSY; |
| goto err_irq; |
| } |
| |
| pci_set_drvdata(pdev, dev); |
| |
| /* chip revision for Hs AMD5536 */ |
| dev->chiprev = pdev->revision; |
| |
| pci_set_master(pdev); |
| pci_try_set_mwi(pdev); |
| |
| dev->phys_addr = resource; |
| dev->irq = pdev->irq; |
| dev->pdev = pdev; |
| dev->dev = &pdev->dev; |
| |
| /* init dma pools */ |
| if (use_dma) { |
| retval = init_dma_pools(dev); |
| if (retval != 0) |
| goto err_dma; |
| } |
| |
| /* general probing */ |
| if (udc_probe(dev)) { |
| retval = -ENODEV; |
| goto err_probe; |
| } |
| |
| udc = dev; |
| |
| return 0; |
| |
| err_probe: |
| if (use_dma) |
| free_dma_pools(dev); |
| err_dma: |
| free_irq(pdev->irq, dev); |
| err_irq: |
| iounmap(dev->virt_addr); |
| err_ioremap: |
| release_mem_region(resource, len); |
| err_memreg: |
| pci_disable_device(pdev); |
| err_pcidev: |
| kfree(dev); |
| return retval; |
| } |
| |
| /* PCI device parameters */ |
| static const struct pci_device_id pci_id[] = { |
| { |
| PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x2096), |
| .class = PCI_CLASS_SERIAL_USB_DEVICE, |
| .class_mask = 0xffffffff, |
| }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(pci, pci_id); |
| |
| /* PCI functions */ |
| static struct pci_driver udc_pci_driver = { |
| .name = (char *) name, |
| .id_table = pci_id, |
| .probe = udc_pci_probe, |
| .remove = udc_pci_remove, |
| }; |
| module_pci_driver(udc_pci_driver); |
| |
| MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION); |
| MODULE_AUTHOR("Thomas Dahlmann"); |
| MODULE_LICENSE("GPL"); |