mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-11-25 17:43:09 +02:00
1404 lines
32 KiB
C
1404 lines
32 KiB
C
|
/*
|
||
|
* JZ4730 USB Device Controller driver
|
||
|
*
|
||
|
* 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 six bulk/interrupt/iso endpoints.
|
||
|
*
|
||
|
* - Endpoint numbering is fixed: ep0, ep1in-int, ep2in-bulk, ep3in-bulk,
|
||
|
* ep4in-iso, ep5out-bulk, ep6out-bulk, ep7out-iso.
|
||
|
* - Gadget drivers can choose ep maxpacket (8/16/32/64).
|
||
|
* - Just PIO mode currently.
|
||
|
*/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/ioport.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/list.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/proc_fs.h>
|
||
|
#include <linux/usb.h>
|
||
|
#include <linux/usb/gadget.h>
|
||
|
|
||
|
#include <asm/byteorder.h>
|
||
|
#include <asm/io.h>
|
||
|
#include <asm/irq.h>
|
||
|
#include <asm/system.h>
|
||
|
|
||
|
#include <asm-mips/mach-jz4730/regs.h>
|
||
|
#include <asm-mips/mach-jz4730/ops.h>
|
||
|
|
||
|
#include "jz4730_udc.h"
|
||
|
|
||
|
//#define DEBUG(fmt,args...) printk(KERN_DEBUG fmt , ## args)
|
||
|
//#define DEBUG_EP0(fmt,args...) printk(KERN_DEBUG fmt , ## args)
|
||
|
|
||
|
#ifndef DEBUG
|
||
|
# define DEBUG(fmt,args...) do {} while(0)
|
||
|
#endif
|
||
|
#ifndef DEBUG_EP0
|
||
|
# define DEBUG_EP0(fmt,args...) do {} while(0)
|
||
|
#endif
|
||
|
|
||
|
#define DRIVER_DESC "JZ4730 USB Device Controller"
|
||
|
#define DRIVER_VERSION "20 Sep 2007"
|
||
|
|
||
|
static const char driver_name [] = "jz4730_udc";
|
||
|
static const char driver_desc [] = DRIVER_DESC;
|
||
|
|
||
|
static unsigned int udc_debug = 0; /* 0: normal mode, 1: test udc cable type mode */
|
||
|
|
||
|
module_param(udc_debug, int, 0);
|
||
|
MODULE_PARM_DESC(udc_debug, "test udc cable type");
|
||
|
|
||
|
#ifdef CONFIG_JZ_UDC_HOTPLUG
|
||
|
extern int jz_udc_active; /* 0: No actions; 1: Have actions */
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Local declarations.
|
||
|
*/
|
||
|
static void nuke(struct jz4730_ep *, int status);
|
||
|
static inline void pio_irq_enable(struct jz4730_ep *ep);
|
||
|
static inline void pio_irq_disable(struct jz4730_ep *ep);
|
||
|
static void jz4730_udc_release (struct device *dev) {}
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
static int jz4730_ep_enable(struct usb_ep *_ep,
|
||
|
const struct usb_endpoint_descriptor *desc)
|
||
|
{
|
||
|
struct jz4730_udc *dev;
|
||
|
struct jz4730_ep *ep;
|
||
|
unsigned long flags;
|
||
|
u32 max;
|
||
|
|
||
|
ep = container_of(_ep, struct jz4730_ep, ep);
|
||
|
if (!_ep || !desc || 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->index != (desc->bEndpointAddress & 0x0f))
|
||
|
return -EINVAL;
|
||
|
|
||
|
switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) {
|
||
|
case USB_ENDPOINT_XFER_BULK:
|
||
|
case USB_ENDPOINT_XFER_INT:
|
||
|
break;
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
// max = le16_to_cpu(get_unaligned(&desc->wMaxPacketSize));
|
||
|
max = 64;
|
||
|
ep->is_in = (USB_DIR_IN & desc->bEndpointAddress) != 0;
|
||
|
|
||
|
spin_lock_irqsave(&ep->dev->lock, flags);
|
||
|
|
||
|
ep->stopped = 0;
|
||
|
ep->desc = desc;
|
||
|
ep->ep.maxpacket = max;
|
||
|
|
||
|
spin_unlock_irqrestore(&ep->dev->lock, flags);
|
||
|
|
||
|
DEBUG("enable %s %s maxpacket %u\n", ep->ep.name,
|
||
|
ep->is_in ? "IN" : "OUT", max);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int jz4730_ep_disable(struct usb_ep *_ep)
|
||
|
{
|
||
|
struct jz4730_ep *ep;
|
||
|
struct jz4730_udc *dev;
|
||
|
unsigned long flags;
|
||
|
|
||
|
ep = container_of(_ep, struct jz4730_ep, ep);
|
||
|
if (!_ep || !ep->desc)
|
||
|
return -ENODEV;
|
||
|
dev = ep->dev;
|
||
|
if (dev->ep0state == EP0_SUSPEND)
|
||
|
return -EBUSY;
|
||
|
|
||
|
DEBUG("disable %s\n", _ep->name);
|
||
|
|
||
|
spin_lock_irqsave(&dev->lock, flags);
|
||
|
|
||
|
/* Nuke all pending requests */
|
||
|
nuke(ep, -ESHUTDOWN);
|
||
|
|
||
|
/* Disable ep IRQ */
|
||
|
pio_irq_disable(ep);
|
||
|
|
||
|
ep->desc = 0;
|
||
|
ep->stopped = 1;
|
||
|
|
||
|
spin_unlock_irqrestore(&dev->lock, flags);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct usb_request *jz4730_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags)
|
||
|
{
|
||
|
struct jz4730_request *req;
|
||
|
|
||
|
req = kzalloc(sizeof *req, gfp_flags);
|
||
|
if (!req)
|
||
|
return 0;
|
||
|
|
||
|
INIT_LIST_HEAD(&req->queue);
|
||
|
return &req->req;
|
||
|
}
|
||
|
|
||
|
static void jz4730_free_request(struct usb_ep *_ep, struct usb_request *_req)
|
||
|
{
|
||
|
struct jz4730_request *req;
|
||
|
|
||
|
req = container_of(_req, struct jz4730_request, req);
|
||
|
WARN_ON(!list_empty(&req->queue));
|
||
|
kfree(req);
|
||
|
}
|
||
|
|
||
|
static void *jz4730_alloc_buffer(struct usb_ep *_ep, unsigned bytes,
|
||
|
dma_addr_t *dma, gfp_t gfp_flags)
|
||
|
{
|
||
|
void *retval;
|
||
|
|
||
|
retval = kmalloc(bytes, gfp_flags & ~(__GFP_DMA | __GFP_HIGHMEM));
|
||
|
if (retval)
|
||
|
*dma = virt_to_phys(retval);
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
static void jz4730_free_buffer(struct usb_ep *_ep, void *buf,
|
||
|
dma_addr_t dma, unsigned bytes)
|
||
|
{
|
||
|
kfree(buf);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* done - retire a request; caller blocked irqs
|
||
|
*/
|
||
|
static void done(struct jz4730_ep *ep, struct jz4730_request *req, int status)
|
||
|
{
|
||
|
struct jz4730_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;
|
||
|
|
||
|
if (status && status != -ESHUTDOWN)
|
||
|
DEBUG("complete %s req %p stat %d len %u/%u\n",
|
||
|
ep->ep.name, &req->req, status,
|
||
|
req->req.actual, req->req.length);
|
||
|
|
||
|
dev = ep->dev;
|
||
|
|
||
|
/* 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(struct jz4730_ep *ep,
|
||
|
struct jz4730_request *req, int max)
|
||
|
{
|
||
|
u8 *buf;
|
||
|
int length, nlong, nbyte;
|
||
|
volatile u32 *fifo = (volatile u32 *)ep->fifo;
|
||
|
|
||
|
buf = req->req.buf + req->req.actual;
|
||
|
prefetch(buf);
|
||
|
|
||
|
length = req->req.length - req->req.actual;
|
||
|
length = min(length, max);
|
||
|
req->req.actual += length;
|
||
|
|
||
|
DEBUG("Write %d (max %d), fifo %p\n", length, max, fifo);
|
||
|
|
||
|
if (!length) {
|
||
|
/* Send ZLP */
|
||
|
writel(0, (unsigned int *)UDC_TXZLP);
|
||
|
writel(0x12345678, (unsigned int *)fifo);
|
||
|
}
|
||
|
else {
|
||
|
nlong = length >> 2;
|
||
|
nbyte = length & 0x3;
|
||
|
while (nlong--) {
|
||
|
*fifo = *((u32 *)buf);
|
||
|
buf += 4;
|
||
|
}
|
||
|
while (nbyte--) {
|
||
|
*((volatile u8 *)fifo) = *buf++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
writel(0, (unsigned int *)UDC_TXCONFIRM);
|
||
|
|
||
|
return length;
|
||
|
}
|
||
|
|
||
|
static __inline__ int read_packet(struct jz4730_ep *ep,
|
||
|
struct jz4730_request *req, int count)
|
||
|
{
|
||
|
u8 *buf;
|
||
|
int length, nlong, nbyte;
|
||
|
volatile u32 *fifo = (volatile u32 *)ep->fifo;
|
||
|
|
||
|
buf = req->req.buf + req->req.actual;
|
||
|
prefetchw(buf);
|
||
|
|
||
|
length = req->req.length - req->req.actual;
|
||
|
length = min(length, count);
|
||
|
req->req.actual += length;
|
||
|
|
||
|
DEBUG("Read %d, fifo %p\n", length, fifo);
|
||
|
|
||
|
nlong = length >> 2;
|
||
|
nbyte = length & 0x3;
|
||
|
while (nlong--) {
|
||
|
*((u32 *)buf) = *fifo;
|
||
|
buf += 4;
|
||
|
}
|
||
|
if (nbyte) {
|
||
|
u32 data = *fifo;
|
||
|
while (nbyte--) {
|
||
|
*buf++ = data & 0x0ff;
|
||
|
data >>= 8;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
REG32(UDC_RXCONFIRM);
|
||
|
|
||
|
return length;
|
||
|
}
|
||
|
|
||
|
/** Write request to FIFO (max write == maxp size)
|
||
|
* Return: 0 = still running, 1 = completed, negative = errno
|
||
|
*/
|
||
|
static int write_fifo(struct jz4730_ep *ep, struct jz4730_request *req)
|
||
|
{
|
||
|
u32 max, count;
|
||
|
int is_last;
|
||
|
|
||
|
max = ep->ep.maxpacket;
|
||
|
|
||
|
count = write_packet(ep, req, max);
|
||
|
|
||
|
/* last packet often short (sometimes a zlp, especially on ep0) */
|
||
|
if (unlikely(count != max)) {
|
||
|
is_last = 1;
|
||
|
} else {
|
||
|
if (likely(req->req.length != req->req.actual)
|
||
|
|| req->req.zero)
|
||
|
is_last = 0;
|
||
|
else
|
||
|
is_last = 1;
|
||
|
}
|
||
|
|
||
|
DEBUG("write %s (%d)(IN) %d bytes%s req %p %d/%d is_last %d\n",
|
||
|
ep->ep.name, ep->index, count,
|
||
|
(count != ep->ep.maxpacket) ? " (short)" : "",
|
||
|
req, req->req.actual, req->req.length, is_last);
|
||
|
|
||
|
/* 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;
|
||
|
}
|
||
|
|
||
|
/** Read to request from FIFO (max read == bytes in fifo)
|
||
|
* Return: 0 = still running, 1 = completed, negative = errno
|
||
|
*/
|
||
|
static int read_fifo(struct jz4730_ep *ep, struct jz4730_request *req, u32 count)
|
||
|
{
|
||
|
int is_short;
|
||
|
|
||
|
is_short = (count < ep->ep.maxpacket);
|
||
|
|
||
|
count = read_packet(ep, req, count);
|
||
|
|
||
|
DEBUG("read %s %u bytes%s OUT req %p %u/%u is_short %d\n",
|
||
|
ep->ep.name, count, (count < ep->ep.maxpacket) ? "(short)" : "",
|
||
|
req, req->req.actual, req->req.length, is_short);
|
||
|
|
||
|
/* completion */
|
||
|
if (is_short || req->req.actual == req->req.length) {
|
||
|
done(ep, req, 0);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* finished that packet. the next one may be waiting... */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static inline void pio_irq_enable(struct jz4730_ep *ep)
|
||
|
{
|
||
|
switch (ep->index) {
|
||
|
case 0:
|
||
|
REG_UDC_EPIntMR &= ~0x1;
|
||
|
break;
|
||
|
case 1:
|
||
|
case 2:
|
||
|
case 3:
|
||
|
case 4:
|
||
|
REG_UDC_EPIntMR &= ~(1 << ep->index);
|
||
|
break;
|
||
|
case 5:
|
||
|
case 6:
|
||
|
case 7:
|
||
|
REG_UDC_EPIntMR &= ~(1 << (ep->index + 16));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void pio_irq_disable(struct jz4730_ep *ep)
|
||
|
{
|
||
|
switch (ep->index) {
|
||
|
case 0:
|
||
|
REG_UDC_EPIntMR |= 0x1;
|
||
|
break;
|
||
|
case 1:
|
||
|
case 2:
|
||
|
case 3:
|
||
|
case 4:
|
||
|
REG_UDC_EPIntMR |= (1 << ep->index);
|
||
|
break;
|
||
|
case 5:
|
||
|
case 6:
|
||
|
case 7:
|
||
|
REG_UDC_EPIntMR |= (1 << (ep->index + 16));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
static int
|
||
|
jz4730_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
|
||
|
{
|
||
|
struct jz4730_request *req;
|
||
|
struct jz4730_ep *ep;
|
||
|
struct jz4730_udc *dev;
|
||
|
unsigned long flags;
|
||
|
int status;
|
||
|
|
||
|
/* always require a cpu-view buffer so pio works */
|
||
|
req = container_of(_req, struct jz4730_request, req);
|
||
|
if (unlikely(!_req || !_req->complete
|
||
|
|| !_req->buf || !list_empty(&req->queue)))
|
||
|
return -EINVAL;
|
||
|
ep = container_of(_ep, struct jz4730_ep, ep);
|
||
|
if (unlikely(!_ep || (!ep->desc && ep->index != 0)))
|
||
|
return -EINVAL;
|
||
|
dev = ep->dev;
|
||
|
if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN))
|
||
|
return -ESHUTDOWN;
|
||
|
|
||
|
DEBUG("%s queue req %p, len %u buf %p\n",
|
||
|
_ep->name, _req, _req->length, _req->buf);
|
||
|
|
||
|
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->index == 0 && ep->is_in))
|
||
|
_req->zero = 1;
|
||
|
|
||
|
/* kickstart this i/o queue? */
|
||
|
status = 0;
|
||
|
if (list_empty(&ep->queue) && likely(!ep->stopped)) {
|
||
|
if (unlikely(ep->index == 0)) {
|
||
|
pio_irq_enable(ep);
|
||
|
if (ep->irq_pending ||
|
||
|
(REG_UDC_EPIntR & UDC_EPIntR_OUTEP0)) {
|
||
|
u32 stats, count;
|
||
|
|
||
|
stats = REG_UDC_EP0OutSR;
|
||
|
if (stats & UDC_EPSR_OUT_RCVDATA) {
|
||
|
ep->irq_pending = 0;
|
||
|
REG_UDC_EP0OutSR &= ~UDC_EPSR_OUT_MASK;
|
||
|
if (REG_UDC_EPIntR & UDC_EPIntR_OUTEP0)
|
||
|
REG_UDC_EPIntR = UDC_EPIntR_OUTEP0;
|
||
|
|
||
|
count = OUT_COUNT(stats);
|
||
|
if (read_fifo(ep, req, count) == 1)
|
||
|
req = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else if (ep->is_in) {
|
||
|
/* EP1 ~ EP4 */
|
||
|
if (ep->irq_pending ||
|
||
|
(REG_UDC_EPIntR & UDC_EPIntR_INEP2)) {
|
||
|
if (REG_UDC_EP2InSR & UDC_EPSR_IN) {
|
||
|
ep->irq_pending = 0;
|
||
|
REG_UDC_EP2InSR &= ~UDC_EPSR_IN;
|
||
|
if (REG_UDC_EPIntR & UDC_EPIntR_INEP2)
|
||
|
REG_UDC_EPIntR = UDC_EPIntR_INEP2;
|
||
|
|
||
|
if (write_fifo(ep, req) == 1)
|
||
|
req = 0;
|
||
|
}
|
||
|
}
|
||
|
pio_irq_enable(ep);
|
||
|
} else {
|
||
|
/* EP5 ~ EP7 */
|
||
|
pio_irq_enable(ep);
|
||
|
|
||
|
if (ep->irq_pending ||
|
||
|
(REG_UDC_EPIntR & UDC_EPIntR_OUTEP5)) {
|
||
|
u32 stats, count;
|
||
|
|
||
|
stats = REG_UDC_EP5OutSR;
|
||
|
if (stats & UDC_EPSR_OUT_RCVDATA) {
|
||
|
ep->irq_pending = 0;
|
||
|
REG_UDC_EP5OutSR &= ~UDC_EPSR_OUT_MASK;
|
||
|
if (REG_UDC_EPIntR & UDC_EPIntR_OUTEP5)
|
||
|
REG_UDC_EPIntR = UDC_EPIntR_OUTEP5;
|
||
|
|
||
|
count = OUT_COUNT(stats);
|
||
|
if (read_fifo(ep, req, count) == 1)
|
||
|
req = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* pio or dma irq handler advances the queue. */
|
||
|
if (likely(req != 0)) {
|
||
|
list_add_tail(&req->queue, &ep->queue);
|
||
|
}
|
||
|
|
||
|
spin_unlock_irqrestore(&dev->lock, flags);
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/* dequeue ALL requests */
|
||
|
static void nuke(struct jz4730_ep *ep, int status)
|
||
|
{
|
||
|
struct jz4730_request *req;
|
||
|
|
||
|
if (list_empty(&ep->queue))
|
||
|
return;
|
||
|
while (!list_empty(&ep->queue)) {
|
||
|
req = list_entry(ep->queue.next, struct jz4730_request, queue);
|
||
|
done(ep, req, status);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* dequeue JUST ONE request */
|
||
|
static int jz4730_dequeue(struct usb_ep *_ep, struct usb_request *_req)
|
||
|
{
|
||
|
struct jz4730_request *req;
|
||
|
struct jz4730_ep *ep;
|
||
|
struct jz4730_udc *dev;
|
||
|
unsigned long flags;
|
||
|
int stopped;
|
||
|
|
||
|
ep = container_of(_ep, struct jz4730_ep, ep);
|
||
|
if (!_ep || !_req || (!ep->desc && ep->index != 0))
|
||
|
return -EINVAL;
|
||
|
dev = ep->dev;
|
||
|
if (!dev->driver)
|
||
|
return -ESHUTDOWN;
|
||
|
|
||
|
DEBUG("%s %s %p\n", __FUNCTION__, _ep->name,_req);
|
||
|
|
||
|
spin_lock_irqsave(&dev->lock, flags);
|
||
|
stopped = ep->stopped;
|
||
|
ep->stopped = 1;
|
||
|
|
||
|
/* 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;
|
||
|
}
|
||
|
|
||
|
/* queue head may be partially complete. */
|
||
|
if (ep->queue.next == &req->queue) {
|
||
|
done (ep, req, -ECONNRESET);
|
||
|
req = 0;
|
||
|
}
|
||
|
|
||
|
if (req)
|
||
|
done (ep, req, -ECONNRESET);
|
||
|
ep->stopped = stopped;
|
||
|
|
||
|
spin_unlock_irqrestore (&ep->dev->lock, flags);
|
||
|
return req ? 0 : -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
static void jz4730_clear_halt(struct jz4730_ep *ep)
|
||
|
{
|
||
|
if (ep->stopped) {
|
||
|
ep->stopped = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int jz4730_set_halt(struct usb_ep *_ep, int value)
|
||
|
{
|
||
|
struct jz4730_ep *ep;
|
||
|
unsigned long flags;
|
||
|
int retval = 0;
|
||
|
|
||
|
if (!_ep)
|
||
|
return -ENODEV;
|
||
|
ep = container_of (_ep, struct jz4730_ep, ep);
|
||
|
if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN)
|
||
|
return -ESHUTDOWN;
|
||
|
if (ep->desc /* not ep0 */ && (ep->desc->bmAttributes & 0x03)
|
||
|
== USB_ENDPOINT_XFER_ISOC)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (ep->index == 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->desc) {
|
||
|
DEBUG("%s %s inactive?\n", __FUNCTION__, ep->ep.name);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave(&ep->dev->lock, flags);
|
||
|
|
||
|
if (!list_empty(&ep->queue))
|
||
|
retval = -EAGAIN;
|
||
|
else if (!value)
|
||
|
jz4730_clear_halt(ep);
|
||
|
else {
|
||
|
ep->stopped = 1;
|
||
|
}
|
||
|
|
||
|
spin_unlock_irqrestore(&ep->dev->lock, flags);
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
static int jz4730_fifo_status(struct usb_ep *_ep)
|
||
|
{
|
||
|
struct jz4730_ep *ep;
|
||
|
u32 size = 0;
|
||
|
|
||
|
if (!_ep)
|
||
|
return -ENODEV;
|
||
|
ep = container_of(_ep, struct jz4730_ep, ep);
|
||
|
|
||
|
/* size is only reported sanely for OUT */
|
||
|
if (ep->is_in)
|
||
|
return -EOPNOTSUPP;
|
||
|
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
static void jz4730_fifo_flush(struct usb_ep *_ep)
|
||
|
{
|
||
|
struct jz4730_ep *ep;
|
||
|
|
||
|
if (!_ep)
|
||
|
return;
|
||
|
ep = container_of(_ep, struct jz4730_ep, ep);
|
||
|
|
||
|
/* don't change EPxSTATUS_EP_INVALID to READY */
|
||
|
if (!ep->desc && ep->index != 0) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static struct usb_ep_ops jz4730_ep_ops = {
|
||
|
.enable = jz4730_ep_enable,
|
||
|
.disable = jz4730_ep_disable,
|
||
|
|
||
|
.alloc_request = jz4730_alloc_request,
|
||
|
.free_request = jz4730_free_request,
|
||
|
#if 0
|
||
|
.alloc_buffer = jz4730_alloc_buffer,
|
||
|
.free_buffer = jz4730_free_buffer,
|
||
|
#endif
|
||
|
.queue = jz4730_queue,
|
||
|
.dequeue = jz4730_dequeue,
|
||
|
|
||
|
.set_halt = jz4730_set_halt,
|
||
|
.fifo_status = jz4730_fifo_status,
|
||
|
.fifo_flush = jz4730_fifo_flush,
|
||
|
};
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
static int jz4730_get_frame(struct usb_gadget *_gadget)
|
||
|
{
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
static const struct usb_gadget_ops jz4730_ops = {
|
||
|
.get_frame = jz4730_get_frame,
|
||
|
// no remote wakeup
|
||
|
// not selfpowered
|
||
|
};
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
static void udc_reinit(struct jz4730_udc *dev)
|
||
|
{
|
||
|
static char *names [] = { "ep0", "ep1in-int", "ep2in-bulk", "ep3in-bulk",
|
||
|
"ep4in-iso", "ep5out-bulk", "ep6out-bulk",
|
||
|
"ep7out-iso" };
|
||
|
int 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;
|
||
|
|
||
|
for (i = 0; i < MAX_EP_NUM; i++) {
|
||
|
struct jz4730_ep *ep = &dev->ep[i];
|
||
|
|
||
|
ep->index = i;
|
||
|
ep->ep.name = names[i];
|
||
|
ep->fifo = ep_fifo[i];
|
||
|
ep->ep.maxpacket = 64;
|
||
|
|
||
|
ep->ep.ops = &jz4730_ep_ops;
|
||
|
list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list);
|
||
|
ep->dev = dev;
|
||
|
INIT_LIST_HEAD(&ep->queue);
|
||
|
|
||
|
ep->desc = 0;
|
||
|
ep->stopped = 1;
|
||
|
ep->irq_pending = 0;
|
||
|
}
|
||
|
|
||
|
dev->ep[0].ep.maxpacket = MAX_EP0_SIZE;
|
||
|
list_del_init(&dev->ep[0].ep.ep_list);
|
||
|
}
|
||
|
|
||
|
/* Reset udc registers */
|
||
|
static void udc_reset(struct jz4730_udc *dev)
|
||
|
{
|
||
|
REG_UDC_DevIntMR = 0x32; /* Enable RESET and SC interrupts */
|
||
|
REG_UDC_EPIntMR = 0x0; /* Enable all EP interrupts */
|
||
|
REG_UDC_DevCFGR = 0x17;
|
||
|
REG_UDC_DevCR = 0x0;
|
||
|
|
||
|
REG_UDC_EP0InCR = (0 << 4) | (1 << 1);
|
||
|
REG_UDC_EP0InCR = (0 << 4);
|
||
|
REG_UDC_EP1InCR = (3 << 4) | (1 << 1);
|
||
|
REG_UDC_EP1InCR = (3 << 4);
|
||
|
REG_UDC_EP2InCR = (2 << 4) | (1 << 1);
|
||
|
REG_UDC_EP2InCR = (2 << 4);
|
||
|
REG_UDC_EP3InCR = (2 << 4) | (1 << 1);
|
||
|
REG_UDC_EP3InCR = (2 << 4);
|
||
|
REG_UDC_EP4InCR = (1 << 4) | (1 << 1);
|
||
|
REG_UDC_EP4InCR = (1 << 4);
|
||
|
|
||
|
REG_UDC_EP0OutCR = (0 << 4);
|
||
|
REG_UDC_EP5OutCR = (2 << 4);
|
||
|
REG_UDC_EP6OutCR = (2 << 4);
|
||
|
REG_UDC_EP7OutCR = (1 << 4);
|
||
|
|
||
|
REG_UDC_EP0InSR = 0;
|
||
|
REG_UDC_EP1InSR = 0;
|
||
|
REG_UDC_EP2InSR = 0;
|
||
|
REG_UDC_EP3InSR = 0;
|
||
|
REG_UDC_EP4InSR = 0;
|
||
|
REG_UDC_EP5OutSR = 0;
|
||
|
REG_UDC_EP6OutSR = 0;
|
||
|
REG_UDC_EP7OutSR = 0;
|
||
|
|
||
|
REG_UDC_EP0InBSR = MAX_EP0_SIZE/4;
|
||
|
REG_UDC_EP1InBSR = MAX_EP1_SIZE/4;
|
||
|
REG_UDC_EP2InBSR = MAX_EP2_SIZE/4;
|
||
|
REG_UDC_EP3InBSR = MAX_EP3_SIZE/4;
|
||
|
REG_UDC_EP4InBSR = MAX_EP4_SIZE/4;
|
||
|
|
||
|
REG_UDC_EP0InMPSR = MAX_EP0_SIZE;
|
||
|
REG_UDC_EP1InMPSR = MAX_EP1_SIZE;
|
||
|
REG_UDC_EP2InMPSR = MAX_EP2_SIZE;
|
||
|
REG_UDC_EP3InMPSR = MAX_EP3_SIZE;
|
||
|
REG_UDC_EP4InMPSR = MAX_EP4_SIZE;
|
||
|
|
||
|
REG_UDC_EP0OutMPSR = MAX_EP0_SIZE;
|
||
|
REG_UDC_EP5OutMPSR = MAX_EP5_SIZE;
|
||
|
REG_UDC_EP6OutMPSR = MAX_EP6_SIZE;
|
||
|
REG_UDC_EP7OutMPSR = MAX_EP7_SIZE;
|
||
|
|
||
|
REG_UDC_EP0InfR = (MAX_EP0_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (0 << 5) | (0 << 4) | (0 << 0);
|
||
|
REG_UDC_EP1InfR = (MAX_EP1_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (3 << 5) | (1 << 4) | (1 << 0);
|
||
|
REG_UDC_EP2InfR = (MAX_EP2_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (2 << 5) | (1 << 4) | (2 << 0);
|
||
|
REG_UDC_EP3InfR = (MAX_EP3_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (2 << 5) | (1 << 4) | (3 << 0);
|
||
|
REG_UDC_EP4InfR = (MAX_EP4_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (1 << 5) | (1 << 4) | (4 << 0);
|
||
|
REG_UDC_EP5InfR = (MAX_EP5_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (2 << 5) | (0 << 4) | (5 << 0);
|
||
|
REG_UDC_EP6InfR = (MAX_EP6_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (2 << 5) | (0 << 4) | (6 << 0);
|
||
|
REG_UDC_EP7InfR = (MAX_EP7_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (1 << 5) | (0 << 4) | (7 << 0);
|
||
|
|
||
|
REG_UDC_STCMAR = 0xffff;
|
||
|
}
|
||
|
|
||
|
static void ep0_start(struct jz4730_udc *dev)
|
||
|
{
|
||
|
udc_reset(dev);
|
||
|
udc_reinit(dev);
|
||
|
|
||
|
/* expect ep0 requests when the host drops reset */
|
||
|
dev->gadget.speed = USB_SPEED_FULL;
|
||
|
dev->ep0state = EP0_IDLE;
|
||
|
}
|
||
|
|
||
|
static void udc_enable(struct jz4730_udc *dev)
|
||
|
{
|
||
|
/* Enable udc and enable all interrupts */
|
||
|
__intc_unmask_irq(IRQ_UDC);
|
||
|
__harb_usb0_udc();
|
||
|
|
||
|
/* start enumeration now, or after power detect irq */
|
||
|
ep0_start(dev);
|
||
|
}
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
/* keeping it simple:
|
||
|
* - one bus driver, initted first;
|
||
|
* - one function driver, initted second
|
||
|
*/
|
||
|
|
||
|
static struct jz4730_udc *the_controller;
|
||
|
|
||
|
/* 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.
|
||
|
*/
|
||
|
int usb_gadget_register_driver(struct usb_gadget_driver *driver)
|
||
|
{
|
||
|
struct jz4730_udc *dev = the_controller;
|
||
|
int retval;
|
||
|
|
||
|
if (!driver
|
||
|
// || driver->speed != USB_SPEED_FULL
|
||
|
|| !driver->bind
|
||
|
|| !driver->unbind
|
||
|
|| !driver->disconnect
|
||
|
|| !driver->setup)
|
||
|
{
|
||
|
printk("\n -EINVAL");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (!dev)
|
||
|
return -ENODEV;
|
||
|
|
||
|
if (dev->driver)
|
||
|
return -EBUSY;
|
||
|
|
||
|
/* hook up the driver */
|
||
|
dev->driver = driver;
|
||
|
retval = driver->bind(&dev->gadget);
|
||
|
if (retval) {
|
||
|
DEBUG("bind to driver %s --> error %d\n",
|
||
|
driver->driver.name, retval);
|
||
|
dev->driver = 0;
|
||
|
return retval;
|
||
|
}
|
||
|
/* then enable host detection and ep0; and we're ready
|
||
|
* for set_configuration as well as eventual disconnect.
|
||
|
*/
|
||
|
udc_enable(dev);
|
||
|
|
||
|
DEBUG("registered gadget driver '%s'\n", driver->driver.name);
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(usb_gadget_register_driver);
|
||
|
|
||
|
static void
|
||
|
stop_activity(struct jz4730_udc *dev, struct usb_gadget_driver *driver)
|
||
|
{
|
||
|
unsigned i;
|
||
|
|
||
|
DEBUG("%s\n", __FUNCTION__);
|
||
|
|
||
|
if (dev->gadget.speed == USB_SPEED_UNKNOWN)
|
||
|
driver = 0;
|
||
|
|
||
|
/* disconnect gadget driver after quiesceing hw and the driver */
|
||
|
udc_reset (dev);
|
||
|
for (i = 0; i < MAX_EP_NUM; i++)
|
||
|
nuke(&dev->ep [i], -ESHUTDOWN);
|
||
|
if (driver) {
|
||
|
spin_unlock(&dev->lock);
|
||
|
driver->disconnect(&dev->gadget);
|
||
|
spin_lock(&dev->lock);
|
||
|
}
|
||
|
|
||
|
if (dev->driver)
|
||
|
udc_enable(dev);
|
||
|
}
|
||
|
|
||
|
int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
|
||
|
{
|
||
|
struct jz4730_udc *dev = the_controller;
|
||
|
unsigned long flags;
|
||
|
|
||
|
/* disable UDC irq */
|
||
|
__intc_mask_irq(IRQ_UDC);
|
||
|
__harb_usb0_uhc();
|
||
|
|
||
|
if (!dev)
|
||
|
return -ENODEV;
|
||
|
if (!driver || driver != dev->driver)
|
||
|
return -EINVAL;
|
||
|
|
||
|
spin_lock_irqsave(&dev->lock, flags);
|
||
|
dev->driver = 0;
|
||
|
stop_activity(dev, driver);
|
||
|
spin_unlock_irqrestore(&dev->lock, flags);
|
||
|
|
||
|
driver->unbind(&dev->gadget);
|
||
|
|
||
|
DEBUG("unregistered driver '%s'\n", driver->driver.name);
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(usb_gadget_unregister_driver);
|
||
|
|
||
|
static void jz4730_epn_out(struct jz4730_udc *dev, int ep_idx, u32 count)
|
||
|
{
|
||
|
struct jz4730_request *req;
|
||
|
struct jz4730_ep *ep = &dev->ep[ep_idx];
|
||
|
|
||
|
req = list_entry(ep->queue.next, struct jz4730_request, queue);
|
||
|
read_fifo(ep, req, count);
|
||
|
}
|
||
|
|
||
|
static void jz4730_epn_in(struct jz4730_udc *dev, int ep_idx)
|
||
|
{
|
||
|
struct jz4730_request *req;
|
||
|
struct jz4730_ep *ep = &dev->ep[ep_idx];
|
||
|
|
||
|
req = list_entry(ep->queue.next, struct jz4730_request, queue);
|
||
|
write_fifo(ep, req);
|
||
|
}
|
||
|
|
||
|
/****************************************************************/
|
||
|
/* End Point 0 related functions */
|
||
|
/****************************************************************/
|
||
|
|
||
|
/* return: 0 = still running, 1 = completed, negative = errno */
|
||
|
static int write_fifo_ep0(struct jz4730_ep *ep, struct jz4730_request *req)
|
||
|
{
|
||
|
u32 max, count;
|
||
|
int is_last;
|
||
|
|
||
|
max = ep->ep.maxpacket;
|
||
|
|
||
|
count = write_packet(ep, req, max);
|
||
|
|
||
|
/* last packet is usually short (or a zlp) */
|
||
|
if (unlikely(count != max))
|
||
|
is_last = 1;
|
||
|
else {
|
||
|
if (likely(req->req.length != req->req.actual) || req->req.zero)
|
||
|
is_last = 0;
|
||
|
else
|
||
|
is_last = 1;
|
||
|
}
|
||
|
|
||
|
DEBUG_EP0("%s: wrote %s %d bytes%s %d left %p\n", __FUNCTION__,
|
||
|
ep->ep.name, count,
|
||
|
is_last ? "/L" : "", req->req.length - req->req.actual, req);
|
||
|
|
||
|
/* requests complete when all IN data is in the FIFO */
|
||
|
if (is_last) {
|
||
|
done(ep, req, 0);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Simulate a USB_REQ_SET_CONFIGURATION to the function driver,
|
||
|
* this is required to enable the endpoints of the function driver.
|
||
|
* UDC should let software have the chance to handle this standard
|
||
|
* request, unfortunately UDC can't do that.
|
||
|
*/
|
||
|
static void psudo_set_config(void)
|
||
|
{
|
||
|
struct jz4730_udc *dev = (struct jz4730_udc *) the_controller;
|
||
|
struct usb_ctrlrequest ctrl;
|
||
|
int tmp;
|
||
|
|
||
|
/* SETUP packet */
|
||
|
ctrl.bRequestType = 0x00;
|
||
|
ctrl.bRequest = USB_REQ_SET_CONFIGURATION;
|
||
|
ctrl.wValue = 1;
|
||
|
ctrl.wIndex = 0;
|
||
|
ctrl.wLength = 0;
|
||
|
|
||
|
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;
|
||
|
} else {
|
||
|
dev->ep[0].is_in = 0;
|
||
|
dev->ep0state = EP0_OUT;
|
||
|
}
|
||
|
|
||
|
/* 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)) {
|
||
|
DEBUG_EP0("req %02x.%02x protocol STALL; err %d\n",
|
||
|
ctrl.bRequestType, ctrl.bRequest, tmp);
|
||
|
dev->ep[0].stopped = 1;
|
||
|
dev->ep0state = EP0_STALL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Read 8 bytes setup packet from EP0 RX buffer
|
||
|
*/
|
||
|
static void read_setup_packet(u8 *buf)
|
||
|
{
|
||
|
u32 *tmp = (u32 *)buf;
|
||
|
|
||
|
*tmp++ = readl((unsigned int *)RXFIFO);
|
||
|
*tmp++ = readl((unsigned int *)RXFIFO);
|
||
|
|
||
|
REG32(UDC_RXCONFIRM);
|
||
|
}
|
||
|
|
||
|
static void jz4730_ep0_setup(struct jz4730_udc *dev)
|
||
|
{
|
||
|
struct jz4730_ep *ep = &dev->ep[0];
|
||
|
struct usb_ctrlrequest ctrl;
|
||
|
int tmp;
|
||
|
|
||
|
/* read control req from fifo (8 bytes) */
|
||
|
read_setup_packet((unsigned char *) &ctrl);
|
||
|
|
||
|
DEBUG_EP0("SETUP %02x.%02x v%04x i%04x l%04x\n",
|
||
|
ctrl.bRequestType, ctrl.bRequest,
|
||
|
ctrl.wValue, ctrl.wIndex, ctrl.wLength);
|
||
|
|
||
|
/* Set direction of EP0 */
|
||
|
if (likely(ctrl.bRequestType & USB_DIR_IN)) {
|
||
|
ep->is_in = 1;
|
||
|
dev->ep0state = EP0_IN;
|
||
|
} else {
|
||
|
ep->is_in = 0;
|
||
|
dev->ep0state = EP0_OUT;
|
||
|
}
|
||
|
|
||
|
/* Nuke all previous transfers */
|
||
|
nuke(ep, 0);
|
||
|
ep->stopped = 0;
|
||
|
|
||
|
/* delegate everything to the gadget driver.
|
||
|
* it may respond after this irq handler returns.
|
||
|
*/
|
||
|
if (likely((u32)dev->driver)) {
|
||
|
/* device-2-host (IN) or no data setup command, process immediately */
|
||
|
spin_unlock(&dev->lock);
|
||
|
tmp = dev->driver->setup(&dev->gadget, &ctrl);
|
||
|
spin_lock(&dev->lock);
|
||
|
|
||
|
if (unlikely(tmp < 0)) {
|
||
|
/* setup processing failed, force stall */
|
||
|
DEBUG_EP0("req %02x.%02x protocol STALL; err %d\n",
|
||
|
ctrl.bRequestType, ctrl.bRequest, tmp);
|
||
|
dev->ep0state = EP0_STALL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int jz4730_ep0_in(struct jz4730_udc *dev)
|
||
|
{
|
||
|
struct jz4730_request *req;
|
||
|
struct jz4730_ep *ep = &dev->ep[0];
|
||
|
int ret;
|
||
|
|
||
|
if (list_empty(&ep->queue))
|
||
|
req = 0;
|
||
|
else
|
||
|
req = list_entry(ep->queue.next, struct jz4730_request, queue);
|
||
|
|
||
|
if (!req) {
|
||
|
DEBUG_EP0("%s: NULL REQ\n", __FUNCTION__);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
ret = write_fifo_ep0(ep, req);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void jz4730_ep0_out(struct jz4730_udc *dev)
|
||
|
{
|
||
|
u32 epsr;
|
||
|
struct jz4730_ep *ep = &dev->ep[0];
|
||
|
|
||
|
epsr = REG_UDC_EP0OutSR;
|
||
|
REG_UDC_EP0OutSR &= ~UDC_EPSR_OUT_MASK;
|
||
|
|
||
|
if (epsr & UDC_EPSR_OUT_RCVSETUP) {
|
||
|
jz4730_ep0_setup(dev);
|
||
|
}
|
||
|
else if (epsr & UDC_EPSR_OUT_RCVDATA) {
|
||
|
u32 count = __udc_ep0out_packet_size();
|
||
|
if (count == 0) {
|
||
|
readl((unsigned int *)UDC_RXCONFIRM); // ack zero packet
|
||
|
}
|
||
|
else {
|
||
|
/* EP0 OUT Data */
|
||
|
if (list_empty(&ep->queue)) {
|
||
|
ep->irq_pending = 1;
|
||
|
pio_irq_disable(ep);
|
||
|
}
|
||
|
else
|
||
|
jz4730_epn_out(dev, 0, count);
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void handle_reset_irq(struct jz4730_udc *dev)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
/* clear any status */
|
||
|
REG_UDC_EPIntR = 0xffffffff;
|
||
|
REG_UDC_DevIntR = 0xffffffff;
|
||
|
|
||
|
/* reset udc */
|
||
|
udc_reset(dev);
|
||
|
|
||
|
/* reset driver status */
|
||
|
for (i = 0; i < MAX_EP_NUM; i++) {
|
||
|
struct jz4730_ep *ep = &dev->ep[i];
|
||
|
|
||
|
ep->irq_pending = 0;
|
||
|
// nuke(ep, 0);
|
||
|
nuke(ep, -ESHUTDOWN);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static irqreturn_t jz4730_udc_irq(int irq, void *_dev)
|
||
|
{
|
||
|
struct jz4730_udc *dev = _dev;
|
||
|
struct jz4730_ep *ep;
|
||
|
|
||
|
u32 intr_dev, intr_ep, stats, count;
|
||
|
|
||
|
spin_lock(&dev->lock);
|
||
|
|
||
|
intr_dev = REG_UDC_DevIntR;
|
||
|
intr_ep = REG_UDC_EPIntR;
|
||
|
|
||
|
DEBUG("*** udc irq intr_dev=0x%x intr_ep=0x%x\n", intr_dev, intr_ep);
|
||
|
|
||
|
if (!intr_dev && !intr_ep) {
|
||
|
spin_unlock(&dev->lock);
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
if (udc_debug) {
|
||
|
#ifdef CONFIG_JZ_UDC_HOTPLUG
|
||
|
jz_udc_active = 1;
|
||
|
#endif
|
||
|
REG_UDC_DevIntR = intr_dev;
|
||
|
REG_UDC_EPIntR = intr_ep;
|
||
|
__harb_usb0_uhc();
|
||
|
__intc_mask_irq(IRQ_UDC);
|
||
|
spin_unlock(&dev->lock);
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
if (intr_dev) {
|
||
|
if (intr_dev & UDC_DevIntR_SC) {
|
||
|
psudo_set_config();
|
||
|
udelay(100);
|
||
|
}
|
||
|
|
||
|
if (intr_dev & UDC_DevIntR_UR) {
|
||
|
#ifdef CONFIG_JZ_UDC_HOTPLUG
|
||
|
jz_udc_active = 1;
|
||
|
#endif
|
||
|
handle_reset_irq(dev);
|
||
|
}
|
||
|
|
||
|
REG_UDC_DevIntR = intr_dev;
|
||
|
}
|
||
|
|
||
|
if (intr_ep & UDC_EPIntR_OUTEP0) {
|
||
|
REG_UDC_EPIntR = UDC_EPIntR_OUTEP0;
|
||
|
jz4730_ep0_out(dev);
|
||
|
}
|
||
|
|
||
|
if (intr_ep & UDC_EPIntR_INEP0) {
|
||
|
ep = &dev->ep[0];
|
||
|
if (list_empty(&ep->queue)) {
|
||
|
pio_irq_disable(ep);
|
||
|
}
|
||
|
else {
|
||
|
stats = REG_UDC_EP0InSR;
|
||
|
if (stats & UDC_EPSR_IN) {
|
||
|
REG_UDC_EPIntR = UDC_EPIntR_INEP0;
|
||
|
REG_UDC_EP0InSR &= ~UDC_EPSR_IN;
|
||
|
|
||
|
jz4730_ep0_in(dev);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (intr_ep & UDC_EPIntR_OUTEP5) {
|
||
|
REG_UDC_EPIntR = UDC_EPIntR_OUTEP5;
|
||
|
ep = &dev->ep[5];
|
||
|
if (list_empty(&ep->queue)) {
|
||
|
ep->irq_pending = 1;
|
||
|
pio_irq_disable(ep);
|
||
|
}
|
||
|
else {
|
||
|
stats = REG_UDC_EP5OutSR;
|
||
|
if (stats & UDC_EPSR_OUT_RCVDATA) {
|
||
|
REG_UDC_EP5OutSR &= ~UDC_EPSR_OUT_MASK;
|
||
|
|
||
|
count = OUT_COUNT(stats);
|
||
|
jz4730_epn_out(dev, 5, count);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (intr_ep & UDC_EPIntR_INEP2) {
|
||
|
ep = &dev->ep[2];
|
||
|
if (list_empty(&ep->queue)) {
|
||
|
ep->irq_pending = 1;
|
||
|
pio_irq_disable(ep);
|
||
|
}
|
||
|
else {
|
||
|
stats = REG_UDC_EP2InSR;
|
||
|
if (stats & UDC_EPSR_IN) {
|
||
|
REG_UDC_EP2InSR &= ~UDC_EPSR_IN;
|
||
|
jz4730_epn_in(dev, 2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
REG_UDC_EPIntR = UDC_EPIntR_INEP2;
|
||
|
}
|
||
|
|
||
|
if (intr_ep & UDC_EPIntR_INEP1) {
|
||
|
ep = &dev->ep[1];
|
||
|
if (list_empty(&ep->queue)) {
|
||
|
ep->irq_pending = 1;
|
||
|
pio_irq_disable(ep);
|
||
|
}
|
||
|
else {
|
||
|
stats = REG_UDC_EP1InSR;
|
||
|
if (stats & UDC_EPSR_IN) {
|
||
|
REG_UDC_EP1InSR &= ~UDC_EPSR_IN;
|
||
|
jz4730_epn_in(dev, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
REG_UDC_EPIntR = UDC_EPIntR_INEP1;
|
||
|
}
|
||
|
|
||
|
spin_unlock(&dev->lock);
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
static struct jz4730_udc udc_dev = {
|
||
|
.usb_address = 0,
|
||
|
|
||
|
.gadget = {
|
||
|
.ops = &jz4730_ops,
|
||
|
.ep0 = &udc_dev.ep[0].ep,
|
||
|
.name = driver_name,
|
||
|
.dev = {
|
||
|
.bus_id = "gadget",
|
||
|
},
|
||
|
},
|
||
|
/* control endpoint no need to init here!*/
|
||
|
/* control endpoint */
|
||
|
};
|
||
|
|
||
|
|
||
|
/* tear down the binding between this driver and the pci device */
|
||
|
static int jz4730_udc_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct jz4730_udc *dev = platform_get_drvdata(pdev);
|
||
|
|
||
|
if (dev->driver)
|
||
|
return -EBUSY;
|
||
|
|
||
|
/* USB port0 as UHC */
|
||
|
__harb_usb0_uhc();
|
||
|
|
||
|
/* reset udc */
|
||
|
udc_reset(dev);
|
||
|
|
||
|
/* clear any status */
|
||
|
REG_UDC_EPIntR = 0xffffffff;
|
||
|
REG_UDC_DevIntR = 0xffffffff;
|
||
|
|
||
|
/* disable all UDC interrupts */
|
||
|
REG_UDC_DevIntMR = 0xffffffff;
|
||
|
REG_UDC_EPIntMR = 0xffffffff;
|
||
|
|
||
|
free_irq(IRQ_UDC, dev);
|
||
|
platform_set_drvdata(pdev, 0);
|
||
|
device_unregister(&dev->gadget.dev);
|
||
|
the_controller = 0;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int jz4730_udc_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct jz4730_udc *dev = &udc_dev;
|
||
|
int retval,rc;
|
||
|
|
||
|
/* if you want to support more than one controller in a system,
|
||
|
* usb_gadget_driver_{register,unregister}() must change.
|
||
|
*/
|
||
|
if (the_controller) {
|
||
|
printk("Check the_controller: %s\n", driver_name);
|
||
|
return -EBUSY;
|
||
|
}
|
||
|
|
||
|
spin_lock_init(&dev->lock);
|
||
|
device_initialize(&dev->gadget.dev);
|
||
|
dev->gadget.dev.parent = &pdev->dev; //if no,can only insmod once!!
|
||
|
dev->gadget.dev.release = jz4730_udc_release;
|
||
|
rc = device_register (&dev->gadget.dev);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
platform_set_drvdata(pdev, dev);
|
||
|
|
||
|
/*
|
||
|
* Note: we just mask INTC irq but allow UDC irq.
|
||
|
* This avoid that we miss any UDC irqs.
|
||
|
*/
|
||
|
|
||
|
/* To avoid any UDC irqs here, we call cli() first */
|
||
|
// cli();
|
||
|
|
||
|
/* disable INTC irq */
|
||
|
__intc_mask_irq(IRQ_UDC);
|
||
|
|
||
|
/* init to known state, then setup irqs */
|
||
|
udc_reset(dev);
|
||
|
udc_reinit(dev);
|
||
|
|
||
|
/* request UDC irq */
|
||
|
if (request_irq(IRQ_UDC, jz4730_udc_irq, IRQF_DISABLED, // SA_INTERRUPT,
|
||
|
driver_name, dev) != 0) {
|
||
|
printk(KERN_INFO "request UDC interrupt %d failed\n", IRQ_UDC);
|
||
|
retval = -EBUSY;
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
/* disable INTC irq again since request_irq has enabled it */
|
||
|
__intc_mask_irq(IRQ_UDC);
|
||
|
__intc_ack_irq(IRQ_UDC);
|
||
|
|
||
|
/* Re-enable irqs */
|
||
|
// sti();
|
||
|
|
||
|
printk(KERN_INFO "%s\n", driver_desc);
|
||
|
printk(KERN_INFO "version: " DRIVER_VERSION "\n");
|
||
|
|
||
|
/* done */
|
||
|
the_controller = dev;
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
done:
|
||
|
if (dev)
|
||
|
jz4730_udc_remove (pdev);
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
static struct platform_driver udc_driver = {
|
||
|
.probe = jz4730_udc_probe,
|
||
|
.remove = jz4730_udc_remove,
|
||
|
.suspend = NULL,
|
||
|
.resume = NULL,
|
||
|
.driver = {
|
||
|
.name = (char *) driver_name,
|
||
|
.owner = THIS_MODULE,
|
||
|
},
|
||
|
};
|
||
|
static struct platform_device the_udc_pdev = {
|
||
|
.name = (char *) driver_name,
|
||
|
.id = -1,
|
||
|
.dev = {
|
||
|
.release = jz4730_udc_release,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
static int __init udc_init (void)
|
||
|
{
|
||
|
platform_driver_register(&udc_driver);
|
||
|
return platform_device_register (&the_udc_pdev);
|
||
|
}
|
||
|
|
||
|
static void __exit udc_exit (void)
|
||
|
{
|
||
|
platform_driver_unregister(&udc_driver);
|
||
|
platform_device_unregister(&the_udc_pdev);
|
||
|
}
|
||
|
|
||
|
module_init(udc_init);
|
||
|
module_exit(udc_exit);
|
||
|
|
||
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
||
|
MODULE_AUTHOR("Wei Jianli <jlwei@ingenic.cn>");
|
||
|
MODULE_LICENSE("GPL");
|