📄 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/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_ISO
static unsigned fifo_mode = 3;
#else
static 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_DMA
static 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, unsigned 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 void
omap_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,
unsigned 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 void
done(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 = errno
static 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 = errno
static int read_fifo(struct omap_ep *ep, struct omap_req *req)
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -