amd5536udc_pci.c 5.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
/*
 * amd5536udc_pci.c -- AMD 5536 UDC high/full speed USB device controller
 *
 * Copyright (C) 2005-2007 AMD (http://www.amd.com)
 * Author: Thomas Dahlmann
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

/*
 * 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);

	/* init dma pools */
	if (use_dma) {
		retval = init_dma_pools(dev);
		if (retval != 0)
			goto err_dma;
	}

	dev->phys_addr = resource;
	dev->irq = pdev->irq;
	dev->pdev = pdev;

	/* general probing */
	if (udc_probe(dev)) {
		retval = -ENODEV;
		goto err_probe;
	}
	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");