📄 omap_udc.c
字号:
/* * omap_udc.c -- for OMAP full speed udc; most chips support OTG. * * Copyright (C) 2004 Texas Instruments, Inc. * Copyright (C) 2004-2005 David Brownell * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */#undef DEBUG#undef VERBOSE#include <linux/config.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/ioport.h>#include <linux/types.h>#include <linux/errno.h>#include <linux/delay.h>#include <linux/sched.h>#include <linux/slab.h>#include <linux/init.h>#include <linux/timer.h>#include <linux/list.h>#include <linux/interrupt.h>#include <linux/proc_fs.h>#include <linux/mm.h>#include <linux/moduleparam.h>#include <linux/platform_device.h>#include <linux/usb_ch9.h>#include <linux/usb_gadget.h>#include <linux/usb_otg.h>#include <linux/dma-mapping.h>#include <asm/byteorder.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include <asm/unaligned.h>#include <asm/mach-types.h>#include <asm/arch/dma.h>#include <asm/arch/usb.h>#include "omap_udc.h"#undef USB_TRACE/* bulk DMA seems to be behaving for both IN and OUT */#define USE_DMA/* ISO too */#define USE_ISO#define DRIVER_DESC "OMAP UDC driver"#define DRIVER_VERSION "4 October 2004"#define DMA_ADDR_INVALID (~(dma_addr_t)0)/* * The OMAP UDC needs _very_ early endpoint setup: before enabling the * D+ pullup to allow enumeration. That's too early for the gadget * framework to use from usb_endpoint_enable(), which happens after * enumeration as part of activating an interface. (But if we add an * optional new "UDC not yet running" state to the gadget driver model, * even just during driver binding, the endpoint autoconfig logic is the * natural spot to manufacture new endpoints.) * * So instead of using endpoint enable calls to control the hardware setup, * this driver defines a "fifo mode" parameter. It's used during driver * initialization to choose among a set of pre-defined endpoint configs. * See omap_udc_setup() for available modes, or to add others. That code * lives in an init section, so use this driver as a module if you need * to change the fifo mode after the kernel boots. * * Gadget drivers normally ignore endpoints they don't care about, and * won't include them in configuration descriptors. That means only * misbehaving hosts would even notice they exist. */#ifdef USE_ISOstatic unsigned fifo_mode = 3;#elsestatic unsigned fifo_mode = 0;#endif/* "modprobe omap_udc fifo_mode=42", or else as a kernel * boot parameter "omap_udc:fifo_mode=42" */module_param (fifo_mode, uint, 0);MODULE_PARM_DESC (fifo_mode, "endpoint setup (0 == default)");#ifdef USE_DMAstatic unsigned use_dma = 1;/* "modprobe omap_udc use_dma=y", or else as a kernel * boot parameter "omap_udc:use_dma=y" */module_param (use_dma, bool, 0);MODULE_PARM_DESC (use_dma, "enable/disable DMA");#else /* !USE_DMA *//* save a bit of code */#define use_dma 0#endif /* !USE_DMA */static const char driver_name [] = "omap_udc";static const char driver_desc [] = DRIVER_DESC;/*-------------------------------------------------------------------------*//* there's a notion of "current endpoint" for modifying endpoint * state, and PIO access to its FIFO. */static void use_ep(struct omap_ep *ep, u16 select){ u16 num = ep->bEndpointAddress & 0x0f; if (ep->bEndpointAddress & USB_DIR_IN) num |= UDC_EP_DIR; UDC_EP_NUM_REG = num | select; /* when select, MUST deselect later !! */}static inline void deselect_ep(void){ UDC_EP_NUM_REG &= ~UDC_EP_SEL; /* 6 wait states before TX will happen */}static void dma_channel_claim(struct omap_ep *ep, unsigned preferred);/*-------------------------------------------------------------------------*/static int omap_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc){ struct omap_ep *ep = container_of(_ep, struct omap_ep, ep); struct omap_udc *udc; unsigned long flags; u16 maxp; /* catch various bogus parameters */ if (!_ep || !desc || ep->desc || desc->bDescriptorType != USB_DT_ENDPOINT || ep->bEndpointAddress != desc->bEndpointAddress || ep->maxpacket < le16_to_cpu (desc->wMaxPacketSize)) { DBG("%s, bad ep or descriptor\n", __FUNCTION__); return -EINVAL; } maxp = le16_to_cpu (desc->wMaxPacketSize); if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK && maxp != ep->maxpacket) || le16_to_cpu(desc->wMaxPacketSize) > ep->maxpacket || !desc->wMaxPacketSize) { DBG("%s, bad %s maxpacket\n", __FUNCTION__, _ep->name); return -ERANGE; }#ifdef USE_ISO if ((desc->bmAttributes == USB_ENDPOINT_XFER_ISOC && desc->bInterval != 1)) { /* hardware wants period = 1; USB allows 2^(Interval-1) */ DBG("%s, unsupported ISO period %dms\n", _ep->name, 1 << (desc->bInterval - 1)); return -EDOM; }#else if (desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { DBG("%s, ISO nyet\n", _ep->name); return -EDOM; }#endif /* xfer types must match, except that interrupt ~= bulk */ if (ep->bmAttributes != desc->bmAttributes && ep->bmAttributes != USB_ENDPOINT_XFER_BULK && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { DBG("%s, %s type mismatch\n", __FUNCTION__, _ep->name); return -EINVAL; } udc = ep->udc; if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) { DBG("%s, bogus device state\n", __FUNCTION__); return -ESHUTDOWN; } spin_lock_irqsave(&udc->lock, flags); ep->desc = desc; ep->irqs = 0; ep->stopped = 0; ep->ep.maxpacket = maxp; /* set endpoint to initial state */ ep->dma_channel = 0; ep->has_dma = 0; ep->lch = -1; use_ep(ep, UDC_EP_SEL); UDC_CTRL_REG = udc->clr_halt; ep->ackwait = 0; deselect_ep(); if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) list_add(&ep->iso, &udc->iso); /* maybe assign a DMA channel to this endpoint */ if (use_dma && desc->bmAttributes == USB_ENDPOINT_XFER_BULK) /* FIXME ISO can dma, but prefers first channel */ dma_channel_claim(ep, 0); /* PIO OUT may RX packets */ if (desc->bmAttributes != USB_ENDPOINT_XFER_ISOC && !ep->has_dma && !(ep->bEndpointAddress & USB_DIR_IN)) { UDC_CTRL_REG = UDC_SET_FIFO_EN; ep->ackwait = 1 + ep->double_buf; } spin_unlock_irqrestore(&udc->lock, flags); VDBG("%s enabled\n", _ep->name); return 0;}static void nuke(struct omap_ep *, int status);static int omap_ep_disable(struct usb_ep *_ep){ struct omap_ep *ep = container_of(_ep, struct omap_ep, ep); unsigned long flags; if (!_ep || !ep->desc) { DBG("%s, %s not enabled\n", __FUNCTION__, _ep ? ep->ep.name : NULL); return -EINVAL; } spin_lock_irqsave(&ep->udc->lock, flags); ep->desc = NULL; nuke (ep, -ESHUTDOWN); ep->ep.maxpacket = ep->maxpacket; ep->has_dma = 0; UDC_CTRL_REG = UDC_SET_HALT; list_del_init(&ep->iso); del_timer(&ep->timer); spin_unlock_irqrestore(&ep->udc->lock, flags); VDBG("%s disabled\n", _ep->name); return 0;}/*-------------------------------------------------------------------------*/static struct usb_request *omap_alloc_request(struct usb_ep *ep, gfp_t gfp_flags){ struct omap_req *req; req = kmalloc(sizeof *req, gfp_flags); if (req) { memset (req, 0, sizeof *req); req->req.dma = DMA_ADDR_INVALID; INIT_LIST_HEAD (&req->queue); } return &req->req;}static voidomap_free_request(struct usb_ep *ep, struct usb_request *_req){ struct omap_req *req = container_of(_req, struct omap_req, req); if (_req) kfree (req);}/*-------------------------------------------------------------------------*/static void *omap_alloc_buffer( struct usb_ep *_ep, unsigned bytes, dma_addr_t *dma, gfp_t gfp_flags){ void *retval; struct omap_ep *ep; ep = container_of(_ep, struct omap_ep, ep); if (use_dma && ep->has_dma) { static int warned; if (!warned && bytes < PAGE_SIZE) { dev_warn(ep->udc->gadget.dev.parent, "using dma_alloc_coherent for " "small allocations wastes memory\n"); warned++; } return dma_alloc_coherent(ep->udc->gadget.dev.parent, bytes, dma, gfp_flags); } retval = kmalloc(bytes, gfp_flags); if (retval) *dma = virt_to_phys(retval); return retval;}static void omap_free_buffer( struct usb_ep *_ep, void *buf, dma_addr_t dma, unsigned bytes){ struct omap_ep *ep; ep = container_of(_ep, struct omap_ep, ep); if (use_dma && _ep && ep->has_dma) dma_free_coherent(ep->udc->gadget.dev.parent, bytes, buf, dma); else kfree (buf);}/*-------------------------------------------------------------------------*/static voiddone(struct omap_ep *ep, struct omap_req *req, int status){ unsigned stopped = ep->stopped; list_del_init(&req->queue); if (req->req.status == -EINPROGRESS) req->req.status = status; else status = req->req.status; if (use_dma && ep->has_dma) { if (req->mapped) { dma_unmap_single(ep->udc->gadget.dev.parent, req->req.dma, req->req.length, (ep->bEndpointAddress & USB_DIR_IN) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); req->req.dma = DMA_ADDR_INVALID; req->mapped = 0; } else dma_sync_single_for_cpu(ep->udc->gadget.dev.parent, req->req.dma, req->req.length, (ep->bEndpointAddress & USB_DIR_IN) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); }#ifndef USB_TRACE if (status && status != -ESHUTDOWN)#endif VDBG("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(&ep->udc->lock); req->req.complete(&ep->ep, &req->req); spin_lock(&ep->udc->lock); ep->stopped = stopped;}/*-------------------------------------------------------------------------*/#define UDC_FIFO_FULL (UDC_NON_ISO_FIFO_FULL | UDC_ISO_FIFO_FULL)#define UDC_FIFO_UNWRITABLE (UDC_EP_HALTED | UDC_FIFO_FULL)#define FIFO_EMPTY (UDC_NON_ISO_FIFO_EMPTY | UDC_ISO_FIFO_EMPTY)#define FIFO_UNREADABLE (UDC_EP_HALTED | FIFO_EMPTY)static inline int write_packet(u8 *buf, struct omap_req *req, unsigned max){ unsigned len; u16 *wp; len = min(req->req.length - req->req.actual, max); req->req.actual += len; max = len; if (likely((((int)buf) & 1) == 0)) { wp = (u16 *)buf; while (max >= 2) { UDC_DATA_REG = *wp++; max -= 2; } buf = (u8 *)wp; } while (max--) *(volatile u8 *)&UDC_DATA_REG = *buf++; return len;}// FIXME change r/w fifo calling convention// return: 0 = still running, 1 = completed, negative = errnostatic int write_fifo(struct omap_ep *ep, struct omap_req *req){ u8 *buf; unsigned count; int is_last; u16 ep_stat; buf = req->req.buf + req->req.actual; prefetch(buf); /* PIO-IN isn't double buffered except for iso */ ep_stat = UDC_STAT_FLG_REG; if (ep_stat & UDC_FIFO_UNWRITABLE) return 0; count = ep->ep.maxpacket; count = write_packet(buf, req, count); UDC_CTRL_REG = UDC_SET_FIFO_EN; ep->ackwait = 1; /* last packet is often short (sometimes a zlp) */ if (count != ep->ep.maxpacket) is_last = 1; else if (req->req.length == req->req.actual && !req->req.zero) is_last = 1; else is_last = 0; /* NOTE: requests complete when all IN data is in a * FIFO (or sometimes later, if a zlp was needed). * Use usb_ep_fifo_status() where needed. */ if (is_last) done(ep, req, 0); return is_last;}static inline int read_packet(u8 *buf, struct omap_req *req, unsigned avail){ unsigned len; u16 *wp; len = min(req->req.length - req->req.actual, avail); req->req.actual += len; avail = len; if (likely((((int)buf) & 1) == 0)) { wp = (u16 *)buf; while (avail >= 2) { *wp++ = UDC_DATA_REG; avail -= 2; } buf = (u8 *)wp; } while (avail--) *buf++ = *(volatile u8 *)&UDC_DATA_REG; return len;}// return: 0 = still running, 1 = queue empty, negative = errnostatic int read_fifo(struct omap_ep *ep, struct omap_req *req){ u8 *buf; unsigned count, avail; int is_last; buf = req->req.buf + req->req.actual; prefetchw(buf); for (;;) { u16 ep_stat = UDC_STAT_FLG_REG; is_last = 0; if (ep_stat & FIFO_EMPTY) { if (!ep->double_buf) break; ep->fnf = 1; } if (ep_stat & UDC_EP_HALTED) break;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -