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

📄 goku_udc.c

📁 LINUX2.4.18内核下的usb GADGET驱动程序
💻 C
📖 第 1 页 / 共 4 页
字号:
/* * 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, &regs->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(&regs->EPxSingle);		writel(tmp, &regs->EPxSingle);		tmp = (ep->dma ? 0x10/*dma*/ : 0x11/*pio*/) << ep->num;		tmp |= readl(&regs->EPxBCS);		writel(tmp, &regs->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, &regs->int_enable);		readl(&regs->int_enable);		if (ep->num < 3) {			struct goku_udc_regs	*regs = ep->dev->regs;			u32			tmp;			tmp = readl(&regs->EPxSingle);			tmp &= ~(0x11 << ep->num);			writel(tmp, &regs->EPxSingle);			tmp = readl(&regs->EPxBCS);			tmp &= ~(0x11 << ep->num);			writel(tmp, &regs->EPxBCS);		}		/* reset dma in case we're still using it */		if (ep->dma) {			u32	master;			master = readl(&regs->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, &regs->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 + -