summaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/udc/omap_udc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget/udc/omap_udc.c')
-rw-r--r--drivers/usb/gadget/udc/omap_udc.c3038
1 files changed, 3038 insertions, 0 deletions
diff --git a/drivers/usb/gadget/udc/omap_udc.c b/drivers/usb/gadget/udc/omap_udc.c
new file mode 100644
index 000000000000..e731373fd4d7
--- /dev/null
+++ b/drivers/usb/gadget/udc/omap_udc.c
@@ -0,0 +1,3038 @@
+/*
+ * omap_udc.c -- for OMAP full speed udc; most chips support OTG.
+ *
+ * Copyright (C) 2004 Texas Instruments, Inc.
+ * Copyright (C) 2004-2005 David Brownell
+ *
+ * OMAP2 & DMA support by Kyungmin Park <kyungmin.park@samsung.com>
+ *
+ * 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.
+ */
+
+#undef DEBUG
+#undef VERBOSE
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/mm.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/otg.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/prefetch.h>
+#include <linux/io.h>
+
+#include <asm/byteorder.h>
+#include <asm/irq.h>
+#include <asm/unaligned.h>
+#include <asm/mach-types.h>
+
+#include <linux/omap-dma.h>
+
+#include <mach/usb.h>
+
+#include "omap_udc.h"
+
+#undef USB_TRACE
+
+/* bulk DMA seems to be behaving for both IN and OUT */
+#define USE_DMA
+
+/* ISO too */
+#define USE_ISO
+
+#define DRIVER_DESC "OMAP UDC driver"
+#define DRIVER_VERSION "4 October 2004"
+
+#define OMAP_DMA_USB_W2FC_TX0 29
+#define OMAP_DMA_USB_W2FC_RX0 26
+
+/*
+ * The OMAP UDC needs _very_ early endpoint setup: before enabling the
+ * D+ pullup to allow enumeration. That's too early for the gadget
+ * framework to use from usb_endpoint_enable(), which happens after
+ * enumeration as part of activating an interface. (But if we add an
+ * optional new "UDC not yet running" state to the gadget driver model,
+ * even just during driver binding, the endpoint autoconfig logic is the
+ * natural spot to manufacture new endpoints.)
+ *
+ * So instead of using endpoint enable calls to control the hardware setup,
+ * this driver defines a "fifo mode" parameter. It's used during driver
+ * initialization to choose among a set of pre-defined endpoint configs.
+ * See omap_udc_setup() for available modes, or to add others. That code
+ * lives in an init section, so use this driver as a module if you need
+ * to change the fifo mode after the kernel boots.
+ *
+ * Gadget drivers normally ignore endpoints they don't care about, and
+ * won't include them in configuration descriptors. That means only
+ * misbehaving hosts would even notice they exist.
+ */
+#ifdef USE_ISO
+static unsigned fifo_mode = 3;
+#else
+static unsigned fifo_mode;
+#endif
+
+/* "modprobe omap_udc fifo_mode=42", or else as a kernel
+ * boot parameter "omap_udc:fifo_mode=42"
+ */
+module_param(fifo_mode, uint, 0);
+MODULE_PARM_DESC(fifo_mode, "endpoint configuration");
+
+#ifdef USE_DMA
+static bool use_dma = 1;
+
+/* "modprobe omap_udc use_dma=y", or else as a kernel
+ * boot parameter "omap_udc:use_dma=y"
+ */
+module_param(use_dma, bool, 0);
+MODULE_PARM_DESC(use_dma, "enable/disable DMA");
+#else /* !USE_DMA */
+
+/* save a bit of code */
+#define use_dma 0
+#endif /* !USE_DMA */
+
+
+static const char driver_name[] = "omap_udc";
+static const char driver_desc[] = DRIVER_DESC;
+
+/*-------------------------------------------------------------------------*/
+
+/* there's a notion of "current endpoint" for modifying endpoint
+ * state, and PIO access to its FIFO.
+ */
+
+static void use_ep(struct omap_ep *ep, u16 select)
+{
+ u16 num = ep->bEndpointAddress & 0x0f;
+
+ if (ep->bEndpointAddress & USB_DIR_IN)
+ num |= UDC_EP_DIR;
+ omap_writew(num | select, UDC_EP_NUM);
+ /* when select, MUST deselect later !! */
+}
+
+static inline void deselect_ep(void)
+{
+ u16 w;
+
+ w = omap_readw(UDC_EP_NUM);
+ w &= ~UDC_EP_SEL;
+ omap_writew(w, UDC_EP_NUM);
+ /* 6 wait states before TX will happen */
+}
+
+static void dma_channel_claim(struct omap_ep *ep, unsigned preferred);
+
+/*-------------------------------------------------------------------------*/
+
+static int omap_ep_enable(struct usb_ep *_ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct omap_ep *ep = container_of(_ep, struct omap_ep, ep);
+ struct omap_udc *udc;
+ unsigned long flags;
+ u16 maxp;
+
+ /* catch various bogus parameters */
+ if (!_ep || !desc
+ || desc->bDescriptorType != USB_DT_ENDPOINT
+ || ep->bEndpointAddress != desc->bEndpointAddress
+ || ep->maxpacket < usb_endpoint_maxp(desc)) {
+ DBG("%s, bad ep or descriptor\n", __func__);
+ return -EINVAL;
+ }
+ maxp = usb_endpoint_maxp(desc);
+ if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK
+ && maxp != ep->maxpacket)
+ || usb_endpoint_maxp(desc) > ep->maxpacket
+ || !desc->wMaxPacketSize) {
+ DBG("%s, bad %s maxpacket\n", __func__, _ep->name);
+ return -ERANGE;
+ }
+
+#ifdef USE_ISO
+ if ((desc->bmAttributes == USB_ENDPOINT_XFER_ISOC
+ && desc->bInterval != 1)) {
+ /* hardware wants period = 1; USB allows 2^(Interval-1) */
+ DBG("%s, unsupported ISO period %dms\n", _ep->name,
+ 1 << (desc->bInterval - 1));
+ return -EDOM;
+ }
+#else
+ if (desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) {
+ DBG("%s, ISO nyet\n", _ep->name);
+ return -EDOM;
+ }
+#endif
+
+ /* xfer types must match, except that interrupt ~= bulk */
+ if (ep->bmAttributes != desc->bmAttributes
+ && ep->bmAttributes != USB_ENDPOINT_XFER_BULK
+ && desc->bmAttributes != USB_ENDPOINT_XFER_INT) {
+ DBG("%s, %s type mismatch\n", __func__, _ep->name);
+ return -EINVAL;
+ }
+
+ udc = ep->udc;
+ if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) {
+ DBG("%s, bogus device state\n", __func__);
+ return -ESHUTDOWN;
+ }
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ ep->ep.desc = desc;
+ ep->irqs = 0;
+ ep->stopped = 0;
+ ep->ep.maxpacket = maxp;
+
+ /* set endpoint to initial state */
+ ep->dma_channel = 0;
+ ep->has_dma = 0;
+ ep->lch = -1;
+ use_ep(ep, UDC_EP_SEL);
+ omap_writew(udc->clr_halt, UDC_CTRL);
+ ep->ackwait = 0;
+ deselect_ep();
+
+ if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC)
+ list_add(&ep->iso, &udc->iso);
+
+ /* maybe assign a DMA channel to this endpoint */
+ if (use_dma && desc->bmAttributes == USB_ENDPOINT_XFER_BULK)
+ /* FIXME ISO can dma, but prefers first channel */
+ dma_channel_claim(ep, 0);
+
+ /* PIO OUT may RX packets */
+ if (desc->bmAttributes != USB_ENDPOINT_XFER_ISOC
+ && !ep->has_dma
+ && !(ep->bEndpointAddress & USB_DIR_IN)) {
+ omap_writew(UDC_SET_FIFO_EN, UDC_CTRL);
+ ep->ackwait = 1 + ep->double_buf;
+ }
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+ VDBG("%s enabled\n", _ep->name);
+ return 0;
+}
+
+static void nuke(struct omap_ep *, int status);
+
+static int omap_ep_disable(struct usb_ep *_ep)
+{
+ struct omap_ep *ep = container_of(_ep, struct omap_ep, ep);
+ unsigned long flags;
+
+ if (!_ep || !ep->ep.desc) {
+ DBG("%s, %s not enabled\n", __func__,
+ _ep ? ep->ep.name : NULL);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&ep->udc->lock, flags);
+ ep->ep.desc = NULL;
+ nuke(ep, -ESHUTDOWN);
+ ep->ep.maxpacket = ep->maxpacket;
+ ep->has_dma = 0;
+ omap_writew(UDC_SET_HALT, UDC_CTRL);
+ list_del_init(&ep->iso);
+ del_timer(&ep->timer);
+
+ spin_unlock_irqrestore(&ep->udc->lock, flags);
+
+ VDBG("%s disabled\n", _ep->name);
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_request *
+omap_alloc_request(struct usb_ep *ep, gfp_t gfp_flags)
+{
+ struct omap_req *req;
+
+ req = kzalloc(sizeof(*req), gfp_flags);
+ if (!req)
+ return NULL;
+
+ INIT_LIST_HEAD(&req->queue);
+
+ return &req->req;
+}
+
+static void
+omap_free_request(struct usb_ep *ep, struct usb_request *_req)
+{
+ struct omap_req *req = container_of(_req, struct omap_req, req);
+
+ kfree(req);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void
+done(struct omap_ep *ep, struct omap_req *req, int status)
+{
+ struct omap_udc *udc = ep->udc;
+ unsigned stopped = ep->stopped;
+
+ list_del_init(&req->queue);
+
+ if (req->req.status == -EINPROGRESS)
+ req->req.status = status;
+ else
+ status = req->req.status;
+
+ if (use_dma && ep->has_dma)
+ usb_gadget_unmap_request(&udc->gadget, &req->req,
+ (ep->bEndpointAddress & USB_DIR_IN));
+
+#ifndef USB_TRACE
+ if (status && status != -ESHUTDOWN)
+#endif
+ VDBG("complete %s req %p stat %d len %u/%u\n",
+ ep->ep.name, &req->req, status,
+ req->req.actual, req->req.length);
+
+ /* don't modify queue heads during completion callback */
+ ep->stopped = 1;
+ spin_unlock(&ep->udc->lock);
+ req->req.complete(&ep->ep, &req->req);
+ spin_lock(&ep->udc->lock);
+ ep->stopped = stopped;
+}
+
+/*-------------------------------------------------------------------------*/
+
+#define UDC_FIFO_FULL (UDC_NON_ISO_FIFO_FULL | UDC_ISO_FIFO_FULL)
+#define UDC_FIFO_UNWRITABLE (UDC_EP_HALTED | UDC_FIFO_FULL)
+
+#define FIFO_EMPTY (UDC_NON_ISO_FIFO_EMPTY | UDC_ISO_FIFO_EMPTY)
+#define FIFO_UNREADABLE (UDC_EP_HALTED | FIFO_EMPTY)
+
+static inline int
+write_packet(u8 *buf, struct omap_req *req, unsigned max)
+{
+ unsigned len;
+ u16 *wp;
+
+ len = min(req->req.length - req->req.actual, max);
+ req->req.actual += len;
+
+ max = len;
+ if (likely((((int)buf) & 1) == 0)) {
+ wp = (u16 *)buf;
+ while (max >= 2) {
+ omap_writew(*wp++, UDC_DATA);
+ max -= 2;
+ }
+ buf = (u8 *)wp;
+ }
+ while (max--)
+ omap_writeb(*buf++, UDC_DATA);
+ return len;
+}
+
+/* FIXME change r/w fifo calling convention */
+
+
+/* return: 0 = still running, 1 = completed, negative = errno */
+static int write_fifo(struct omap_ep *ep, struct omap_req *req)
+{
+ u8 *buf;
+ unsigned count;
+ int is_last;
+ u16 ep_stat;
+
+ buf = req->req.buf + req->req.actual;
+ prefetch(buf);
+
+ /* PIO-IN isn't double buffered except for iso */
+ ep_stat = omap_readw(UDC_STAT_FLG);
+ if (ep_stat & UDC_FIFO_UNWRITABLE)
+ return 0;
+
+ count = ep->ep.maxpacket;
+ count = write_packet(buf, req, count);
+ omap_writew(UDC_SET_FIFO_EN, UDC_CTRL);
+ ep->ackwait = 1;
+
+ /* last packet is often short (sometimes a zlp) */
+ if (count != ep->ep.maxpacket)
+ is_last = 1;
+ else if (req->req.length == req->req.actual
+ && !req->req.zero)
+ is_last = 1;
+ else
+ is_last = 0;
+
+ /* NOTE: requests complete when all IN data is in a
+ * FIFO (or sometimes later, if a zlp was needed).
+ * Use usb_ep_fifo_status() where needed.
+ */
+ if (is_last)
+ done(ep, req, 0);
+ return is_last;
+}
+
+static inline int
+read_packet(u8 *buf, struct omap_req *req, unsigned avail)
+{
+ unsigned len;
+ u16 *wp;
+
+ len = min(req->req.length - req->req.actual, avail);
+ req->req.actual += len;
+ avail = len;
+
+ if (likely((((int)buf) & 1) == 0)) {
+ wp = (u16 *)buf;
+ while (avail >= 2) {
+ *wp++ = omap_readw(UDC_DATA);
+ avail -= 2;
+ }
+ buf = (u8 *)wp;
+ }
+ while (avail--)
+ *buf++ = omap_readb(UDC_DATA);
+ return len;
+}
+
+/* return: 0 = still running, 1 = queue empty, negative = errno */
+static int read_fifo(struct omap_ep *ep, struct omap_req *req)
+{
+ u8 *buf;
+ unsigned count, avail;
+ int is_last;
+
+ buf = req->req.buf + req->req.actual;
+ prefetchw(buf);
+
+ for (;;) {
+ u16 ep_stat = omap_readw(UDC_STAT_FLG);
+
+ is_last = 0;
+ if (ep_stat & FIFO_EMPTY) {
+ if (!ep->double_buf)
+ break;
+ ep->fnf = 1;
+ }
+ if (ep_stat & UDC_EP_HALTED)
+ break;
+
+ if (ep_stat & UDC_FIFO_FULL)
+ avail = ep->ep.maxpacket;
+ else {
+ avail = omap_readw(UDC_RXFSTAT);
+ ep->fnf = ep->double_buf;
+ }
+ count = read_packet(buf, req, avail);
+
+ /* partial packet reads may not be errors */
+ if (count < ep->ep.maxpacket) {
+ is_last = 1;
+ /* overflowed this request? flush extra data */
+ if (count != avail) {
+ req->req.status = -EOVERFLOW;
+ avail -= count;
+ while (avail--)
+ omap_readw(UDC_DATA);
+ }
+ } else if (req->req.length == req->req.actual)
+ is_last = 1;
+ else
+ is_last = 0;
+
+ if (!ep->bEndpointAddress)
+ break;
+ if (is_last)
+ done(ep, req, 0);
+ break;
+ }
+ return is_last;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static u16 dma_src_len(struct omap_ep *ep, dma_addr_t start)
+{
+ dma_addr_t end;
+
+ /* IN-DMA needs this on fault/cancel paths, so 15xx misreports
+ * the last transfer's bytecount by more than a FIFO's worth.
+ */
+ if (cpu_is_omap15xx())
+ return 0;
+
+ end = omap_get_dma_src_pos(ep->lch);
+ if (end == ep->dma_counter)
+ return 0;
+
+ end |= start & (0xffff << 16);
+ if (end < start)
+ end += 0x10000;
+ return end - start;
+}
+
+static u16 dma_dest_len(struct omap_ep *ep, dma_addr_t start)
+{
+ dma_addr_t end;
+
+ end = omap_get_dma_dst_pos(ep->lch);
+ if (end == ep->dma_counter)
+ return 0;
+
+ end |= start & (0xffff << 16);
+ if (cpu_is_omap15xx())
+ end++;
+ if (end < start)
+ end += 0x10000;
+ return end - start;
+}
+
+
+/* Each USB transfer request using DMA maps to one or more DMA transfers.
+ * When DMA completion isn't request completion, the UDC continues with
+ * the next DMA transfer for that USB transfer.
+ */
+
+static void next_in_dma(struct omap_ep *ep, struct omap_req *req)
+{
+ u16 txdma_ctrl, w;
+ unsigned length = req->req.length - req->req.actual;
+ const int sync_mode = cpu_is_omap15xx()
+ ? OMAP_DMA_SYNC_FRAME
+ : OMAP_DMA_SYNC_ELEMENT;
+ int dma_trigger = 0;
+
+ /* measure length in either bytes or packets */
+ if ((cpu_is_omap16xx() && length <= UDC_TXN_TSC)
+ || (cpu_is_omap15xx() && length < ep->maxpacket)) {
+ txdma_ctrl = UDC_TXN_EOT | length;
+ omap_set_dma_transfer_params(ep->lch, OMAP_DMA_DATA_TYPE_S8,
+ length, 1, sync_mode, dma_trigger, 0);
+ } else {
+ length = min(length / ep->maxpacket,
+ (unsigned) UDC_TXN_TSC + 1);
+ txdma_ctrl = length;
+ omap_set_dma_transfer_params(ep->lch, OMAP_DMA_DATA_TYPE_S16,
+ ep->ep.maxpacket >> 1, length, sync_mode,
+ dma_trigger, 0);
+ length *= ep->maxpacket;
+ }
+ omap_set_dma_src_params(ep->lch, OMAP_DMA_PORT_EMIFF,
+ OMAP_DMA_AMODE_POST_INC, req->req.dma + req->req.actual,
+ 0, 0);
+
+ omap_start_dma(ep->lch);
+ ep->dma_counter = omap_get_dma_src_pos(ep->lch);
+ w = omap_readw(UDC_DMA_IRQ_EN);
+ w |= UDC_TX_DONE_IE(ep->dma_channel);
+ omap_writew(w, UDC_DMA_IRQ_EN);
+ omap_writew(UDC_TXN_START | txdma_ctrl, UDC_TXDMA(ep->dma_channel));
+ req->dma_bytes = length;
+}
+
+static void finish_in_dma(struct omap_ep *ep, struct omap_req *req, int status)
+{
+ u16 w;
+
+ if (status == 0) {
+ req->req.actual += req->dma_bytes;
+
+ /* return if this request needs to send data or zlp */
+ if (req->req.actual < req->req.length)
+ return;
+ if (req->req.zero
+ && req->dma_bytes != 0
+ && (req->req.actual % ep->maxpacket) == 0)
+ return;
+ } else
+ req->req.actual += dma_src_len(ep, req->req.dma
+ + req->req.actual);
+
+ /* tx completion */
+ omap_stop_dma(ep->lch);
+ w = omap_readw(UDC_DMA_IRQ_EN);
+ w &= ~UDC_TX_DONE_IE(ep->dma_channel);
+ omap_writew(w, UDC_DMA_IRQ_EN);
+ done(ep, req, status);
+}
+
+static void next_out_dma(struct omap_ep *ep, struct omap_req *req)
+{
+ unsigned packets = req->req.length - req->req.actual;
+ int dma_trigger = 0;
+ u16 w;
+
+ /* set up this DMA transfer, enable the fifo, start */
+ packets /= ep->ep.maxpacket;
+ packets = min(packets, (unsigned)UDC_RXN_TC + 1);
+ req->dma_bytes = packets * ep->ep.maxpacket;
+ omap_set_dma_transfer_params(ep->lch, OMAP_DMA_DATA_TYPE_S16,
+ ep->ep.maxpacket >> 1, packets,
+ OMAP_DMA_SYNC_ELEMENT,
+ dma_trigger, 0);
+ omap_set_dma_dest_params(ep->lch, OMAP_DMA_PORT_EMIFF,
+ OMAP_DMA_AMODE_POST_INC, req->req.dma + req->req.actual,
+ 0, 0);
+ ep->dma_counter = omap_get_dma_dst_pos(ep->lch);
+
+ omap_writew(UDC_RXN_STOP | (packets - 1), UDC_RXDMA(ep->dma_channel));
+ w = omap_readw(UDC_DMA_IRQ_EN);
+ w |= UDC_RX_EOT_IE(ep->dma_channel);
+ omap_writew(w, UDC_DMA_IRQ_EN);
+ omap_writew(ep->bEndpointAddress & 0xf, UDC_EP_NUM);
+ omap_writew(UDC_SET_FIFO_EN, UDC_CTRL);
+
+ omap_start_dma(ep->lch);
+}
+
+static void
+finish_out_dma(struct omap_ep *ep, struct omap_req *req, int status, int one)
+{
+ u16 count, w;
+
+ if (status == 0)
+ ep->dma_counter = (u16) (req->req.dma + req->req.actual);
+ count = dma_dest_len(ep, req->req.dma + req->req.actual);
+ count += req->req.actual;
+ if (one)
+ count--;
+ if (count <= req->req.length)
+ req->req.actual = count;
+
+ if (count != req->dma_bytes || status)
+ omap_stop_dma(ep->lch);
+
+ /* if this wasn't short, request may need another transfer */
+ else if (req->req.actual < req->req.length)
+ return;
+
+ /* rx completion */
+ w = omap_readw(UDC_DMA_IRQ_EN);
+ w &= ~UDC_RX_EOT_IE(ep->dma_channel);
+ omap_writew(w, UDC_DMA_IRQ_EN);
+ done(ep, req, status);
+}
+
+static void dma_irq(struct omap_udc *udc, u16 irq_src)
+{
+ u16 dman_stat = omap_readw(UDC_DMAN_STAT);
+ struct omap_ep *ep;
+ struct omap_req *req;
+
+ /* IN dma: tx to host */
+ if (irq_src & UDC_TXN_DONE) {
+ ep = &udc->ep[16 + UDC_DMA_TX_SRC(dman_stat)];
+ ep->irqs++;
+ /* can see TXN_DONE after dma abort */
+ if (!list_empty(&ep->queue)) {
+ req = container_of(ep->queue.next,
+ struct omap_req, queue);
+ finish_in_dma(ep, req, 0);
+ }
+ omap_writew(UDC_TXN_DONE, UDC_IRQ_SRC);
+
+ if (!list_empty(&ep->queue)) {
+ req = container_of(ep->queue.next,
+ struct omap_req, queue);
+ next_in_dma(ep, req);
+ }
+ }
+
+ /* OUT dma: rx from host */
+ if (irq_src & UDC_RXN_EOT) {
+ ep = &udc->ep[UDC_DMA_RX_SRC(dman_stat)];
+ ep->irqs++;
+ /* can see RXN_EOT after dma abort */
+ if (!list_empty(&ep->queue)) {
+ req = container_of(ep->queue.next,
+ struct omap_req, queue);
+ finish_out_dma(ep, req, 0, dman_stat & UDC_DMA_RX_SB);
+ }
+ omap_writew(UDC_RXN_EOT, UDC_IRQ_SRC);
+
+ if (!list_empty(&ep->queue)) {
+ req = container_of(ep->queue.next,
+ struct omap_req, queue);
+ next_out_dma(ep, req);
+ }
+ }
+
+ if (irq_src & UDC_RXN_CNT) {
+ ep = &udc->ep[UDC_DMA_RX_SRC(dman_stat)];
+ ep->irqs++;
+ /* omap15xx does this unasked... */
+ VDBG("%s, RX_CNT irq?\n", ep->ep.name);
+ omap_writew(UDC_RXN_CNT, UDC_IRQ_SRC);
+ }
+}
+
+static void dma_error(int lch, u16 ch_status, void *data)
+{
+ struct omap_ep *ep = data;
+
+ /* if ch_status & OMAP_DMA_DROP_IRQ ... */
+ /* if ch_status & OMAP1_DMA_TOUT_IRQ ... */
+ ERR("%s dma error, lch %d status %02x\n", ep->ep.name, lch, ch_status);
+
+ /* complete current transfer ... */
+}
+
+static void dma_channel_claim(struct omap_ep *ep, unsigned channel)
+{
+ u16 reg;
+ int status, restart, is_in;
+ int dma_channel;
+
+ is_in = ep->bEndpointAddress & USB_DIR_IN;
+ if (is_in)
+ reg = omap_readw(UDC_TXDMA_CFG);
+ else
+ reg = omap_readw(UDC_RXDMA_CFG);
+ reg |= UDC_DMA_REQ; /* "pulse" activated */
+
+ ep->dma_channel = 0;
+ ep->lch = -1;
+ if (channel == 0 || channel > 3) {
+ if ((reg & 0x0f00) == 0)
+ channel = 3;
+ else if ((reg & 0x00f0) == 0)
+ channel = 2;
+ else if ((reg & 0x000f) == 0) /* preferred for ISO */
+ channel = 1;
+ else {
+ status = -EMLINK;
+ goto just_restart;
+ }
+ }
+ reg |= (0x0f & ep->bEndpointAddress) << (4 * (channel - 1));
+ ep->dma_channel = channel;
+
+ if (is_in) {
+ dma_channel = OMAP_DMA_USB_W2FC_TX0 - 1 + channel;
+ status = omap_request_dma(dma_channel,
+ ep->ep.name, dma_error, ep, &ep->lch);
+ if (status == 0) {
+ omap_writew(reg, UDC_TXDMA_CFG);
+ /* EMIFF or SDRC */
+ omap_set_dma_src_burst_mode(ep->lch,
+ OMAP_DMA_DATA_BURST_4);
+ omap_set_dma_src_data_pack(ep->lch, 1);
+ /* TIPB */
+ omap_set_dma_dest_params(ep->lch,
+ OMAP_DMA_PORT_TIPB,
+ OMAP_DMA_AMODE_CONSTANT,
+ UDC_DATA_DMA,
+ 0, 0);
+ }
+ } else {
+ dma_channel = OMAP_DMA_USB_W2FC_RX0 - 1 + channel;
+ status = omap_request_dma(dma_channel,
+ ep->ep.name, dma_error, ep, &ep->lch);
+ if (status == 0) {
+ omap_writew(reg, UDC_RXDMA_CFG);
+ /* TIPB */
+ omap_set_dma_src_params(ep->lch,
+ OMAP_DMA_PORT_TIPB,
+ OMAP_DMA_AMODE_CONSTANT,
+ UDC_DATA_DMA,
+ 0, 0);
+ /* EMIFF or SDRC */
+ omap_set_dma_dest_burst_mode(ep->lch,
+ OMAP_DMA_DATA_BURST_4);
+ omap_set_dma_dest_data_pack(ep->lch, 1);
+ }
+ }
+ if (status)
+ ep->dma_channel = 0;
+ else {
+ ep->has_dma = 1;
+ omap_disable_dma_irq(ep->lch, OMAP_DMA_BLOCK_IRQ);
+
+ /* channel type P: hw synch (fifo) */
+ if (!cpu_is_omap15xx())
+ omap_set_dma_channel_mode(ep->lch, OMAP_DMA_LCH_P);
+ }
+
+just_restart:
+ /* restart any queue, even if the claim failed */
+ restart = !ep->stopped && !list_empty(&ep->queue);
+
+ if (status)
+ DBG("%s no dma channel: %d%s\n", ep->ep.name, status,
+ restart ? " (restart)" : "");
+ else
+ DBG("%s claimed %cxdma%d lch %d%s\n", ep->ep.name,
+ is_in ? 't' : 'r',
+ ep->dma_channel - 1, ep->lch,
+ restart ? " (restart)" : "");
+
+ if (restart) {
+ struct omap_req *req;
+ req = container_of(ep->queue.next, struct omap_req, queue);
+ if (ep->has_dma)
+ (is_in ? next_in_dma : next_out_dma)(ep, req);
+ else {
+ use_ep(ep, UDC_EP_SEL);
+ (is_in ? write_fifo : read_fifo)(ep, req);
+ deselect_ep();
+ if (!is_in) {
+ omap_writew(UDC_SET_FIFO_EN, UDC_CTRL);
+ ep->ackwait = 1 + ep->double_buf;
+ }
+ /* IN: 6 wait states before it'll tx */
+ }
+ }
+}
+
+static void dma_channel_release(struct omap_ep *ep)
+{
+ int shift = 4 * (ep->dma_channel - 1);
+ u16 mask = 0x0f << shift;
+ struct omap_req *req;
+ int active;
+
+ /* abort any active usb transfer request */
+ if (!list_empty(&ep->queue))
+ req = container_of(ep->queue.next, struct omap_req, queue);
+ else
+ req = NULL;
+
+ active = omap_get_dma_active_status(ep->lch);
+
+ DBG("%s release %s %cxdma%d %p\n", ep->ep.name,
+ active ? "active" : "idle",
+ (ep->bEndpointAddress & USB_DIR_IN) ? 't' : 'r',
+ ep->dma_channel - 1, req);
+
+ /* NOTE: re-setting RX_REQ/TX_REQ because of a chip bug (before
+ * OMAP 1710 ES2.0) where reading the DMA_CFG can clear them.
+ */
+
+ /* wait till current packet DMA finishes, and fifo empties */
+ if (ep->bEndpointAddress & USB_DIR_IN) {
+ omap_writew((omap_readw(UDC_TXDMA_CFG) & ~mask) | UDC_DMA_REQ,
+ UDC_TXDMA_CFG);
+
+ if (req) {
+ finish_in_dma(ep, req, -ECONNRESET);
+
+ /* clear FIFO; hosts probably won't empty it */
+ use_ep(ep, UDC_EP_SEL);
+ omap_writew(UDC_CLR_EP, UDC_CTRL);
+ deselect_ep();
+ }
+ while (omap_readw(UDC_TXDMA_CFG) & mask)
+ udelay(10);
+ } else {
+ omap_writew((omap_readw(UDC_RXDMA_CFG) & ~mask) | UDC_DMA_REQ,
+ UDC_RXDMA_CFG);
+
+ /* dma empties the fifo */
+ while (omap_readw(UDC_RXDMA_CFG) & mask)
+ udelay(10);
+ if (req)
+ finish_out_dma(ep, req, -ECONNRESET, 0);
+ }
+ omap_free_dma(ep->lch);
+ ep->dma_channel = 0;
+ ep->lch = -1;
+ /* has_dma still set, till endpoint is fully quiesced */
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int
+omap_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
+{
+ struct omap_ep *ep = container_of(_ep, struct omap_ep, ep);
+ struct omap_req *req = container_of(_req, struct omap_req, req);
+ struct omap_udc *udc;
+ unsigned long flags;
+ int is_iso = 0;
+
+ /* catch various bogus parameters */
+ if (!_req || !req->req.complete || !req->req.buf
+ || !list_empty(&req->queue)) {
+ DBG("%s, bad params\n", __func__);
+ return -EINVAL;
+ }
+ if (!_ep || (!ep->ep.desc && ep->bEndpointAddress)) {
+ DBG("%s, bad ep\n", __func__);
+ return -EINVAL;
+ }
+ if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) {
+ if (req->req.length > ep->ep.maxpacket)
+ return -EMSGSIZE;
+ is_iso = 1;
+ }
+
+ /* this isn't bogus, but OMAP DMA isn't the only hardware to
+ * have a hard time with partial packet reads... reject it.
+ */
+ if (use_dma
+ && ep->has_dma
+ && ep->bEndpointAddress != 0
+ && (ep->bEndpointAddress & USB_DIR_IN) == 0
+ && (req->req.length % ep->ep.maxpacket) != 0) {
+ DBG("%s, no partial packet OUT reads\n", __func__);
+ return -EMSGSIZE;
+ }
+
+ udc = ep->udc;
+ if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN)
+ return -ESHUTDOWN;
+
+ if (use_dma && ep->has_dma)
+ usb_gadget_map_request(&udc->gadget, &req->req,
+ (ep->bEndpointAddress & USB_DIR_IN));
+
+ VDBG("%s queue req %p, len %d buf %p\n",
+ ep->ep.name, _req, _req->length, _req->buf);
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ req->req.status = -EINPROGRESS;
+ req->req.actual = 0;
+
+ /* maybe kickstart non-iso i/o queues */
+ if (is_iso) {
+ u16 w;
+
+ w = omap_readw(UDC_IRQ_EN);
+ w |= UDC_SOF_IE;
+ omap_writew(w, UDC_IRQ_EN);
+ } else if (list_empty(&ep->queue) && !ep->stopped && !ep->ackwait) {
+ int is_in;
+
+ if (ep->bEndpointAddress == 0) {
+ if (!udc->ep0_pending || !list_empty(&ep->queue)) {
+ spin_unlock_irqrestore(&udc->lock, flags);
+ return -EL2HLT;
+ }
+
+ /* empty DATA stage? */
+ is_in = udc->ep0_in;
+ if (!req->req.length) {
+
+ /* chip became CONFIGURED or ADDRESSED
+ * earlier; drivers may already have queued
+ * requests to non-control endpoints
+ */
+ if (udc->ep0_set_config) {
+ u16 irq_en = omap_readw(UDC_IRQ_EN);
+
+ irq_en |= UDC_DS_CHG_IE | UDC_EP0_IE;
+ if (!udc->ep0_reset_config)
+ irq_en |= UDC_EPN_RX_IE
+ | UDC_EPN_TX_IE;
+ omap_writew(irq_en, UDC_IRQ_EN);
+ }
+
+ /* STATUS for zero length DATA stages is
+ * always an IN ... even for IN transfers,
+ * a weird case which seem to stall OMAP.
+ */
+ omap_writew(UDC_EP_SEL | UDC_EP_DIR,
+ UDC_EP_NUM);
+ omap_writew(UDC_CLR_EP, UDC_CTRL);
+ omap_writew(UDC_SET_FIFO_EN, UDC_CTRL);
+ omap_writew(UDC_EP_DIR, UDC_EP_NUM);
+
+ /* cleanup */
+ udc->ep0_pending = 0;
+ done(ep, req, 0);
+ req = NULL;
+
+ /* non-empty DATA stage */
+ } else if (is_in) {
+ omap_writew(UDC_EP_SEL | UDC_EP_DIR,
+ UDC_EP_NUM);
+ } else {
+ if (udc->ep0_setup)
+ goto irq_wait;
+ omap_writew(UDC_EP_SEL, UDC_EP_NUM);
+ }
+ } else {
+ is_in = ep->bEndpointAddress & USB_DIR_IN;
+ if (!ep->has_dma)
+ use_ep(ep, UDC_EP_SEL);
+ /* if ISO: SOF IRQs must be enabled/disabled! */
+ }
+
+ if (ep->has_dma)
+ (is_in ? next_in_dma : next_out_dma)(ep, req);
+ else if (req) {
+ if ((is_in ? write_fifo : read_fifo)(ep, req) == 1)
+ req = NULL;
+ deselect_ep();
+ if (!is_in) {
+ omap_writew(UDC_SET_FIFO_EN, UDC_CTRL);
+ ep->ackwait = 1 + ep->double_buf;
+ }
+ /* IN: 6 wait states before it'll tx */
+ }
+ }
+
+irq_wait:
+ /* irq handler advances the queue */
+ if (req != NULL)
+ list_add_tail(&req->queue, &ep->queue);
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ return 0;
+}
+
+static int omap_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
+{
+ struct omap_ep *ep = container_of(_ep, struct omap_ep, ep);
+ struct omap_req *req;
+ unsigned long flags;
+
+ if (!_ep || !_req)
+ return -EINVAL;
+
+ spin_lock_irqsave(&ep->udc->lock, flags);
+
+ /* make sure it's actually 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->udc->lock, flags);
+ return -EINVAL;
+ }
+
+ if (use_dma && ep->dma_channel && ep->queue.next == &req->queue) {
+ int channel = ep->dma_channel;
+
+ /* releasing the channel cancels the request,
+ * reclaiming the channel restarts the queue
+ */
+ dma_channel_release(ep);
+ dma_channel_claim(ep, channel);
+ } else
+ done(ep, req, -ECONNRESET);
+ spin_unlock_irqrestore(&ep->udc->lock, flags);
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int omap_ep_set_halt(struct usb_ep *_ep, int value)
+{
+ struct omap_ep *ep = container_of(_ep, struct omap_ep, ep);
+ unsigned long flags;
+ int status = -EOPNOTSUPP;
+
+ spin_lock_irqsave(&ep->udc->lock, flags);
+
+ /* just use protocol stalls for ep0; real halts are annoying */
+ if (ep->bEndpointAddress == 0) {
+ if (!ep->udc->ep0_pending)
+ status = -EINVAL;
+ else if (value) {
+ if (ep->udc->ep0_set_config) {
+ WARNING("error changing config?\n");
+ omap_writew(UDC_CLR_CFG, UDC_SYSCON2);
+ }
+ omap_writew(UDC_STALL_CMD, UDC_SYSCON2);
+ ep->udc->ep0_pending = 0;
+ status = 0;
+ } else /* NOP */
+ status = 0;
+
+ /* otherwise, all active non-ISO endpoints can halt */
+ } else if (ep->bmAttributes != USB_ENDPOINT_XFER_ISOC && ep->ep.desc) {
+
+ /* IN endpoints must already be idle */
+ if ((ep->bEndpointAddress & USB_DIR_IN)
+ && !list_empty(&ep->queue)) {
+ status = -EAGAIN;
+ goto done;
+ }
+
+ if (value) {
+ int channel;
+
+ if (use_dma && ep->dma_channel
+ && !list_empty(&ep->queue)) {
+ channel = ep->dma_channel;
+ dma_channel_release(ep);
+ } else
+ channel = 0;
+
+ use_ep(ep, UDC_EP_SEL);
+ if (omap_readw(UDC_STAT_FLG) & UDC_NON_ISO_FIFO_EMPTY) {
+ omap_writew(UDC_SET_HALT, UDC_CTRL);
+ status = 0;
+ } else
+ status = -EAGAIN;
+ deselect_ep();
+
+ if (channel)
+ dma_channel_claim(ep, channel);
+ } else {
+ use_ep(ep, 0);
+ omap_writew(ep->udc->clr_halt, UDC_CTRL);
+ ep->ackwait = 0;
+ if (!(ep->bEndpointAddress & USB_DIR_IN)) {
+ omap_writew(UDC_SET_FIFO_EN, UDC_CTRL);
+ ep->ackwait = 1 + ep->double_buf;
+ }
+ }
+ }
+done:
+ VDBG("%s %s halt stat %d\n", ep->ep.name,
+ value ? "set" : "clear", status);
+
+ spin_unlock_irqrestore(&ep->udc->lock, flags);
+ return status;
+}
+
+static struct usb_ep_ops omap_ep_ops = {
+ .enable = omap_ep_enable,
+ .disable = omap_ep_disable,
+
+ .alloc_request = omap_alloc_request,
+ .free_request = omap_free_request,
+
+ .queue = omap_ep_queue,
+ .dequeue = omap_ep_dequeue,
+
+ .set_halt = omap_ep_set_halt,
+ /* fifo_status ... report bytes in fifo */
+ /* fifo_flush ... flush fifo */
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int omap_get_frame(struct usb_gadget *gadget)
+{
+ u16 sof = omap_readw(UDC_SOF);
+ return (sof & UDC_TS_OK) ? (sof & UDC_TS) : -EL2NSYNC;
+}
+
+static int omap_wakeup(struct usb_gadget *gadget)
+{
+ struct omap_udc *udc;
+ unsigned long flags;
+ int retval = -EHOSTUNREACH;
+
+ udc = container_of(gadget, struct omap_udc, gadget);
+
+ spin_lock_irqsave(&udc->lock, flags);
+ if (udc->devstat & UDC_SUS) {
+ /* NOTE: OTG spec erratum says that OTG devices may
+ * issue wakeups without host enable.
+ */
+ if (udc->devstat & (UDC_B_HNP_ENABLE|UDC_R_WK_OK)) {
+ DBG("remote wakeup...\n");
+ omap_writew(UDC_RMT_WKP, UDC_SYSCON2);
+ retval = 0;
+ }
+
+ /* NOTE: non-OTG systems may use SRP TOO... */
+ } else if (!(udc->devstat & UDC_ATT)) {
+ if (!IS_ERR_OR_NULL(udc->transceiver))
+ retval = otg_start_srp(udc->transceiver->otg);
+ }
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ return retval;
+}
+
+static int
+omap_set_selfpowered(struct usb_gadget *gadget, int is_selfpowered)
+{
+ struct omap_udc *udc;
+ unsigned long flags;
+ u16 syscon1;
+
+ udc = container_of(gadget, struct omap_udc, gadget);
+ spin_lo