⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 omap_udc.c

📁 linux-2.6.15.6
💻 C
📖 第 1 页 / 共 5 页
字号:
/* * 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 + -