📄 jz4740_udc.c
字号:
/* * linux/drivers/usb/gadget/jz4740_udc.c * * Ingenic JZ4740 on-chip high speed USB device controller * * Copyright (C) 2006 - 2008 Ingenic Semiconductor Inc. * Author: <jlwei@ingenic.cn> * * 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 device has ep0, two bulk-in/interrupt-in endpoints, and one bulk-out endpoint. * * - Endpoint numbering is fixed: ep0, ep1in-int, ep2in-bulk, ep1out-bulk. * - DMA works with bulk-in (channel 1) and bulk-out (channel 2) endpoints. */#include <linux/kernel.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/delay.h>#include <linux/ioport.h>#include <linux/slab.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/list.h>#include <linux/interrupt.h>#include <linux/proc_fs.h>#include <linux/usb.h>#include <linux/usb/gadget.h>#include <asm/byteorder.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include <asm/jzsoc.h>#include "jz4740_udc.h"//#define DEBUG(fmt,args...) printk(KERN_DEBUG fmt , ## args)//#define DEBUG(fmt,args...) printk(fmt , ## args)//#define DEBUG_EP0(fmt,args...) printk(fmt , ## args)//#define DEBUG_SETUP(fmt,args...) printk(fmt , ## args)#ifndef DEBUG# define DEBUG(fmt,args...) do {} while(0)#endif#ifndef DEBUG_EP0# define NO_STATES# define DEBUG_EP0(fmt,args...) do {} while(0)#endif#ifndef DEBUG_SETUP# define DEBUG_SETUP(fmt,args...) do {} while(0)#endifstatic unsigned int udc_debug = 0; /* 0: normal mode, 1: test udc cable type mode */module_param(udc_debug, int, 0);MODULE_PARM_DESC(udc_debug, "test udc cable or power type");static unsigned int use_dma = 1; /* 1: use DMA, 0: use PIO */module_param(use_dma, int, 0);MODULE_PARM_DESC(use_dma, "DMA mode enable flag");#ifdef CONFIG_JZ_UDC_HOTPLUGextern int jz_udc_active; /* 0: No actions; 1: Have actions */#endif/* * Local definintions. */#define DRIVER_VERSION "13-Mar-2008"#define DRIVER_DESC "JZ4740 USB Device Controller"static const char gadget_name [] = "jz4740_udc";struct jz4740_udc *the_controller;static const char driver_name [] = "jz4740_udc";static const char driver_desc [] = DRIVER_DESC;static const char ep0name[] = "ep0";#ifndef NO_STATESstatic char *state_names[] = { "WAIT_FOR_SETUP", "DATA_STATE_XMIT", "DATA_STATE_NEED_ZLP", "WAIT_FOR_OUT_STATUS", "DATA_STATE_RECV"};#endif/* * Local declarations. */static int jz4740_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc);static int jz4740_ep_disable(struct usb_ep *_ep);static struct usb_request *jz4740_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags);static void jz4740_free_request(struct usb_ep *_ep, struct usb_request *_req);static int jz4740_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags);static int jz4740_dequeue(struct usb_ep *_ep, struct usb_request *_req);static int jz4740_set_halt(struct usb_ep *_ep, int value);static int jz4740_fifo_status(struct usb_ep *_ep);static void jz4740_fifo_flush(struct usb_ep *_ep);static void jz4740_ep0_kick(struct jz4740_udc *dev, struct jz4740_ep *ep);static void jz4740_handle_ep0(struct jz4740_udc *dev, u32 intr);static void done(struct jz4740_ep *ep, struct jz4740_request *req, int status);static void pio_irq_enable(struct jz4740_ep *ep);static void pio_irq_disable(struct jz4740_ep *ep);static void stop_activity(struct jz4740_udc *dev, struct usb_gadget_driver *driver);static void nuke(struct jz4740_ep *ep, int status);static void flush(struct jz4740_ep *ep);static void udc_enable(struct jz4740_udc *dev);static void udc_set_address(struct jz4740_udc *dev, unsigned char address);static void jz4740_udc_release (struct device *dev) {}extern void *dma_alloc_noncoherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag);extern void dma_free_noncoherent(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle);static struct usb_ep_ops jz4740_ep_ops = { .enable = jz4740_ep_enable, .disable = jz4740_ep_disable, .alloc_request = jz4740_alloc_request, .free_request = jz4740_free_request, .queue = jz4740_queue, .dequeue = jz4740_dequeue, .set_halt = jz4740_set_halt, .fifo_status = jz4740_fifo_status, .fifo_flush = jz4740_fifo_flush,};/*-------------------------------------------------------------------------*//* inline functions of register read/write/set/clear */static __inline__ u8 usb_readb(u32 port){ return *(volatile u8 *)port;}static __inline__ u16 usb_readw(u32 port){ return *(volatile u16 *)port;}static __inline__ u32 usb_readl(u32 port){ return *(volatile u32 *)port;}static __inline__ void usb_writeb(u32 port, u8 val){ *(volatile u8 *)port = val;}static __inline__ void usb_writew(u32 port, u16 val){ *(volatile u16 *)port = val;}static __inline__ void usb_writel(u32 port, u32 val){ *(volatile u32 *)port = val;}static __inline__ void usb_setb(u32 port, u8 val){ volatile u8 *ioport = (volatile u8 *)(port); *ioport = (*ioport) | val;}static __inline__ void usb_setw(u32 port, u16 val){ volatile u16 *ioport = (volatile u16 *)(port); *ioport = (*ioport) | val;}static __inline__ void usb_setl(u32 port, u32 val){ volatile u32 *ioport = (volatile u32 *)(port); *ioport = (*ioport) | val;}static __inline__ void usb_clearb(u32 port, u8 val){ volatile u8 *ioport = (volatile u8 *)(port); *ioport = (*ioport) & ~val;}static __inline__ void usb_clearw(u32 port, u16 val){ volatile u16 *ioport = (volatile u16 *)(port); *ioport = (*ioport) & ~val;}static __inline__ void usb_clearl(u32 port, u32 val){ volatile u32 *ioport = (volatile u32 *)(port); *ioport = (*ioport) & ~val;}/*-------------------------------------------------------------------------*/static __inline__ int write_packet(struct jz4740_ep *ep, struct jz4740_request *req, int max){ u8 *buf; int length, nlong, nbyte; volatile u32 *fifo = (volatile u32 *)ep->fifo; buf = req->req.buf + req->req.actual; prefetch(buf); length = req->req.length - req->req.actual; length = min(length, max); req->req.actual += length; DEBUG("Write %d (max %d), fifo %p\n", length, max, fifo); nlong = length >> 2; nbyte = length & 0x3; while (nlong--) { *fifo = *((u32 *)buf); buf += 4; } while (nbyte--) { *((volatile u8 *)fifo) = *buf++; } return length;}static __inline__ int read_packet(struct jz4740_ep *ep, struct jz4740_request *req, int count){ u8 *buf; int length, nlong, nbyte; volatile u32 *fifo = (volatile u32 *)ep->fifo; buf = req->req.buf + req->req.actual; prefetchw(buf); length = req->req.length - req->req.actual; length = min(length, count); req->req.actual += length; DEBUG("Read %d, fifo %p\n", length, fifo); nlong = length >> 2; nbyte = length & 0x3; while (nlong--) { *((u32 *)buf) = *fifo; buf += 4; } while (nbyte--) { *buf++ = *((volatile u8 *)fifo); } return length;}/*-------------------------------------------------------------------------*//* * udc_disable - disable USB device controller */static void udc_disable(struct jz4740_udc *dev){ DEBUG("%s, %p\n", __FUNCTION__, dev); udc_set_address(dev, 0); /* Disable interrupts */ usb_writew(USB_REG_INTRINE, 0); usb_writew(USB_REG_INTROUTE, 0); usb_writeb(USB_REG_INTRUSBE, 0); /* Disable DMA */ usb_writel(USB_REG_CNTL1, 0); usb_writel(USB_REG_CNTL2, 0); /* Disconnect from usb */ usb_clearb(USB_REG_POWER, USB_POWER_SOFTCONN); /* Disable the USB PHY */ REG_CPM_SCR &= ~CPM_SCR_USBPHY_ENABLE; dev->ep0state = WAIT_FOR_SETUP; dev->gadget.speed = USB_SPEED_UNKNOWN;}/* * udc_reinit - initialize software state */static void udc_reinit(struct jz4740_udc *dev){ u32 i; DEBUG("%s, %p\n", __FUNCTION__, dev); /* device/ep0 records init */ INIT_LIST_HEAD(&dev->gadget.ep_list); INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); dev->ep0state = WAIT_FOR_SETUP; for (i = 0; i < UDC_MAX_ENDPOINTS; i++) { struct jz4740_ep *ep = &dev->ep[i]; if (i != 0) list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); INIT_LIST_HEAD(&ep->queue); ep->desc = 0; ep->stopped = 0; ep->pio_irqs = 0; }}/* until it's enabled, this UDC should be completely invisible * to any USB host. */static void udc_enable(struct jz4740_udc *dev){ int i; DEBUG("%s, %p\n", __FUNCTION__, dev); dev->gadget.speed = USB_SPEED_UNKNOWN; /* Flush FIFO for each */ for (i = 0; i < UDC_MAX_ENDPOINTS; i++) { struct jz4740_ep *ep = &dev->ep[i]; usb_set_index(ep_index(ep)); flush(ep); } /* Set this bit to allow the UDC entering low-power mode when * there are no actions on the USB bus. * UDC still works during this bit was set. */ __cpm_stop_udc(); /* Enable the USB PHY */ REG_CPM_SCR |= CPM_SCR_USBPHY_ENABLE; /* Disable interrupts */ usb_writew(USB_REG_INTRINE, 0); usb_writew(USB_REG_INTROUTE, 0); usb_writeb(USB_REG_INTRUSBE, 0); /* Enable interrupts */ usb_setw(USB_REG_INTRINE, USB_INTR_EP0); usb_setb(USB_REG_INTRUSBE, USB_INTR_RESET); /* Don't enable rest of the interrupts */ /* usb_setw(USB_REG_INTRINE, USB_INTR_INEP1 | USB_INTR_INEP2); usb_setw(USB_REG_INTROUTE, USB_INTR_OUTEP1); */ /* Enable SUSPEND */ /* usb_setb(USB_REG_POWER, USB_POWER_SUSPENDM); */ /* Enable HS Mode */ usb_setb(USB_REG_POWER, USB_POWER_HSENAB); /* Let host detect UDC: * Software must write a 1 to the PMR:USB_POWER_SOFTCONN bit to turn this * transistor on and pull the USBDP pin HIGH. */ usb_setb(USB_REG_POWER, USB_POWER_SOFTCONN);}/*-------------------------------------------------------------------------*//* keeping it simple: * - one bus driver, initted first; * - one function driver, initted second *//* * Register entry point for the peripheral controller driver. */int usb_gadget_register_driver(struct usb_gadget_driver *driver){ struct jz4740_udc *dev = the_controller; int retval; if (!driver || !driver->bind || !driver->unbind || !driver->disconnect || !driver->setup) { printk("\n-EINVAL"); return -EINVAL; } if (!dev) { printk("\n-ENODEV"); return -ENODEV; } if (dev->driver) { printk("\n-ENODEV"); return -EBUSY; } /* hook up the driver */ dev->driver = driver; retval = driver->bind(&dev->gadget); if (retval) { DEBUG("%s: bind to driver %s --> error %d\n", dev->gadget.name, driver->driver.name, retval); dev->driver = 0; return retval; } /* then enable host detection and ep0; and we're ready * for set_configuration as well as eventual disconnect. */ udc_enable(dev); DEBUG("%s: registered gadget driver '%s'\n", dev->gadget.name, driver->driver.name); return 0;}EXPORT_SYMBOL(usb_gadget_register_driver);static void stop_activity(struct jz4740_udc *dev, struct usb_gadget_driver *driver){ int i; DEBUG("%s\n", __FUNCTION__); /* don't disconnect drivers more than once */ if (dev->gadget.speed == USB_SPEED_UNKNOWN) driver = 0; dev->gadget.speed = USB_SPEED_UNKNOWN; /* prevent new request submissions, kill any outstanding requests */ for (i = 0; i < UDC_MAX_ENDPOINTS; i++) { struct jz4740_ep *ep = &dev->ep[i]; ep->stopped = 1; usb_set_index(ep_index(ep)); nuke(ep, -ESHUTDOWN); } /* report disconnect; the driver is already quiesced */ if (driver) { spin_unlock(&dev->lock); driver->disconnect(&dev->gadget); spin_lock(&dev->lock); } /* re-init driver-visible data structures */ udc_reinit(dev);}/* * Unregister entry point for the peripheral controller driver. */int usb_gadget_unregister_driver(struct usb_gadget_driver *driver){ struct jz4740_udc *dev = the_controller; unsigned long flags; if (!dev) return -ENODEV; if (!driver || driver != dev->driver) return -EINVAL; spin_lock_irqsave(&dev->lock, flags); dev->driver = 0; stop_activity(dev, driver); spin_unlock_irqrestore(&dev->lock, flags); driver->unbind(&dev->gadget); udc_disable(dev); DEBUG("unregistered driver '%s'\n", driver->driver.name); return 0;}EXPORT_SYMBOL(usb_gadget_unregister_driver);/*-------------------------------------------------------------------------*//* * Starting DMA using mode 1 */static void kick_dma(struct jz4740_ep *ep, struct jz4740_request *req){ u32 count = req->req.length; u32 physaddr = virt_to_phys((void *)req->req.buf); usb_set_index(ep_index(ep)); if (ep_is_in(ep)) { /* Bulk-IN transfer using DMA channel 1 */ ep->reg_addr = USB_REG_ADDR1; dma_cache_wback_inv((unsigned long)req->req.buf, count); pio_irq_enable(ep); usb_writeb(USB_REG_INCSRH, USB_INCSRH_DMAREQENAB | USB_INCSRH_AUTOSET | USB_INCSRH_DMAREQMODE); usb_writel(USB_REG_ADDR1, physaddr); usb_writel(USB_REG_COUNT1, count); usb_writel(USB_REG_CNTL1, USB_CNTL_ENA | USB_CNTL_DIR_IN | USB_CNTL_MODE_1 | USB_CNTL_INTR_EN | USB_CNTL_BURST_16 | USB_CNTL_EP(ep_index(ep))); } else { /* Bulk-OUT transfer using DMA channel 2 */ ep->reg_addr = USB_REG_ADDR2; dma_cache_wback_inv((unsigned long)req->req.buf, count); pio_irq_enable(ep); usb_setb(USB_REG_OUTCSRH, USB_OUTCSRH_DMAREQENAB | USB_OUTCSRH_AUTOCLR | USB_OUTCSRH_DMAREQMODE); usb_writel(USB_REG_ADDR2, physaddr); usb_writel(USB_REG_COUNT2, count); usb_writel(USB_REG_CNTL2, USB_CNTL_ENA | USB_CNTL_MODE_1 | USB_CNTL_INTR_EN | USB_CNTL_BURST_16 | USB_CNTL_EP(ep_index(ep))); }}/*-------------------------------------------------------------------------*//** Write request to FIFO (max write == maxp size) * Return: 0 = still running, 1 = completed, negative = errno * NOTE: INDEX register must be set for EP */static int write_fifo(struct jz4740_ep *ep, struct jz4740_request *req){ u32 max, csr;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -