📄 n9604.c
字号:
/* * National 9603/4 USB Device Controller driver * Copyright (C) 2004 Technical Solutions Inc. (support@techsol.ca) * ported from : The Goku-S driver * 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 six semi-configurable bulk/interrupt endpoints. * * - Endpoint numbering is fixed: * Endpoint 0: ep0 * Endpoint 1: ep1in (tx) * Endpoint 2: ep2out (rx) * Endpoint 3: ep3in (tx) * Endpoint 4: ep4out (rx) * Endpoint 5: ep5in (tx) * Endpoint 6: ep6out (rx) *//* * The ep->stage information refers to the state of a setup transaction * * state 0: no setup packet has been received * state 1: setup packet has been received * state 2: data has been sent/received * state 3: ZLP has been received/sent */#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/usb_ch9.h>#include <linux/usb_gadget.h>#include <asm/byteorder.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include <asm/unaligned.h>#include "n9604.h"#include "n9604regs.h"inline void Flush_and_enable(u8 control_reg) { write_9604(RXC_FLUSH, control_reg); while (read_9604(control_reg) & RXC_FLUSH); write_9604(RXC_RX_EN, control_reg);}inline void Flush(u8 control_reg) { write_9604(RXC_FLUSH, control_reg); while (read_9604(control_reg) & RXC_FLUSH);}#define DRIVER_DESC "N9604 USB Device Controller"#define DRIVER_VERSION "29-Oct 2004"static const char driver_name [] = "n9604_udc";static const char driver_desc [] = DRIVER_DESC;MODULE_AUTHOR("support@techsol.ca");MODULE_DESCRIPTION(DRIVER_DESC);MODULE_LICENSE("GPL");static void nuke(struct n9604_ep *ep, int status);inline void send_zero_length(int endpoint, struct n9604_udc *dev);u8 * USBN9604_Offset; //device virtual address/* FIXME all the IRQ stuff is board-specific */#define h7201_readl readl#define h7201_writel writel#define ETHER_IRQ_IP_OFFSET 0#define ETHER_IRQ_BIT_POS 0#define ETHER_IRQ_IM_OFFSET 0#define IRQ_GPIOC -1#define USBD_ENABLE_IRQ {h7201_writel( h7201_readl(ETHER_IRQ_IP_OFFSET) | (1 << ETHER_IRQ_BIT_POS), ETHER_IRQ_IP_OFFSET); h7201_writel( h7201_readl(ETHER_IRQ_IM_OFFSET) | (1 << ETHER_IRQ_BIT_POS), ETHER_IRQ_IM_OFFSET);}#define USBD_DISABLE_IRQ h7201_writel( h7201_readl(ETHER_IRQ_IM_OFFSET) & ~(1 << ETHER_IRQ_BIT_POS), ETHER_IRQ_IM_OFFSET);/*-------------------------------------------------------------------------*///enable an end point, of description descstatic int n9604_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) { struct n9604_udc *dev; struct n9604_ep *ep; u16 max; ep = container_of(_ep, struct n9604_ep, ep); if (!_ep || !desc || ep->desc || desc->bDescriptorType != USB_DT_ENDPOINT) return -EINVAL; dev = ep->dev; if (!ep->num) return -EINVAL; if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) return -ESHUTDOWN; if (ep->num && !(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; } write_9604((ep->numActual & EPC_EP_MASK) | EPC_EP_EN | (EPC_ISO * ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_ISOC)), ep->control); if (ep->is_in) Flush(ep->command); else Flush_and_enable(ep->command); max = le16_to_cpu(get_unaligned(&desc->wMaxPacketSize)); ep->ep.maxpacket = min_t(u16, max, MAX_FIFO_SIZE); ep->desc = desc; return 0;}static int n9604_ep_disable(struct usb_ep *_ep)//ep > 0{ struct n9604_ep *ep; struct n9604_udc *dev; unsigned long flags; ep = container_of(_ep, struct n9604_ep, ep); if (!_ep || !ep->desc) return -ENODEV; dev = ep->dev; spin_lock_irqsave(&dev->lock, flags); nuke(ep, -ESHUTDOWN); write_9604(0, ep->command); ep->desc = NULL; spin_unlock_irqrestore(&dev->lock, flags); return 0;}/*-------------------------------------------------------------------------*/static struct usb_request *n9604_alloc_request(struct usb_ep *_ep, int gfp_flags){ struct n9604_request *req; if (!_ep) return 0; req = kmalloc(sizeof *req, gfp_flags); if (!req) return 0; memset(req, 0, sizeof *req); INIT_LIST_HEAD(&req->queue); return &req->req;}static voidn9604_free_request(struct usb_ep *_ep, struct usb_request *_req){ struct n9604_request *req; if (!_ep || !_req) return; req = container_of(_req, struct n9604_request, req); WARN_ON(!list_empty(&req->queue)); kfree(req);}/*-------------------------------------------------------------------------*/static void done(struct n9604_ep *ep, struct n9604_request *req, int status);static inline intwrite_packet(struct n9604_ep *ep, u8 *buf, struct n9604_request *req){ unsigned written_length, desired_length, available_length, maximum_length, flags, loop_length; u8 fifo = ep->fifo; u8 command = ep->command; u8 status = ep->status; if (!ep->num) { fifo = TXD0; command = TXC0; status = TXS0; } if (read_9604(command) & TXC_TX_EN) return -EBUSY; ep->packets++; desired_length = req->req.length - req->req.actual; available_length = read_9604(status) & TXS_TCOUNT_MASK;//might be greater written_length = 0; if (ep->num) maximum_length = MAX_FIFO_SIZE; else maximum_length = MAX_EP0_SIZE; while ((loop_length = min(min(available_length, desired_length), maximum_length))) { int i = loop_length; while (i) { write_9604(*buf++,fifo); i--; } written_length += loop_length; desired_length -= loop_length; maximum_length -= loop_length; if (desired_length && maximum_length)//check if really need to read the chip again available_length = (read_9604(status) & TXS_TCOUNT_MASK); } req->req.actual += written_length; flags = TXC_TX_EN; if (ep->num) flags |= TXC_LAST; if (ep->toggle) flags |= TXC_TOGGLE; write_9604(flags, command); ep->toggle = !(ep->toggle); if (!written_length) req->req.zero = 0;//just wrote zero bytes, there is no more need for req.zero return written_length;} // return: 0 = still running, 1 = completed, negative = errnostatic int write_fifo(struct n9604_ep *ep, struct n9604_request *req){ struct n9604_udc *dev = ep->dev; u8 *buf; unsigned count; int is_last; buf = req->req.buf + req->req.actual; prefetch(buf); dev = ep->dev; count = write_packet(ep, buf, req); if (count < 0) return count; /* last packet often short (sometimes a zlp, especially on ep0) */ if ((req->req.length != req->req.actual) || req->req.zero) is_last = 0; else is_last = 1; /* 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 inline void pio_irq_enable(struct n9604_ep *ep);static int read_fifo(struct n9604_ep *ep, struct n9604_request *req){ u32 size; u8 *buf; int bufferspace_available, fifospace_left, num_bytes_read; int fifo, status; ep->packets++; if (!ep->num) { fifo = RXD0; status = RXS0; } else { fifo = ep->fifo; status = ep->status; } num_bytes_read = 0; buf = req->req.buf + req->req.actual; bufferspace_available = req->req.length - req->req.actual; size = read_9604(status) & (RXS_RCOUNTMASK | RXS_RX_ERR);//number of bytes ready to be read (15 if greater than 15) if (ep->num && (size & RXS_RX_ERR)) { ERROR(ep->dev, "DATA ERROR!!!! on ep%d\nFlushing Fifo", ep->num); Flush_and_enable(ep->command); goto leave; } size = size & ~RXS_RX_ERR;//clear the bit if (ep->num) fifospace_left = MAX_FIFO_SIZE; else fifospace_left = MAX_EP0_SIZE;loop: /* read all bytes from this packet */ while (size-- != 0) { u8 byte = read_9604(fifo); if (unlikely(bufferspace_available == 0)) { /* this happens when the driver's buffer * is smaller than what the host sent. * discard the extra data in this packet. */ done(ep, req, -EOVERFLOW); return 1; } else { *buf++ = byte; bufferspace_available--; fifospace_left--; num_bytes_read++; } } if ((size = (read_9604(status) & RXS_RCOUNTMASK))) { goto loop;//since there is more data } /* completion */ req->req.actual = req->req.actual + num_bytes_read; if (fifospace_left || req->req.actual == req->req.length) { done(ep, req, 0); return 1; }leave: pio_irq_enable(ep);//turn the interrupt back on return 0;}/*-------------------------------------------------------------------------*/static inline voidpio_irq_enable(struct n9604_ep *ep){ if (ep->is_in) write_9604(read_9604(TXMSK) | 1 << ep->fifoNum | 0x10 << ep->fifoNum, TXMSK); else { u8 command = ep->command; if (!ep->num) command = RXC0; write_9604(read_9604(RXMSK) | 1 << ep->fifoNum | 0x10 << ep->fifoNum, RXMSK); write_9604(RXC_RX_EN | RXC_RFWL0 | RXC_RFWL1, command); }}static inline voidpio_irq_disable(struct n9604_ep *ep)//epnum != 0{ if (ep->is_in) write_9604(read_9604(TXMSK) & ~(1 << ep->fifoNum) & ~(0x10 << ep->fifoNum), TXMSK); else write_9604(read_9604(RXMSK) & ~(1 << ep->fifoNum) & ~(0x10 << ep->fifoNum), RXMSK);}static int request_voodoo = 0;//number of bytes the host requestedstatic inline voidpio_advance(struct n9604_ep *ep){ struct n9604_request *req; if (list_empty (&ep->queue)) { if (!ep->num) { if (ep->is_in && (ep->stage == 2)) { ep->is_in = 0;//switch modes Flush_and_enable(RXC0);//needed to receive a ZLP after tx ep->stage++;//and bump the stage number } else if (ep->stage == 3) { ep->stage = 0; } } return; } req = list_entry(ep->queue.next, struct n9604_request, queue); (ep->is_in ? write_fifo : read_fifo)(ep, req);}/*-------------------------------------------------------------------------*/static void * n9604_alloc_buffer(struct usb_ep *_ep, unsigned bytes, dma_addr_t *dma, int gfp_flags){ return kmalloc(bytes, gfp_flags);}static void n9604_free_buffer(struct usb_ep *_ep, void *buf, dma_addr_t dma, unsigned bytes){ kfree (buf);}/*-------------------------------------------------------------------------*/static voiddone(struct n9604_ep *ep, struct n9604_request *req, int status){ struct n9604_udc *dev; list_del_init(&req->queue); ep->queue_active--; if (req->req.status == -EINPROGRESS) req->req.status = status; else status = req->req.status; dev = ep->dev; /* don't modify queue heads during completion callback */ if (ep->num) pio_irq_disable(ep); else if (!ep->nuking) { ep->stage++; ep->toggle = 1;//other endpoints stay in their flipping mode between transactions if (ep->stage == 2) {//we are in stage 2 now if (!ep->is_in) { ep->is_in = 1;//switch modes request_voodoo = 1;//prevents n9604_queue from calling us again before doing anything send_zero_length(0, dev); } else {//we have to receive a ZLP //this will happen when the tx is complete, the pio_advance fcn will activate it for us } } } req->req.complete(&ep->ep, &req->req);}/*-------------------------------------------------------------------------*/static intn9604_queue(struct usb_ep *_ep, struct usb_request *_req, int gfp_flags){ struct n9604_request *req; struct n9604_ep *ep; struct n9604_udc *dev; unsigned long flags; int status; req = container_of(_req, struct n9604_request, req); if (unlikely(!_req || !_req->complete || !_req->buf || !list_empty(&req->queue))) return -EINVAL; ep = container_of(_ep, struct n9604_ep, ep); if (unlikely(!_ep || (!ep->desc && ep->num != 0))) return -EINVAL; dev = ep->dev; if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { return -ESHUTDOWN; } if (ep->nuking) return -ESHUTDOWN; spin_lock_irqsave(&dev->lock, flags); ep->queue_reqs++; ep->queue_active++; _req->status = -EINPROGRESS; _req->actual = 0; /* for ep0 IN without premature status, zlp is required and * writing EOP starts the status stage (OUT). */ if (ep->num == 0) { if ((request_voodoo > _req->length) && !(_req->length % MAX_EP0_SIZE) && (_req->length != 0)) { _req->zero = 1; } if (!request_voodoo && !ep->is_in) {//this is a zero length request spin_unlock_irqrestore(&dev->lock, flags);//David done(ep, req, 0);//this doesn't check if the list is empty (probably not an issue) return 0; //shouldn't this be handled by the rx irq fcn, and passed to pio_advance }//that may conflict with the voodoo stuff, maybe best to leave it } /* kickstart this i/o queue? */ status = 0; if (list_empty(&ep->queue) && ep->is_in) { status = write_fifo(ep, req); if (status == -EBUSY) ;//we should queue up the request then else { if (status != 0) { if (status > 0) status = 0; req = 0; } } } /* else pio or dma irq handler advances the queue. */ if (req != 0) { list_add_tail(&req->queue, &ep->queue); pio_irq_enable(ep); } spin_unlock_irqrestore(&dev->lock, flags); return status;}/* dequeue ALL requests */static void nuke(struct n9604_ep *ep, int status){ struct n9604_request *req; if (list_empty(&ep->queue)) return; ep->nuking = 1; while (!list_empty(&ep->queue)) { req = list_entry(ep->queue.next, struct n9604_request, queue); done(ep, req, status); } ep->nuking = 0;}/* dequeue JUST ONE request */static int n9604_dequeue(struct usb_ep *_ep, struct usb_request *_req){ struct n9604_request *req; struct n9604_ep *ep; struct n9604_udc *dev; unsigned long flags; ep = container_of(_ep, struct n9604_ep, ep); if (!_ep || !_req || (!ep->desc && ep->num != 0)) return -EINVAL; dev = ep->dev; if (!dev->driver) return -ESHUTDOWN; spin_lock_irqsave(&dev->lock, flags); /* make sure it's actually queued on this endpoint */ list_for_each_entry (req, &ep->queue, queue) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -