📄 goku_udc.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). */#undef DEBUG// #define VERBOSE /* extra debug messages (success too) */// #define USB_TRACE /* packet-level success messages */#include <linux/config.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/pci.h>#include <linux/delay.h>#include <linux/ioport.h>#include <linux/sched.h>#include <linux/slab.h>#include <linux/smp_lock.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/timer.h>#include <linux/list.h>#include <linux/interrupt.h>#include <linux/proc_fs.h>#include <linux/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 "goku_udc.h"#define DRIVER_DESC "TC86C001 USB Device Controller"#define DRIVER_VERSION "30-Oct 2003"#define DMA_ADDR_INVALID (~(dma_addr_t)0)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 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 voidcommand(struct goku_udc_regs *regs, int command, unsigned epnum){ writel(COMMAND_EP(epnum) | command, ®s->Command); udelay(300);}static intgoku_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 || 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 != (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; } 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 = le16_to_cpu(get_unaligned(&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_DIR_IN & desc->bEndpointAddress) != 0; 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 *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->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 *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 *regs = ep->dev->regs; u32 tmp; tmp = readl(®s->EPxSingle); tmp &= ~(0x11 << ep->num); writel(tmp, ®s->EPxSingle); tmp = readl(®s->EPxBCS); tmp &= ~(0x11 << ep->num); writel(tmp, ®s->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); } } ep->ep.maxpacket = MAX_FIFO_SIZE; ep->desc = 0; 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->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, int gfp_flags){ struct goku_request *req; if (!_ep) return 0; req = kmalloc(sizeof *req, gfp_flags); if (!req) return 0; memset(req, 0, sizeof *req); req->req.dma = DMA_ADDR_INVALID; INIT_LIST_HEAD(&req->queue); return &req->req;}static voidgoku_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);}/*-------------------------------------------------------------------------*/#undef USE_KMALLOC/* many common platforms have dma-coherent caches, which means that it's * safe to use kmalloc() memory for all i/o buffers without using any * cache flushing calls. (unless you're trying to share cache lines * between dma and non-dma activities, which is a slow idea in any case.) * * other platforms need more care, with 2.6 having a moderately general * solution except for the common "buffer is smaller than a page" case. */#if defined(CONFIG_X86)#define USE_KMALLOC#elif defined(CONFIG_MIPS) && !defined(CONFIG_NONCOHERENT_IO)#define USE_KMALLOC#elif defined(CONFIG_PPC) && !defined(CONFIG_NOT_COHERENT_CACHE)#define USE_KMALLOC#endif/* allocating buffers this way eliminates dma mapping overhead, which * on some platforms will mean eliminating a per-io buffer copy. with * some kinds of system caches, further tweaks may still be needed. */static void *goku_alloc_buffer(struct usb_ep *_ep, unsigned bytes, dma_addr_t *dma, int gfp_flags){ void *retval; struct goku_ep *ep; ep = container_of(_ep, struct goku_ep, ep); if (!_ep) return 0; *dma = DMA_ADDR_INVALID;#if defined(USE_KMALLOC) retval = kmalloc(bytes, gfp_flags); if (retval) *dma = virt_to_phys(retval);#else if (ep->dma) { /* one problem with this call is that it wastes memory on * typical 1/N page allocations: it allocates 1-N pages. * another is that it always uses GFP_ATOMIC. */#warning Using pci_alloc_consistent even with buffers smaller than a page. retval = pci_alloc_consistent(ep->dev->pdev, bytes, dma); } else retval = kmalloc(bytes, gfp_flags);#endif return retval;}static voidgoku_free_buffer(struct usb_ep *_ep, void *buf, dma_addr_t dma, unsigned bytes){ /* free memory into the right allocator */#ifndef USE_KMALLOC if (dma != DMA_ADDR_INVALID) { struct goku_ep *ep; ep = container_of(_ep, struct goku_ep, ep); if (!_ep) return; /* one problem with this call is that some platforms * don't allow it to be used in_irq(). */ pci_free_consistent(ep->dev->pdev, bytes, buf, dma); } else#endif kfree (buf);}/*-------------------------------------------------------------------------*/static voiddone(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 (req->mapped) { pci_unmap_single(dev->pdev, req->req.dma, req->req.length, ep->is_in ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); req->req.dma = DMA_ADDR_INVALID; req->mapped = 0; }#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 intwrite_packet(u32 *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 = errnostatic 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 *regs; u32 size, set;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -