Messages in this thread Patch in this message | | | From | <> | Subject | **SPAM** [PATCH 3/3] usb gadget driver for MQ11xx graphics chip | Date | Tue, 2 Aug 2005 15:20:47 -0400 (EDT) |
| |
This patch adds USB gadget support for the USB peripheral controller on the MQ11xx graphics chip.
Signed-off-by: Jamey Hicks <jamey@handhelds.org> Signed-off-by: Andrew Zabolotny <anpaza@mail.ru>
--- commit 9b2407962e0786705efeed755ab1d399deba8685 tree 7c9e5187687bec3112af0f83e9e3831b18c41fa5 parent d6631d9e21df12578ae5e7a2878b7e7132cfa28c author <jamey@lspace.crl.dec.com> Tue, 02 Aug 2005 11:28:38 -0400 committer <jamey@lspace.crl.dec.com> Tue, 02 Aug 2005 11:28:38 -0400
drivers/usb/gadget/Kconfig | 15 drivers/usb/gadget/Makefile | 1 drivers/usb/gadget/mq11xx_udc.c | 1710 +++++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/mq11xx_udc.h | 115 +++ 4 files changed, 1841 insertions(+), 0 deletions(-)
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -142,6 +142,21 @@ config USB_GOKU select USB_GADGET_SELECTED +config USB_GADGET_MQ11XX + boolean "MediaQ 11xx USB Device Controller" + depends on PLATFORM_MQ11XX + help + Enable this if you want to use MediaQ chip's integrated + USB device controller. + + It has three fixed-function endpoints, as well as endpoint + zero (for control transfers). + +config USB_MQ11XX + tristate + depends on USB_GADGET_MQ11XX + default USB_GADGET + config USB_GADGET_LH7A40X boolean "LH7A40X" depends on ARCH_LH7A40X diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_USB_PXA2XX) += pxa2xx_udc.o obj-$(CONFIG_USB_GOKU) += goku_udc.o obj-$(CONFIG_USB_OMAP) += omap_udc.o obj-$(CONFIG_USB_LH7A40X) += lh7a40x_udc.o +obj-$(CONFIG_USB_MQ11XX) += mq11xx_udc.o # # USB gadget drivers diff --git a/drivers/usb/gadget/mq11xx_udc.c b/drivers/usb/gadget/mq11xx_udc.c new file mode 100644 --- /dev/null +++ b/drivers/usb/gadget/mq11xx_udc.c @@ -0,0 +1,1710 @@ +/* + * MediaQ 11xx USB Device Controller driver + * Goku-S UDC driver used as a template. + * + * Copyright (C) 2004 Andrew Zabolotny <zap@homelink.ru> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/module.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/proc_fs.h> +#include <linux/device.h> +#include <linux/usb_ch9.h> +#include <linux/usb_gadget.h> + +#include <asm/unaligned.h> + +#define __MQ11XX_INTERNAL +#include "mq11xx_udc.h" + +/* + * This device has ep0 and three semi-configurable interrupt/bulk/isochronous + * endpoints. + * + * - Endpoints 1 and 2 are IN, 3 is OUT + * - Endpoint 1 is for interrupt transfers; endpoints 2 and 3 can be + * configured to either bulk or isochronous mode. + * - Endpoint 0 and 1 use FIFO, endpoints 2 and 3 use DMA transfers. + * + * Note that when using debugging output, the delay between the IN token + * and the actual time of filling the FIFO can exceed 18 bit times + * (as stated in section 7.1.19.1 of the USB specs), and the transfer + * times out. But when the host retries the transfer, the data is already + * in the FIFO so it gets transferred correctly, except that you can see + * "usb_control/bulk_msg: timeout" messages on the host side. Also on bulk + * endpoints you can eventually easily get timeouts/misbehaviour from time + * to time, especially if you're using small rx/tx buffers. + * + * NOTE: Isochronous mode is not fully implemented yet. + * + * NOTE: Due to crappy chip design, there are still some hardware race + * conditions and such. I've tried to make these dangerous time periods + * as short as possible, but nothing guaranteed. I've marked these places + * with [CRAP] marks; if you have a better solution, go ahead and fix it. + */ + +/* Enable debug logs in general (The Big Red Switch) */ +//#define USB_DEBUG +/* Enable debugging of packets */ +//#define USB_DEBUG_TRACE +/* Enable debugging of packet contents */ +//#define USB_DEBUG_DATA +/* Log time of every debug event */ +//#define USB_DEBUG_TIME + +#ifdef USB_DEBUG_TIME +# ifdef CONFIG_ARCH_PXA +# include <asm/arch/hardware.h> + /* Approximative division by 3.6864 to get microseconds */ +# define USB_DEBUG_TIMER ((OSCR*139)/512) +# else +# define USB_DEBUG_TIMER jiffies +# endif +#endif + +#ifdef USB_DEBUG +# ifdef USB_DEBUG_TIMER +# define debug(s, args...) printk (KERN_INFO "(%7u) %s: " s, USB_DEBUG_TIMER, __FUNCTION__, ##args) +# else +# define debug(s, args...) printk (KERN_INFO "%s: " s, __FUNCTION__, ##args) +# endif +#else +# define debug(s, args...) +#endif +#define info(s, args...) printk (KERN_INFO "%s: " s, driver_name, ##args) + +/* Endpoint 0 and 1 FIFO depth in bytes */ +#define MQ_EP01_MAXPACKET (MQ_UDC_FIFO_DEPTH * sizeof (u32)) +/* Endpoint 2 and 3 packet size */ +#define MQ_EP23_MAXPACKET 64 + +static const char driver_name [] = "mq11xx_udc"; + +MODULE_DESCRIPTION("MediaQ 11xx USB Device Controller"); +MODULE_AUTHOR("Andrew Zabolotny <zap@homelink.ru>"); +MODULE_LICENSE("GPL"); + +/* The size for one buffer in MediaQ internal RAM */ +static unsigned rxbs = 4096; +MODULE_PARM (rxbs, "i"); +MODULE_PARM_DESC (rxbs, "DMA receive buffer size (two buffers)"); + +static unsigned txbs = 4096; +MODULE_PARM (txbs, "i"); +MODULE_PARM_DESC (txbs, "DMA transmit buffer size (two buffers)"); + +/* Static variables are EVIL :) but for now anyways the UDC architecture + * allows only one UDC controller at a time. + */ +struct mq11xx_udc_mach_info *mach_info; + +static int mq_set_halt(struct usb_ep *, int); +static void mq_ep_finish(struct mq_ep *, int status); +static void mq_done_request (struct mq_ep *ep, struct mq_request *req, + int status); + +#ifdef USB_DEBUG_DATA +static void debug_dump (u8 *data, int count, int base) +{ + char line [80]; + u32 i, j, offs = 0; + while (count > 0) { + j = sprintf (line, "%08x | ", base + offs); + for (i = 0; i < 16; i++) + if (i < count) + j += sprintf (line + j, "%02x ", data [offs + i]); + else + j += sprintf (line + j, " "); + j += sprintf (line + j, "| "); + for (i = 0; i < 16; i++) { + u8 c = data [offs + i]; + line [j++] = (i >= count) ? ' ' : + ((c < 32 || c > 126) ? '.' : c); + } + line [j] = 0; + printk ("%s\n", line); + offs += 16; + count -= 16; + } +} +#else +#define debug_dump(x,y,z) +#endif + +static int mq_ep_enable (struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct mqudc *mqdev; + struct mq_ep *ep; + u32 max, dmactl; + unsigned long flags; + volatile struct mediaq11xx_regs *regs; + + debug ("%s type:%d addr:%x maxpkt:%d\n", _ep->name, + desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK, + desc->bEndpointAddress, + desc->wMaxPacketSize); + + ep = container_of(_ep, struct mq_ep, ep); + if (!_ep || !desc || ep->desc || + desc->bDescriptorType != USB_DT_ENDPOINT || + (desc->bEndpointAddress & 0x0f) > 3) + return -EINVAL; + mqdev = ep->mqdev; + if (ep == &mqdev->ep[0]) + return -EINVAL; + if (!mqdev->driver) + return -ESHUTDOWN; + if (ep->num != (desc->bEndpointAddress & 0x0f)) + return -EINVAL; + + regs = mqdev->base->regs; + + /* Endpoint 1 cannot do anything but interrupt transfers + while endpoints 2 and 3 can do only bulk/isochronous transfers. */ + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_INT: + if (ep->num != 1) + return -EINVAL; + break; + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_ISOC: + if (ep->num > 1) { + max = (ep->num == 2) ? MQ_UDC_EP2_ISOCHRONOUS : + MQ_UDC_EP3_ISOCHRONOUS; + if ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK) { + regs->UDC.control &= ~max; + ep->iso = 0; + ep->regs [0] &= ~MQ_UDC_FORCE_DATA0; + } else { + regs->UDC.control |= max; + ep->iso = 1; + ep->regs [0] |= MQ_UDC_FORCE_DATA0; + } + break; + } + default: + return -EINVAL; + } + + if (ep->regs [0] & MQ_UDC_EP_ENABLE) + debug ("%s: already enabled\n", ep->ep.name); + + /* MediaQ UDC understands only 16 byte packets for EP0 and EP1, + * and 64-byte packets for EP2 & EP3. + */ + max = le16_to_cpu (get_unaligned (&desc->wMaxPacketSize)); + if (max > ((ep->num < 2) ? MQ_EP01_MAXPACKET : MQ_EP23_MAXPACKET)) + return -EINVAL; + + /* Check pipe direction - 1 & 2 are IN, 3 is OUT */ + if (desc->bEndpointAddress & USB_DIR_IN) { + if (ep->num > 2) + return -EINVAL; + } else + if (ep->num < 3) + return -EINVAL; + + spin_lock_irqsave (&ep->mqdev->lock, flags); + + ep->regs [0] |= MQ_UDC_EP_ENABLE; + + /* ep2 and ep3 can do double buffering and/or dma */ + if (ep->dmaregs) { + ep->active_buffer = 0; + /* DMA burst transfer length - 24 bytes */ +// regs->MIU.miu_1 = (regs->MIU.miu_1 & ~MQ_MIU_UDC_BURST_MASK) | +// MQ_MIU_UDC_BURST_COUNT_6; + /* For isochronous mode: burst threshold for RX is 6 free + * locations in the RX FIFO; for TX is when 6 locations in + * FIFO are received (6 locations == 24 bytes). + */ +// ep->regs [0] = (ep->regs [0] & ~MQ_UDC_FIFO_THRESHOLD_MASK) | +// MQ_UDC_FIFO_THRESHOLD (6); + /* Switch between 2 DMA buffers on RX/TX */ + dmactl = MQ_UDC_DMA_ENABLE | MQ_UDC_DMA_PINGPONG | \ + MQ_UDC_DMA_NUMBUFF (2); + if (ep->num == 3) + dmactl |= MQ_UDC_DMA_BUFF1_OWNER | MQ_UDC_DMA_BUFF2_OWNER; + ep->dmaregs [0] = dmactl; + } + + mq_set_halt (_ep, 0); + ep->ep.maxpacket = max; + ep->stopped = 1; + ep->desc = desc; + spin_unlock_irqrestore (&ep->mqdev->lock, flags); + + return 0; +} + +static void mq_ep_clear_fifo (struct mq_ep *ep) +{ + if (ep->num < 2) { + ep->quick_buff_bytes = 0; + ep->regs [0] |= MQ_UDC_CLEAR_FIFO; + ep->regs [0] &= ~MQ_UDC_CLEAR_FIFO; + if (ep->num == 0) { + ep->regs [2] |= MQ_UDC_CLEAR_FIFO; + ep->regs [2] &= ~MQ_UDC_CLEAR_FIFO; + } + } +} + +static void mq_ep_reset (struct mq_ep *ep) +{ + /* Disable the endpoint (if not ep0) or clear the FIFO (if ep0) */ + ep->regs [0] = MQ_UDC_CLEAR_FIFO; + ep->regs [0] = 0; + if (ep->num == 0) { + ep->regs [2] = MQ_UDC_CLEAR_FIFO; + ep->regs [2] = 0; + } + if (ep->dmaregs) + ep->dmaregs [0] = 0; + + ep->ep.maxpacket = (ep->num < 2) ? MQ_EP01_MAXPACKET : MQ_EP23_MAXPACKET; + ep->desc = 0; + ep->stopped = 1; +} + +/* enable bit 0 - enable primary endpoint interrupt; + * bit 1 - enable secondary endpoint interrupt (TX for ep0). + */ +static void mq_ep_ints (struct mq_ep *ep, int enable) +{ + volatile struct mediaq11xx_regs *regs = ep->mqdev->base->regs; + u32 rxmask = 0, txmask = 0; + + debug ("%s: rx:%d tx:%d\n", ep->ep.name, enable & 1, (enable >> 1) & 1); + + switch (ep->num) { + case 0: + rxmask = MQ_UDC_IEN_EP0_RX; + txmask = MQ_UDC_IEN_EP0_TX; + break; + case 1: + txmask = MQ_UDC_IEN_EP1_TX; + break; + case 2: + txmask = /*MQ_UDC_IEN_EP2_TX_EOT | */MQ_UDC_IEN_DMA_TX; + break; + case 3: + rxmask = /*MQ_UDC_IEN_EP3_RX_EOT | */MQ_UDC_IEN_DMA_RX | MQ_UDC_IEN_DMA_RX_EOT; + break; + default: + return; + } + + if (enable & 1) + regs->UDC.control |= rxmask; + else + regs->UDC.control &= ~rxmask; + + if (enable & 2) + regs->UDC.control |= txmask; + else + regs->UDC.control &= ~txmask; +} + +static int mq_ep_disable (struct usb_ep *_ep) +{ + struct mq_ep *ep; + struct mqudc *mqdev; + unsigned long flags; + + ep = container_of(_ep, struct mq_ep, ep); + if (!_ep || !ep->desc) + return -ENODEV; + mqdev = ep->mqdev; + if (mqdev->ep0state == EP0_SUSPEND) + return -EBUSY; + + spin_lock_irqsave(&mqdev->lock, flags); + + /* Drop all pending requests */ + mq_ep_finish(ep, -ESHUTDOWN); + + /* Disable endpoint interrupts */ + mq_ep_ints (ep, 0); + + /* Set the endpoint to RESET state */ + mq_ep_reset(ep); + + spin_unlock_irqrestore(&mqdev->lock, flags); + + return 0; +} + +/*---------------------------------------------------------------------------*/ + +/* Send a packed through endpoint 0 or 1 using FIFO. + */ +static void mq_tx_fifo (struct mqudc *mqdev, struct mq_ep *ep, int rst_status) +{ + struct mq_request *req; + int bytes_left, packet_len; + u32 *buf; + volatile u32 *fifo; + + /* Acknowledge all status bits, we already got the status */ + if (rst_status) + ep->regs [1] = 0xff; + + req = list_empty (&ep->list) ? NULL : + list_entry (ep->list.next, struct mq_request, queue); + + /* If there's a short packet to be sent on endpoint 0, do it. */ + if ((ep->quick_buff_bytes) && + (!req || req->req.actual >= req->req.length)) { + bytes_left = ep->quick_buff_bytes - 1; + ep->quick_buff_bytes = 0; + buf = &ep->quick_buff; + req = NULL; + goto send_packet; + } + + if (unlikely (!req)) + return; + + /* Since the start of request buffer is always aligned to + * word boundary, we can move whole words to FIFO until we + * get to the last (incomplete) word. Also since memory is + * never allocated in chunks smaller than one word, we can + * safely read the last incomplete 32-bit word, to avoid + * copying bytes by one. + */ + bytes_left = req->req.length - req->req.actual; + if (bytes_left > ep->ep.maxpacket) + bytes_left = ep->ep.maxpacket; + buf = (u32 *)(req->req.actual + (u8 *)req->req.buf); + req->req.actual += bytes_left; + +send_packet: + packet_len = bytes_left; + fifo = (ep->num == 0) ? &ep->regs [5] : &ep->regs [2]; + while (bytes_left > 0) { + *fifo = le32_to_cpu (*buf++); + bytes_left -= sizeof (u32); + } + + ep->regs [0] = (ep->regs [0] & MQ_UDC_EP_ENABLE) | mqdev->toggle | + MQ_UDC_TX_EDT | MQ_UDC_TX_LAST_ENABLE (4 + bytes_left); + + debug ("%s: Pktsize %d ctrl %08x stat %08x\n", ep->ep.name, packet_len, + ep->regs [0], ep->regs [1]); + + /* Toggle DATA0/DATA1 */ + mqdev->toggle ^= MQ_UDC_TX_PID_DATA1; + + if (!req || (req->req.actual >= req->req.length)) { + /* Emmit a ZLP if the last packet is full size and + * req->zero is set. + */ + if (unlikely (req && req->req.zero && + (packet_len == ep->ep.maxpacket))) { + /* Send ZLP on next interrupt */ + ep->quick_buff_bytes = 1; + debug ("%s: ZLP\n", ep->ep.name); + } + + /* In any case, the request has been submitted. + * Note that inside done_request we can get new requests + * hooked on this endpoint, so be careful. + */ + if (req) + mq_done_request (ep, req, 0); + + if (!ep->stopped && list_empty (&ep->list) && !ep->quick_buff_bytes) { + /* If this is the last packet of a request, report + * successful completion to the gadget driver and + * disable further TX interrupts (enable them only + * when needed). + */ + ep->stopped = 1; + /* Disable transmit interrupts */ + mq_ep_ints (ep, 1); + if (ep->num == 0) + mqdev->ep0state = EP0_IDLE; + debug ("%s: Stop TX\n", ep->ep.name); + } + } +} + +/* Send a packet through endpoint 2 using DMA. + */ +static void mq_tx_dma (struct mqudc *mqdev, struct mq_ep *ep) +{ + struct mq_request *req; + int transfer_size; + u32 dmactl, dmadesc, buffno, owner_mask, eot_mask; + + debug ("dmactl:%08x status:%08x\n", ep->dmaregs [0], ep->regs [1]); + + for (;;) { + dmactl = ep->dmaregs [0]; + + if (list_empty (&ep->list)) { + /* Disable DMA only after both DMA buffers are transferred */ + if (dmactl & (MQ_UDC_DMA_BUFF1_OWNER | MQ_UDC_DMA_BUFF2_OWNER)) + return; + mq_ep_ints (ep, 1); + ep->stopped = 1; + debug ("%s: stop TX (ctrl:%08x)\n", ep->ep.name, ep->dmaregs [0]); + return; + } + + if ((buffno = ep->active_buffer) == 0) { + owner_mask = MQ_UDC_DMA_BUFF1_OWNER; + eot_mask = MQ_UDC_DMA_BUFF1_EOT; + } else { + owner_mask = MQ_UDC_DMA_BUFF2_OWNER; + eot_mask = MQ_UDC_DMA_BUFF2_EOT; + } + + /* Ok, DMA buffers loaded up - return */ + if (dmactl & owner_mask) + return; + + req = list_entry (ep->list.next, struct mq_request, queue); + + transfer_size = req->req.length - req->req.actual; + if (transfer_size > txbs) + transfer_size = txbs; + + /* Zero-length transfers not supported (?) */ + if (!transfer_size) { + debug ("WARNING: ZERO-LENGTH TRANSFER REQUESTED BUT NOT SUPPORTED BY HARDWARE!\n"); + mq_done_request (ep, req, 0); + continue; + } + + /* Transfer next portion of data to MediaQ RAM */ + memcpy ((u8 *)mqdev->base->ram + ep->dmabuff + (buffno ? txbs : 0), + req->req.actual + (u8 *)req->req.buf, transfer_size); + req->req.actual += transfer_size; + ep->active_buffer ^= 1; + + /* Now construct the buffer descriptor */ + dmadesc = MQ_UDC_DMA_BUFFER_ADDR (ep->dmabuff + (buffno ? txbs : 0)) | + MQ_UDC_DMA_BUFFER_SIZE (transfer_size); + + /* Find if this is last packet and multiple of maxpacket size */ + if ((req->req.actual >= req->req.length) && + !(transfer_size & 63)) + dmadesc |= MQ_UDC_DMA_BUFFER_LAST; + ep->dmaregs [1 + buffno] = dmadesc; + + /* Decide if we need a ZLP after last packet */ + if (!ep->iso && req->req.zero && + (req->req.actual >= req->req.length)) + dmactl = ~0; + else { + dmactl = ~eot_mask; + eot_mask = 0; + } + + /* Read register value again in the case it has been + * changed in the meantime (esp. during memcpy()). + * [CRAP] while we're holding dmaregs[0] in CPU registers, + * it could change in hardware. Thus this period of time + * must be as short as possible. + */ + dmactl &= (ep->dmaregs [0] | owner_mask | eot_mask); + ep->dmaregs [0] = dmactl; + + debug ("buff:%d size:%d dmactrl:%08x dmadesc:%08x\n", + buffno, transfer_size, dmactl, ep->dmaregs [1 + buffno]); + debug_dump ((u8 *)req->req.buf + req->req.actual - transfer_size, + transfer_size, ep->dmabuff + (buffno ? txbs : 0)); + + if (req->req.actual >= req->req.length) + mq_done_request (ep, req, 0); + } +} + +/* Prepare to receive packets through endpoint 3 using DMA. + */ +static void mq_rx_dma_prepare (struct mq_ep *ep) +{ + u32 dmactl, addr, buffno; + + dmactl = ep->dmaregs [0] | MQ_UDC_DMA_BUFF1_OWNER | MQ_UDC_DMA_BUFF2_OWNER; + + for (buffno = 0; buffno <= 1; buffno++) { + if (buffno == 0) + dmactl &= ~(MQ_UDC_DMA_BUFF1_OWNER | MQ_UDC_DMA_BUFF1_EOT); + else + dmactl &= ~(MQ_UDC_DMA_BUFF2_OWNER | MQ_UDC_DMA_BUFF2_EOT); + + addr = ep->dmabuff + (buffno ? rxbs : 0); + ep->dmaregs [1 + buffno] = MQ_UDC_DMA_BUFFER_ADDR (addr) | + MQ_UDC_DMA_BUFFER_EADDR (addr + rxbs); + debug ("prepare buff %d, addr %08x\n", buffno, addr); + } + + ep->dmaregs [0] = dmactl; +} + +/* Transfer all the data in DMA buffers to pending request. + * After this re-submit the buffers to chip DMA controller. + */ +static void mq_rx_dma (struct mqudc *mqdev, struct mq_ep *ep) +{ + struct mq_request *req = NULL; + int end_transf, transfer_size, space_avail; + u32 dmactl, buffno, owner_mask, eot_mask; + volatile struct mediaq11xx_regs *regs = mqdev->base->regs; + + end_transf = 0; +try_again: + dmactl = ep->dmaregs [0]; + debug ("ctrl:%08x status:%08x\n", dmactl, ep->regs [1]); + + for (;;) { + /** + * [CRAP] the MQ_UDC_INT_DMA_RX_EOT is set for ANY of the + * two buffers being completely received. Thus we have to + * do a lot of guesswork in the case when two buffers are + * marked as "owned by CPU", e.g. contain received data. + */ + if (ep->iso) { + // ??? todo + if (dmactl & MQ_UDC_ISO_TRANSFER_END) + end_transf |= ~ep->dmaregs [0] & (MQ_UDC_DMA_BUFF1_OWNER | + MQ_UDC_DMA_BUFF2_OWNER); + } else { + if (regs->UDC.intstatus & MQ_UDC_INT_DMA_RX_EOT) { + end_transf |= ~ep->dmaregs [0] & (MQ_UDC_DMA_BUFF1_OWNER | + MQ_UDC_DMA_BUFF2_OWNER); + regs->UDC.intstatus = MQ_UDC_INT_DMA_RX_EOT; + } + } + + if ((buffno = ep->active_buffer) == 0) { + owner_mask = MQ_UDC_DMA_BUFF1_OWNER; + eot_mask = MQ_UDC_DMA_BUFF1_EOT; + } else { + owner_mask = MQ_UDC_DMA_BUFF2_OWNER; + eot_mask = MQ_UDC_DMA_BUFF2_EOT; + } + + /* No more received buffers? */ + if (!(dmactl & owner_mask)) { + if (dmactl & (owner_mask ^ (MQ_UDC_DMA_BUFF1_EOT | MQ_UDC_DMA_BUFF2_EOT))) { + debug ("Desyncronization! (buff %d, ctrl %08x)\n", + buffno, dmactl); + ep->active_buffer ^= 1; + continue; + } + if (end_transf) { + debug ("EOT received w/o data\n"); + goto try_again; + } + return; + } + + ep->active_buffer ^= 1; + + if (list_empty (&ep->list)) { + ep->stopped = 1; + debug ("Received data, but no requests in queue!\n"); + return; + } + + /* Get the next pending receive request */ + req = list_entry (ep->list.next, struct mq_request, queue); + + /* Find out the size of data pending in DMA buffers */ + transfer_size = rxbs; + if (!ep->iso && (dmactl & eot_mask)) + transfer_size = ep->dmaregs [3 + buffno]; + + /* Find out the available space in request' buffer */ + space_avail = req->req.length - req->req.actual; + if (transfer_size > space_avail) { + transfer_size = space_avail; + if (!ep->iso) + req->req.status = -EOVERFLOW; + } + + /* Copy data from DMA buffers to request buffer */ + memcpy (req->req.actual + (u8 *)req->req.buf, + (u8 *)mqdev->base->ram + ep->dmabuff + (buffno ? rxbs : 0), + transfer_size); + + /* Mark immediately the DMA buffer as processed and free + * for more incoming data (maximize throughput). + * + * [CRAP] While we hold the old value in CPU registers, + * the hardware register state can change and we'll write + * the OLD value back. + */ + dmactl = ~(owner_mask | eot_mask | MQ_UDC_ISO_TRANSFER_END); + dmactl &= ep->dmaregs [0]; + ep->dmaregs [0] = dmactl; + + debug ("buff:%d size:%d dmactl:%08x end:%x\n", + buffno, transfer_size, dmactl, end_transf); + debug_dump ((u8 *)req->req.buf + req->req.actual, transfer_size, + ep->dmabuff + (buffno ? rxbs : 0)); + + req->req.actual += transfer_size; + + /* If we have a short buffer, we obviously have a end + * of transfer. + */ + if (transfer_size < rxbs) + end_transf = owner_mask; + /* If we have a full buffer, and the other buffer is available, + * consider the EOP belonging to the next buffer, not this. + * This often happens when we have an incoming packet with + * the length equal to rxbs+a little (say, 4 bytes). + */ + else if (end_transf & ~owner_mask) + end_transf &= ~owner_mask; + + /* NOTE: we don't handle for now properly the case + * when a incoming ISO packet can fill several requests. + */ + + if ((ep->iso && + (end_transf || (req->req.actual >= req->req.length))) || + (!ep->iso && + (end_transf & owner_mask))) { + if (ep->iso) + end_transf = 0; + else + end_transf &= ~owner_mask; + mq_done_request (ep, req, 0); + } + } +} + +/*---------------------------------------------------------------------------*/ + +static struct usb_request *mq_alloc_request (struct usb_ep *_ep, + int gfp_flags) +{ + struct mq_request *req; + + req = kmalloc (sizeof (*req), gfp_flags); + if (!req) + return 0; + + memset (req, 0, sizeof (*req)); + INIT_LIST_HEAD (&req->queue); + + return &req->req; +} + +static void mq_free_request (struct usb_ep *_ep, struct usb_request *_req) +{ + struct mq_request *req; + + if (!_ep || !_req) + return; + + req = container_of (_req, struct mq_request, req); + + if (!list_empty (&req->queue)) { + debug ("%s: request %p still processed, unlinking\n", + _ep->name, _req); + list_del (&req->queue); + if (likely (req->req.status == -EINPROGRESS)) + req->req.status = -ESHUTDOWN; + } + kfree (req); +} + +static void *mq_alloc_buffer (struct usb_ep *_ep, unsigned bytes, + dma_addr_t *dma, int gfp_flags) +{ + return kmalloc (bytes, gfp_flags); +} + +static void mq_free_buffer (struct usb_ep *_ep, void *buf, dma_addr_t dma, + unsigned bytes) +{ + kfree (buf); +} + +static void mq_done_request (struct mq_ep *ep, struct mq_request *req, + int status) +{ + struct mqudc *mqdev; + + list_del_init (&req->queue); + + if (likely (req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + mqdev = ep->mqdev; + +#ifndef USB_DEBUG_TRACE + if (status && status != -ESHUTDOWN) +#endif + debug ("complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + spin_unlock (&mqdev->lock); + req->req.complete (&ep->ep, &req->req); + spin_lock (&mqdev->lock); +} + +static int mq_start_queue (struct mq_ep *ep) +{ + unsigned long flags; + + debug ("%s\n", ep->ep.name); + + spin_lock_irqsave (&ep->mqdev->lock, flags); + + /* Enable both RX and TX interrupts from this endpoint */ + if (ep->stopped) + mq_ep_ints (ep, 3); + + /* For DMA-backed endpoints we must pre-fill the DMA buffers + * and then transmit buffers ownership to the mq chip; for FIFO + * endpoints we must fill the FIFO. + */ + if (!ep->dmaregs) { + ep->stopped = 0; + if (ep->is_in) + /* Fill the transmit FIFO for this endpoint */ + mq_tx_fifo (ep->mqdev, ep, 1); + } else if (ep->stopped) { + ep->stopped = 0; + if (ep->is_in) + mq_tx_dma (ep->mqdev, ep); + else + mq_rx_dma_prepare (ep); + } + + spin_unlock_irqrestore (&ep->mqdev->lock, flags); + + return 0; +} + +static int mq_queue (struct usb_ep *_ep, struct usb_request *_req, + int gfp_flags) +{ + struct mq_ep *ep; + struct mqudc *mqdev; + unsigned long flags; + int status; + struct mq_request *req = container_of (_req, struct mq_request, req); + + if (unlikely(!_req || !req->req.complete + || !req->req.buf || !list_empty (&req->queue))) { + debug ("%s: Invalid request (%p %p %p %d)\n", + _ep->name, req, req->req.complete, req->req.buf, + list_empty (&req->queue)); + return -EINVAL; + } + + ep = container_of(_ep, struct mq_ep, ep); + if (unlikely (!_ep || + ((ep->num != 0) && + (!ep->desc || !(ep->regs [0] & MQ_UDC_EP_ENABLE))))) { + debug ("%s: Invalid endpoint\n", _ep->name); + return -EINVAL; + } + + mqdev = ep->mqdev; + if (unlikely(!mqdev->driver)) + return -ESHUTDOWN; + + /* can't touch registers when suspended */ + if (mqdev->ep0state == EP0_SUSPEND) + return -EBUSY; + +#ifdef USB_DEBUG_TRACE + debug("%s queue req %p, len %u buf %p\n", + _ep->name, _req, req->req.length, req->req.buf); +#endif + + spin_lock_irqsave (&mqdev->lock, flags); + + req->req.status = -EINPROGRESS; + req->req.actual = 0; + + /* for ep0 IN without premature status, zlp is required and + * writing EOP starts the status stage (OUT). + */ + if (unlikely (ep->num == 0 && ep->is_in)) + req->req.zero = 1; + + list_add_tail (&req->queue, &ep->list); + + /* kickstart this i/o queue? */ + status = mq_start_queue (ep); + + spin_unlock_irqrestore (&mqdev->lock, flags); + + return status; +} + +/* dequeue ALL requests */ +static void mq_ep_finish (struct mq_ep *ep, int status) +{ + struct mq_request *req; + unsigned long flags; + + spin_lock_irqsave (&ep->mqdev->lock, flags); + + /* Disable tx interrupts */ + mq_ep_ints (ep, 1); + ep->stopped = 1; + /* Clear both FIFOs */ + mq_ep_clear_fifo (ep); + + while (!list_empty(&ep->list)) { + req = list_entry (ep->list.next, struct mq_request, queue); + mq_done_request (ep, req, status); + } + + spin_unlock_irqrestore (&ep->mqdev->lock, flags); +} + +static int mq_dequeue (struct usb_ep *_ep, struct usb_request *_req) +{ + struct mq_ep *ep; + struct mqudc *mqdev; + unsigned long flags; + struct mq_request *req = container_of (_req, struct mq_request, req); + + if (!_req || list_empty (&req->queue)) + return -EINVAL; + ep = container_of (_ep, struct mq_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0)) + return -EINVAL; + mqdev = ep->mqdev; + if (!mqdev->driver) + return -ESHUTDOWN; + + /* we can't touch registers when suspended */ + if (mqdev->ep0state == EP0_SUSPEND) + return -EBUSY; + + spin_lock_irqsave (&mqdev->lock, flags); + mq_done_request (ep, req, -ECONNRESET); + spin_unlock_irqrestore (&mqdev->lock, flags); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int mq_set_halt(struct usb_ep *_ep, int value) +{ + struct mq_ep *ep; + + if (!_ep) + return -ENODEV; + + ep = container_of (_ep, struct mq_ep, ep); + debug ("%s %s halt\n", _ep->name, value ? "set" : "clear"); + + if (ep->num == 0) { + if (value) + ep->mqdev->ep0state = EP0_STALL; + else + return -EINVAL; + } + + if (value) { + ep->regs [0] |= MQ_UDC_STALL; + if (ep->num == 0) + ep->regs [2] |= MQ_UDC_STALL; + ep->stopped = 1; + } else { + ep->regs [0] &= ~MQ_UDC_STALL; + if (ep->num == 0) + ep->regs [2] &= ~MQ_UDC_STALL; + ep->stopped = 0; + } + + return 0; +} + +static int mq_fifo_status(struct usb_ep *_ep) +{ + struct mq_ep *ep; + u32 status; + + if (!_ep) + return -ENODEV; + ep = container_of (_ep, struct mq_ep, ep); + + if (ep->num > 1) + return -EOPNOTSUPP; + + if (ep->num == 0 && ep->is_in) + status = ep->regs [3]; + else + status = ep->regs [1]; + + return (MQ_UDC_FIFO_DEPTH - MQ_UDC_FIFO (status)) * 4; +} + +static void mq_fifo_flush(struct usb_ep *_ep) +{ + struct mq_ep *ep; + + if (!_ep) + return; + ep = container_of (_ep, struct mq_ep, ep); + + ep->regs [0] |= MQ_UDC_CLEAR_FIFO; + ep->regs [0] &= ~MQ_UDC_CLEAR_FIFO; + if (ep->num == 0) { + /* For endpoint 0, clear also RX FIFO */ + ep->regs [2] |= MQ_UDC_CLEAR_FIFO; + ep->regs [2] &= ~MQ_UDC_CLEAR_FIFO; + } + return; +} + +static struct usb_ep_ops mq_ep_ops = { + .enable = mq_ep_enable, + .disable = mq_ep_disable, + + .alloc_request = mq_alloc_request, + .free_request = mq_free_request, + + .alloc_buffer = mq_alloc_buffer, + .free_buffer = mq_free_buffer, + + .queue = mq_queue, + .dequeue = mq_dequeue, + + .set_halt = mq_set_halt, + .fifo_status = mq_fifo_status, + .fifo_flush = mq_fifo_flush, +}; + +/*-------------------------------------------------------------------------*/ + +static int mq_get_frame(struct usb_gadget *gadget) +{ + struct mqudc *mqdev = container_of (gadget, struct mqudc, gadget); + return mqdev->base->regs->UDC.frame_number & MQ_UDC_FRAME_MASK; +} + +int mq_wakeup (struct usb_gadget *gadget) +{ + struct mqudc *mqdev = container_of (gadget, struct mqudc, gadget); + mqdev->base->regs->UDC.control |= MQ_UDC_WAKEUP_USBHOST; + return 0; +} + +int mq_set_selfpowered (struct usb_gadget *gadget, int value) +{ + if (value) + return 0; + return -EOPNOTSUPP; +} + +static const struct usb_gadget_ops mq_ops = { + .get_frame = mq_get_frame, + .wakeup = mq_wakeup, + .set_selfpowered = mq_set_selfpowered, +}; + +/*-------------------------------------------------------------------------*/ + +static void mq_reinit (struct mqudc *mqdev) +{ + volatile struct mediaq11xx_regs *regs = mqdev->base->regs; + unsigned i; + + mqdev->ep0state = EP0_IDLE; + + for (i = 0; i < 4; i++) { + struct mq_ep *ep = &mqdev->ep[i]; + + INIT_LIST_HEAD (&ep->list); + mq_ep_reset(ep); + } + + regs->UDC.control = (regs->UDC.control & 0xffff) | + MQ_UDC_IEN_EP0_RX | + MQ_UDC_IEN_GLOBAL_SUSPEND | + MQ_UDC_IEN_WAKEUP | MQ_UDC_IEN_RESET | + MQ_UDC_SUSPEND_ENABLE | + MQ_UDC_REMOTEHOST_WAKEUP_ENABLE; + + /* Set endpoint address to zero after reset */ + regs->UDC.address = 0; + /* Drop endpoint status bits, whatever they were */ + regs->UDC.ep0txstatus = 0xff; + regs->UDC.ep0rxstatus = 0xff; +} + +static void mq_reset(struct mqudc *mqdev) +{ + volatile struct mediaq11xx_regs *regs = mqdev->base->regs; + + /* Set D+ to Hi-Z (no pullup) */ + mach_info->udc_command (MQ11XX_UDC_CMD_DISCONNECT); + + /* Initialize device registers */ + regs->UDC.control = 0; + regs->UDC.address = 0; + regs->UDC.ep0txcontrol = MQ_UDC_CLEAR_FIFO; + regs->UDC.ep0txcontrol = 0; + regs->UDC.ep0rxcontrol = MQ_UDC_CLEAR_FIFO; + regs->UDC.ep0rxcontrol = 0; + regs->UDC.ep1control = 0; + regs->UDC.ep2control = 0; + regs->UDC.ep3control = 0; + regs->UDC.intstatus = 0xffffffff; + regs->UDC.dmatxcontrol = 0; + regs->UDC.dmarxcontrol = 0; +} + +static void mq_enable(struct mqudc *mqdev) +{ + mq_reset (mqdev); + mq_reinit (mqdev); + + /* Enable 1.5k D+ pullup resistor */ + mach_info->udc_command (MQ11XX_UDC_CMD_CONNECT); +} + +/*---------------------------------------------------------------------------*/ +/* USB Gadget exported API */ +/*---------------------------------------------------------------------------*/ + +static struct mqudc *the_controller; + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct mqudc *mqdev = the_controller; + int rc; + + if (!driver + || driver->speed != USB_SPEED_FULL + || !driver->bind + || !driver->unbind + || !driver->disconnect + || !driver->setup) + return -EINVAL; + if (!mqdev) + return -ENODEV; + if (mqdev->driver) + return -EBUSY; + + /* hook up the driver */ + driver->driver.bus = 0; + mqdev->driver = driver; + mqdev->gadget.dev.driver = &driver->driver; + rc = driver->bind (&mqdev->gadget); + if (rc) { + debug ("error %d while binding to driver %s\n", + rc, driver->driver.name); + mqdev->driver = 0; + mqdev->gadget.dev.driver = 0; + return rc; + } + + /* enable host detection and ep0; and we're ready + * for set_configuration as well as eventual disconnect. + */ + mq_enable (mqdev); + + info ("Registered gadget driver '%s'\n", driver->driver.name); + return 0; +} +EXPORT_SYMBOL(usb_gadget_register_driver); + +static void +stop_activity(struct mqudc *mqdev, struct usb_gadget_driver *driver) +{ + unsigned i; + + mq_reset (mqdev); + for (i = 0; i < 4; i++) + mq_ep_finish(&mqdev->ep [i], -ESHUTDOWN); + if (driver) { + spin_unlock(&mqdev->lock); + driver->disconnect(&mqdev->gadget); + spin_lock(&mqdev->lock); + } + + if (mqdev->driver) + mq_enable(mqdev); +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct mqudc *mqdev = the_controller; + unsigned long flags; + + if (!mqdev) + return -ENODEV; + if (!driver || driver != mqdev->driver) + return -EINVAL; + + spin_lock_irqsave(&mqdev->lock, flags); + mqdev->driver = 0; + stop_activity(mqdev, driver); + spin_unlock_irqrestore(&mqdev->lock, flags); + + driver->unbind(&mqdev->gadget); + + debug ("unregistered driver '%s'\n", driver->driver.name); + + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +/*---------------------------------------------------------------------------*/ +/* IRQ handling */ +/*---------------------------------------------------------------------------*/ + +static void ep01_transmit (struct mqudc *mqdev, struct mq_ep *ep) +{ + u32 txstatus = ep->regs [1]; + + /* Clear the status of last packet sent */ + ep->regs [1] = 0xff; + + /* If no ACK for last sent packet, or the End Of Data flag is + * still set, don't disturb the transmitter - it's not ready yet. + */ + if (!(txstatus & MQ_UDC_ACK) && + (ep->regs [0] & MQ_UDC_TX_EDT)) { + debug ("No ACK and EDT is still set\n"); + return; + } + + if (MQ_UDC_FIFO (txstatus) < MQ_UDC_FIFO_DEPTH) { + debug ("TX FIFO not empty yet\n"); + return; + } + + if ((ep->num == 0) && (mqdev->ep0state == EP0_STALL)) { + ep->regs [0] |= MQ_UDC_STALL; + return; + } + + mq_tx_fifo (mqdev, ep, 0); +} + +static void ep0_receive (struct mqudc *mqdev) +{ + volatile struct mediaq11xx_regs *regs = mqdev->base->regs; + struct mq_ep *ep = &mqdev->ep[0]; + struct usb_ctrlrequest *ctrl; + u32 rxstatus = regs->UDC.ep0rxstatus; + u32 rxbuff [4]; + int i, count; + + /* Acknowledge all status bits, we already got the status */ + regs->UDC.ep0rxstatus = 0xff; + + /* If the data is broken, ignore it - the hardware already sent a NAK + * if the packet is not a SETUP packet, and won't generate an + * interrupt if the packet is a broken SETUP packet... + */ + if ((rxstatus & (MQ_UDC_ERR | MQ_UDC_TIMEOUT | MQ_UDC_FIFO_OVERRUN | MQ_UDC_ACK)) != MQ_UDC_ACK) { + regs->UDC.ep0rxcontrol = MQ_UDC_CLEAR_FIFO; + regs->UDC.ep0rxcontrol = 0; + return; + } + + /* Get the data from RX FIFO */ + count = MQ_UDC_FIFO (rxstatus); + for (i = 0; i < count; i++) + /* According to docs, lowest bits are always earlier bytes */ + rxbuff [i] = cpu_to_le32 (regs->UDC.ep0rxfifo); + if (count) { + i = MQ_UDC_RX_VALID_BYTES (rxstatus); + if (i == 0) + i = 4; + count = (count - 1) * 4 + i; + } + + /* Set output DATA0/DATA1 PID from the PID of incoming packet */ + mqdev->toggle = (rxstatus & MQ_UDC_RX_PID_DATA1) ? 0 : MQ_UDC_TX_PID_DATA1; + + if (!(rxstatus & MQ_UDC_RX_TOKEN_SETUP)) { + struct mq_request *req; + + if (unlikely (ep->is_in)) + return; + if (unlikely (list_empty (&ep->list))) + return; + + req = list_entry (ep->list.next, struct mq_request, queue); + + if (req->req.actual + count > req->req.length) { + debug ("%s: buffer overflow, got %d, max %d\n", + ep->ep.name, count, req->req.length); + req->req.status = -EOVERFLOW; + count = req->req.length - req->req.actual; + } + + memcpy (req->req.buf + req->req.actual, &rxbuff, count); + req->req.actual += count; + + /* If packet is short, or the transfer is complete, finish */ + if (count < ep->ep.maxpacket || + req->req.actual >= req->req.length) { + ep->stopped = 1; + mqdev->ep0state = EP0_IDLE; + mq_done_request (ep, req, 0); + } + + return; + } + + ctrl = (struct usb_ctrlrequest *)&rxbuff; + + le16_to_cpus (&ctrl->wValue); + le16_to_cpus (&ctrl->wIndex); + le16_to_cpus (&ctrl->wLength); + + mq_ep_finish (ep, 0); + + if (likely (ctrl->bRequestType & USB_DIR_IN)) { + ep->is_in = 1; + mqdev->ep0state = EP0_IN; + } else { + ep->is_in = 0; + mqdev->ep0state = EP0_OUT; + } + +#ifdef USB_DEBUG_TRACE + debug ("SETUP %02x.%02x v%04x i%04x l%04x\n", + ctrl->bRequestType, ctrl->bRequest, + ctrl->wValue, ctrl->wIndex, ctrl->wLength); +#endif + + /* Handle some SETUP packets ourselves */ + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + if (ctrl->bRequestType != (USB_TYPE_STANDARD | USB_RECIP_DEVICE)) + break; + + debug ("SET_ADDRESS (%d)\n", ctrl->wValue); + + regs->UDC.address = ctrl->wValue; + /* Reply with a ZLP on next IN token */ + ep->quick_buff_bytes = 1; + mq_tx_fifo (mqdev, ep, 1); + return; + + case USB_REQ_GET_STATUS: { + struct mq_ep *qep; + int epn = ctrl->wIndex & ~USB_DIR_IN; + + /* hw handles device and interface status */ + if (ctrl->bRequestType != (USB_DIR_IN | USB_RECIP_ENDPOINT) || + ctrl->wLength > 2 || epn > 3) + break; + + debug ("GET_STATUS (%x)\n", ctrl->wIndex); + + qep = &mqdev->ep [epn]; + if (qep->is_in != ((ctrl->wIndex & USB_DIR_IN) ? 1 : 0)) + break; + + /* Return status on next IN token */ + ep->quick_buff_bytes = 2; + ep->quick_buff = cpu_to_le32 (qep->stopped); + mq_tx_fifo (mqdev, ep, 1); + return; + } + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + if (ctrl->bRequestType == USB_RECIP_ENDPOINT) { + struct mq_ep *qep; + + debug ("SET/CLR_FEATURE (%d)\n", ctrl->wValue); + + /* Support only HALT feature */ + if (ctrl->wValue != 0 || + ctrl->wLength != 0 || + ctrl->wIndex > 3) + goto ep0_stall; + + qep = &mqdev->ep [ctrl->wIndex]; + if (ctrl->bRequest == USB_REQ_SET_FEATURE) { + qep->regs [0] |= MQ_UDC_STALL; + if (ctrl->wIndex == 0) + qep->regs [2] |= MQ_UDC_STALL; + } else { + qep->regs [0] &= ~MQ_UDC_STALL; + if (ctrl->wIndex == 0) + qep->regs [2] &= ~MQ_UDC_STALL; + } + + /* Reply with a ZLP on next IN token */ + ep->quick_buff_bytes = 1; + mq_tx_fifo (mqdev, ep, 1); + return; + } + break; + } + + if (!mqdev->driver) + return; + + /* delegate everything to the gadget driver. + * it may respond after this irq handler returns. + */ + spin_unlock (&mqdev->lock); + i = mqdev->driver->setup (&mqdev->gadget, ctrl); + spin_lock (&mqdev->lock); + + if (unlikely (i < 0)) { +ep0_stall: +#ifdef USB_DEBUG_TRACE + debug("req %02x.%02x protocol STALL\n", + ctrl->bRequestType, ctrl->bRequest); +#endif + ep->stopped = 1; + mqdev->ep0state = EP0_STALL; + regs->UDC.ep0txcontrol |= MQ_UDC_STALL; + } +} + +static irqreturn_t mq_dev_irq(int irq, void *data, struct pt_regs *r) +{ + struct mqudc *mqdev = (struct mqudc *)data; + volatile struct mediaq11xx_regs *regs = mqdev->base->regs; + int irq_handled = 0; + u32 mask; + + spin_lock(&mqdev->lock); + + while ((mask = regs->UDC.intstatus) != 0) { + irq_handled = 1; + + /* Sort in decreasing importance order */ + if (likely (mask & MQ_UDC_INT_DMA_TX)) { + /* EP2 DMA end-of-transmit buffer reached */ + regs->UDC.intstatus = MQ_UDC_INT_DMA_TX; + mq_tx_dma (mqdev, &mqdev->ep [2]); + } else if (likely (mask & (MQ_UDC_INT_DMA_RX | MQ_UDC_INT_DMA_RX_EOT))) { + /* EP3 DMA end-of-receive buffer reached */ + regs->UDC.intstatus = mask & MQ_UDC_INT_DMA_RX; + mq_rx_dma (mqdev, &mqdev->ep [3]); + } else if (likely (mask & MQ_UDC_INT_EP0_RX)) { + /* EP0 packet received */ + regs->UDC.intstatus = MQ_UDC_INT_EP0_RX; + ep0_receive (mqdev); + } else if (likely (mask & MQ_UDC_INT_EP0_TX)) { + /* EP0 packet transmitted (FIFO empty) */ + regs->UDC.intstatus = MQ_UDC_INT_EP0_TX; + ep01_transmit (mqdev, &mqdev->ep [0]); + } else if (likely (mask & MQ_UDC_INT_EP1_TX)) { + /* EP1 packet transmitted (FIFO empty) */ + regs->UDC.intstatus = MQ_UDC_INT_EP1_TX; + ep01_transmit (mqdev, &mqdev->ep [1]); + } else if (unlikely (mask & MQ_UDC_INT_RESET)) { + debug ("reset\n"); + /* Device reset */ + regs->UDC.intstatus = MQ_UDC_INT_RESET; + mq_reinit (mqdev); + } else if (unlikely (mask & MQ_UDC_INT_WAKEUP)) { + regs->UDC.intstatus = MQ_UDC_INT_WAKEUP; + + if (mqdev->ep0state == EP0_SUSPEND) { + debug ("resume\n"); + mqdev->ep0state = EP0_IDLE; + if (mqdev->driver && mqdev->driver->resume) { + spin_unlock (&mqdev->lock); + mqdev->driver->resume (&mqdev->gadget); + spin_lock (&mqdev->lock); + } + } + } else if (unlikely (mask & MQ_UDC_INT_GLOBAL_SUSPEND)) { + regs->UDC.intstatus = MQ_UDC_INT_GLOBAL_SUSPEND; + + if (mqdev->ep0state != EP0_DISCONNECT && + mqdev->ep0state != EP0_SUSPEND) { + debug ("suspend\n"); + mqdev->ep0state = EP0_SUSPEND; + if (mqdev->driver && mqdev->driver->suspend) { + spin_unlock (&mqdev->lock); + mqdev->driver->suspend (&mqdev->gadget); + spin_lock (&mqdev->lock); + } + } + } else { + /* + * MQ_UDC_INT_SOF + * MQ_UDC_INT_EP2_TX_EOT + * MQ_UDC_INT_EP3_RX_EOT + */ + debug ("unhandled mask: %08x\n", mask); + regs->UDC.intstatus = mask; + } + } + + spin_unlock(&mqdev->lock); + + return IRQ_RETVAL (irq_handled); +} + +static irqreturn_t mq_wup_irq(int irq, void *data, struct pt_regs *r) +{ + debug ("WakeUP IRQ\n"); + + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef MQ_IRQ_MULTIPLEX +static struct mq_irq_desc +{ + char *desc; + int delta; + irqreturn_t (*handler) (int, void *, struct pt_regs *); +} mq_irq_desc [] = +{ + { "MediaQ UDC", IRQ_MQ_UDC, mq_dev_irq }, + { "MediaQ UDC wake-up", IRQ_MQ_UDC_WAKE_UP, mq_wup_irq } +}; +#endif + +static int mq_remove(struct device *dev); + +static void gadget_release(struct device *dev) +{ + struct mqudc *mqdev = dev_get_drvdata (dev); + kfree (mqdev); +} + +static int mq_probe (struct device *dev) +{ + static char *names [] = { "ep0", "ep1in-int", "ep2in", "ep3out" }; + struct mqudc *mqdev; + struct mediaq11xx_base *base = (struct mediaq11xx_base *)dev->platform_data; + volatile struct mediaq11xx_regs *regs = base->regs; + int i; + + if (!base) + return -ENXIO; +#ifdef MQ_IRQ_MULTIPLEX + if (!base->irq_base) + return -ENXIO; +#endif + + mqdev = kmalloc (sizeof (*mqdev), GFP_KERNEL); + if (!mqdev) + return -ENOMEM; + + memset(mqdev, 0, sizeof (*mqdev)); + mqdev->base = base; + spin_lock_init(&mqdev->lock); + mqdev->gadget.ops = &mq_ops; + + strcpy(mqdev->gadget.dev.bus_id, "gadget"); + mqdev->gadget.ep0 = &mqdev->ep [0].ep; + mqdev->gadget.dev.parent = dev; + mqdev->gadget.name = driver_name; + mqdev->gadget.dev.release = gadget_release; + mqdev->gadget.speed = USB_SPEED_FULL; + + /* Initialize UDC */ + base->set_power (base, MEDIAQ_11XX_UDC_DEVICE_ID, 1); + /* Enable UDC transceiver */ + base->regs->DC.config_4 |= MQ_CONFIG_UDC_TRANSCEIVER_ENABLE; + mqdev->enabled = 1; + + dev_set_drvdata(dev, mqdev); + + /* Initialize endpoint list */ + INIT_LIST_HEAD (&mqdev->gadget.ep_list); + for (i = 0; i < 4; i++) { + struct mq_ep *ep = &mqdev->ep[i]; + + ep->num = i; + ep->ep.name = names[i]; + ep->mqdev = mqdev; + ep->ep.ops = &mq_ep_ops; + /* Endpoint 0 is not on the UDC's list of endpoints */ + if (i) + list_add_tail (&ep->ep.ep_list, &mqdev->gadget.ep_list); + } + + mqdev->ep[0].ep.maxpacket = MQ_EP01_MAXPACKET; + + mqdev->ep[0].is_in = 0; + mqdev->ep[1].is_in = 1; + mqdev->ep[2].is_in = 1; + mqdev->ep[3].is_in = 0; + + mqdev->ep[0].regs = ®s->UDC.ep0txcontrol; + mqdev->ep[1].regs = ®s->UDC.ep1control; + mqdev->ep[2].regs = ®s->UDC.ep2control; + mqdev->ep[3].regs = ®s->UDC.ep3control; + + mqdev->ep[2].dmaregs = ®s->UDC.dmatxcontrol; + mqdev->ep[3].dmaregs = ®s->UDC.dmarxcontrol; + + /* Grab all used IRQs */ +#ifdef MQ_IRQ_MULTIPLEX + for (i = 0; i < ARRAY_SIZE (mq_irq_desc); i++) { + int irqn = base->irq_base + mq_irq_desc [i].delta; + debug ("Requesting IRQ %d for %s\n", irqn, mq_irq_desc [i].desc); + if (request_irq (irqn, mq_irq_desc [i].handler, SA_INTERRUPT, + mq_irq_desc [i].desc, mqdev)) { + printk (KERN_ERR "%s: failed to request %s IRQ %d\n", + driver_name, mq_irq_desc [i].desc, irqn); + mqdev->got_irq = i - 1; + mq_remove (dev); + return -EBUSY; + } + } + mqdev->got_irq = ARRAY_SIZE (mq_irq_desc); +#endif + + /* Init the device to known state */ + mq_reset (mqdev); + mq_reinit (mqdev); + + /* Allocate two 4k buffers (used in turn) in MediaQ RAM for + every DMA-aware endpoint for MediaQ internal DMA transfers */ + for (i = 2; i <= 3; i++) { + int buffsize = 2 * ((i == 2) ? txbs : rxbs); + mqdev->ep[i].dmabuff = base->alloc (base, buffsize, 0); + if (mqdev->ep[i].dmabuff == (u32)-1) { + printk (KERN_ERR "%s: cannot allocate %d bytes in onboard RAM\n", + driver_name, buffsize); + mq_remove (dev); + return -ENOMEM; + } + else + debug ("Allocated %d bytes in MediaQ RAM, addr = 0x%x\n", + buffsize, mqdev->ep[i].dmabuff); + } + + device_register (&mqdev->gadget.dev); + mqdev->registered = 1; + + the_controller = mqdev; + + info ("MediaQ 11xx UDC driver successfully initialized\n"); + return 0; +} + +static int mq_remove(struct device *dev) +{ + struct mqudc *mqdev = dev_get_drvdata (dev); + int i; + + debug ("\n"); + + if (!mqdev) + return 0; + + /* start with the driver above us */ + if (mqdev->driver) { + /* should have been done already by driver model core */ + debug ("driver '%s' is still registered\n", + mqdev->driver->driver.name); + usb_gadget_unregister_driver(mqdev->driver); + } + + if (mqdev->base) + mq_reset(mqdev); + + for (i = 3; i >= 2; i--) + if (mqdev->ep[i].dmabuff) { + int buffsize = 2 * ((i == 2) ? txbs : rxbs); + debug ("Freeing MediaQ RAM at 0x%x, size %d\n", + mqdev->ep[i].dmabuff, buffsize); + mqdev->base->free (mqdev->base, mqdev->ep[i].dmabuff, + buffsize); + } + +#ifdef MQ_IRQ_MULTIPLEX + for (i = ((int)mqdev->got_irq) - 1; i >= 0; i--) { + int irqn = mqdev->base->irq_base + mq_irq_desc [i].delta; + debug ("Freeing IRQ %d\n", irqn); + free_irq(irqn, mqdev); + } +#endif + + if (mqdev->enabled) { + /* Disable UDC transceiver */ + mqdev->base->regs->DC.config_4 &= ~MQ_CONFIG_UDC_TRANSCEIVER_ENABLE; + mqdev->base->set_power (mqdev->base, MEDIAQ_11XX_UDC_DEVICE_ID, 0); + } + + if (mqdev->registered) + device_unregister (&mqdev->gadget.dev); + + dev_set_drvdata (dev, NULL); + the_controller = NULL; + + kfree (mqdev); + return 0; +} + +static void mq_shutdown(struct device *dev) +{ + debug ("\n"); +} + +static int mq_suspend (struct device *dev, u32 state, u32 level) +{ + debug ("\n"); + + return 0; +} + +static int mq_resume (struct device *dev, u32 level) +{ + debug ("\n"); + + return 0; +} + +struct device_driver mq_platform_driver = { + .name = (char *) driver_name, + .probe = mq_probe, + .remove = mq_remove, + .shutdown = mq_shutdown, + .suspend = mq_suspend, + .resume = mq_resume +}; + +static int __init mq_init(void) +{ + return driver_register(&mq_platform_driver); +} + +static void __exit mq_exit(void) +{ + driver_unregister (&mq_platform_driver); +} + +module_init (mq_init); +module_exit (mq_exit); diff --git a/drivers/usb/gadget/mq11xx_udc.h b/drivers/usb/gadget/mq11xx_udc.h new file mode 100644 --- /dev/null +++ b/drivers/usb/gadget/mq11xx_udc.h @@ -0,0 +1,115 @@ +/* + * MediaQ 11xx USB Device Controller driver + * Based on Goku-S UDC driver + * + * Copyright (C) 2004 Andrew Zabolotny <zap@homelink.ru> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#ifndef _MQ11XX_UDC_H +#define _MQ11XX_UDC_H + +/* Public structure provided by the platform device */ +struct mq11xx_udc_mach_info { + int (*udc_is_connected)(void); /* do we see host? */ + void (*udc_command)(int cmd); +#define MQ11XX_UDC_CMD_CONNECT 0 /* let host see us */ +#define MQ11XX_UDC_CMD_DISCONNECT 1 /* so host won't see us */ +}; + +#ifdef __MQ11XX_INTERNAL + +#include "../drivers/platform/mq11xx.h" + +/* MediaQ UDC request structure. + */ +struct mq_request { + /* The public request structure */ + struct usb_request req; + /* The chained list to link requests belonging to one endpoint */ + struct list_head queue; +}; + +/* USB Endpoint structure. + */ +struct mq_ep { + /* Public endpoint structure */ + struct usb_ep ep; + /* A chained list of mq_request structures */ + struct list_head list; + /* Endpoint number */ + unsigned num:8; + /* 1 if endpoint is INput (e.g. TX from UDC point of view) */ + unsigned is_in:1; + /* 1 if endpoint is stopped */ + unsigned stopped:1; + /* 1 if MediaQ RAM has been allocated for this endpoint */ + unsigned got_mqram:1; + /* 1 if endpoint configured in isochronous mode */ + unsigned iso:1; + /* Toggles between active buffers on DMA transfers */ + unsigned active_buffer:1; + /* Number of bytes in quick_buff to send plus one */ + unsigned quick_buff_bytes:3; + /* A quick buffer for small transfers + * (typically used during SETUP phase and for ZLP). + */ + u32 quick_buff; + /* A pointer to MediaQ registers associated with this endpoint */ + volatile u32 *regs; + /* A pointer to MediaQ registers responsible for DMA transfers */ + volatile u32 *dmaregs; + /* The offset in MediaQ internal RAM of two consecutive buffers + * used for DMA transfers. + */ + volatile u32 dmabuff; + /* Parent device */ + struct mqudc *mqdev; + /* Endpoint descriptor */ + const struct usb_endpoint_descriptor *desc; +}; + +enum ep0state { + EP0_DISCONNECT, /* no host */ + EP0_SUSPEND, /* usb suspend */ + EP0_IDLE, /* between STATUS ack and SETUP report */ + EP0_IN, EP0_OUT, /* data stage */ + EP0_STALL, /* something really bad happened */ +}; + +/* The USB Device Controller structure. + */ +struct mqudc { + /* Public gadget structure */ + struct usb_gadget gadget; + /* Spinlock for accessing the UDC */ + spinlock_t lock; + /* All of UDC endpoints */ + struct mq_ep ep[4]; + /* A pointer to bound gadget driver */ + struct usb_gadget_driver *driver; + /* DATA0/DATA1 toggle */ + u32 toggle; + + /* Current endpoint 0 state */ + enum ep0state ep0state; + + /* MediaQ 11xx base SoC driver public structure pointer */ + struct mediaq11xx_base *base; + + /* The following is used for device cleanup */ + + /* Number of IRQs requested so far */ + unsigned got_irq:2; + /* 1 if the UDC is enabled */ + unsigned enabled:1; + /* 1 if device has been registered with the kernel */ + unsigned registered:1; +}; + +#endif + +#endif /* _MQ11XX_UDC_H */ - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
| |