Intel Poulsbo chipset is one for Intel MID(Mobile Internet Device) platform. The chipset has a USB client controller on it, which is a PCI peripheral and supports full speed and high speed USB data transfers. It has three IN and three OUT configurable endpoints, in addition to endpoint zero. The driver adds device mode support of the controller to Linux USB gadget. The gadgets supported includes: -- file storage gadget -- ether gadget -- g_zero gadget The tests passed include: - hotplug, with and without hubs. - g_file_storage: file copying for 12 hours at full speed and high speed. - g_file_storage: USBCV chap9 tests. - g_file_storage: USBCV MSC tests. - g_ether: scp files for 12 hours at full speed and high speed. - g_ether: USBCV chap9 tests. Signed-off-by: Xiaochen Shen <xiaochen.shen@xxxxxxxxx> Signed-off-by: Helen Chen <helen.chen@xxxxxxxxx> --- drivers/usb/gadget/Kconfig | 21 + drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/gadget_chips.h | 7 + drivers/usb/gadget/iusbc.c | 2574 +++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/iusbc.h | 207 +++ 5 files changed, 2810 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/gadget/iusbc.c create mode 100644 drivers/usb/gadget/iusbc.h diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 3219d13..5b8f62b 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -416,6 +416,27 @@ config USB_CI13XXX default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_IUSBC + boolean "Intel USB Client Controller" + depends on PCI + select USB_GADGET_DUALSPEED + help + Intel USB client controller is a PCI peripheral + which supports both full speed and high speed USB 2.0 + data transfers. + It has three IN and three OUT configurable endpoints, + as well as endpoint zero (for control transfers). + + Say "y" to link the driver statically, or "m" to build + a dynamically linked module called "iusbc" and force + all gadget drivers to also be dynamically linked. + +config USB_IUSBC + tristate + depends on USB_GADGET_IUSBC + default USB_GADGET + select USB_GADGET_SELECTED + config USB_GADGET_NET2280 boolean "NetChip 228x" depends on PCI diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 39a51d7..8c71d80 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_CI13XXX) += ci13xxx_udc.o +obj-$(CONFIG_USB_IUSBC) += iusbc.o # # USB gadget drivers diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index ec6d439..3ce80b1 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -163,6 +163,12 @@ #define gadget_is_ci13xxx(g) 0 #endif +#ifdef CONFIG_USB_GADGET_IUSBC +#define gadget_is_iusbc(g) (!strcmp("iusbc", (g)->name)) +#else +#define gadget_is_iusbc(g) 0 +#endif + // CONFIG_USB_GADGET_SX2 // CONFIG_USB_GADGET_AU1X00 // ... @@ -231,6 +237,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x22; else if (gadget_is_ci13xxx(gadget)) return 0x23; + else if (gadget_is_iusbc(gadget)) + return 0x24; return -ENOENT; } diff --git a/drivers/usb/gadget/iusbc.c b/drivers/usb/gadget/iusbc.c new file mode 100644 index 0000000..c1fe662 --- /dev/null +++ b/drivers/usb/gadget/iusbc.c @@ -0,0 +1,2574 @@ +/* + * Intel Poulsbo USB Client Controller Driver + * Copyright (C) 2006-08, Intel Corporation. + * + * Author: Xiaochen Shen <xiaochen.shen@xxxxxxxxx> + * Helen Chen <helen.chen@xxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, + * MA 02110-1301 USA. + */ + + +#undef DEBUG /* messages on error and most fault paths */ +#undef VERBOSE_DEBUG /* extra debug messages (success too) */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/dma-mapping.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +/* Power management */ +#include <linux/pm.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/unaligned.h> + +#include "iusbc.h" + +#define DRIVER_DESC "Intel Poulsbo USB Client Controller Driver" +#define DRIVER_VERSION "Dec 2008" +#define DRIVER_AUTHOR "Xiaochen Shen, Helen Chen" + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) +#define STEPPING_D2 0x07 + +#define USBC_DELAY_POST_RESET 5 + +static const char driver_name[] = "iusbc"; +static const char driver_desc[] = DRIVER_DESC; + +static const char *const ep_name[] = { + "ep0-in", "ep0-out", + "ep1in-bulk", "ep1out-bulk", + "ep2in-int", "ep2out-int", + "ep3in-iso", "ep3out-iso", +}; + +/* module parameter */ + +/* + * force_fullspeed -- device will be forced to full speed operation + * default value: 0 for high speed + */ +static int force_fullspeed; + +/* modprobe iusbc force_fullspeed=n */ +module_param(force_fullspeed, bool, S_IRUGO); +MODULE_PARM_DESC(force_fullspeed, "true to force full speed mode"); + +#ifdef VERBOSE_DEBUG +static inline void print_all_registers(struct iusbc_regs *regs) +{ + unsigned i, j; + /* device */ + printk(KERN_DEBUG "Intel USB-C Memory Space Registers\n"); + printk(KERN_DEBUG "Register Length: 0x%x\n", + sizeof(struct iusbc_regs)); + printk(KERN_DEBUG "gcap=0x%08x\n", readl(®s->gcap)); + printk(KERN_DEBUG "dev_sts=0x%08x\n", readl(®s->dev_sts)); + printk(KERN_DEBUG "frame=0x%04x\n", readw(®s->frame)); + printk(KERN_DEBUG "int_sts=0x%08x\n", readl(®s->int_sts)); + printk(KERN_DEBUG "int_ctrl=0x%08x\n", readl(®s->int_ctrl)); + printk(KERN_DEBUG "dev_ctrl=0x%08x\n", readl(®s->dev_ctrl)); + + /* endpoints */ + for (i = 0; i < 5; i++) { + printk(KERN_DEBUG "ep[%d]_base_low_32=0x%08x\n", + i, readl(®s->ep[i].ep_base_low_32)); + printk(KERN_DEBUG "ep[%d]_base_hi_32=0x%08x\n", + i, readl(®s->ep[i].ep_base_hi_32)); + printk(KERN_DEBUG "ep[%d]_len=0x%04x\n", + i, readw(®s->ep[i].ep_len)); + printk(KERN_DEBUG "ep[%d]_pib=0x%04x\n", + i, readw(®s->ep[i].ep_pib)); + printk(KERN_DEBUG "ep[%d]_dil=0x%04x\n", + i, readw(®s->ep[i].ep_dil)); + printk(KERN_DEBUG "ep[%d]_tiq=0x%04x\n", + i, readw(®s->ep[i].ep_tiq)); + printk(KERN_DEBUG "ep[%d]_max=0x%04x\n", + i, readw(®s->ep[i].ep_max)); + printk(KERN_DEBUG "ep[%d]_sts=0x%04x\n", + i, readw(®s->ep[i].ep_sts)); + printk(KERN_DEBUG "ep[%d]_cfg=0x%04x\n", + i, readw(®s->ep[i].ep_cfg)); + + if (1 == i) { /* ep0 out */ + printk(KERN_DEBUG + "ep-out setup_pkt_sts=0x%02x\n", + readb(®s->ep[i].setup_pkt_sts)); + for (j = 0; j < 8; j++) { + printk(KERN_DEBUG "ep0 out " + "setup_pkt[%d]=0x%02x\n", j, + readb(®s->ep[i].setup_pkt[j])); + } + } + } +} +#endif /* VERBOSE_DEBUG */ + +#define DIR_STRING(bAddress) (((bAddress) & USB_DIR_IN) ? "in" : "out") + +#if defined(CONFIG_USB_GADGET_DEBUG_FILES) || defined(DEBUG) +static char *type_string(u8 bmAttributes) +{ + switch ((bmAttributes) & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + return "bulk"; + case USB_ENDPOINT_XFER_ISOC: + return "iso"; + case USB_ENDPOINT_XFER_INT: + return "int"; + }; + return "control"; +} +#endif + +/* configure endpoint, making it usable */ +static int iusbc_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct iusbc *dev; + struct iusbc_ep *ep; + u16 val_16, max; + u8 val_8; + unsigned long flags; + unsigned i; + int retval; + + ep = container_of(_ep, struct iusbc_ep, ep); + + if (!_ep || !desc || strcmp(_ep->name, ep_name[0]) == 0 + || strcmp(_ep->name, ep_name[1]) == 0 + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + /* wMaxPacketSize up to 1024 bytes */ + max = le16_to_cpu(desc->wMaxPacketSize) & 0x3ff; + + spin_lock_irqsave(&dev->lock, flags); + ep->ep.maxpacket = max; + + if (!ep->desc) + ep->desc = desc; + + /* ep_reset() has already been called */ + ep->stopped = 0; + ep->wedged = 0; + ep->is_in = (USB_DIR_IN & desc->bEndpointAddress) != 0; + + /* sanity check type, direction, address */ + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + if ((dev->gadget.speed == USB_SPEED_HIGH + && max != 512) + || (dev->gadget.speed == USB_SPEED_FULL + && max > 64)) { + goto done; + } + break; + case USB_ENDPOINT_XFER_INT: + if (strstr(ep->ep.name, "-iso")) /* bulk is ok */ + goto done; + + switch (dev->gadget.speed) { + case USB_SPEED_HIGH: + if (max <= 1024) + break; + case USB_SPEED_FULL: + if (max <= 64) + break; + default: + if (max <= 8) + break; + goto done; + } + break; + case USB_ENDPOINT_XFER_ISOC: + if (strstr(ep->ep.name, "-bulk") + || strstr(ep->ep.name, "-int")) + goto done; + + switch (dev->gadget.speed) { + case USB_SPEED_HIGH: + if (max <= 1024) + break; + case USB_SPEED_FULL: + if (max <= 1023) + break; + default: + goto done; + } + break; + default: + goto done; + } + + /* ep_type */ + ep->ep_type = (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK); + + /* DMA modes, only support Linear Mode now */ + if (ep->dev->sg_mode_dma) + ep->dma_mode = SCATTER_GATHER_MODE; + else + ep->dma_mode = LINEAR_MODE; + + /* reset ep registers to default value */ + i = ep->num; + writew(0, &dev->regs->ep[i].ep_cfg); + + /* set endpoints valid */ + val_16 = readw(&dev->regs->ep[i].ep_cfg); + val_16 |= INTR_BAD_PID_TYPE + | INTR_CRC_ERROR + | INTR_FIFO_ERROR + | INTR_DMA_ERROR + | INTR_TRANS_COMPLETE + /* | INTR_PING_NAK_SENT */ + | INTR_DMA_IOC + | ep->dma_mode << 6 + | ep->ep_type << 4 + /* | EP_ENABLE */ + | EP_VALID; + + /* will set EP_ENABLE later to start dma */ + writew(val_16, &dev->regs->ep[i].ep_cfg); + + val_16 = readw(&dev->regs->ep[i].ep_cfg); + dev_vdbg(&dev->pdev->dev, "%s.ep_cfg = 0x%04x\n", + _ep->name, val_16); + + val_8 = desc->bEndpointAddress; + retval = 0; +done: + spin_unlock_irqrestore(&dev->lock, flags); + return retval; +} + +static const struct usb_ep_ops iusbc_ep_ops; + +static void +ep_reset(struct iusbc_regs __iomem *regs, struct iusbc_ep *ep) +{ + unsigned i = ep->num; + + /* reset ep values */ + ep->stopped = 1; + ep->ep.maxpacket = ~0; + ep->ep.ops = &iusbc_ep_ops; + + /* reset ep registers to default value + * clear all interrupt and status + * clear and reset all DMA FIFOs and state machine + * hardware shall minimize power usage + */ + writew(0, ®s->ep[i].ep_cfg); +} + +static void nuke(struct iusbc_ep *); + +static int iusbc_ep_disable(struct usb_ep *_ep) +{ + struct iusbc_ep *ep; + unsigned long flags; + struct iusbc *dev; + unsigned i; + u16 val_16; + + ep = container_of(_ep, struct iusbc_ep, ep); + + if (!_ep || !ep->desc + || strcmp(_ep->name, ep_name[0]) == 0 + || strcmp(_ep->name, ep_name[1]) == 0) + return -EINVAL; + + dev = ep->dev; + if (dev->ep0state == EP0_SUSPEND) + return -EBUSY; + + spin_lock_irqsave(&ep->dev->lock, flags); + nuke(ep); + ep_reset(ep->dev->regs, ep); + + i = ep->num; + /* display endpoint configuration register */ + val_16 = readw(&dev->regs->ep[i].ep_cfg); + spin_unlock_irqrestore(&ep->dev->lock, flags); + return 0; +} + +/* allocate a request object to use with this endpoint */ +static struct usb_request * +iusbc_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct iusbc_ep *ep; + struct iusbc_request *req; + struct iusbc_dma *td; + + if (!_ep) + return NULL; + ep = container_of(_ep, struct iusbc_ep, ep); + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + req->req.dma = DMA_ADDR_INVALID; + INIT_LIST_HEAD(&req->queue); + + /* this dma descriptor may be swapped with the previous dummy */ + td = pci_pool_alloc(ep->dev->requests, + gfp_flags, + &req->td_dma); + + if (!td) { + kfree(req); + return NULL; + } + + td->dmacount = 0; /* not VALID */ + td->dmaaddr = __constant_cpu_to_le32(DMA_ADDR_INVALID); + req->td = td; + + return &req->req; +} + +/* frees a request object */ +static void +iusbc_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct iusbc_ep *ep; + struct iusbc_request *req; + + ep = container_of(_ep, struct iusbc_ep, ep); + if (!_ep || !_req) + return; + + req = container_of(_req, struct iusbc_request, req); + + WARN_ON(!list_empty(&req->queue)); + + if (req->td) + pci_pool_free(ep->dev->requests, req->td, req->td_dma); + kfree(req); +} + +struct free_record { + struct list_head list; + struct device *dev; + unsigned bytes; + dma_addr_t dma; +}; + +/* fill out dma descriptor to match a given request */ +static void fill_dma(struct iusbc_ep *ep, struct iusbc_request *req) +{ + struct iusbc_dma *td = req->td; + u16 dmacount; + + dmacount = req->req.length; + + td->dmaaddr = cpu_to_le32(req->req.dma); + td->dmacount = cpu_to_le16(dmacount); +} + +static void start_dma(struct iusbc_ep *ep, struct iusbc_request *req) +{ + u16 val_16; + u32 val_32; + unsigned i; + + i = ep->num; + + /* init req->td, pointing to the current dummy */ + fill_dma(ep, req); + + /* ep_base_low_32 */ + writel(cpu_to_le32(req->req.dma), + &ep->dev->regs->ep[i].ep_base_low_32); + val_32 = readl(&ep->dev->regs->ep[i].ep_base_low_32); + + /* ep_base_hi_32 */ + writel(0, &ep->dev->regs->ep[i].ep_base_hi_32); + val_32 = readl(&ep->dev->regs->ep[i].ep_base_hi_32); + + writew(le16_to_cpu(req->td->dmacount), + &ep->dev->regs->ep[i].ep_len); + val_16 = readw(&ep->dev->regs->ep[i].ep_len); + + /* endpoint maximum transaction size, up to 1024 Bytes */ + writew((ep->ep.maxpacket & 0x3ff), + &ep->dev->regs->ep[i].ep_max); + val_16 = readw(&ep->dev->regs->ep[i].ep_max); + + /* validate endpoint, enable DMA */ + val_16 = readw(&ep->dev->regs->ep[i].ep_cfg); + val_16 |= EP_VALID | EP_ENABLE; + writew(val_16, &ep->dev->regs->ep[i].ep_cfg); + + val_16 = readw(&ep->dev->regs->ep[i].ep_cfg); +} + +/* queues I/O requests in endpoint queue */ +static inline void +queue_dma(struct iusbc_ep *ep, struct iusbc_request *req) +{ + struct iusbc_dma *end; + dma_addr_t tmp_dma_addr; + + /* swap new dummy for old, link; fill and maybe activate */ + end = ep->dummy; + ep->dummy = req->td; + req->td = end; + + tmp_dma_addr = ep->td_dma; + ep->td_dma = req->td_dma; + req->td_dma = tmp_dma_addr; + + fill_dma(ep, req); +} + +static void +done(struct iusbc_ep *ep, struct iusbc_request *req, int status) +__releases(ep->dev->lock) +__acquires(ep->dev->lock) +{ + struct iusbc *dev; + unsigned stopped = ep->stopped; + + list_del_init(&req->queue); + + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + dev = ep->dev; + if (req->mapped) { + pci_unmap_single(dev->pdev, req->req.dma, + req->req.length, + ep->is_in ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + } + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + + spin_unlock(&dev->lock); + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + spin_lock(&dev->lock); + ep->stopped = stopped; +} + +/* queues (submits) an I/O requests to an endpoint */ +static int iusbc_queue(struct usb_ep *_ep, + struct usb_request *_req, gfp_t gfp_flags) +{ + struct iusbc_request *req; + struct iusbc_ep *ep; + struct iusbc *dev; + unsigned long flags; + u16 val_16; + unsigned zlflag = 0; + + /* always require a cpu-view buffer */ + req = container_of(_req, struct iusbc_request, req); + ep = container_of(_ep, struct iusbc_ep, ep); + dev = ep->dev; + + if (!_req || !_req->complete || !_req->buf + || !list_empty(&req->queue)) { + return -EINVAL; + } + + if (!_ep || (!ep->desc && ep->num > 1)) + return -ESHUTDOWN; + + if (dev->ep0state == EP0_DISCONNECT) + return -ESHUTDOWN; + + if (unlikely(!dev->driver || + dev->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + + /* can't touch registers when suspended */ + if (dev->ep0state == EP0_SUSPEND) + return -EBUSY; + + /* set up dma mapping in case the caller didn't */ + if (_req->dma == DMA_ADDR_INVALID) { + /* WORKAROUND: WARN_ON(size == 0) */ + if (_req->length == 0) { + zlflag = 1; + _req->length++; + } + + _req->dma = pci_map_single(dev->pdev, _req->buf, + _req->length, + ep->is_in ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); + + if (zlflag && (_req->length == 1)) { + zlflag = 0; + _req->length = 0; + } + + req->mapped = 1; + } + spin_lock_irqsave(&dev->lock, flags); + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* set ZLP flag for ep0 in, when writting data, + * makes the last packet be "short" by adding a zero + * length packet as needed + */ + if (unlikely(ep->num == 0 && ep->is_in)) + _req->zero = 1; + + /* kickstart this I/O queue */ + if (list_empty(&ep->queue) && !ep->stopped) { + start_dma(ep, req); + val_16 = readw(&ep->dev->regs->ep[ep->num].ep_pib); + val_16 = readw(&ep->dev->regs->ep[ep->num].ep_sts); + } else + queue_dma(ep, req); + if (likely(req != NULL)) + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; +} + +static inline void +dma_done(struct iusbc_ep *ep, struct iusbc_request *req, int status) +{ + unsigned i; + + i = ep->num; + req->req.actual = readw(&ep->dev->regs->ep[i].ep_pib); + done(ep, req, status); +} + +/* restart dma in endpoint */ +static void restart_dma(struct iusbc_ep *ep) +{ + struct iusbc_request *req; + if (ep->stopped) + return; + req = list_entry(ep->queue.next, struct iusbc_request, queue); + start_dma(ep, req); + return; +} + +/* dequeue ALL requests */ +static void nuke(struct iusbc_ep *ep) +{ + struct iusbc_request *req; + /* called with spinlock held */ + ep->stopped = 1; + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct iusbc_request, + queue); + done(ep, req, -ESHUTDOWN); + } +} + +/* dequeues (cancels, unlinks) an I/O request from an endpoint */ +static int iusbc_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct iusbc_ep *ep; + struct iusbc *dev; + struct iusbc_request *req; + unsigned long flags; + int stopped; + + ep = container_of(_ep, struct iusbc_ep, ep); + if (!_ep || (!ep->desc && ep->num > 1) || !_req) + return -EINVAL; + + dev = ep->dev; + if (!dev->driver) + return -ESHUTDOWN; + + /* can't touch registers when suspended */ + if (dev->ep0state == EP0_SUSPEND) + return -EBUSY; + + spin_lock_irqsave(&ep->dev->lock, flags); + stopped = ep->stopped; + + /* quiesce dma while we patch the queue */ + ep->stopped = 1; + + /* make sure it's still queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + + if (&req->req != _req) { + spin_unlock_irqrestore(&ep->dev->lock, flags); + return -EINVAL; + } + + /* queue head may be partially complete. */ + if (ep->queue.next == &req->queue) { + dev_dbg(&ep->dev->pdev->dev, "unlink (%s) dma\n", + _ep->name); + _req->status = -ECONNRESET; + if (likely(ep->queue.next == &req->queue)) { + req->td->dmacount = 0; /* invalidate */ + dma_done(ep, req, -ECONNRESET); + } + req = NULL; + } + + if (req) + done(ep, req, -ECONNRESET); + + ep->stopped = stopped; + + if (!list_empty(&ep->queue) && (!ep->stopped)) { + /* resume current request, or start new one */ + if (!req) + start_dma(ep, list_entry(ep->queue.next, + struct iusbc_request, queue)); + } + + spin_unlock_irqrestore(&ep->dev->lock, flags); + return 0; +} + +static void ep0_start(struct iusbc *dev); +static void ep_ack(struct iusbc_ep *ep); +static int iusbc_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc); + +static void clear_halt(struct iusbc_ep *ep) +__releases(ep->dev->lock) +__acquires(ep->dev->lock) +{ + u16 val_16; + unsigned i = ep->num; + int rc = 0; + struct iusbc_request *req; + const struct usb_endpoint_descriptor *desc; + + /* validate and enable endpoint */ + val_16 = readw(&ep->dev->regs->ep[i].ep_cfg); + val_16 |= EP_VALID | EP_ENABLE; + writew(val_16, &ep->dev->regs->ep[i].ep_cfg); + + /* re-enable endpoint */ + if (i < 2) { /* ep0 in and out */ + ep0_start(ep->dev); + } else { + spin_unlock_irq(&ep->dev->lock); + /* remember ep->desc */ + desc = ep->desc; + rc = iusbc_ep_enable(&ep->ep, desc); + spin_lock_irq(&ep->dev->lock); + } + + val_16 = readw(&ep->dev->regs->ep[i].ep_cfg); + ep->stopped = 0; + if (list_empty(&ep->queue)) + return; + req = list_entry(ep->queue.next, struct iusbc_request, queue); + start_dma(ep, req); +} + +static void set_halt(struct iusbc_ep *ep) +{ + u16 val_16; + unsigned i = ep->num; + + /* reset data buffer zero length */ + writew(0, &ep->dev->regs->ep[i].ep_len); + /* invalidate and disable endpoint */ + val_16 = readw(&ep->dev->regs->ep[i].ep_cfg); + val_16 &= (~EP_VALID & ~EP_ENABLE); + writew(val_16, &ep->dev->regs->ep[i].ep_cfg); + ep->stopped = 1; +} + +static int iusbc_fifo_status(struct usb_ep *_ep); + +static int +iusbc_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedged) +{ + struct iusbc_ep *ep; + unsigned long flags; + int retval = 0; + + ep = container_of(_ep, struct iusbc_ep, ep); + if (!_ep || (!ep->desc && ep->num > 1)) + return -EINVAL; + if (!ep->dev->driver || + ep->dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + if (ep->desc && (ep->desc->bmAttributes & 0x03) + == USB_ENDPOINT_XFER_ISOC) + return -EINVAL; + spin_lock_irqsave(&ep->dev->lock, flags); + + /* transfer requests are still queued */ + if (!list_empty(&ep->queue)) + retval = -EAGAIN; + else if (ep->is_in && value && iusbc_fifo_status(_ep) != 0) { + /* FIFO holds bytes, the host hasn't collected */ + dev_dbg(&ep->dev->pdev->dev, "%s FIFO holds bytes\n", + _ep->name); + + /* reset position in buffer register */ + writew(0, &ep->dev->regs->ep[ep->num].ep_pib); + + retval = -EAGAIN; + } else { + if (value) { + if (ep->num < 2) /* ep0 should not halt. */ + ep->dev->ep0state = EP0_STALL; + else + set_halt(ep); + if (wedged) + ep->wedged = 1; + } else { + clear_halt(ep); + ep->wedged = 0; + } + + } + spin_unlock_irqrestore(&ep->dev->lock, flags); + return retval; +} + +static int iusbc_set_halt(struct usb_ep *_ep, int value) +{ + return iusbc_set_halt_and_wedge(_ep, value, 0); +} + +static int iusbc_set_wedge(struct usb_ep *_ep) +{ + if (!_ep || strcmp(_ep->name, ep_name[0]) == 0 + || strcmp(_ep->name, ep_name[1]) == 0) + return -EINVAL; + return iusbc_set_halt_and_wedge(_ep, 1, 1); +} + +static int iusbc_fifo_status(struct usb_ep *_ep) +{ + struct iusbc_ep *ep; + unsigned i; + u16 nbytes, fifo_size; + + ep = container_of(_ep, struct iusbc_ep, ep); + if (!_ep || (!ep->desc && ep->num > 1)) + return -ENODEV; + if (!ep->dev->driver || + ep->dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + i = ep->num; + fifo_size = readw(&ep->dev->regs->ep[i].ep_len); + nbytes = readw(&ep->dev->regs->ep[i].ep_pib); + + if (nbytes > fifo_size) + return -EOVERFLOW; + return nbytes; +} + +static void ep_nak(struct iusbc_ep *ep); + +static void iusbc_fifo_flush(struct usb_ep *_ep) +{ + struct iusbc_ep *ep; + unsigned i; + u16 val_16; + + ep = container_of(_ep, struct iusbc_ep, ep); + if (!_ep || (!ep->desc && ep->num > 1)) + return; + if (!ep->dev->driver || + ep->dev->gadget.speed == USB_SPEED_UNKNOWN) + return; + i = ep->num; + ep_nak(ep); + + /* reset position in buffer register */ + val_16 = readw(&ep->dev->regs->ep[i].ep_pib); + dev_dbg(&ep->dev->pdev->dev, "%s.ep_pib = 0x%04x\n", + _ep->name, val_16); + writew(0, &ep->dev->regs->ep[i].ep_pib); +} + +static const struct usb_ep_ops iusbc_ep_ops = { + .enable = iusbc_ep_enable, + .disable = iusbc_ep_disable, + + .alloc_request = iusbc_alloc_request, + .free_request = iusbc_free_request, + + .queue = iusbc_queue, + .dequeue = iusbc_dequeue, + + .set_halt = iusbc_set_halt, + .set_wedge = iusbc_set_wedge, + + .fifo_status = iusbc_fifo_status, + .fifo_flush = iusbc_fifo_flush, +}; + +/* returns the current frame number */ +static int iusbc_get_frame(struct usb_gadget *_gadget) +{ + struct iusbc *dev; + unsigned long flags; + u16 retval; + + if (!_gadget) + return -ENODEV; + + dev = container_of(_gadget, struct iusbc, gadget); + spin_lock_irqsave(&dev->lock, flags); + retval = readw(&dev->regs->frame); + spin_unlock_irqrestore(&dev->lock, flags); + + return retval; +} + +/* software-controlled connect/disconnect to USB host */ +static int iusbc_pullup(struct usb_gadget *_gadget, int is_on) +{ + struct iusbc *dev; + u32 val_32; + unsigned long flags; + + if (!_gadget) + return -ENODEV; + dev = container_of(_gadget, struct iusbc, gadget); + + spin_lock_irqsave(&dev->lock, flags); + val_32 = readl(&dev->regs->dev_ctrl); + dev->connected = (is_on != 0); + if (is_on) + val_32 |= CONNECTION_ENABLE; + else + val_32 &= ~CONNECTION_ENABLE; + + writel(val_32, &dev->regs->dev_ctrl); + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + + +static const struct usb_gadget_ops iusbc_ops = { + + /* returns the current frame number */ + .get_frame = iusbc_get_frame, + + /* software-controlled connect/disconnect to USB host */ + .pullup = iusbc_pullup, +}; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +/* "function" sysfs attribute */ +static ssize_t show_function(struct device *_dev, + struct device_attribute *attr, + char *buf) +{ + struct iusbc *dev = dev_get_drvdata(_dev); + + if (!dev->driver + || !dev->driver->function + || strlen(dev->driver->function) > PAGE_SIZE) + return 0; + + return scnprintf(buf, PAGE_SIZE, "%s\n", dev->driver->function); +} +static DEVICE_ATTR(function, S_IRUGO, show_function, NULL); + + +static ssize_t show_registers(struct device *_dev, + struct device_attribute *attr, + char *buf) +{ + struct iusbc *dev; + char *next; + unsigned size; + unsigned t; + unsigned i; + unsigned long flags; + const char *name; + const char *speed; + u32 dev_sts; + const char *stepping; + + dev = dev_get_drvdata(_dev); + next = buf; + size = PAGE_SIZE; + spin_lock_irqsave(&dev->lock, flags); + + if (dev->stepping >= STEPPING_D2) + stepping = "D2"; + else + stepping = "D1"; + + if (dev->driver) + name = dev->driver->driver.name; + else + name = "(none)"; + + + dev_sts = readl(&dev->regs->dev_sts); + if (dev->gadget.speed == USB_SPEED_HIGH + && (dev_sts & CONNECTED)) + speed = "high speed"; + else if (dev->gadget.speed == USB_SPEED_FULL + && (dev_sts & CONNECTED)) + speed = "full speed"; + else + speed = "unknown speed"; + + /* device information */ + t = scnprintf(next, size, + "%s %s - %s\n" + "Stepping: %s\n" + "Version: %s\n" + "Gadget driver: %s\n" + "Speed mode: %s\n", + driver_name, pci_name(dev->pdev), driver_desc, + stepping, + DRIVER_VERSION, + name, + speed); + size -= t; + next += t; + + /* device memory space registers */ + t = scnprintf(next, size, + "\nDevice registers:\n" + "\tgcap=0x%08x\n" + "\tdev_sts=0x%08x\n" + "\tframe=0x%04x\n" + "\tint_sts=0x%08x\n" + "\tint_ctrl=0x%08x\n" + "\tdev_ctrl=0x%08x\n", + readl(&dev->regs->gcap), + readl(&dev->regs->dev_sts), + readw(&dev->regs->frame), + readl(&dev->regs->int_sts), + readl(&dev->regs->int_ctrl), + readl(&dev->regs->dev_ctrl) + ); + size -= t; + next += t; + + /* endpoints memory space registers */ + t = scnprintf(next, size, "\nEndpoints registers:\n"); + size -= t; + next += t; + + for (i = 0; i < 5; i++) { + struct iusbc_ep *ep; + ep = &dev->ep[i]; + + if (i > 1 && !ep->desc) + continue; + + name = ep->ep.name; + t = scnprintf(next, size, + "\t%s.ep_base_low_32=0x%08x\n" + "\t%s.ep_base_hi_32=0x%08x\n" + "\t%s.ep_len=0x%04x\n" + "\t%s.ep_pib=0x%04x\n" + "\t%s.ep_dil=0x%04x\n" + "\t%s.ep_tiq=0x%04x\n" + "\t%s.ep_max=0x%04x\n" + "\t%s.ep_sts=0x%04x\n" + "\t%s.ep_cfg=0x%04x\n", + name, readl(&dev->regs->ep[i].ep_base_low_32), + name, readl(&dev->regs->ep[i].ep_base_hi_32), + name, readw(&dev->regs->ep[i].ep_len), + name, readw(&dev->regs->ep[i].ep_pib), + name, readw(&dev->regs->ep[i].ep_dil), + name, readw(&dev->regs->ep[i].ep_tiq), + name, readw(&dev->regs->ep[i].ep_max), + name, readw(&dev->regs->ep[i].ep_sts), + name, readw(&dev->regs->ep[i].ep_cfg)); + size -= t; + next += t; + } + + /* ep0 out setup packet registers */ + t = scnprintf(next, size, + "\tsetup_pkt_sts=0x%02x\n", + readb(&dev->regs->ep[1].setup_pkt_sts)); + size -= t; + next += t; + + for (i = 0; i < 8; i++) { + t = scnprintf(next, size, + "\tsetup_pkt[%d]=0x%02x\n", + i, + readb(&dev->regs->ep[1].setup_pkt[i]) + ); + size -= t; + next += t; + } + + /* Irq statistics */ + t = scnprintf(next, size, "\nIrq statistics:\n"); + size -= t; + next += t; + + for (i = 0; i < 5; i++) { + struct iusbc_ep *ep; + ep = &dev->ep[i]; + + if (i && !ep->irqs) + continue; + + t = scnprintf(next, size, + "\t%s/%lu\n", + ep->ep.name, ep->irqs); + size -= t; + next += t; + } + + spin_unlock_irqrestore(&dev->lock, flags); + + return PAGE_SIZE - size; +} +static DEVICE_ATTR(registers, S_IRUGO, show_registers, NULL); + + +static ssize_t show_queues(struct device *_dev, + struct device_attribute *attr, + char *buf) +{ + struct iusbc *dev; + char *next; + unsigned size, i; + unsigned long flags; + + dev = dev_get_drvdata(_dev); + next = buf; + size = PAGE_SIZE; + spin_lock_irqsave(&dev->lock, flags); + + for (i = 0; i < 5; i++) { + struct iusbc_ep *ep = &dev->ep[i]; + struct iusbc_request *req; + struct iusbc_dma *td; + int t; + int addr; + + if (i > 1 && !ep->desc) + continue; + + /* ep0 in/out */ + if (i == 0 || i == 1) { + t = scnprintf(next, size, + "%s (ep%d%s-%s), " + "max %04x, dma_mode: %02x\n", + ep->ep.name, + 0, + ep->is_in ? "in" : "out", + "control", + ep->ep.maxpacket, + ep->dma_mode); + } else { + addr = ep->desc->bEndpointAddress; + t = scnprintf(next, size, + "\n%s (ep%d%s-%s), " + "max %04x, dma_mode: %02x\n", + ep->ep.name, + addr & USB_ENDPOINT_NUMBER_MASK, + DIR_STRING(addr), + type_string(ep->desc->bmAttributes), + ep->ep.maxpacket, + ep->dma_mode); + } + + if (t <= 0 || t > size) + goto done; + + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, + "\t(nothing queued)\n"); + if (t <= 0 || t > size) + goto done; + + size -= t; + next += t; + continue; + } + + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "\treq %p, len %u/%u, " + "buf %p, dma 0x%08x)\n", + &req->req, req->req.actual, + req->req.length, req->req.buf, + req->req.dma); + + if (t <= 0 || t > size) + goto done; + + size -= t; + next += t; + + td = req->td; + t = scnprintf(next, size, "\ttd 0x%08x, " + " count 0x%08x, buf 0x%08x\n", + (u32) req->td_dma, + le32_to_cpu(td->dmacount), + le32_to_cpu(td->dmaaddr)); + + if (t <= 0 || t > size) + goto done; + + size -= t; + next += t; + } + } + +done: + spin_unlock_irqrestore(&dev->lock, flags); + return PAGE_SIZE - size; +} +static DEVICE_ATTR(queues, S_IRUGO, show_queues, NULL); + +#else + +#define device_create_file(a, b) (0) +#define device_remove_file(a, b) do { } while (0) + +#endif /*CONFIG_USB_GADGET_DEBUG_FILES */ + +/* global variable */ +static struct iusbc *the_controller; + +static void iusbc_reset(struct iusbc *dev) +{ + /* disable irqs */ + writel(0, &dev->regs->int_ctrl); + + /* set device power stauts */ + dev->powered = 1; + + /* set device remote wakeup flag */ + dev->enable_wakeup = 0; + + /* 16 bits status data for GET_STATUS */ + dev->status_d = 0; +} + +static void iusbc_reinit(struct iusbc *dev) +{ + unsigned i; + + INIT_LIST_HEAD(&dev->gadget.ep_list); + + /* ep0-in */ + dev->gadget.ep0 = &dev->ep[0].ep; + + /* init ep0 in and ep0 out driver_data */ + dev->ep[0].ep.driver_data = get_gadget_data(&dev->gadget); + dev->ep[1].ep.driver_data = get_gadget_data(&dev->gadget); + + dev->ep0state = EP0_DISCONNECT; + + /* basic endpoint init */ + /* 2 ep0, 3 data ep */ + for (i = 0; i < 5; i++) { + struct iusbc_ep *ep = &dev->ep[i]; + ep->dev = dev; + + INIT_LIST_HEAD(&ep->queue); + + ep->desc = NULL; + ep->num = i; + ep->stopped = 1; + ep->ep.name = ep_name[i]; + ep->ep.maxpacket = ~0; + ep->ep.ops = &iusbc_ep_ops; + + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + ep_reset(dev->regs, ep); + } + + /* set ep0 maxpacket */ + dev->ep[0].ep.maxpacket = 64; /* ep0_in */ + dev->ep[1].ep.maxpacket = 64; /* ep0_out */ + + dev->ep[0].stopped = 0; + dev->ep[1].stopped = 0; + + list_del_init(&dev->ep[0].ep.ep_list); + list_del_init(&dev->ep[1].ep.ep_list); +} + + +static void ep0_start(struct iusbc *dev) +{ + u16 val_16; + u32 val_32; + unsigned i; + + iusbc_reset(dev); + iusbc_reinit(dev); + + for (i = 0; i < 2; i++) { + struct iusbc_ep *ep = &dev->ep[i]; + + /* ep[0]: ep0-in, ep[1]: ep0-out */ + ep->is_in = (i == 0 ? 1 : 0); + + /* ep0 ep_type */ + ep->ep_type = USB_ENDPOINT_XFER_CONTROL; + + /* linear mode only, control mode is useless */ + ep->dma_mode = LINEAR_MODE; + + /* reset ep0 in/out registers to default value */ + writew(0, &dev->regs->ep[i].ep_cfg); + + /* set ep0 in/out endpoints valid */ + val_16 = readw(&dev->regs->ep[i].ep_cfg); + val_16 |= INTR_BAD_PID_TYPE + | INTR_CRC_ERROR + | INTR_FIFO_ERROR + | INTR_DMA_ERROR + | INTR_TRANS_COMPLETE + /* | INTR_PING_NAK_SENT */ + | INTR_DMA_IOC + | ep->dma_mode << 6 + | ep->ep_type << 4 + /* | EP_ENABLE */ + | EP_VALID; + + writew(val_16, &dev->regs->ep[i].ep_cfg); + + val_16 = readw(&dev->regs->ep[i].ep_cfg); + } + + /* enable irqs */ + val_32 = readl(&dev->regs->int_ctrl); + val_32 |= RESET_INTR_ENABLE + | CONNECT_INTR_ENABLE + | SUSPEND_INTR_ENABLE + /* | EP3_OUT_INTR_ENABLE */ + /* | EP3_IN_INTR_ENABLE */ + /* | EP2_OUT_INTR_ENABLE */ + | EP2_IN_INTR_ENABLE + | EP1_OUT_INTR_ENABLE + | EP1_IN_INTR_ENABLE + | EP0_OUT_INTR_ENABLE + | EP0_IN_INTR_ENABLE; + + writel(val_32, &dev->regs->int_ctrl); + val_32 = readl(&dev->regs->int_ctrl); + dev->ep0state = EP0_IDLE; +} + +static void device_start(struct iusbc *dev) +{ + u32 val_32; + struct device *dbg_dev; + + /* reset all registers */ + writel(0, &dev->regs->dev_ctrl); + + /* PCI enable: write 1 to DeviceEnable */ + writel(DEVICE_ENABLE, &dev->regs->dev_ctrl); + /* within 5 ms the DeviceEnable bit is suppose to be 1. */ + mdelay(USBC_DELAY_POST_RESET); + val_32 = readl(&dev->regs->dev_ctrl); + dbg_dev = &dev->pdev->dev; + if (!(val_32 & DEVICE_ENABLE)) + dev_err(dbg_dev, "hardware reset error\n"); + + /* hardware transfer to running state now */ + val_32 |= DEVICE_ENABLE + /* | CONNECTION_ENABLE */ + /* | SIGNAL_RESUME */ + | CHARGE_ENABLE; + + /* module parameter: force_fullspeed */ + if (force_fullspeed) { + val_32 |= FORCE_FULLSPEED; + dev->force_fullspeed = 1; + } else + dev->force_fullspeed = 0; + + writel(val_32, &dev->regs->dev_ctrl); + + if (val_32 & CONNECTED) { + dev->connected = 1; + dev_vdbg(dbg_dev, "device_start: USB attached\n"); + } else { + dev->connected = 0; + dev_vdbg(dbg_dev, "device_start: USB detached\n"); + } + if (val_32 & SUSPEND) + dev->suspended = 1; + else + dev->suspended = 0; + + /* set device reset flag */ + dev->is_reset = 0; + iusbc_pullup(&dev->gadget, 1); + /* enable ep0 and host detection */ + ep0_start(dev); +} + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct iusbc *dev = the_controller; + int retval; + unsigned i; + + if (!driver || !driver->bind + || !driver->disconnect || !driver->setup) + return -EINVAL; + if (!dev) + return -ENODEV; + if (dev->driver) + return -EBUSY; + + dev->irqs = 0; + + /* 2 ep0, 3 data eps */ + for (i = 0; i < 5; i++) + dev->ep[i].irqs = 0; + + /* hook up the driver ... */ + driver->driver.bus = NULL; + dev->driver = driver; + dev->gadget.dev.driver = &driver->driver; + + retval = driver->bind(&dev->gadget); + if (retval) { + dev_dbg(&dev->pdev->dev, "bind to driver %s --> %d\n", + driver->driver.name, retval); + dev->driver = NULL; + dev->gadget.dev.driver = NULL; + return retval; + } + + retval = device_create_file(&dev->pdev->dev, + &dev_attr_function); + if (retval) + goto err_unbind; + + retval = device_create_file(&dev->pdev->dev, &dev_attr_queues); + if (retval) + goto err_func; + + device_start(dev); + return 0; + +err_func: + device_remove_file(&dev->pdev->dev, &dev_attr_function); + +err_unbind: + driver->unbind(&dev->gadget); + dev->gadget.dev.driver = NULL; + dev->driver = NULL; + return retval; +} +EXPORT_SYMBOL(usb_gadget_register_driver); + +static void +stop_activity(struct iusbc *dev, struct usb_gadget_driver *driver) +__releases(dev->lock) +__acquires(dev->lock) +{ + unsigned i; + + /* don't disconnect if it's not connected */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + + /* stop hardware; prevent new request submissions; + * and kill any outstanding requests. + */ + iusbc_reset(dev); + + /* Mark ep0state to disconnect to avoid upper layer to start + * next transfer immediately after the following nuke operation + */ + dev->ep0state = EP0_DISCONNECT; + + /* 2 ep0, 3 data ep */ + for (i = 0; i < 5; i++) + nuke(&dev->ep[i]); + + /* report disconnect; the driver is already quiesced */ + if (driver) { + spin_unlock(&dev->lock); + driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + + iusbc_reinit(dev); +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct iusbc *dev = the_controller; + unsigned long flags; + + if (!dev) + return -ENODEV; + if (!driver || driver != dev->driver || !driver->unbind) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + stop_activity(dev, driver); + spin_unlock_irqrestore(&dev->lock, flags); + + iusbc_pullup(&dev->gadget, 0); + + driver->unbind(&dev->gadget); + dev->gadget.dev.driver = NULL; + dev->driver = NULL; + + device_remove_file(&dev->pdev->dev, &dev_attr_function); + device_remove_file(&dev->pdev->dev, &dev_attr_queues); + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +static struct iusbc_ep * +get_ep_by_addr(struct iusbc *dev, u16 wIndex) +{ + struct iusbc_ep *ep; + + if ((wIndex & USB_ENDPOINT_NUMBER_MASK) == 0) + return &dev->ep[0]; + + list_for_each_entry(ep, &dev->gadget.ep_list, ep.ep_list) { + u8 bEndpointAddress; + if (!ep->desc) + continue; + + bEndpointAddress = ep->desc->bEndpointAddress; + if ((wIndex ^ bEndpointAddress) & USB_DIR_IN) + continue; + + if ((wIndex & USB_ENDPOINT_NUMBER_MASK) == + (bEndpointAddress & USB_ENDPOINT_NUMBER_MASK)) + return ep; + } + return NULL; +} + +/* NAK an endpoint */ +static void ep_nak(struct iusbc_ep *ep) +{ + u16 val_16; + unsigned i = ep->num; + + val_16 = readw(&ep->dev->regs->ep[i].ep_cfg); + val_16 &= ~EP_ENABLE; + val_16 |= EP_VALID; + writew(val_16, &ep->dev->regs->ep[i].ep_cfg); + + val_16 = readw(&ep->dev->regs->ep[i].ep_cfg); +} + + +/* ACK an out transfer with a zero length packet (ZLP) */ +static void ep_ack(struct iusbc_ep *ep) +{ + u16 val_16; + unsigned i = ep->num; + + /* reset data buffer zero length */ + writew(0, &ep->dev->regs->ep[i].ep_len); + val_16 = readw(&ep->dev->regs->ep[i].ep_len); + + /* validate endpoint, enable DMA */ + val_16 = readw(&ep->dev->regs->ep[i].ep_cfg); + val_16 |= (EP_VALID | EP_ENABLE); + writew(val_16, &ep->dev->regs->ep[i].ep_cfg); + + val_16 = readw(&ep->dev->regs->ep[i].ep_cfg); +} + +static void ep0_setup(struct iusbc *dev) +__releases(dev->lock) +__acquires(dev->lock) +{ + struct usb_ctrlrequest ctrl; + struct iusbc_ep *epn; + unsigned i = 0; + int tmp = 0; + u8 addr_new, setup_pkt[8]; + struct device *dbg_dev; + + dbg_dev = &dev->pdev->dev; + + for (i = 0; i < 8; i++) + setup_pkt[i] = readb(&dev->regs->ep[1].setup_pkt[i]); + + /* read SETUP packet and enter DATA stage */ + ctrl.bRequestType = setup_pkt[0]; + ctrl.bRequest = setup_pkt[1]; + ctrl.wValue = cpu_to_le16((setup_pkt[3] << 8) + | setup_pkt[2]); + ctrl.wIndex = cpu_to_le16((setup_pkt[5] << 8) + | setup_pkt[4]); + ctrl.wLength = cpu_to_le16((setup_pkt[7] << 8) + | setup_pkt[6]); + + dev->ep[1].stopped = 0; + + /* data stage direction */ + if (ctrl.bRequestType & USB_DIR_IN) + dev->ep0state = EP0_IN; + else + dev->ep0state = EP0_OUT; + + /* For RNDIS */ + /* SEND_ENCAPSULATED_COMMAND and GET_ENCAPSULATED_RESPONSE */ + if ((ctrl.bRequestType == 0x21 && ctrl.bRequest == 0x00) + || (ctrl.bRequestType == 0xa1 && ctrl.bRequest == 0x01)) + goto delegate; + + switch (ctrl.bRequest) { + /* GET_STATUS is handled by software */ + case USB_REQ_GET_STATUS: + switch (ctrl.bRequestType & + __constant_cpu_to_le16(USB_RECIP_MASK)) { + case USB_RECIP_DEVICE: + /* bit 0: USB_DEVICE_SELF_POWERED + * bit 1: USB_DEVICE_REMOTE_WAKEUP + */ + if (dev->enable_wakeup) + dev->status_d = + __constant_cpu_to_le16(1 << 1 | 1); + else + dev->status_d = + __constant_cpu_to_le16(1); + break; + case USB_RECIP_INTERFACE: + /* all bits zero */ + dev->status_d = __constant_cpu_to_le16(0); + break; + case USB_RECIP_ENDPOINT: + epn = get_ep_by_addr(dev, + le16_to_cpu(ctrl.wIndex)); + if (epn->stopped) /* halted */ + dev->status_d = + __constant_cpu_to_le16(1); + else + dev->status_d = + __constant_cpu_to_le16(0); + break; + } + /* GET_STATUS is partially handled in gadget driver */ + goto delegate; + + case USB_REQ_CLEAR_FEATURE: + switch (ctrl.bRequestType) { + case USB_RECIP_DEVICE: + if (ctrl.wValue == __constant_cpu_to_le16( + USB_DEVICE_REMOTE_WAKEUP)) { + dev->enable_wakeup = 0; + goto end; + } else + goto stall; + break; + case USB_RECIP_INTERFACE: + goto stall; + case USB_RECIP_ENDPOINT: + if (ctrl.wValue != __constant_cpu_to_le16( + USB_ENDPOINT_HALT) + || le16_to_cpu(ctrl.wLength) != 0) + goto stall; + epn = get_ep_by_addr(dev, + le16_to_cpu(ctrl.wIndex)); + if (epn == NULL) + goto stall; + if (epn->wedged) { + dev_vdbg(dbg_dev, + "%s wedged, halt not cleared\n", + epn->ep.name); + } else { + dev_vdbg(dbg_dev, "%s clear halt\n", + epn->ep.name); + clear_halt(epn); + ep_ack(&dev->ep[0]); + dev->ep0state = EP0_STATUS; + } + goto end; + } + break; + + case USB_REQ_SET_FEATURE: + switch (ctrl.bRequestType) { + case USB_RECIP_DEVICE: + if (ctrl.wValue == __constant_cpu_to_le16( + USB_DEVICE_REMOTE_WAKEUP)) { + dev->enable_wakeup = 1; + goto end; + } else + goto stall; + break; + case USB_RECIP_INTERFACE: + goto stall; + case USB_RECIP_ENDPOINT: + if (ctrl.wValue != __constant_cpu_to_le16( + USB_ENDPOINT_HALT) + || le16_to_cpu(ctrl.wLength) != 0) + goto stall; + epn = get_ep_by_addr(dev, + le16_to_cpu(ctrl.wIndex)); + if (epn == NULL) + goto stall; + if (epn->ep.name == ep_name[0] + || epn->ep.name == ep_name[1]) + goto stall; + set_halt(epn); + ep_ack(&dev->ep[0]); + goto end; + } + break; + + case USB_REQ_SET_ADDRESS: + /* hw handles set_address, address range: 1-127 */ + if (setup_pkt[1] == USB_REQ_SET_ADDRESS) { + addr_new = le16_to_cpu(ctrl.wValue) & 0x7f; + /* hardware didn't ACK SET_ADDRESS */ + ep_ack(&dev->ep[0]); + } + goto end; + + case USB_REQ_GET_DESCRIPTOR: + goto delegate; + + case USB_REQ_SET_DESCRIPTOR: + goto stall; + break; + + case USB_REQ_GET_CONFIGURATION: + goto delegate; + + case USB_REQ_SET_CONFIGURATION: + goto delegate; + + case USB_REQ_GET_INTERFACE: + goto delegate; + + case USB_REQ_SET_INTERFACE: + goto delegate; + + case USB_REQ_SYNCH_FRAME: + goto stall; + break; + default: + /* delegate usb standard requests to the gadget driver. + * it may respond after this irq handler returns. + */ + goto delegate; +delegate: + dev_vdbg(dbg_dev, "SETUP %02x.%02x v%04x i%04x l%04x\n", + ctrl.bRequestType, ctrl.bRequest, + le16_to_cpu(ctrl.wValue), + le16_to_cpu(ctrl.wIndex), + le16_to_cpu(ctrl.wLength)); + + /* For RNDIS */ + /* CDC: SEND_ENCAPSULATED_COMMAND */ + if ((ctrl.bRequestType == 0x21) + && (ctrl.bRequest == 0x00)) { + /* CDC: SEND_ENCAPSULATED_COMMAND */ + dev_dbg(dbg_dev, + "SEND_ENCAPSULATED_COMMAND\n"); + dev->gadget.ep0 = &dev->ep[1].ep; + spin_unlock(&dev->lock); + tmp = dev->driver->setup(&dev->gadget, &ctrl); + spin_lock(&dev->lock); + + /* switch back to ep0 in */ + dev->gadget.ep0 = &dev->ep[0].ep; + + /* hardware didn't ACK */ + ep_ack(&dev->ep[0]); + } else { + dev->gadget.ep0 = &dev->ep[0].ep; + spin_unlock(&dev->lock); + tmp = dev->driver->setup(&dev->gadget, &ctrl); + /* spin_lock(&dev->lock);*/ + + dev_dbg(dbg_dev, "tmp = %d\n. ", tmp); + /* CDC: GET_ENCAPSULATED_RESPONSE */ + if ((ctrl.bRequestType == 0xa1) + && (ctrl.bRequest == 0x01)) + dev_vdbg(dbg_dev, + "GET_ENCAPSULATED_RESPONSE\n"); + spin_lock(&dev->lock); + } + break; + } + + if (tmp < 0) { +stall: + dev_dbg(dbg_dev, + "req %02x.%02x protocol STALL; err %d\n", + ctrl.bRequestType, ctrl.bRequest, tmp); + dev->ep0state = EP0_STALL; + dev_vdbg(dbg_dev, "ep0state: EP0_STALL\n"); + } +end: + dev_vdbg(dbg_dev, "<--- ep0_setup() \n"); +} + +static void handle_device_irqs(struct iusbc *dev) +__releases(dev->lock) +__acquires(dev->lock) +{ + u32 stat; + u32 dev_sts; + unsigned is_connected = 0; + struct device *dbg_dev; + + dbg_dev = &dev->pdev->dev; + stat = dev->int_sts; + + /* get device status, set USB speed */ + if ((stat & RESET_INTR) || (stat & CONNECT_INTR) + || (stat & SUSPEND_INTR)) { + /* check device status */ + dev_sts = readl(&dev->regs->dev_sts); + dev_vdbg(dbg_dev, + "handle_device_irqs: dev_sts = 0x%08x\n", + dev_sts); + + if (dev_sts & CONNECTED) { + is_connected = 1; + dev_dbg(dbg_dev, "device connected\n"); + + /* check suspend/resume status now */ + if (dev_sts & SUSPEND) + dev->suspended = 1; + else + dev->suspended = 0; + + /* RATE: high/full speed flag + * 1 clock cycle after CONNECT, + * read one more time until RATE bit set + */ + dev_sts = readl(&dev->regs->dev_sts); + dev_dbg(dbg_dev, + "read for RATE: dev_sts = 0x%08x\n", + dev_sts); + if (dev_sts & RATE) { + if (dev->force_fullspeed) { + dev->gadget.speed = + USB_SPEED_FULL; + dev_dbg(dbg_dev, + "speed: Full Speed\n"); + } else { + dev->gadget.speed = + USB_SPEED_HIGH; + dev_dbg(dbg_dev, + "speed: High Speed\n"); + } + } else { + dev->gadget.speed = USB_SPEED_FULL; + dev_dbg(dbg_dev, + "speed: Full Speed\n"); + } + } else { /* disconnected */ + is_connected = 0; + dev_dbg(dbg_dev, "device disconnected\n"); + } + } + + /* USB reset interrupt indication */ + if ((stat & RESET_INTR) && (!dev->is_reset)) { + dev_dbg(dbg_dev, + "USB Reset interrupt: stat = 0x%08x\n", stat); + /* ACK RESET_INTR */ + stat &= ~RESET_INTR; + dev->irqs++; + dev_vdbg(dbg_dev, "dev->irqs: %lu\n", dev->irqs); + + /* set device reset flag */ + dev->is_reset = 1; + + stop_activity(dev, dev->driver); + ep0_start(dev); + dev_dbg(dbg_dev, "reset: ep0_start()\n"); + + goto end; + } + + /* connect interrupt indication */ + if (stat & CONNECT_INTR) { + dev_dbg(dbg_dev, + "CONNECT interrupt: stat = 0x%08x\n", stat); + /* ACK CONNECT_INTR */ + stat &= ~CONNECT_INTR; + dev->irqs++; + dev_dbg(dbg_dev, "dev->irqs: %lu\n", dev->irqs); + + /* connected status has changed */ + if (dev->connected != is_connected) { + dev_dbg(dbg_dev, "connect status changed\n"); + dev->connected = is_connected; + if (is_connected) { + ep0_start(dev); + dev_dbg(dbg_dev, "connect %s\n", + dev->driver->driver.name); + } else { /* disconnected */ + /* set device reset flag */ + dev->is_reset = 0; + stop_activity(dev, dev->driver); + dev_dbg(dbg_dev, "disconnect %s\n", + dev->driver->driver.name); + dev->ep0state = EP0_DISCONNECT; + dev_vdbg(dbg_dev, + "ep0state: EP0_DISCONNECT\n"); + } + } + } + + /* host suspend interrupt indication */ + if (stat & SUSPEND_INTR) { + dev_dbg(dbg_dev, + "SUSPEND interrupt: stat = 0x%08x\n", stat); + /* ACK SUSPEND_INTR */ + stat &= ~SUSPEND_INTR; + dev->irqs++; + dev_vdbg(dbg_dev, "dev->irqs: %lu\n", dev->irqs); + + /* call gadget driver suspend/resume routines */ + if (dev->suspended) { + if (dev->driver->suspend) { + spin_unlock(&dev->lock); + dev->driver->suspend(&dev->gadget); + spin_lock(&dev->lock); + } + dev_dbg(dbg_dev, "suspend %s\n", + dev->driver->driver.name); + } else { + if (dev->driver->resume) { + spin_unlock(&dev->lock); + dev->driver->resume(&dev->gadget); + spin_lock(&dev->lock); + } + dev_dbg(dbg_dev, "resume %s\n", + dev->driver->driver.name); + } + } + + /* if haven't USB Reset yet, wait for the next*/ + if (!dev->is_reset) + dev_dbg(dbg_dev, + "Skip other interrupts until RESET\n"); + +end: + dev_vdbg(dbg_dev, + "handle device_irq finish: int_sts = 0x%08x, " + "stat = 0x%08x\n", dev->int_sts, stat); +} + +static void handle_ep0_irqs(struct iusbc *dev) +{ + u16 ep_sts, val_16; + u32 stat; + u8 setup_pkt_sts; + struct iusbc_request *req; + struct device *dbg_dev; + + dbg_dev = &dev->pdev->dev; + stat = dev->int_sts; + dev_vdbg(dbg_dev, "stat = 0x%08x\n", stat); + + /* ep0 in interrupt */ + if (stat & EP0_IN_INTR) { + dev->ep[0].irqs++; + dev_vdbg(dbg_dev, "%s.irqs = %lu\n", + dev->ep[0].ep.name, dev->ep[0].irqs); + + ep_sts = readw(&dev->regs->ep[0].ep_sts); + dev_vdbg(dbg_dev, "%s.ep_sts = 0x%04x\n", + dev->ep[0].ep.name, ep_sts); + + /* W1C ep0 in status register */ + writew(ep_sts, &dev->regs->ep[0].ep_sts); + + if ((ep_sts & BAD_PID_TYPE) + || (ep_sts & CRC_ERROR) + || (ep_sts & FIFO_ERROR) + || (ep_sts & DMA_ERROR) + || (ep_sts & DMA_IOC)) + dev_dbg(dbg_dev, "%s error: 0x%04x \n", + dev->ep[0].ep.name, ep_sts); + + if (ep_sts & TRANS_COMPLETE) { + dev_vdbg(dbg_dev, "handle ep0 in interrupt\n"); + if (!list_empty(&dev->ep[0].queue)) { + req = list_entry(dev->ep[0].queue.next, + struct iusbc_request, + queue); + dev_dbg(dbg_dev, "dmacount = %d\n", + req->td->dmacount); + dma_done(&dev->ep[0], req, 0); + dev_dbg(dbg_dev, "%s dma_done()\n", + dev->ep[0].ep.name); + } + + /* handle next standard IN packet */ + if (!list_empty(&dev->ep[0].queue)) { + restart_dma(&dev->ep[0]); + dev_vdbg(dbg_dev, "%s restart_dma()\n", + dev->ep[0].ep.name); + } + } else { + if (ep_sts & FIFO_ERROR) + dev_dbg(dbg_dev, + "ep0 in FIFO_ERROR,TC=0\n"); + } + } + + /* ep0 out interrupt */ + if (stat & EP0_OUT_INTR) { + dev->ep[1].irqs++; + dev_dbg(dbg_dev, "%s.irqs = %lu\n", + dev->ep[1].ep.name, dev->ep[1].irqs); + + ep_sts = readw(&dev->regs->ep[1].ep_sts); + dev_dbg(dbg_dev, "%s.ep_sts = 0x%04x\n", + dev->ep[1].ep.name, ep_sts); + + /* W1C ep0 out status register */ + writew(ep_sts, &dev->regs->ep[1].ep_sts); + + if ((ep_sts & BAD_PID_TYPE) + || (ep_sts & CRC_ERROR) + || (ep_sts & FIFO_ERROR) + || (ep_sts & DMA_ERROR) + || (ep_sts & DMA_IOC)) { + dev_dbg(dbg_dev, "%s error: 0x%04x \n", + dev->ep[1].ep.name, ep_sts); + } + + if (ep_sts & TRANS_COMPLETE) { + dev_dbg(dbg_dev, "handle ep0 out interrupt\n"); + + setup_pkt_sts = readb( + &dev->regs->ep[1].setup_pkt_sts); + dev_dbg(dbg_dev, "setup_pkt_sts = 0x%02x\n", + setup_pkt_sts); + /* ep0 out SETUP packet */ + if (setup_pkt_sts) { + dev_dbg(dbg_dev, + "ep0 out SETUP packet\n"); + + /* W1C ep0 out setup_pkt_sts Register*/ + writeb(1, + &dev->regs->ep[1].setup_pkt_sts); + + /* read ep0 out Setup Packet Register + * then handle ep0 out SETUP packet + */ + ep0_setup(dev); + } else { + /* ep0 standard OUT packet */ + dev_dbg(dbg_dev, "ep0 OUT packet\n"); + if (!list_empty(&dev->ep[1].queue)) { + req = list_entry( + dev->ep[1].queue.next, + struct iusbc_request, + queue); + dev_vdbg(dbg_dev, + "dmacount = %d\n", + req->td->dmacount); + dma_done(&dev->ep[1], req, 0); + dev_dbg(dbg_dev, + "%s dma_done()\n", + dev->ep[1].ep.name); + } + + /* handle next standard OUT packet */ + if (!list_empty(&dev->ep[1].queue)) { + restart_dma(&dev->ep[1]); + dev_vdbg(dbg_dev, + "%s restart_dma()\n", + dev->ep[1].ep.name); + } + } + } else { + if (ep_sts & FIFO_ERROR) + dev_dbg(dbg_dev, + "ep0 out FIFO_ERROR, TC=0\n"); + } + + /* enable DMA again */ + val_16 = readw(&dev->regs->ep[1].ep_cfg); + val_16 |= EP_ENABLE; + writew(val_16, &dev->regs->ep[1].ep_cfg); + + val_16 = readw(&dev->regs->ep[1].ep_cfg); + dev_vdbg(dbg_dev, "enable %s DMA transfer...\n", + dev->ep[1].ep.name); + dev_vdbg(dbg_dev, + "%s EP_ENABLE again, ep_cfg=0x%04x\n", + dev->ep[1].ep.name, val_16); + } +} + +static void handle_ep_irqs(struct iusbc *dev) +{ + u16 ep_sts; + u32 stat; + unsigned i; + struct iusbc_request *req; + struct device *dbg_dev; + + dbg_dev = &dev->pdev->dev; + stat = dev->int_sts; + dev_vdbg(dbg_dev, "stat = 0x%08x\n", stat); + + /* ep1in-bulk, ep1out-bulk and ep2in-int */ + for (i = 2; i < 5; i++) { + if ((1 << i) & stat) { + dev->ep[i].irqs++; + ep_sts = readw(&dev->regs->ep[i].ep_sts); + if (ep_sts) + dev_vdbg(dbg_dev, + "%s.ep_sts = 0x%04x\n", + dev->ep[i].ep.name, ep_sts); + + /* W1C ep status register */ + writew(ep_sts, &dev->regs->ep[i].ep_sts); + + if ((ep_sts & BAD_PID_TYPE) + || (ep_sts & CRC_ERROR) + || (ep_sts & FIFO_ERROR) + || (ep_sts & DMA_ERROR) + || (ep_sts & DMA_IOC)) { + dev_dbg(dbg_dev, "%s error: 0x%04x \n", + dev->ep[i].ep.name, ep_sts); + } + + if (ep_sts & TRANS_COMPLETE) { + dev_vdbg(dbg_dev, + "handle %s interrupt\n", + dev->ep[i].ep.name); + dev_vdbg(dbg_dev, + "data ep TRANS_COMPLETE\n"); + if (!list_empty(&dev->ep[i].queue)) { + req = list_entry( + dev->ep[i].queue.next, + struct iusbc_request, + queue); + dev_vdbg(dbg_dev, + "dmacount = %d\n", + req->td->dmacount); + dma_done(&dev->ep[i], req, 0); + dev_vdbg(dbg_dev, + "%s dma_done()\n", + dev->ep[i].ep.name); + } + + /* handle next IN/OUT packet */ + if (!list_empty(&dev->ep[i].queue)) { + restart_dma(&dev->ep[i]); + dev_vdbg(dbg_dev, + "%s restart_dma()\n", + dev->ep[i].ep.name); + } + } else + if (ep_sts & FIFO_ERROR) + dev_dbg(dbg_dev, + "%s FIFO_ERROR,TC=0\n", + dev->ep[i].ep.name); + } + } +} + +static irqreturn_t iusbc_irq(int irq, void *_dev) +{ + struct iusbc *dev = _dev; + u32 int_sts, + int_ctrl, + val_32; + u32 dev_sts; + struct device *dbg_dev; + + dbg_dev = &dev->pdev->dev; + + /* interrupt control */ + int_ctrl = readl(&dev->regs->int_ctrl); + dev_vdbg(dbg_dev, "int_ctrl = 0x%08x\n", int_ctrl); + + /* interrupt status */ + int_sts = readl(&dev->regs->int_sts); + dev_vdbg(dbg_dev, "int_sts = 0x%08x\n", int_sts); + + if (!int_sts || !int_ctrl) { + dev_vdbg(dbg_dev, "handle IRQ_NONE\n"); + return IRQ_NONE; + } + + spin_lock(&dev->lock); + + /* disable irqs, will re-enable later */ + writel(0, &dev->regs->int_ctrl); + + /* W1C interrupt status register */ + writel(int_sts, &dev->regs->int_sts); + val_32 = readl(&dev->regs->int_sts); + dev_vdbg(dbg_dev, "W1C: regs->int_sts = 0x%08x\n", val_32); + + dev->int_sts = int_sts; + + /* check device status */ + val_32 = readl(&dev->regs->dev_sts); + dev_vdbg(dbg_dev, "iusbc_irq: dev_sts = 0x%08x\n", val_32); + + /* device-wide reset, connect, suspend */ + if (dev->int_sts & + (RESET_INTR | CONNECT_INTR | SUSPEND_INTR)) { + dev_vdbg(dbg_dev, "iusbc_irq -> handle_device_irqs\n"); + handle_device_irqs(dev); + } + + /* Full Speed is disabled for D1, and enabled for D2. */ + dev_sts = readl(&dev->regs->dev_sts); + if (dev->stepping >= STEPPING_D2) { + dev_dbg(dbg_dev, "iusbc_irq -> D2 boards\n"); + + /* ep0 in/out control requests and interrupt */ + if (dev->int_sts & (EP0_IN_INTR | EP0_OUT_INTR)) { + dev_dbg(dbg_dev, "handle_ep0_irqs\n"); + handle_ep0_irqs(dev); + } + + if (dev->int_sts & + (EP1_IN_INTR | EP1_OUT_INTR | EP2_IN_INTR)) { + dev_dbg(dbg_dev, "handle_ep_irqs.\n"); + handle_ep_irqs(dev); + } + } else { + dev_vdbg(dbg_dev, "iusbc_irq -> D1 boards\n"); + if ((dev_sts & RATE) && (dev_sts & CONNECTED)) { + /* ep0 in/out control requests and interrupt */ + if (dev->int_sts & + (EP0_IN_INTR | EP0_OUT_INTR)) { + dev_dbg(dbg_dev, "handle_ep0_irqs\n"); + handle_ep0_irqs(dev); + } + + if (dev->int_sts & (EP1_IN_INTR | EP1_OUT_INTR + | EP2_IN_INTR)) { + /* data endpoint requests interrupt */ + dev_dbg(dbg_dev, "handle_ep_irqs\n"); + handle_ep_irqs(dev); + } + } else if (dev_sts & CONNECTED) { + stop_activity(dev, dev->driver); + dev->ep0state = EP0_DISCONNECT; + } + } + /* enable irqs again */ + val_32 = 0; + val_32 |= RESET_INTR_ENABLE + | CONNECT_INTR_ENABLE + | SUSPEND_INTR_ENABLE + /* | EP3_OUT_INTR_ENABLE */ + /* | EP3_IN_INTR_ENABLE */ + /* | EP2_OUT_INTR_ENABLE */ + | EP2_IN_INTR_ENABLE + | EP1_OUT_INTR_ENABLE + | EP1_IN_INTR_ENABLE + | EP0_OUT_INTR_ENABLE + | EP0_IN_INTR_ENABLE; + + writel(val_32, &dev->regs->int_ctrl); + val_32 = readl(&dev->regs->int_ctrl); + spin_unlock(&dev->lock); + + dev_vdbg(dbg_dev, + "enable irqs again, int_ctrl = 0x%08x\n", val_32); + + return IRQ_HANDLED; +} + +static void gadget_release(struct device *_dev) +{ + struct iusbc *dev; + + dev_vdbg(_dev, "---> gadget_release() \n"); + + dev = dev_get_drvdata(_dev); + kfree(dev); + + dev_vdbg(_dev, "<--- gadget_release() \n"); +} + +/* tear down the binding between this driver and the pci device */ +static void iusbc_remove(struct pci_dev *pdev) +{ + struct iusbc *dev; + + dev = pci_get_drvdata(pdev); + BUG_ON(dev->driver); + + dev_vdbg(&pdev->dev, "---> iusbc_remove() \n"); + + /* then clean up the resources we allocated during probe() */ + if (dev->requests) { + unsigned i; + /* 2 ep0, 3 data ep */ + for (i = 0; i < 5; i++) { + if (!dev->ep[i].dummy) + continue; + + pci_pool_free(dev->requests, dev->ep[i].dummy, + dev->ep[i].td_dma); + } + pci_pool_destroy(dev->requests); + } + + if (dev->got_irq) + free_irq(pdev->irq, dev); + + if (dev->regs) + iounmap(dev->regs); + + if (dev->region) + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + + if (dev->enabled) + pci_disable_device(pdev); + + device_unregister(&dev->gadget.dev); + device_remove_file(&pdev->dev, &dev_attr_registers); + pci_set_drvdata(pdev, NULL); + + dev_vdbg(&pdev->dev, "unbind\n"); + + the_controller = NULL; + + dev_vdbg(&pdev->dev, "<--- iusbc_remove() \n"); +} + +static int +iusbc_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct iusbc *dev; + unsigned long resource, len; + void __iomem *base = NULL; + int retval; + unsigned i; + u32 val_32; + u8 stepping; + + /* if you want to support more than one controller in a system, + * usb_gadget_{register,unregister}_driver() must change. + */ + if (the_controller) { + dev_warn(&pdev->dev, "ignoring\n"); + return -EBUSY; + } + + /* alloc, and start init */ + dev = kzalloc(sizeof *dev, GFP_KERNEL); + if (dev == NULL) { + retval = -ENOMEM; + goto done; + } + + pci_set_drvdata(pdev, dev); + spin_lock_init(&dev->lock); + dev->pdev = pdev; + + dev_dbg(&pdev->dev, "---> iusbc_probe() \n"); + + dev->gadget.ops = &iusbc_ops; + dev->gadget.is_dualspeed = 1; /* support high/full speed */ + + /* the "gadget" abstracts/virtualizes the controller */ + dev_set_name(&dev->gadget.dev, "gadget"); + dev->gadget.dev.parent = &pdev->dev; + dev->gadget.dev.dma_mask = pdev->dev.dma_mask; + dev->gadget.dev.release = gadget_release; + dev->gadget.name = driver_name; + + /* now all the pci goodies ... */ + if (pci_enable_device(pdev) < 0) { + retval = -ENODEV; + goto done; + } + + dev->enabled = 1; + + /* control register: BAR 0 */ + resource = pci_resource_start(pdev, 0); + len = pci_resource_len(pdev, 0); + if (!request_mem_region(resource, len, driver_name)) { + dev_dbg(&pdev->dev, "controller already in use\n"); + retval = -EBUSY; + goto done; + } + dev->region = 1; + + base = ioremap_nocache(resource, len); + if (base == NULL) { + dev_dbg(&pdev->dev, "can't map memory\n"); + retval = -EFAULT; + goto done; + } + dev->regs = (struct iusbc_regs __iomem *) base; + + /* global capabilities, ep number and dma modes */ + val_32 = readl(&dev->regs->gcap); + if (val_32 & TRANSFER_MODE_CAP) + dev->transfer_mode_dma = 1; + + if (val_32 & SCATTER_GATHER_MODE_CAP) + dev->sg_mode_dma = 1; + + if (val_32 & LINEAR_MODE_CAP) + dev->linear_mode_dma = 1; + + if (val_32 & CONTROL_MODE_CAP) + dev->control_mode_dma = 1; + + dev->ep_cap = (val_32 >> 28); + + /* reset all registers */ + writel(0, &dev->regs->dev_ctrl); + + /* init usb client controller device */ + iusbc_reset(dev); + iusbc_reinit(dev); + + /* irq setup after old hardware is cleaned up */ + if (!pdev->irq) { + dev_err(&pdev->dev, "No IRQ. Check PCI setup!\n"); + retval = -ENODEV; + goto done; + } + + if (request_irq(pdev->irq, iusbc_irq, IRQF_SHARED, + driver_name, dev) != 0) { + dev_err(&pdev->dev, "request interrupt %d failed\n", + pdev->irq); + retval = -EBUSY; + goto done; + } + dev->got_irq = 1; + + /* DMA setup + * NOTE: only the 32 LSBs of dma addresses may be nonzero + */ + dev->requests = pci_pool_create("requests", pdev, + sizeof(struct iusbc_dma), + 0 /* no alignment requirements */, + 0 /* or page-crossing issues */); + + if (!dev->requests) { + dev_dbg(&pdev->dev, "can't get request pool\n"); + retval = -ENOMEM; + goto done; + } + + /* assgined DMA */ + /* 2 ep0, 3 data ep */ + for (i = 0; i < 5 ; i++) { + struct iusbc_dma *td; + td = pci_pool_alloc(dev->requests, + GFP_KERNEL, + &dev->ep[i].td_dma); + + if (!td) { + dev_dbg(&pdev->dev, "can't get dummy %d\n", i); + retval = -ENOMEM; + goto done; + } + + td->dmacount = 0; /* not VALID */ + td->dmaaddr = __constant_cpu_to_le32(DMA_ADDR_INVALID); + + dev->ep[i].dummy = td; + } + + /* enables bus-mastering for device dev */ + pci_set_master(pdev); + + pci_read_config_byte(pdev, 0x08, &stepping); + dev->stepping = stepping; + + /* done */ + dev_dbg(&pdev->dev, "%s\n", driver_desc); + dev_dbg(&pdev->dev, "irq %d, pci mem %p\n", pdev->irq, base); + dev_dbg(&pdev->dev, "version: " DRIVER_VERSION "\n"); + dev_dbg(&pdev->dev, "support (max) %d endpoints\n", + dev->ep_cap * 2); + +#ifdef VERBOSE_DEBUG + /* only for debugging, print mapped memory registers */ + dev_vdbg(&pdev->dev, + "After iusbc_probe(), print register values:\n"); + print_all_registers(dev->regs); +#endif + + the_controller = dev; + retval = device_register(&dev->gadget.dev); + + if (retval) + goto done; + + retval = device_create_file(&pdev->dev, &dev_attr_registers); + + if (retval) + goto done; + + dev_dbg(&pdev->dev, "<--- iusbc_probe() \n"); + + return 0; + +done: + if (dev) + iusbc_remove(pdev); + return retval; +} + +#ifdef CONFIG_PM +/* client suspend */ +static int iusbc_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct iusbc *dev; + unsigned long flags; + + dev = pci_get_drvdata(pdev); + spin_lock_irqsave(&dev->lock, flags); + stop_activity(dev, dev->driver); + spin_unlock_irqrestore(&dev->lock, flags); + iusbc_pullup(&dev->gadget, 0); + pci_save_state(pdev); + pci_set_power_state(pdev, PCI_D3hot); + return 0; +} + +/* client resume */ +static int iusbc_resume(struct pci_dev *pdev) +{ + struct iusbc *dev; + + dev = pci_get_drvdata(pdev); + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + if (dev->driver) + device_start(dev); + return 0; +} +#endif + +static void iusbc_shutdown(struct pci_dev *pdev) +{ + struct iusbc *dev; + u32 val_32; + + dev = pci_get_drvdata(pdev); + + /* disable irqs */ + writel(0, &dev->regs->int_ctrl); + + /* reset all registers */ + writel(0, &dev->regs->dev_ctrl); + val_32 = readl(&dev->regs->dev_ctrl); + dev_dbg(&pdev->dev, "dev_ctrl = 0x%08x\n", val_32); +} + +static const struct pci_device_id pci_ids[] = { { + .class = ((PCI_CLASS_SERIAL_USB << 8) | 0x80), + .class_mask = ~0, + .vendor = 0x8086, /* Intel */ + .device = 0x8118, /* Poulsbo USB Client Controller */ + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, +}, { /* end: all zeroes */ } +}; + +MODULE_DEVICE_TABLE(pci, pci_ids); + +/* pci driver glue; this is a "new style" PCI driver module */ +static struct pci_driver iusbc_pci_driver = { + .name = (char *) driver_name, + .id_table = pci_ids, + + .probe = iusbc_probe, + .remove = iusbc_remove, + +#ifdef CONFIG_PM + .suspend = iusbc_suspend, + .resume = iusbc_resume, +#endif + + .shutdown = iusbc_shutdown, +}; + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); + +static int __init init(void) +{ + return pci_register_driver(&iusbc_pci_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + pci_unregister_driver(&iusbc_pci_driver); +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/iusbc.h b/drivers/usb/gadget/iusbc.h new file mode 100644 index 0000000..09009a7 --- /dev/null +++ b/drivers/usb/gadget/iusbc.h @@ -0,0 +1,207 @@ +/* + * Intel Poulsbo USB Client Controller Driver + * Copyright (C) 2006-07, Intel Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms and conditions of the GNU General Public + * License, version 2, as published by the Free Software Foundation. + * This program is distributed in the hope it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, + * MA 02110-1301 USA. + * + */ + + +/* + * MEMORY SPACE REGISTERS + */ + +struct iusbc_ep_regs { /* 0x20 bytes */ + u32 ep_base_low_32; + u32 ep_base_hi_32; + u16 ep_len; + u16 ep_pib; + u16 ep_dil; + u16 ep_tiq; + u16 ep_max; + u16 ep_sts; +#define BAD_PID_TYPE (1 << 15) +#define CRC_ERROR (1 << 14) +#define FIFO_ERROR (1 << 13) +#define DMA_ERROR (1 << 12) +#define TRANS_COMPLETE (1 << 11) +#define PING_NAK_SENT (1 << 10) +#define DMA_IOC (1 << 9) + u16 ep_cfg; +#define INTR_BAD_PID_TYPE (1 << 15) +#define INTR_CRC_ERROR (1 << 14) +#define INTR_FIFO_ERROR (1 << 13) +#define INTR_DMA_ERROR (1 << 12) +#define INTR_TRANS_COMPLETE (1 << 11) +#define INTR_PING_NAK_SENT (1 << 10) +#define INTR_DMA_IOC (1 << 9) +#define LINEAR_MODE 0 +#define SCATTER_GATHER_MODE 1 +#define TRANSFER_MODE 2 +#define CONTROL_MODE 3 +#define EP_ENABLE (1 << 1) +#define EP_VALID (1 << 0) + u8 _unused; + u8 setup_pkt_sts; +#define SETUPPACKET_VALID (1 << 0) + u8 setup_pkt[8]; +} __attribute__((packed)); + + +struct iusbc_regs { + /* offset 0x0000 */ + u32 gcap; +#define DMA_IOC_CAP (1 << 4) +#define TRANSFER_MODE_CAP (1 << 3) +#define SCATTER_GATHER_MODE_CAP (1 << 2) +#define LINEAR_MODE_CAP (1 << 1) +#define CONTROL_MODE_CAP (1 << 0) + u8 _unused0[0x100-0x004]; + + /* offset 0x0100 */ + u32 dev_sts; +#define RATE (1 << 3) +#define CONNECTED (1 << 1) +#define SUSPEND (1 << 0) + u16 frame; + u8 _unused1[0x10c-0x106]; + + /* offset 0x010c */ + u32 int_sts; +#define RESET_INTR (1 << 18) +#define CONNECT_INTR (1 << 17) +#define SUSPEND_INTR (1 << 16) +#define EP3_OUT_INTR (1 << 7) +#define EP3_IN_INTR (1 << 6) +#define EP2_OUT_INTR (1 << 5) +#define EP2_IN_INTR (1 << 4) +#define EP1_OUT_INTR (1 << 3) +#define EP1_IN_INTR (1 << 2) +#define EP0_OUT_INTR (1 << 1) +#define EP0_IN_INTR (1 << 0) + u32 int_ctrl; +#define RESET_INTR_ENABLE (1 << 18) +#define CONNECT_INTR_ENABLE (1 << 17) +#define SUSPEND_INTR_ENABLE (1 << 16) +#define EP3_OUT_INTR_ENABLE (1 << 7) +#define EP3_IN_INTR_ENABLE (1 << 6) +#define EP2_OUT_INTR_ENABLE (1 << 5) +#define EP2_IN_INTR_ENABLE (1 << 4) +#define EP1_OUT_INTR_ENABLE (1 << 3) +#define EP1_IN_INTR_ENABLE (1 << 2) +#define EP0_OUT_INTR_ENABLE (1 << 1) +#define EP0_IN_INTR_ENABLE (1 << 0) + u32 dev_ctrl; +#define DEVICE_ENABLE (1 << 31) +#define CONNECTION_ENABLE (1 << 30) +#define DMA3_DISABLED (1 << 15) +#define DMA2_DISABLED (1 << 14) +#define DMA1_DISABLED (1 << 13) +#define DMA0_DISABLED (1 << 12) +#define CPU_SET_ADDRESS (1 << 11) +#define DISABLE_NYET (1 << 9) +#define TEST_MODE (1 << 8) +#define SIGNAL_RESUME (1 << 4) +#define CHARGE_ENABLE (5 << 1) +#define FORCE_FULLSPEED (1 << 0) + u8 _unused2[0x200-0x118]; + + /* offset: 0x200, 0x220, ..., 0x2e0 */ + struct iusbc_ep_regs ep[8]; +} __attribute__((packed)); + +/* DRIVER DATA STRUCTURES and UTILITIES */ +struct iusbc_dma { + __le16 dmacount; + __le32 dmaaddr; /* the buffer */ + __le32 dmadesc; /* next dma descriptor */ + __le32 _reserved; +} __attribute__((aligned(16))); + +struct iusbc_ep { + struct usb_ep ep; + struct iusbc_dma *dummy; + dma_addr_t td_dma; /* of dummy */ + struct iusbc *dev; + unsigned long irqs; + + /* analogous to a host-side qh */ + struct list_head queue; + const struct usb_endpoint_descriptor *desc; + unsigned num:8, + stopped : 1, + wedged : 1, + is_in : 1, + dma_mode : 2, + ep_type : 2; +}; + +struct iusbc_request { + struct usb_request req; + struct iusbc_dma *td; + dma_addr_t td_dma; + struct list_head queue; + unsigned mapped:1, + valid : 1; +}; + +enum ep0state { + EP0_DISCONNECT, /* no host */ + EP0_IDLE, /* between STATUS ack and SETUP */ + EP0_IN, EP0_OUT, /* data stage */ + EP0_STATUS, /* status stage */ + EP0_STALL, /* data or status stages */ + EP0_SUSPEND, /* usb suspend */ +}; + +struct iusbc { + /* each pci device provides one gadget, several endpoints */ + struct usb_gadget gadget; + spinlock_t lock; + struct iusbc_ep ep[8]; + struct usb_gadget_driver *driver; + enum ep0state ep0state; + unsigned ep_cap; + unsigned enabled:1, + powered : 1, + enable_wakeup : 1, + force_fullspeed : 1, + rate : 1, + connected : 1, + suspended : 1, + got_irq : 1, + region : 1, + transfer_mode_dma : 1, + sg_mode_dma : 1, + linear_mode_dma : 1, + control_mode_dma : 1, + is_reset : 1; + + /* pci state used to access those endpoints */ + struct pci_dev *pdev; + struct iusbc_regs __iomem *regs; + struct pci_pool *requests; + + /* statistics... */ + unsigned long irqs; + + /* 16 bits status data for GET_STATUS */ + __le16 status_d; + + /* interrupt status register value */ + u32 int_sts; + + /* Poulsbo USB-c Chipset stepping */ + u8 stepping; +}; -- 1.5.6.3 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html