![Robert Baldyga](/assets/img/avatar_default.png)
This patch adds "maxpacket_limit" to struct usb_ep. This field contains maximum value of maxpacket supported by driver, and is set in driver probe. This value should be used by autoconfig() function, because value of field "maxpacket" is set to value from endpoint descriptor when endpoint becomes enabled. So when autoconfig() function will be called again for this endpoint, "maxpacket" value will contain wMaxPacketSize from descriptior instead of maximum packet size for this endpoint. For this reason this patch adds new field "maxpacket_limit" which contains value of maximum packet size (which defines maximum endpoint capabilities). This value is used in ep_matches() function used by autoconfig(). Value of "maxpacket_limit" should be set in UDC driver probe function, using usb_ep_set_maxpacket_limit() function, defined in gadget.h. This function set choosen value to both "maxpacket_limit" and "maxpacket" fields. This patch modifies UDC drivers by adding support for maxpacket_limit. Signed-off-by: Robert Baldyga <r.baldyga@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
1825 lines
45 KiB
C
1825 lines
45 KiB
C
/*
|
|
* Toshiba TC86C001 ("Goku-S") USB Device Controller driver
|
|
*
|
|
* Copyright (C) 2000-2002 Lineo
|
|
* by Stuart Lynne, Tom Rushworth, and Bruce Balden
|
|
* Copyright (C) 2002 Toshiba Corporation
|
|
* Copyright (C) 2003 MontaVista Software (source@mvista.com)
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*/
|
|
|
|
/*
|
|
* This device has ep0 and three semi-configurable bulk/interrupt endpoints.
|
|
*
|
|
* - Endpoint numbering is fixed: ep{1,2,3}-bulk
|
|
* - Gadget drivers can choose ep maxpacket (8/16/32/64)
|
|
* - Gadget drivers can choose direction (IN, OUT)
|
|
* - DMA works with ep1 (OUT transfers) and ep2 (IN transfers).
|
|
*/
|
|
|
|
// #define VERBOSE /* extra debug messages (success too) */
|
|
// #define USB_TRACE /* packet-level success messages */
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/slab.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/seq_file.h>
|
|
#include <linux/device.h>
|
|
#include <linux/usb/ch9.h>
|
|
#include <linux/usb/gadget.h>
|
|
#include <linux/prefetch.h>
|
|
|
|
#include <asm/byteorder.h>
|
|
#include <asm/io.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/unaligned.h>
|
|
|
|
|
|
#include "goku_udc.h"
|
|
|
|
#define DRIVER_DESC "TC86C001 USB Device Controller"
|
|
#define DRIVER_VERSION "30-Oct 2003"
|
|
|
|
static const char driver_name [] = "goku_udc";
|
|
static const char driver_desc [] = DRIVER_DESC;
|
|
|
|
MODULE_AUTHOR("source@mvista.com");
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
/*
|
|
* IN dma behaves ok under testing, though the IN-dma abort paths don't
|
|
* seem to behave quite as expected. Used by default.
|
|
*
|
|
* OUT dma documents design problems handling the common "short packet"
|
|
* transfer termination policy; it couldn't be enabled by default, even
|
|
* if the OUT-dma abort problems had a resolution.
|
|
*/
|
|
static unsigned use_dma = 1;
|
|
|
|
#if 0
|
|
//#include <linux/moduleparam.h>
|
|
/* "modprobe goku_udc use_dma=1" etc
|
|
* 0 to disable dma
|
|
* 1 to use IN dma only (normal operation)
|
|
* 2 to use IN and OUT dma
|
|
*/
|
|
module_param(use_dma, uint, S_IRUGO);
|
|
#endif
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static void nuke(struct goku_ep *, int status);
|
|
|
|
static inline void
|
|
command(struct goku_udc_regs __iomem *regs, int command, unsigned epnum)
|
|
{
|
|
writel(COMMAND_EP(epnum) | command, ®s->Command);
|
|
udelay(300);
|
|
}
|
|
|
|
static int
|
|
goku_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc)
|
|
{
|
|
struct goku_udc *dev;
|
|
struct goku_ep *ep;
|
|
u32 mode;
|
|
u16 max;
|
|
unsigned long flags;
|
|
|
|
ep = container_of(_ep, struct goku_ep, ep);
|
|
if (!_ep || !desc
|
|
|| desc->bDescriptorType != USB_DT_ENDPOINT)
|
|
return -EINVAL;
|
|
dev = ep->dev;
|
|
if (ep == &dev->ep[0])
|
|
return -EINVAL;
|
|
if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)
|
|
return -ESHUTDOWN;
|
|
if (ep->num != usb_endpoint_num(desc))
|
|
return -EINVAL;
|
|
|
|
switch (usb_endpoint_type(desc)) {
|
|
case USB_ENDPOINT_XFER_BULK:
|
|
case USB_ENDPOINT_XFER_INT:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((readl(ep->reg_status) & EPxSTATUS_EP_MASK)
|
|
!= EPxSTATUS_EP_INVALID)
|
|
return -EBUSY;
|
|
|
|
/* enabling the no-toggle interrupt mode would need an api hook */
|
|
mode = 0;
|
|
max = get_unaligned_le16(&desc->wMaxPacketSize);
|
|
switch (max) {
|
|
case 64: mode++;
|
|
case 32: mode++;
|
|
case 16: mode++;
|
|
case 8: mode <<= 3;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
mode |= 2 << 1; /* bulk, or intr-with-toggle */
|
|
|
|
/* ep1/ep2 dma direction is chosen early; it works in the other
|
|
* direction, with pio. be cautious with out-dma.
|
|
*/
|
|
ep->is_in = usb_endpoint_dir_in(desc);
|
|
if (ep->is_in) {
|
|
mode |= 1;
|
|
ep->dma = (use_dma != 0) && (ep->num == UDC_MSTRD_ENDPOINT);
|
|
} else {
|
|
ep->dma = (use_dma == 2) && (ep->num == UDC_MSTWR_ENDPOINT);
|
|
if (ep->dma)
|
|
DBG(dev, "%s out-dma hides short packets\n",
|
|
ep->ep.name);
|
|
}
|
|
|
|
spin_lock_irqsave(&ep->dev->lock, flags);
|
|
|
|
/* ep1 and ep2 can do double buffering and/or dma */
|
|
if (ep->num < 3) {
|
|
struct goku_udc_regs __iomem *regs = ep->dev->regs;
|
|
u32 tmp;
|
|
|
|
/* double buffer except (for now) with pio in */
|
|
tmp = ((ep->dma || !ep->is_in)
|
|
? 0x10 /* double buffered */
|
|
: 0x11 /* single buffer */
|
|
) << ep->num;
|
|
tmp |= readl(®s->EPxSingle);
|
|
writel(tmp, ®s->EPxSingle);
|
|
|
|
tmp = (ep->dma ? 0x10/*dma*/ : 0x11/*pio*/) << ep->num;
|
|
tmp |= readl(®s->EPxBCS);
|
|
writel(tmp, ®s->EPxBCS);
|
|
}
|
|
writel(mode, ep->reg_mode);
|
|
command(ep->dev->regs, COMMAND_RESET, ep->num);
|
|
ep->ep.maxpacket = max;
|
|
ep->stopped = 0;
|
|
ep->ep.desc = desc;
|
|
spin_unlock_irqrestore(&ep->dev->lock, flags);
|
|
|
|
DBG(dev, "enable %s %s %s maxpacket %u\n", ep->ep.name,
|
|
ep->is_in ? "IN" : "OUT",
|
|
ep->dma ? "dma" : "pio",
|
|
max);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ep_reset(struct goku_udc_regs __iomem *regs, struct goku_ep *ep)
|
|
{
|
|
struct goku_udc *dev = ep->dev;
|
|
|
|
if (regs) {
|
|
command(regs, COMMAND_INVALID, ep->num);
|
|
if (ep->num) {
|
|
if (ep->num == UDC_MSTWR_ENDPOINT)
|
|
dev->int_enable &= ~(INT_MSTWREND
|
|
|INT_MSTWRTMOUT);
|
|
else if (ep->num == UDC_MSTRD_ENDPOINT)
|
|
dev->int_enable &= ~INT_MSTRDEND;
|
|
dev->int_enable &= ~INT_EPxDATASET (ep->num);
|
|
} else
|
|
dev->int_enable &= ~INT_EP0;
|
|
writel(dev->int_enable, ®s->int_enable);
|
|
readl(®s->int_enable);
|
|
if (ep->num < 3) {
|
|
struct goku_udc_regs __iomem *r = ep->dev->regs;
|
|
u32 tmp;
|
|
|
|
tmp = readl(&r->EPxSingle);
|
|
tmp &= ~(0x11 << ep->num);
|
|
writel(tmp, &r->EPxSingle);
|
|
|
|
tmp = readl(&r->EPxBCS);
|
|
tmp &= ~(0x11 << ep->num);
|
|
writel(tmp, &r->EPxBCS);
|
|
}
|
|
/* reset dma in case we're still using it */
|
|
if (ep->dma) {
|
|
u32 master;
|
|
|
|
master = readl(®s->dma_master) & MST_RW_BITS;
|
|
if (ep->num == UDC_MSTWR_ENDPOINT) {
|
|
master &= ~MST_W_BITS;
|
|
master |= MST_WR_RESET;
|
|
} else {
|
|
master &= ~MST_R_BITS;
|
|
master |= MST_RD_RESET;
|
|
}
|
|
writel(master, ®s->dma_master);
|
|
}
|
|
}
|
|
|
|
usb_ep_set_maxpacket_limit(&ep->ep, MAX_FIFO_SIZE);
|
|
ep->ep.desc = NULL;
|
|
ep->stopped = 1;
|
|
ep->irqs = 0;
|
|
ep->dma = 0;
|
|
}
|
|
|
|
static int goku_ep_disable(struct usb_ep *_ep)
|
|
{
|
|
struct goku_ep *ep;
|
|
struct goku_udc *dev;
|
|
unsigned long flags;
|
|
|
|
ep = container_of(_ep, struct goku_ep, ep);
|
|
if (!_ep || !ep->ep.desc)
|
|
return -ENODEV;
|
|
dev = ep->dev;
|
|
if (dev->ep0state == EP0_SUSPEND)
|
|
return -EBUSY;
|
|
|
|
VDBG(dev, "disable %s\n", _ep->name);
|
|
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
nuke(ep, -ESHUTDOWN);
|
|
ep_reset(dev->regs, ep);
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static struct usb_request *
|
|
goku_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags)
|
|
{
|
|
struct goku_request *req;
|
|
|
|
if (!_ep)
|
|
return NULL;
|
|
req = kzalloc(sizeof *req, gfp_flags);
|
|
if (!req)
|
|
return NULL;
|
|
|
|
INIT_LIST_HEAD(&req->queue);
|
|
return &req->req;
|
|
}
|
|
|
|
static void
|
|
goku_free_request(struct usb_ep *_ep, struct usb_request *_req)
|
|
{
|
|
struct goku_request *req;
|
|
|
|
if (!_ep || !_req)
|
|
return;
|
|
|
|
req = container_of(_req, struct goku_request, req);
|
|
WARN_ON(!list_empty(&req->queue));
|
|
kfree(req);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static void
|
|
done(struct goku_ep *ep, struct goku_request *req, int status)
|
|
{
|
|
struct goku_udc *dev;
|
|
unsigned stopped = ep->stopped;
|
|
|
|
list_del_init(&req->queue);
|
|
|
|
if (likely(req->req.status == -EINPROGRESS))
|
|
req->req.status = status;
|
|
else
|
|
status = req->req.status;
|
|
|
|
dev = ep->dev;
|
|
|
|
if (ep->dma)
|
|
usb_gadget_unmap_request(&dev->gadget, &req->req, ep->is_in);
|
|
|
|
#ifndef USB_TRACE
|
|
if (status && status != -ESHUTDOWN)
|
|
#endif
|
|
VDBG(dev, "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(&dev->lock);
|
|
req->req.complete(&ep->ep, &req->req);
|
|
spin_lock(&dev->lock);
|
|
ep->stopped = stopped;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static inline int
|
|
write_packet(u32 __iomem *fifo, u8 *buf, struct goku_request *req, unsigned max)
|
|
{
|
|
unsigned length, count;
|
|
|
|
length = min(req->req.length - req->req.actual, max);
|
|
req->req.actual += length;
|
|
|
|
count = length;
|
|
while (likely(count--))
|
|
writel(*buf++, fifo);
|
|
return length;
|
|
}
|
|
|
|
// return: 0 = still running, 1 = completed, negative = errno
|
|
static int write_fifo(struct goku_ep *ep, struct goku_request *req)
|
|
{
|
|
struct goku_udc *dev = ep->dev;
|
|
u32 tmp;
|
|
u8 *buf;
|
|
unsigned count;
|
|
int is_last;
|
|
|
|
tmp = readl(&dev->regs->DataSet);
|
|
buf = req->req.buf + req->req.actual;
|
|
prefetch(buf);
|
|
|
|
dev = ep->dev;
|
|
if (unlikely(ep->num == 0 && dev->ep0state != EP0_IN))
|
|
return -EL2HLT;
|
|
|
|
/* NOTE: just single-buffered PIO-IN for now. */
|
|
if (unlikely((tmp & DATASET_A(ep->num)) != 0))
|
|
return 0;
|
|
|
|
/* clear our "packet available" irq */
|
|
if (ep->num != 0)
|
|
writel(~INT_EPxDATASET(ep->num), &dev->regs->int_status);
|
|
|
|
count = write_packet(ep->reg_fifo, buf, req, ep->ep.maxpacket);
|
|
|
|
/* last packet often short (sometimes a zlp, especially on ep0) */
|
|
if (unlikely(count != ep->ep.maxpacket)) {
|
|
writel(~(1<<ep->num), &dev->regs->EOP);
|
|
if (ep->num == 0) {
|
|
dev->ep[0].stopped = 1;
|
|
dev->ep0state = EP0_STATUS;
|
|
}
|
|
is_last = 1;
|
|
} else {
|
|
if (likely(req->req.length != req->req.actual)
|
|
|| req->req.zero)
|
|
is_last = 0;
|
|
else
|
|
is_last = 1;
|
|
}
|
|
#if 0 /* printk seemed to trash is_last...*/
|
|
//#ifdef USB_TRACE
|
|
VDBG(dev, "wrote %s %u bytes%s IN %u left %p\n",
|
|
ep->ep.name, count, is_last ? "/last" : "",
|
|
req->req.length - req->req.actual, req);
|
|
#endif
|
|
|
|
/* requests complete when all IN data is in the FIFO,
|
|
* or sometimes later, if a zlp was needed.
|
|
*/
|
|
if (is_last) {
|
|
done(ep, req, 0);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int read_fifo(struct goku_ep *ep, struct goku_request *req)
|
|
{
|
|
struct goku_udc_regs __iomem *regs;
|
|
u32 size, set;
|
|
u8 *buf;
|
|
unsigned bufferspace, is_short, dbuff;
|
|
|
|
regs = ep->dev->regs;
|
|
top:
|
|
buf = req->req.buf + req->req.actual;
|
|
prefetchw(buf);
|
|
|
|
if (unlikely(ep->num == 0 && ep->dev->ep0state != EP0_OUT))
|
|
return -EL2HLT;
|
|
|
|
dbuff = (ep->num == 1 || ep->num == 2);
|
|
do {
|
|
/* ack dataset irq matching the status we'll handle */
|
|
if (ep->num != 0)
|
|
writel(~INT_EPxDATASET(ep->num), ®s->int_status);
|
|
|
|
set = readl(®s->DataSet) & DATASET_AB(ep->num);
|
|
size = readl(®s->EPxSizeLA[ep->num]);
|
|
bufferspace = req->req.length - req->req.actual;
|
|
|
|
/* usually do nothing without an OUT packet */
|
|
if (likely(ep->num != 0 || bufferspace != 0)) {
|
|
if (unlikely(set == 0))
|
|
break;
|
|
/* use ep1/ep2 double-buffering for OUT */
|
|
if (!(size & PACKET_ACTIVE))
|
|
size = readl(®s->EPxSizeLB[ep->num]);
|
|
if (!(size & PACKET_ACTIVE)) /* "can't happen" */
|
|
break;
|
|
size &= DATASIZE; /* EPxSizeH == 0 */
|
|
|
|
/* ep0out no-out-data case for set_config, etc */
|
|
} else
|
|
size = 0;
|
|
|
|
/* read all bytes from this packet */
|
|
req->req.actual += size;
|
|
is_short = (size < ep->ep.maxpacket);
|
|
#ifdef USB_TRACE
|
|
VDBG(ep->dev, "read %s %u bytes%s OUT req %p %u/%u\n",
|
|
ep->ep.name, size, is_short ? "/S" : "",
|
|
req, req->req.actual, req->req.length);
|
|
#endif
|
|
while (likely(size-- != 0)) {
|
|
u8 byte = (u8) readl(ep->reg_fifo);
|
|
|
|
if (unlikely(bufferspace == 0)) {
|
|
/* this happens when the driver's buffer
|
|
* is smaller than what the host sent.
|
|
* discard the extra data in this packet.
|
|
*/
|
|
if (req->req.status != -EOVERFLOW)
|
|
DBG(ep->dev, "%s overflow %u\n",
|
|
ep->ep.name, size);
|
|
req->req.status = -EOVERFLOW;
|
|
} else {
|
|
*buf++ = byte;
|
|
bufferspace--;
|
|
}
|
|
}
|
|
|
|
/* completion */
|
|
if (unlikely(is_short || req->req.actual == req->req.length)) {
|
|
if (unlikely(ep->num == 0)) {
|
|
/* non-control endpoints now usable? */
|
|
if (ep->dev->req_config)
|
|
writel(ep->dev->configured
|
|
? USBSTATE_CONFIGURED
|
|
: 0,
|
|
®s->UsbState);
|
|
/* ep0out status stage */
|
|
writel(~(1<<0), ®s->EOP);
|
|
ep->stopped = 1;
|
|
ep->dev->ep0state = EP0_STATUS;
|
|
}
|
|
done(ep, req, 0);
|
|
|
|
/* empty the second buffer asap */
|
|
if (dbuff && !list_empty(&ep->queue)) {
|
|
req = list_entry(ep->queue.next,
|
|
struct goku_request, queue);
|
|
goto top;
|
|
}
|
|
return 1;
|
|
}
|
|
} while (dbuff);
|
|
return 0;
|
|
}
|
|
|
|
static inline void
|
|
pio_irq_enable(struct goku_udc *dev,
|
|
struct goku_udc_regs __iomem *regs, int epnum)
|
|
{
|
|
dev->int_enable |= INT_EPxDATASET (epnum);
|
|
writel(dev->int_enable, ®s->int_enable);
|
|
/* write may still be posted */
|
|
}
|
|
|
|
static inline void
|
|
pio_irq_disable(struct goku_udc *dev,
|
|
struct goku_udc_regs __iomem *regs, int epnum)
|
|
{
|
|
dev->int_enable &= ~INT_EPxDATASET (epnum);
|
|
writel(dev->int_enable, ®s->int_enable);
|
|
/* write may still be posted */
|
|
}
|
|
|
|
static inline void
|
|
pio_advance(struct goku_ep *ep)
|
|
{
|
|
struct goku_request *req;
|
|
|
|
if (unlikely(list_empty (&ep->queue)))
|
|
return;
|
|
req = list_entry(ep->queue.next, struct goku_request, queue);
|
|
(ep->is_in ? write_fifo : read_fifo)(ep, req);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
// return: 0 = q running, 1 = q stopped, negative = errno
|
|
static int start_dma(struct goku_ep *ep, struct goku_request *req)
|
|
{
|
|
struct goku_udc_regs __iomem *regs = ep->dev->regs;
|
|
u32 master;
|
|
u32 start = req->req.dma;
|
|
u32 end = start + req->req.length - 1;
|
|
|
|
master = readl(®s->dma_master) & MST_RW_BITS;
|
|
|
|
/* re-init the bits affecting IN dma; careful with zlps */
|
|
if (likely(ep->is_in)) {
|
|
if (unlikely(master & MST_RD_ENA)) {
|
|
DBG (ep->dev, "start, IN active dma %03x!!\n",
|
|
master);
|
|
// return -EL2HLT;
|
|
}
|
|
writel(end, ®s->in_dma_end);
|
|
writel(start, ®s->in_dma_start);
|
|
|
|
master &= ~MST_R_BITS;
|
|
if (unlikely(req->req.length == 0))
|
|
master = MST_RD_ENA | MST_RD_EOPB;
|
|
else if ((req->req.length % ep->ep.maxpacket) != 0
|
|
|| req->req.zero)
|
|
master = MST_RD_ENA | MST_EOPB_ENA;
|
|
else
|
|
master = MST_RD_ENA | MST_EOPB_DIS;
|
|
|
|
ep->dev->int_enable |= INT_MSTRDEND;
|
|
|
|
/* Goku DMA-OUT merges short packets, which plays poorly with
|
|
* protocols where short packets mark the transfer boundaries.
|
|
* The chip supports a nonstandard policy with INT_MSTWRTMOUT,
|
|
* ending transfers after 3 SOFs; we don't turn it on.
|
|
*/
|
|
} else {
|
|
if (unlikely(master & MST_WR_ENA)) {
|
|
DBG (ep->dev, "start, OUT active dma %03x!!\n",
|
|
master);
|
|
// return -EL2HLT;
|
|
}
|
|
writel(end, ®s->out_dma_end);
|
|
writel(start, ®s->out_dma_start);
|
|
|
|
master &= ~MST_W_BITS;
|
|
master |= MST_WR_ENA | MST_TIMEOUT_DIS;
|
|
|
|
ep->dev->int_enable |= INT_MSTWREND|INT_MSTWRTMOUT;
|
|
}
|
|
|
|
writel(master, ®s->dma_master);
|
|
writel(ep->dev->int_enable, ®s->int_enable);
|
|
return 0;
|
|
}
|
|
|
|
static void dma_advance(struct goku_udc *dev, struct goku_ep *ep)
|
|
{
|
|
struct goku_request *req;
|
|
struct goku_udc_regs __iomem *regs = ep->dev->regs;
|
|
u32 master;
|
|
|
|
master = readl(®s->dma_master);
|
|
|
|
if (unlikely(list_empty(&ep->queue))) {
|
|
stop:
|
|
if (ep->is_in)
|
|
dev->int_enable &= ~INT_MSTRDEND;
|
|
else
|
|
dev->int_enable &= ~(INT_MSTWREND|INT_MSTWRTMOUT);
|
|
writel(dev->int_enable, ®s->int_enable);
|
|
return;
|
|
}
|
|
req = list_entry(ep->queue.next, struct goku_request, queue);
|
|
|
|
/* normal hw dma completion (not abort) */
|
|
if (likely(ep->is_in)) {
|
|
if (unlikely(master & MST_RD_ENA))
|
|
return;
|
|
req->req.actual = readl(®s->in_dma_current);
|
|
} else {
|
|
if (unlikely(master & MST_WR_ENA))
|
|
return;
|
|
|
|
/* hardware merges short packets, and also hides packet
|
|
* overruns. a partial packet MAY be in the fifo here.
|
|
*/
|
|
req->req.actual = readl(®s->out_dma_current);
|
|
}
|
|
req->req.actual -= req->req.dma;
|
|
req->req.actual++;
|
|
|
|
#ifdef USB_TRACE
|
|
VDBG(dev, "done %s %s dma, %u/%u bytes, req %p\n",
|
|
ep->ep.name, ep->is_in ? "IN" : "OUT",
|
|
req->req.actual, req->req.length, req);
|
|
#endif
|
|
done(ep, req, 0);
|
|
if (list_empty(&ep->queue))
|
|
goto stop;
|
|
req = list_entry(ep->queue.next, struct goku_request, queue);
|
|
(void) start_dma(ep, req);
|
|
}
|
|
|
|
static void abort_dma(struct goku_ep *ep, int status)
|
|
{
|
|
struct goku_udc_regs __iomem *regs = ep->dev->regs;
|
|
struct goku_request *req;
|
|
u32 curr, master;
|
|
|
|
/* NAK future host requests, hoping the implicit delay lets the
|
|
* dma engine finish reading (or writing) its latest packet and
|
|
* empty the dma buffer (up to 16 bytes).
|
|
*
|
|
* This avoids needing to clean up a partial packet in the fifo;
|
|
* we can't do that for IN without side effects to HALT and TOGGLE.
|
|
*/
|
|
command(regs, COMMAND_FIFO_DISABLE, ep->num);
|
|
req = list_entry(ep->queue.next, struct goku_request, queue);
|
|
master = readl(®s->dma_master) & MST_RW_BITS;
|
|
|
|
/* FIXME using these resets isn't usably documented. this may
|
|
* not work unless it's followed by disabling the endpoint.
|
|
*
|
|
* FIXME the OUT reset path doesn't even behave consistently.
|
|
*/
|
|
if (ep->is_in) {
|
|
if (unlikely((readl(®s->dma_master) & MST_RD_ENA) == 0))
|
|
goto finished;
|
|
curr = readl(®s->in_dma_current);
|
|
|
|
writel(curr, ®s->in_dma_end);
|
|
writel(curr, ®s->in_dma_start);
|
|
|
|
master &= ~MST_R_BITS;
|
|
master |= MST_RD_RESET;
|
|
writel(master, ®s->dma_master);
|
|
|
|
if (readl(®s->dma_master) & MST_RD_ENA)
|
|
DBG(ep->dev, "IN dma active after reset!\n");
|
|
|
|
} else {
|
|
if (unlikely((readl(®s->dma_master) & MST_WR_ENA) == 0))
|
|
goto finished;
|
|
curr = readl(®s->out_dma_current);
|
|
|
|
writel(curr, ®s->out_dma_end);
|
|
writel(curr, ®s->out_dma_start);
|
|
|
|
master &= ~MST_W_BITS;
|
|
master |= MST_WR_RESET;
|
|
writel(master, ®s->dma_master);
|
|
|
|
if (readl(®s->dma_master) & MST_WR_ENA)
|
|
DBG(ep->dev, "OUT dma active after reset!\n");
|
|
}
|
|
req->req.actual = (curr - req->req.dma) + 1;
|
|
req->req.status = status;
|
|
|
|
VDBG(ep->dev, "%s %s %s %d/%d\n", __func__, ep->ep.name,
|
|
ep->is_in ? "IN" : "OUT",
|
|
req->req.actual, req->req.length);
|
|
|
|
command(regs, COMMAND_FIFO_ENABLE, ep->num);
|
|
|
|
return;
|
|
|
|
finished:
|
|
/* dma already completed; no abort needed */
|
|
command(regs, COMMAND_FIFO_ENABLE, ep->num);
|
|
req->req.actual = req->req.length;
|
|
req->req.status = 0;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static int
|
|
goku_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
|
|
{
|
|
struct goku_request *req;
|
|
struct goku_ep *ep;
|
|
struct goku_udc *dev;
|
|
unsigned long flags;
|
|
int status;
|
|
|
|
/* always require a cpu-view buffer so pio works */
|
|
req = container_of(_req, struct goku_request, req);
|
|
if (unlikely(!_req || !_req->complete
|
|
|| !_req->buf || !list_empty(&req->queue)))
|
|
return -EINVAL;
|
|
ep = container_of(_ep, struct goku_ep, ep);
|
|
if (unlikely(!_ep || (!ep->ep.desc && ep->num != 0)))
|
|
return -EINVAL;
|
|
dev = ep->dev;
|
|
if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN))
|
|
return -ESHUTDOWN;
|
|
|
|
/* can't touch registers when suspended */
|
|
if (dev->ep0state == EP0_SUSPEND)
|
|
return -EBUSY;
|
|
|
|
/* set up dma mapping in case the caller didn't */
|
|
if (ep->dma) {
|
|
status = usb_gadget_map_request(&dev->gadget, &req->req,
|
|
ep->is_in);
|
|
if (status)
|
|
return status;
|
|
}
|
|
|
|
#ifdef USB_TRACE
|
|
VDBG(dev, "%s queue req %p, len %u buf %p\n",
|
|
_ep->name, _req, _req->length, _req->buf);
|
|
#endif
|
|
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
|
|
_req->status = -EINPROGRESS;
|
|
_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->zero = 1;
|
|
|
|
/* kickstart this i/o queue? */
|
|
status = 0;
|
|
if (list_empty(&ep->queue) && likely(!ep->stopped)) {
|
|
/* dma: done after dma completion IRQ (or error)
|
|
* pio: done after last fifo operation
|
|
*/
|
|
if (ep->dma)
|
|
status = start_dma(ep, req);
|
|
else
|
|
status = (ep->is_in ? write_fifo : read_fifo)(ep, req);
|
|
|
|
if (unlikely(status != 0)) {
|
|
if (status > 0)
|
|
status = 0;
|
|
req = NULL;
|
|
}
|
|
|
|
} /* else pio or dma irq handler advances the queue. */
|
|
|
|
if (likely(req != NULL))
|
|
list_add_tail(&req->queue, &ep->queue);
|
|
|
|
if (likely(!list_empty(&ep->queue))
|
|
&& likely(ep->num != 0)
|
|
&& !ep->dma
|
|
&& !(dev->int_enable & INT_EPxDATASET (ep->num)))
|
|
pio_irq_enable(dev, dev->regs, ep->num);
|
|
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
|
|
/* pci writes may still be posted */
|
|
return status;
|
|
}
|
|
|
|
/* dequeue ALL requests */
|
|
static void nuke(struct goku_ep *ep, int status)
|
|
{
|
|
struct goku_request *req;
|
|
|
|
ep->stopped = 1;
|
|
if (list_empty(&ep->queue))
|
|
return;
|
|
if (ep->dma)
|
|
abort_dma(ep, status);
|
|
while (!list_empty(&ep->queue)) {
|
|
req = list_entry(ep->queue.next, struct goku_request, queue);
|
|
done(ep, req, status);
|
|
}
|
|
}
|
|
|
|
/* dequeue JUST ONE request */
|
|
static int goku_dequeue(struct usb_ep *_ep, struct usb_request *_req)
|
|
{
|
|
struct goku_request *req;
|
|
struct goku_ep *ep;
|
|
struct goku_udc *dev;
|
|
unsigned long flags;
|
|
|
|
ep = container_of(_ep, struct goku_ep, ep);
|
|
if (!_ep || !_req || (!ep->ep.desc && ep->num != 0))
|
|
return -EINVAL;
|
|
dev = ep->dev;
|
|
if (!dev->driver)
|
|
return -ESHUTDOWN;
|
|
|
|
/* we can't touch (dma) registers when suspended */
|
|
if (dev->ep0state == EP0_SUSPEND)
|
|
return -EBUSY;
|
|
|
|
VDBG(dev, "%s %s %s %s %p\n", __func__, _ep->name,
|
|
ep->is_in ? "IN" : "OUT",
|
|
ep->dma ? "dma" : "pio",
|
|
_req);
|
|
|
|
spin_lock_irqsave(&dev->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 (&dev->lock, flags);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ep->dma && ep->queue.next == &req->queue && !ep->stopped) {
|
|
abort_dma(ep, -ECONNRESET);
|
|
done(ep, req, -ECONNRESET);
|
|
dma_advance(dev, ep);
|
|
} else if (!list_empty(&req->queue))
|
|
done(ep, req, -ECONNRESET);
|
|
else
|
|
req = NULL;
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
|
|
return req ? 0 : -EOPNOTSUPP;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static void goku_clear_halt(struct goku_ep *ep)
|
|
{
|
|
// assert (ep->num !=0)
|
|
VDBG(ep->dev, "%s clear halt\n", ep->ep.name);
|
|
command(ep->dev->regs, COMMAND_SETDATA0, ep->num);
|
|
command(ep->dev->regs, COMMAND_STALL_CLEAR, ep->num);
|
|
if (ep->stopped) {
|
|
ep->stopped = 0;
|
|
if (ep->dma) {
|
|
struct goku_request *req;
|
|
|
|
if (list_empty(&ep->queue))
|
|
return;
|
|
req = list_entry(ep->queue.next, struct goku_request,
|
|
queue);
|
|
(void) start_dma(ep, req);
|
|
} else
|
|
pio_advance(ep);
|
|
}
|
|
}
|
|
|
|
static int goku_set_halt(struct usb_ep *_ep, int value)
|
|
{
|
|
struct goku_ep *ep;
|
|
unsigned long flags;
|
|
int retval = 0;
|
|
|
|
if (!_ep)
|
|
return -ENODEV;
|
|
ep = container_of (_ep, struct goku_ep, ep);
|
|
|
|
if (ep->num == 0) {
|
|
if (value) {
|
|
ep->dev->ep0state = EP0_STALL;
|
|
ep->dev->ep[0].stopped = 1;
|
|
} else
|
|
return -EINVAL;
|
|
|
|
/* don't change EPxSTATUS_EP_INVALID to READY */
|
|
} else if (!ep->ep.desc) {
|
|
DBG(ep->dev, "%s %s inactive?\n", __func__, ep->ep.name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&ep->dev->lock, flags);
|
|
if (!list_empty(&ep->queue))
|
|
retval = -EAGAIN;
|
|
else if (ep->is_in && value
|
|
/* data in (either) packet buffer? */
|
|
&& (readl(&ep->dev->regs->DataSet)
|
|
& DATASET_AB(ep->num)))
|
|
retval = -EAGAIN;
|
|
else if (!value)
|
|
goku_clear_halt(ep);
|
|
else {
|
|
ep->stopped = 1;
|
|
VDBG(ep->dev, "%s set halt\n", ep->ep.name);
|
|
command(ep->dev->regs, COMMAND_STALL, ep->num);
|
|
readl(ep->reg_status);
|
|
}
|
|
spin_unlock_irqrestore(&ep->dev->lock, flags);
|
|
return retval;
|
|
}
|
|
|
|
static int goku_fifo_status(struct usb_ep *_ep)
|
|
{
|
|
struct goku_ep *ep;
|
|
struct goku_udc_regs __iomem *regs;
|
|
u32 size;
|
|
|
|
if (!_ep)
|
|
return -ENODEV;
|
|
ep = container_of(_ep, struct goku_ep, ep);
|
|
|
|
/* size is only reported sanely for OUT */
|
|
if (ep->is_in)
|
|
return -EOPNOTSUPP;
|
|
|
|
/* ignores 16-byte dma buffer; SizeH == 0 */
|
|
regs = ep->dev->regs;
|
|
size = readl(®s->EPxSizeLA[ep->num]) & DATASIZE;
|
|
size += readl(®s->EPxSizeLB[ep->num]) & DATASIZE;
|
|
VDBG(ep->dev, "%s %s %u\n", __func__, ep->ep.name, size);
|
|
return size;
|
|
}
|
|
|
|
static void goku_fifo_flush(struct usb_ep *_ep)
|
|
{
|
|
struct goku_ep *ep;
|
|
struct goku_udc_regs __iomem *regs;
|
|
u32 size;
|
|
|
|
if (!_ep)
|
|
return;
|
|
ep = container_of(_ep, struct goku_ep, ep);
|
|
VDBG(ep->dev, "%s %s\n", __func__, ep->ep.name);
|
|
|
|
/* don't change EPxSTATUS_EP_INVALID to READY */
|
|
if (!ep->ep.desc && ep->num != 0) {
|
|
DBG(ep->dev, "%s %s inactive?\n", __func__, ep->ep.name);
|
|
return;
|
|
}
|
|
|
|
regs = ep->dev->regs;
|
|
size = readl(®s->EPxSizeLA[ep->num]);
|
|
size &= DATASIZE;
|
|
|
|
/* Non-desirable behavior: FIFO_CLEAR also clears the
|
|
* endpoint halt feature. For OUT, we _could_ just read
|
|
* the bytes out (PIO, if !ep->dma); for in, no choice.
|
|
*/
|
|
if (size)
|
|
command(regs, COMMAND_FIFO_CLEAR, ep->num);
|
|
}
|
|
|
|
static struct usb_ep_ops goku_ep_ops = {
|
|
.enable = goku_ep_enable,
|
|
.disable = goku_ep_disable,
|
|
|
|
.alloc_request = goku_alloc_request,
|
|
.free_request = goku_free_request,
|
|
|
|
.queue = goku_queue,
|
|
.dequeue = goku_dequeue,
|
|
|
|
.set_halt = goku_set_halt,
|
|
.fifo_status = goku_fifo_status,
|
|
.fifo_flush = goku_fifo_flush,
|
|
};
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static int goku_get_frame(struct usb_gadget *_gadget)
|
|
{
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static int goku_udc_start(struct usb_gadget *g,
|
|
struct usb_gadget_driver *driver);
|
|
static int goku_udc_stop(struct usb_gadget *g,
|
|
struct usb_gadget_driver *driver);
|
|
|
|
static const struct usb_gadget_ops goku_ops = {
|
|
.get_frame = goku_get_frame,
|
|
.udc_start = goku_udc_start,
|
|
.udc_stop = goku_udc_stop,
|
|
// no remote wakeup
|
|
// not selfpowered
|
|
};
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static inline const char *dmastr(void)
|
|
{
|
|
if (use_dma == 0)
|
|
return "(dma disabled)";
|
|
else if (use_dma == 2)
|
|
return "(dma IN and OUT)";
|
|
else
|
|
return "(dma IN)";
|
|
}
|
|
|
|
#ifdef CONFIG_USB_GADGET_DEBUG_FILES
|
|
|
|
static const char proc_node_name [] = "driver/udc";
|
|
|
|
#define FOURBITS "%s%s%s%s"
|
|
#define EIGHTBITS FOURBITS FOURBITS
|
|
|
|
static void dump_intmask(struct seq_file *m, const char *label, u32 mask)
|
|
{
|
|
/* int_status is the same format ... */
|
|
seq_printf(m,
|
|
"%s %05X =" FOURBITS EIGHTBITS EIGHTBITS "\n",
|
|
label, mask,
|
|
(mask & INT_PWRDETECT) ? " power" : "",
|
|
(mask & INT_SYSERROR) ? " sys" : "",
|
|
(mask & INT_MSTRDEND) ? " in-dma" : "",
|
|
(mask & INT_MSTWRTMOUT) ? " wrtmo" : "",
|
|
|
|
(mask & INT_MSTWREND) ? " out-dma" : "",
|
|
(mask & INT_MSTWRSET) ? " wrset" : "",
|
|
(mask & INT_ERR) ? " err" : "",
|
|
(mask & INT_SOF) ? " sof" : "",
|
|
|
|
(mask & INT_EP3NAK) ? " ep3nak" : "",
|
|
(mask & INT_EP2NAK) ? " ep2nak" : "",
|
|
(mask & INT_EP1NAK) ? " ep1nak" : "",
|
|
(mask & INT_EP3DATASET) ? " ep3" : "",
|
|
|
|
(mask & INT_EP2DATASET) ? " ep2" : "",
|
|
(mask & INT_EP1DATASET) ? " ep1" : "",
|
|
(mask & INT_STATUSNAK) ? " ep0snak" : "",
|
|
(mask & INT_STATUS) ? " ep0status" : "",
|
|
|
|
(mask & INT_SETUP) ? " setup" : "",
|
|
(mask & INT_ENDPOINT0) ? " ep0" : "",
|
|
(mask & INT_USBRESET) ? " reset" : "",
|
|
(mask & INT_SUSPEND) ? " suspend" : "");
|
|
}
|
|
|
|
|
|
static int udc_proc_read(struct seq_file *m, void *v)
|
|
{
|
|
struct goku_udc *dev = m->private;
|
|
struct goku_udc_regs __iomem *regs = dev->regs;
|
|
unsigned long flags;
|
|
int i, is_usb_connected;
|
|
u32 tmp;
|
|
|
|
local_irq_save(flags);
|
|
|
|
/* basic device status */
|
|
tmp = readl(®s->power_detect);
|
|
is_usb_connected = tmp & PW_DETECT;
|
|
seq_printf(m,
|
|
"%s - %s\n"
|
|
"%s version: %s %s\n"
|
|
"Gadget driver: %s\n"
|
|
"Host %s, %s\n"
|
|
"\n",
|
|
pci_name(dev->pdev), driver_desc,
|
|
driver_name, DRIVER_VERSION, dmastr(),
|
|
dev->driver ? dev->driver->driver.name : "(none)",
|
|
is_usb_connected
|
|
? ((tmp & PW_PULLUP) ? "full speed" : "powered")
|
|
: "disconnected",
|
|
({const char *state;
|
|
switch(dev->ep0state){
|
|
case EP0_DISCONNECT: state = "ep0_disconnect"; break;
|
|
case EP0_IDLE: state = "ep0_idle"; break;
|
|
case EP0_IN: state = "ep0_in"; break;
|
|
case EP0_OUT: state = "ep0_out"; break;
|
|
case EP0_STATUS: state = "ep0_status"; break;
|
|
case EP0_STALL: state = "ep0_stall"; break;
|
|
case EP0_SUSPEND: state = "ep0_suspend"; break;
|
|
default: state = "ep0_?"; break;
|
|
} state; })
|
|
);
|
|
|
|
dump_intmask(m, "int_status", readl(®s->int_status));
|
|
dump_intmask(m, "int_enable", readl(®s->int_enable));
|
|
|
|
if (!is_usb_connected || !dev->driver || (tmp & PW_PULLUP) == 0)
|
|
goto done;
|
|
|
|
/* registers for (active) device and ep0 */
|
|
if (seq_printf(m, "\nirqs %lu\ndataset %02x "
|
|
"single.bcs %02x.%02x state %x addr %u\n",
|
|
dev->irqs, readl(®s->DataSet),
|
|
readl(®s->EPxSingle), readl(®s->EPxBCS),
|
|
readl(®s->UsbState),
|
|
readl(®s->address)) < 0)
|
|
goto done;
|
|
|
|
tmp = readl(®s->dma_master);
|
|
if (seq_printf(m,
|
|
"dma %03X =" EIGHTBITS "%s %s\n", tmp,
|
|
(tmp & MST_EOPB_DIS) ? " eopb-" : "",
|
|
(tmp & MST_EOPB_ENA) ? " eopb+" : "",
|
|
(tmp & MST_TIMEOUT_DIS) ? " tmo-" : "",
|
|
(tmp & MST_TIMEOUT_ENA) ? " tmo+" : "",
|
|
|
|
(tmp & MST_RD_EOPB) ? " eopb" : "",
|
|
(tmp & MST_RD_RESET) ? " in_reset" : "",
|
|
(tmp & MST_WR_RESET) ? " out_reset" : "",
|
|
(tmp & MST_RD_ENA) ? " IN" : "",
|
|
|
|
(tmp & MST_WR_ENA) ? " OUT" : "",
|
|
(tmp & MST_CONNECTION)
|
|
? "ep1in/ep2out"
|
|
: "ep1out/ep2in") < 0)
|
|
goto done;
|
|
|
|
/* dump endpoint queues */
|
|
for (i = 0; i < 4; i++) {
|
|
struct goku_ep *ep = &dev->ep [i];
|
|
struct goku_request *req;
|
|
|
|
if (i && !ep->ep.desc)
|
|
continue;
|
|
|
|
tmp = readl(ep->reg_status);
|
|
if (seq_printf(m,
|
|
"%s %s max %u %s, irqs %lu, "
|
|
"status %02x (%s) " FOURBITS "\n",
|
|
ep->ep.name,
|
|
ep->is_in ? "in" : "out",
|
|
ep->ep.maxpacket,
|
|
ep->dma ? "dma" : "pio",
|
|
ep->irqs,
|
|
tmp, ({ char *s;
|
|
switch (tmp & EPxSTATUS_EP_MASK) {
|
|
case EPxSTATUS_EP_READY:
|
|
s = "ready"; break;
|
|
case EPxSTATUS_EP_DATAIN:
|
|
s = "packet"; break;
|
|
case EPxSTATUS_EP_FULL:
|
|
s = "full"; break;
|
|
case EPxSTATUS_EP_TX_ERR: // host will retry
|
|
s = "tx_err"; break;
|
|
case EPxSTATUS_EP_RX_ERR:
|
|
s = "rx_err"; break;
|
|
case EPxSTATUS_EP_BUSY: /* ep0 only */
|
|
s = "busy"; break;
|
|
case EPxSTATUS_EP_STALL:
|
|
s = "stall"; break;
|
|
case EPxSTATUS_EP_INVALID: // these "can't happen"
|
|
s = "invalid"; break;
|
|
default:
|
|
s = "?"; break;
|
|
} s; }),
|
|
(tmp & EPxSTATUS_TOGGLE) ? "data1" : "data0",
|
|
(tmp & EPxSTATUS_SUSPEND) ? " suspend" : "",
|
|
(tmp & EPxSTATUS_FIFO_DISABLE) ? " disable" : "",
|
|
(tmp & EPxSTATUS_STAGE_ERROR) ? " ep0stat" : ""
|
|
) < 0)
|
|
goto done;
|
|
|
|
if (list_empty(&ep->queue)) {
|
|
if (seq_puts(m, "\t(nothing queued)\n") < 0)
|
|
goto done;
|
|
continue;
|
|
}
|
|
list_for_each_entry(req, &ep->queue, queue) {
|
|
if (ep->dma && req->queue.prev == &ep->queue) {
|
|
if (i == UDC_MSTRD_ENDPOINT)
|
|
tmp = readl(®s->in_dma_current);
|
|
else
|
|
tmp = readl(®s->out_dma_current);
|
|
tmp -= req->req.dma;
|
|
tmp++;
|
|
} else
|
|
tmp = req->req.actual;
|
|
|
|
if (seq_printf(m,
|
|
"\treq %p len %u/%u buf %p\n",
|
|
&req->req, tmp, req->req.length,
|
|
req->req.buf) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
done:
|
|
local_irq_restore(flags);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* seq_file wrappers for procfile show routines.
|
|
*/
|
|
static int udc_proc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, udc_proc_read, PDE_DATA(file_inode(file)));
|
|
}
|
|
|
|
static const struct file_operations udc_proc_fops = {
|
|
.open = udc_proc_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
#endif /* CONFIG_USB_GADGET_DEBUG_FILES */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static void udc_reinit (struct goku_udc *dev)
|
|
{
|
|
static char *names [] = { "ep0", "ep1-bulk", "ep2-bulk", "ep3-bulk" };
|
|
|
|
unsigned i;
|
|
|
|
INIT_LIST_HEAD (&dev->gadget.ep_list);
|
|
dev->gadget.ep0 = &dev->ep [0].ep;
|
|
dev->gadget.speed = USB_SPEED_UNKNOWN;
|
|
dev->ep0state = EP0_DISCONNECT;
|
|
dev->irqs = 0;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
struct goku_ep *ep = &dev->ep[i];
|
|
|
|
ep->num = i;
|
|
ep->ep.name = names[i];
|
|
ep->reg_fifo = &dev->regs->ep_fifo [i];
|
|
ep->reg_status = &dev->regs->ep_status [i];
|
|
ep->reg_mode = &dev->regs->ep_mode[i];
|
|
|
|
ep->ep.ops = &goku_ep_ops;
|
|
list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list);
|
|
ep->dev = dev;
|
|
INIT_LIST_HEAD (&ep->queue);
|
|
|
|
ep_reset(NULL, ep);
|
|
}
|
|
|
|
dev->ep[0].reg_mode = NULL;
|
|
usb_ep_set_maxpacket_limit(&dev->ep[0].ep, MAX_EP0_SIZE);
|
|
list_del_init (&dev->ep[0].ep.ep_list);
|
|
}
|
|
|
|
static void udc_reset(struct goku_udc *dev)
|
|
{
|
|
struct goku_udc_regs __iomem *regs = dev->regs;
|
|
|
|
writel(0, ®s->power_detect);
|
|
writel(0, ®s->int_enable);
|
|
readl(®s->int_enable);
|
|
dev->int_enable = 0;
|
|
|
|
/* deassert reset, leave USB D+ at hi-Z (no pullup)
|
|
* don't let INT_PWRDETECT sequence begin
|
|
*/
|
|
udelay(250);
|
|
writel(PW_RESETB, ®s->power_detect);
|
|
readl(®s->int_enable);
|
|
}
|
|
|
|
static void ep0_start(struct goku_udc *dev)
|
|
{
|
|
struct goku_udc_regs __iomem *regs = dev->regs;
|
|
unsigned i;
|
|
|
|
VDBG(dev, "%s\n", __func__);
|
|
|
|
udc_reset(dev);
|
|
udc_reinit (dev);
|
|
//writel(MST_EOPB_ENA | MST_TIMEOUT_ENA, ®s->dma_master);
|
|
|
|
/* hw handles set_address, set_feature, get_status; maybe more */
|
|
writel( G_REQMODE_SET_INTF | G_REQMODE_GET_INTF
|
|
| G_REQMODE_SET_CONF | G_REQMODE_GET_CONF
|
|
| G_REQMODE_GET_DESC
|
|
| G_REQMODE_CLEAR_FEAT
|
|
, ®s->reqmode);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
dev->ep[i].irqs = 0;
|
|
|
|
/* can't modify descriptors after writing UsbReady */
|
|
for (i = 0; i < DESC_LEN; i++)
|
|
writel(0, ®s->descriptors[i]);
|
|
writel(0, ®s->UsbReady);
|
|
|
|
/* expect ep0 requests when the host drops reset */
|
|
writel(PW_RESETB | PW_PULLUP, ®s->power_detect);
|
|
dev->int_enable = INT_DEVWIDE | INT_EP0;
|
|
writel(dev->int_enable, &dev->regs->int_enable);
|
|
readl(®s->int_enable);
|
|
dev->gadget.speed = USB_SPEED_FULL;
|
|
dev->ep0state = EP0_IDLE;
|
|
}
|
|
|
|
static void udc_enable(struct goku_udc *dev)
|
|
{
|
|
/* start enumeration now, or after power detect irq */
|
|
if (readl(&dev->regs->power_detect) & PW_DETECT)
|
|
ep0_start(dev);
|
|
else {
|
|
DBG(dev, "%s\n", __func__);
|
|
dev->int_enable = INT_PWRDETECT;
|
|
writel(dev->int_enable, &dev->regs->int_enable);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* keeping it simple:
|
|
* - one bus driver, initted first;
|
|
* - one function driver, initted second
|
|
*/
|
|
|
|
/* when a driver is successfully registered, it will receive
|
|
* control requests including set_configuration(), which enables
|
|
* non-control requests. then usb traffic follows until a
|
|
* disconnect is reported. then a host may connect again, or
|
|
* the driver might get unbound.
|
|
*/
|
|
static int goku_udc_start(struct usb_gadget *g,
|
|
struct usb_gadget_driver *driver)
|
|
{
|
|
struct goku_udc *dev = to_goku_udc(g);
|
|
|
|
/* hook up the driver */
|
|
driver->driver.bus = NULL;
|
|
dev->driver = driver;
|
|
|
|
/*
|
|
* then enable host detection and ep0; and we're ready
|
|
* for set_configuration as well as eventual disconnect.
|
|
*/
|
|
udc_enable(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void stop_activity(struct goku_udc *dev)
|
|
{
|
|
unsigned i;
|
|
|
|
DBG (dev, "%s\n", __func__);
|
|
|
|
/* disconnect gadget driver after quiesceing hw and the driver */
|
|
udc_reset (dev);
|
|
for (i = 0; i < 4; i++)
|
|
nuke(&dev->ep [i], -ESHUTDOWN);
|
|
|
|
if (dev->driver)
|
|
udc_enable(dev);
|
|
}
|
|
|
|
static int goku_udc_stop(struct usb_gadget *g,
|
|
struct usb_gadget_driver *driver)
|
|
{
|
|
struct goku_udc *dev = to_goku_udc(g);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->lock, flags);
|
|
dev->driver = NULL;
|
|
stop_activity(dev);
|
|
spin_unlock_irqrestore(&dev->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static void ep0_setup(struct goku_udc *dev)
|
|
{
|
|
struct goku_udc_regs __iomem *regs = dev->regs;
|
|
struct usb_ctrlrequest ctrl;
|
|
int tmp;
|
|
|
|
/* read SETUP packet and enter DATA stage */
|
|
ctrl.bRequestType = readl(®s->bRequestType);
|
|
ctrl.bRequest = readl(®s->bRequest);
|
|
ctrl.wValue = cpu_to_le16((readl(®s->wValueH) << 8)
|
|
| readl(®s->wValueL));
|
|
ctrl.wIndex = cpu_to_le16((readl(®s->wIndexH) << 8)
|
|
| readl(®s->wIndexL));
|
|
ctrl.wLength = cpu_to_le16((readl(®s->wLengthH) << 8)
|
|
| readl(®s->wLengthL));
|
|
writel(0, ®s->SetupRecv);
|
|
|
|
nuke(&dev->ep[0], 0);
|
|
dev->ep[0].stopped = 0;
|
|
if (likely(ctrl.bRequestType & USB_DIR_IN)) {
|
|
dev->ep[0].is_in = 1;
|
|
dev->ep0state = EP0_IN;
|
|
/* detect early status stages */
|
|
writel(ICONTROL_STATUSNAK, &dev->regs->IntControl);
|
|
} else {
|
|
dev->ep[0].is_in = 0;
|
|
dev->ep0state = EP0_OUT;
|
|
|
|
/* NOTE: CLEAR_FEATURE is done in software so that we can
|
|
* synchronize transfer restarts after bulk IN stalls. data
|
|
* won't even enter the fifo until the halt is cleared.
|
|
*/
|
|
switch (ctrl.bRequest) {
|
|
case USB_REQ_CLEAR_FEATURE:
|
|
switch (ctrl.bRequestType) {
|
|
case USB_RECIP_ENDPOINT:
|
|
tmp = le16_to_cpu(ctrl.wIndex) & 0x0f;
|
|
/* active endpoint */
|
|
if (tmp > 3 ||
|
|
(!dev->ep[tmp].ep.desc && tmp != 0))
|
|
goto stall;
|
|
if (ctrl.wIndex & cpu_to_le16(
|
|
USB_DIR_IN)) {
|
|
if (!dev->ep[tmp].is_in)
|
|
goto stall;
|
|
} else {
|
|
if (dev->ep[tmp].is_in)
|
|
goto stall;
|
|
}
|
|
if (ctrl.wValue != cpu_to_le16(
|
|
USB_ENDPOINT_HALT))
|
|
goto stall;
|
|
if (tmp)
|
|
goku_clear_halt(&dev->ep[tmp]);
|
|
succeed:
|
|
/* start ep0out status stage */
|
|
writel(~(1<<0), ®s->EOP);
|
|
dev->ep[0].stopped = 1;
|
|
dev->ep0state = EP0_STATUS;
|
|
return;
|
|
case USB_RECIP_DEVICE:
|
|
/* device remote wakeup: always clear */
|
|
if (ctrl.wValue != cpu_to_le16(1))
|
|
goto stall;
|
|
VDBG(dev, "clear dev remote wakeup\n");
|
|
goto succeed;
|
|
case USB_RECIP_INTERFACE:
|
|
goto stall;
|
|
default: /* pass to gadget driver */
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef USB_TRACE
|
|
VDBG(dev, "SETUP %02x.%02x v%04x i%04x l%04x\n",
|
|
ctrl.bRequestType, ctrl.bRequest,
|
|
le16_to_cpu(ctrl.wValue), le16_to_cpu(ctrl.wIndex),
|
|
le16_to_cpu(ctrl.wLength));
|
|
#endif
|
|
|
|
/* hw wants to know when we're configured (or not) */
|
|
dev->req_config = (ctrl.bRequest == USB_REQ_SET_CONFIGURATION
|
|
&& ctrl.bRequestType == USB_RECIP_DEVICE);
|
|
if (unlikely(dev->req_config))
|
|
dev->configured = (ctrl.wValue != cpu_to_le16(0));
|
|
|
|
/* delegate everything to the gadget driver.
|
|
* it may respond after this irq handler returns.
|
|
*/
|
|
spin_unlock (&dev->lock);
|
|
tmp = dev->driver->setup(&dev->gadget, &ctrl);
|
|
spin_lock (&dev->lock);
|
|
if (unlikely(tmp < 0)) {
|
|
stall:
|
|
#ifdef USB_TRACE
|
|
VDBG(dev, "req %02x.%02x protocol STALL; err %d\n",
|
|
ctrl.bRequestType, ctrl.bRequest, tmp);
|
|
#endif
|
|
command(regs, COMMAND_STALL, 0);
|
|
dev->ep[0].stopped = 1;
|
|
dev->ep0state = EP0_STALL;
|
|
}
|
|
|
|
/* expect at least one data or status stage irq */
|
|
}
|
|
|
|
#define ACK(irqbit) { \
|
|
stat &= ~irqbit; \
|
|
writel(~irqbit, ®s->int_status); \
|
|
handled = 1; \
|
|
}
|
|
|
|
static irqreturn_t goku_irq(int irq, void *_dev)
|
|
{
|
|
struct goku_udc *dev = _dev;
|
|
struct goku_udc_regs __iomem *regs = dev->regs;
|
|
struct goku_ep *ep;
|
|
u32 stat, handled = 0;
|
|
unsigned i, rescans = 5;
|
|
|
|
spin_lock(&dev->lock);
|
|
|
|
rescan:
|
|
stat = readl(®s->int_status) & dev->int_enable;
|
|
if (!stat)
|
|
goto done;
|
|
dev->irqs++;
|
|
|
|
/* device-wide irqs */
|
|
if (unlikely(stat & INT_DEVWIDE)) {
|
|
if (stat & INT_SYSERROR) {
|
|
ERROR(dev, "system error\n");
|
|
stop_activity(dev);
|
|
stat = 0;
|
|
handled = 1;
|
|
// FIXME have a neater way to prevent re-enumeration
|
|
dev->driver = NULL;
|
|
goto done;
|
|
}
|
|
if (stat & INT_PWRDETECT) {
|
|
writel(~stat, ®s->int_status);
|
|
if (readl(&dev->regs->power_detect) & PW_DETECT) {
|
|
VDBG(dev, "connect\n");
|
|
ep0_start(dev);
|
|
} else {
|
|
DBG(dev, "disconnect\n");
|
|
if (dev->gadget.speed == USB_SPEED_FULL)
|
|
stop_activity(dev);
|
|
dev->ep0state = EP0_DISCONNECT;
|
|
dev->int_enable = INT_DEVWIDE;
|
|
writel(dev->int_enable, &dev->regs->int_enable);
|
|
}
|
|
stat = 0;
|
|
handled = 1;
|
|
goto done;
|
|
}
|
|
if (stat & INT_SUSPEND) {
|
|
ACK(INT_SUSPEND);
|
|
if (readl(®s->ep_status[0]) & EPxSTATUS_SUSPEND) {
|
|
switch (dev->ep0state) {
|
|
case EP0_DISCONNECT:
|
|
case EP0_SUSPEND:
|
|
goto pm_next;
|
|
default:
|
|
break;
|
|
}
|
|
DBG(dev, "USB suspend\n");
|
|
dev->ep0state = EP0_SUSPEND;
|
|
if (dev->gadget.speed != USB_SPEED_UNKNOWN
|
|
&& dev->driver
|
|
&& dev->driver->suspend) {
|
|
spin_unlock(&dev->lock);
|
|
dev->driver->suspend(&dev->gadget);
|
|
spin_lock(&dev->lock);
|
|
}
|
|
} else {
|
|
if (dev->ep0state != EP0_SUSPEND) {
|
|
DBG(dev, "bogus USB resume %d\n",
|
|
dev->ep0state);
|
|
goto pm_next;
|
|
}
|
|
DBG(dev, "USB resume\n");
|
|
dev->ep0state = EP0_IDLE;
|
|
if (dev->gadget.speed != USB_SPEED_UNKNOWN
|
|
&& dev->driver
|
|
&& dev->driver->resume) {
|
|
spin_unlock(&dev->lock);
|
|
dev->driver->resume(&dev->gadget);
|
|
spin_lock(&dev->lock);
|
|
}
|
|
}
|
|
}
|
|
pm_next:
|
|
if (stat & INT_USBRESET) { /* hub reset done */
|
|
ACK(INT_USBRESET);
|
|
INFO(dev, "USB reset done, gadget %s\n",
|
|
dev->driver->driver.name);
|
|
}
|
|
// and INT_ERR on some endpoint's crc/bitstuff/... problem
|
|
}
|
|
|
|
/* progress ep0 setup, data, or status stages.
|
|
* no transition {EP0_STATUS, EP0_STALL} --> EP0_IDLE; saves irqs
|
|
*/
|
|
if (stat & INT_SETUP) {
|
|
ACK(INT_SETUP);
|
|
dev->ep[0].irqs++;
|
|
ep0_setup(dev);
|
|
}
|
|
if (stat & INT_STATUSNAK) {
|
|
ACK(INT_STATUSNAK|INT_ENDPOINT0);
|
|
if (dev->ep0state == EP0_IN) {
|
|
ep = &dev->ep[0];
|
|
ep->irqs++;
|
|
nuke(ep, 0);
|
|
writel(~(1<<0), ®s->EOP);
|
|
dev->ep0state = EP0_STATUS;
|
|
}
|
|
}
|
|
if (stat & INT_ENDPOINT0) {
|
|
ACK(INT_ENDPOINT0);
|
|
ep = &dev->ep[0];
|
|
ep->irqs++;
|
|
pio_advance(ep);
|
|
}
|
|
|
|
/* dma completion */
|
|
if (stat & INT_MSTRDEND) { /* IN */
|
|
ACK(INT_MSTRDEND);
|
|
ep = &dev->ep[UDC_MSTRD_ENDPOINT];
|
|
ep->irqs++;
|
|
dma_advance(dev, ep);
|
|
}
|
|
if (stat & INT_MSTWREND) { /* OUT */
|
|
ACK(INT_MSTWREND);
|
|
ep = &dev->ep[UDC_MSTWR_ENDPOINT];
|
|
ep->irqs++;
|
|
dma_advance(dev, ep);
|
|
}
|
|
if (stat & INT_MSTWRTMOUT) { /* OUT */
|
|
ACK(INT_MSTWRTMOUT);
|
|
ep = &dev->ep[UDC_MSTWR_ENDPOINT];
|
|
ep->irqs++;
|
|
ERROR(dev, "%s write timeout ?\n", ep->ep.name);
|
|
// reset dma? then dma_advance()
|
|
}
|
|
|
|
/* pio */
|
|
for (i = 1; i < 4; i++) {
|
|
u32 tmp = INT_EPxDATASET(i);
|
|
|
|
if (!(stat & tmp))
|
|
continue;
|
|
ep = &dev->ep[i];
|
|
pio_advance(ep);
|
|
if (list_empty (&ep->queue))
|
|
pio_irq_disable(dev, regs, i);
|
|
stat &= ~tmp;
|
|
handled = 1;
|
|
ep->irqs++;
|
|
}
|
|
|
|
if (rescans--)
|
|
goto rescan;
|
|
|
|
done:
|
|
(void)readl(®s->int_enable);
|
|
spin_unlock(&dev->lock);
|
|
if (stat)
|
|
DBG(dev, "unhandled irq status: %05x (%05x, %05x)\n", stat,
|
|
readl(®s->int_status), dev->int_enable);
|
|
return IRQ_RETVAL(handled);
|
|
}
|
|
|
|
#undef ACK
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static void gadget_release(struct device *_dev)
|
|
{
|
|
struct goku_udc *dev = dev_get_drvdata(_dev);
|
|
|
|
kfree(dev);
|
|
}
|
|
|
|
/* tear down the binding between this driver and the pci device */
|
|
|
|
static void goku_remove(struct pci_dev *pdev)
|
|
{
|
|
struct goku_udc *dev = pci_get_drvdata(pdev);
|
|
|
|
DBG(dev, "%s\n", __func__);
|
|
|
|
usb_del_gadget_udc(&dev->gadget);
|
|
|
|
BUG_ON(dev->driver);
|
|
|
|
#ifdef CONFIG_USB_GADGET_DEBUG_FILES
|
|
remove_proc_entry(proc_node_name, NULL);
|
|
#endif
|
|
if (dev->regs)
|
|
udc_reset(dev);
|
|
if (dev->got_irq)
|
|
free_irq(pdev->irq, dev);
|
|
if (dev->regs)
|
|
iounmap(dev->regs);
|
|
if (dev->got_region)
|
|
release_mem_region(pci_resource_start (pdev, 0),
|
|
pci_resource_len (pdev, 0));
|
|
if (dev->enabled)
|
|
pci_disable_device(pdev);
|
|
|
|
dev->regs = NULL;
|
|
|
|
INFO(dev, "unbind\n");
|
|
}
|
|
|
|
/* wrap this driver around the specified pci device, but
|
|
* don't respond over USB until a gadget driver binds to us.
|
|
*/
|
|
|
|
static int goku_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|
{
|
|
struct goku_udc *dev = NULL;
|
|
unsigned long resource, len;
|
|
void __iomem *base = NULL;
|
|
int retval;
|
|
|
|
if (!pdev->irq) {
|
|
printk(KERN_ERR "Check PCI %s IRQ setup!\n", pci_name(pdev));
|
|
retval = -ENODEV;
|
|
goto err;
|
|
}
|
|
|
|
/* alloc, and start init */
|
|
dev = kzalloc (sizeof *dev, GFP_KERNEL);
|
|
if (dev == NULL){
|
|
pr_debug("enomem %s\n", pci_name(pdev));
|
|
retval = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
spin_lock_init(&dev->lock);
|
|
dev->pdev = pdev;
|
|
dev->gadget.ops = &goku_ops;
|
|
dev->gadget.max_speed = USB_SPEED_FULL;
|
|
|
|
/* the "gadget" abstracts/virtualizes the controller */
|
|
dev->gadget.name = driver_name;
|
|
|
|
/* now all the pci goodies ... */
|
|
retval = pci_enable_device(pdev);
|
|
if (retval < 0) {
|
|
DBG(dev, "can't enable, %d\n", retval);
|
|
goto err;
|
|
}
|
|
dev->enabled = 1;
|
|
|
|
resource = pci_resource_start(pdev, 0);
|
|
len = pci_resource_len(pdev, 0);
|
|
if (!request_mem_region(resource, len, driver_name)) {
|
|
DBG(dev, "controller already in use\n");
|
|
retval = -EBUSY;
|
|
goto err;
|
|
}
|
|
dev->got_region = 1;
|
|
|
|
base = ioremap_nocache(resource, len);
|
|
if (base == NULL) {
|
|
DBG(dev, "can't map memory\n");
|
|
retval = -EFAULT;
|
|
goto err;
|
|
}
|
|
dev->regs = (struct goku_udc_regs __iomem *) base;
|
|
|
|
pci_set_drvdata(pdev, dev);
|
|
INFO(dev, "%s\n", driver_desc);
|
|
INFO(dev, "version: " DRIVER_VERSION " %s\n", dmastr());
|
|
INFO(dev, "irq %d, pci mem %p\n", pdev->irq, base);
|
|
|
|
/* init to known state, then setup irqs */
|
|
udc_reset(dev);
|
|
udc_reinit (dev);
|
|
if (request_irq(pdev->irq, goku_irq, IRQF_SHARED,
|
|
driver_name, dev) != 0) {
|
|
DBG(dev, "request interrupt %d failed\n", pdev->irq);
|
|
retval = -EBUSY;
|
|
goto err;
|
|
}
|
|
dev->got_irq = 1;
|
|
if (use_dma)
|
|
pci_set_master(pdev);
|
|
|
|
|
|
#ifdef CONFIG_USB_GADGET_DEBUG_FILES
|
|
proc_create_data(proc_node_name, 0, NULL, &udc_proc_fops, dev);
|
|
#endif
|
|
|
|
retval = usb_add_gadget_udc_release(&pdev->dev, &dev->gadget,
|
|
gadget_release);
|
|
if (retval)
|
|
goto err;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
if (dev)
|
|
goku_remove (pdev);
|
|
return retval;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static const struct pci_device_id pci_ids[] = { {
|
|
.class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe),
|
|
.class_mask = ~0,
|
|
.vendor = 0x102f, /* Toshiba */
|
|
.device = 0x0107, /* this UDC */
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
|
|
}, { /* end: all zeroes */ }
|
|
};
|
|
MODULE_DEVICE_TABLE (pci, pci_ids);
|
|
|
|
static struct pci_driver goku_pci_driver = {
|
|
.name = (char *) driver_name,
|
|
.id_table = pci_ids,
|
|
|
|
.probe = goku_probe,
|
|
.remove = goku_remove,
|
|
|
|
/* FIXME add power management support */
|
|
};
|
|
|
|
module_pci_driver(goku_pci_driver);
|