diff options
Diffstat (limited to 'drivers/usb/gadget/udc/omap_udc.c')
-rw-r--r-- | drivers/usb/gadget/udc/omap_udc.c | 3038 |
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 |