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

📄 s3c2410_udc.c

📁 ARM S3C2410 USB SLAVE LINUX驱动
💻 C
📖 第 1 页 / 共 3 页
字号:
/*
 * linux/drivers/usb/gadget/s3c2410_udc.c
 * Samsung on-chip full speed USB device controllers
 *
 * Copyright (C) 2004 Herbert Pötzl - Arnaud Patard
 *
 * 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
 *
 */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.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/version.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/unaligned.h>
#include <asm/arch/irqs.h>

#include <asm/arch/hardware.h>
#include <asm/arch/regs-clock.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/regs-udc.h>
#include <asm/arch/udc.h>
#include <asm/hardware/clock.h>

#include <asm/mach-types.h>

#include "s3c2410_udc.h"

#define ENABLE_SYSFS

#define DRIVER_DESC     "S3C2410 USB Device Controller Gadget"
#define DRIVER_VERSION  "28 Aug 2005"
#define DRIVER_AUTHOR	"Herbert Pötzl <herbert@13thfloor.at>, Arnaud Patard <arnaud.patard@rtp-net.org>"

static const char       gadget_name [] = "s3c2410_udc";
static const char	driver_desc [] = DRIVER_DESC;

static struct s3c2410_udc	*the_controller;
static struct clk      		*udc_clock;
static void __iomem 		*base_addr;
static u64			rsrc_start;
static u64			rsrc_len;

static inline u32 udc_readl(u32 reg)
{
	return readl(base_addr+reg);
}
static inline void udc_writel(u32 value, u32 reg)
{
	writel(value,base_addr+reg);
}

static struct s3c2410_udc_mach_info *udc_info;

/*************************** DEBUG FUNCTION ***************************/
#define DEBUG_NORMAL	1
#define DEBUG_VERBOSE	1

//#define CONFIG_USB_S3C2410_DEBUG
#ifdef CONFIG_USB_S3C2410_DEBUG
#define USB_S3C2410_DEBUG_LEVEL 1

static uint32_t s3c2410_ticks=0;

static int dprintk(int level, const char *fmt, ...)
{
	static char printk_buf[1024];
	static long prevticks;
	static int invocation;
	va_list args;
	int len;

	if (level > USB_S3C2410_DEBUG_LEVEL)
		return 0;

	if (s3c2410_ticks != prevticks) {
		prevticks = s3c2410_ticks;
		invocation = 0;
	}

	len = scnprintf(printk_buf, \
			sizeof(printk_buf), "%1lu.%02d USB: ", \
			prevticks, invocation++);

	va_start(args, fmt);
	len = vscnprintf(printk_buf+len, \
			sizeof(printk_buf)-len, fmt, args);
	va_end(args);

	return printk("%s", printk_buf);
}
#else
static int dprintk(int level, const char *fmt, ...)  { return 0; }
#endif

//#define ENABLE_SYSFS
#ifdef ENABLE_SYSFS
static ssize_t s3c2410udc_regs_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	u32 addr_reg,pwr_reg,ep_int_reg,usb_int_reg;
	u32 ep_int_en_reg, usb_int_en_reg, ep0_csr;
	u32 ep1_i_csr1,ep1_i_csr2,ep1_o_csr1,ep1_o_csr2;
	u32 ep2_i_csr1,ep2_i_csr2,ep2_o_csr1,ep2_o_csr2;

	addr_reg       = udc_readl(S3C2410_UDC_FUNC_ADDR_REG);
	pwr_reg        = udc_readl(S3C2410_UDC_PWR_REG);
	ep_int_reg     = udc_readl(S3C2410_UDC_EP_INT_REG);
	usb_int_reg    = udc_readl(S3C2410_UDC_USB_INT_REG);
	ep_int_en_reg  = udc_readl(S3C2410_UDC_EP_INT_EN_REG);
	usb_int_en_reg = udc_readl(S3C2410_UDC_USB_INT_EN_REG);
	udc_writel(0, S3C2410_UDC_INDEX_REG);
	ep0_csr        = udc_readl(S3C2410_UDC_IN_CSR1_REG);
	udc_writel(1, S3C2410_UDC_INDEX_REG);
	ep1_i_csr1     = udc_readl(S3C2410_UDC_IN_CSR1_REG);
	ep1_i_csr2     = udc_readl(S3C2410_UDC_IN_CSR2_REG);
	ep1_o_csr1     = udc_readl(S3C2410_UDC_IN_CSR1_REG);
	ep1_o_csr2     = udc_readl(S3C2410_UDC_IN_CSR2_REG);
	udc_writel(2, S3C2410_UDC_INDEX_REG);
	ep2_i_csr1     = udc_readl(S3C2410_UDC_IN_CSR1_REG);
	ep2_i_csr2     = udc_readl(S3C2410_UDC_IN_CSR2_REG);
	ep2_o_csr1     = udc_readl(S3C2410_UDC_IN_CSR1_REG);
	ep2_o_csr2     = udc_readl(S3C2410_UDC_IN_CSR2_REG);


	return snprintf(buf, PAGE_SIZE,        \
		 "FUNC_ADDR_REG  : 0x%04X\n"   \
		 "PWR_REG        : 0x%04X\n"   \
		 "EP_INT_REG     : 0x%04X\n"   \
		 "USB_INT_REG    : 0x%04X\n"   \
		 "EP_INT_EN_REG  : 0x%04X\n"   \
		 "USB_INT_EN_REG : 0x%04X\n"   \
		 "EP0_CSR        : 0x%04X\n"   \
		 "EP1_I_CSR1     : 0x%04X\n"   \
		 "EP1_I_CSR2     : 0x%04X\n"   \
		 "EP1_O_CSR1     : 0x%04X\n"   \
		 "EP1_O_CSR2     : 0x%04X\n"   \
		 "EP2_I_CSR1     : 0x%04X\n"   \
		 "EP2_I_CSR2     : 0x%04X\n"   \
		 "EP2_O_CSR1     : 0x%04X\n"   \
		 "EP2_O_CSR2     : 0x%04X\n",  \
		 addr_reg,pwr_reg,ep_int_reg,usb_int_reg,     \
		 ep_int_en_reg, usb_int_en_reg, ep0_csr,      \
		 ep1_i_csr1,ep1_i_csr2,ep1_o_csr1,ep1_o_csr2, \
		 ep2_i_csr1,ep2_i_csr2,ep2_o_csr1,ep2_o_csr2  \
		 );
}

static DEVICE_ATTR(regs, 0444,
		   s3c2410udc_regs_show,
		   NULL);
#endif
/*------------------------- I/O ----------------------------------*/
static void nuke (struct s3c2410_udc *udc, struct s3c2410_ep *ep)
{
	/* Sanity check */
	if (&ep->queue != NULL)
		while (!list_empty (&ep->queue)) {
			struct s3c2410_request  *req;
			req = list_entry (ep->queue.next, struct s3c2410_request, queue);
			list_del_init (&req->queue);
			req->req.status = -ESHUTDOWN;
			req->req.complete (&ep->ep, &req->req);
		}
}

/*
 * 	done
 */
static void done(struct s3c2410_ep *ep, struct s3c2410_request *req, int status)
{
	list_del_init(&req->queue);

	if (likely (req->req.status == -EINPROGRESS))
		req->req.status = status;
	else
		status = req->req.status;

	spin_unlock(&ep->dev->lock);
	req->req.complete(&ep->ep, &req->req);
	spin_lock(&ep->dev->lock);
}

static inline void clear_ep_state (struct s3c2410_udc *dev)
{
	        unsigned i;

		/* hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint
		 * fifos, and pending transactions mustn't be continued in any case.
		 */
		 for (i = 1; i < S3C2410_ENDPOINTS; i++)
			 nuke(dev, &dev->ep[i]);
}

static inline int fifo_count_out(void)
{
	int tmp;

	tmp = udc_readl(S3C2410_UDC_OUT_FIFO_CNT2_REG) << 8;
	tmp |= udc_readl(S3C2410_UDC_OUT_FIFO_CNT1_REG);

	return tmp & 0xffff;
}

/*
 * 	write_packet
 */
static inline int
write_packet(int fifo, struct s3c2410_request *req, unsigned max)
{
	unsigned	len;
	u8		*buf;
	int	i;

	buf = req->req.buf + req->req.actual;
	len = min(req->req.length - req->req.actual, max);
	dprintk(DEBUG_VERBOSE, "write_packet %d %d %d ",req->req.actual,req->req.length,len);
	req->req.actual += len;
	dprintk(DEBUG_VERBOSE, "%d\n",req->req.actual);

//	writesb(base_addr+fifo, buf, len);
	for(i=0;i<len;i++)	
	{
		__raw_writel(*buf++,fifo+base_addr);
	}

	return len;
}


/*
 * 	write_fifo
 */
// return:  0 = still running, 1 = completed, negative = errno
static int write_fifo(struct s3c2410_ep *ep, struct s3c2410_request *req)
{
	u8		*buf;
	unsigned	count;
	int		is_last;
	u32		idx;
	int		fifo_reg;
	u32		ep_csr;


	switch(ep->bEndpointAddress&0x7F)
	{
		default:
		case 0: idx = 0;
			fifo_reg = S3C2410_UDC_EP0_FIFO_REG;
			break;
		case 1:
			idx = 1;
			fifo_reg = S3C2410_UDC_EP1_FIFO_REG;
			break;
		case 2:
			idx = 2;
			fifo_reg = S3C2410_UDC_EP2_FIFO_REG;
			break;

		case 3:
			idx = 3;
			fifo_reg = S3C2410_UDC_EP3_FIFO_REG;
			break;

		case 4:
			idx = 4;
			fifo_reg = S3C2410_UDC_EP4_FIFO_REG;
			break;
	}

	buf = req->req.buf + req->req.actual;
	prefetch(buf);

	count = write_packet(fifo_reg, req, ep->ep.maxpacket);

	/* 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 = 0;
	else
		is_last = 2;

	/* Only ep0 debug messages are interesting */
	if (!idx)
		dprintk(DEBUG_NORMAL, "Written ep%d %d.%d of %d b [last %d,z %d]\n",idx,count,req->req.actual,req->req.length,is_last,req->req.zero);

	if (is_last)
	{
		/* The order is important. It prevents to send 2 packet at the same time
		 **/
		if (!idx)
		{
			/* If we got a reset signal, no need to say 'data sent' */
			if (! (udc_readl(S3C2410_UDC_USB_INT_REG) & S3C2410_UDC_USBINT_RESET))
				set_ep0_de_in(base_addr);
			ep->dev->ep0state=EP0_IDLE;
		}
		else
		{
			 udc_writel(idx, S3C2410_UDC_INDEX_REG);
			 ep_csr=udc_readl(S3C2410_UDC_IN_CSR1_REG);
			 udc_writel(idx, S3C2410_UDC_INDEX_REG);
			 udc_writel(ep_csr|S3C2410_UDC_ICSR1_PKTRDY,S3C2410_UDC_IN_CSR1_REG);
		}
		done(ep, req, 0);
		if (!list_empty(&ep->queue))
		{
			is_last=0;
			req = container_of(ep->queue.next,
				struct s3c2410_request, queue);
		}
		else
			is_last=1;
	}
	else
	{
		if (!idx)
		{
			/* If we got a reset signal, no need to say 'data sent' */
			if (! (udc_readl(S3C2410_UDC_USB_INT_REG) & S3C2410_UDC_USBINT_RESET))
				set_ep0_ipr(base_addr);
		}
		else
		{
			udc_writel(idx, S3C2410_UDC_INDEX_REG);
			ep_csr=udc_readl(S3C2410_UDC_IN_CSR1_REG);
			udc_writel(idx, S3C2410_UDC_INDEX_REG);
			udc_writel(ep_csr|S3C2410_UDC_ICSR1_PKTRDY,S3C2410_UDC_IN_CSR1_REG);
		}
	}


	return is_last;
}

static inline int
read_packet(int fifo, u8 *buf, struct s3c2410_request *req, unsigned avail)
{
	unsigned	len;
	int	i;

	len = min(req->req.length - req->req.actual, avail);
	req->req.actual += len;

//	readsb(fifo + base_addr, buf, len);
	for(i=0;i<len;i++)	
	{
		*buf++=(unsigned char)__raw_readl(fifo+base_addr);
	}
	return len;
}

// return:  0 = still running, 1 = queue empty, negative = errno
static int read_fifo(struct s3c2410_ep *ep, struct s3c2410_request *req)
{
	u8		*buf;
	u32		ep_csr;
	unsigned	bufferspace;
	int 		is_last=1;
	unsigned 	avail;
	int 		fifo_count = 0;
	u32		idx;
	int		fifo_reg;

	
	switch(ep->bEndpointAddress&0x7F)
	{
		default:
		case 0: idx = 0;
			fifo_reg = S3C2410_UDC_EP0_FIFO_REG;
			break;
		case 1:
			idx = 1;
			fifo_reg = S3C2410_UDC_EP1_FIFO_REG;
			break;
		case 2:
			idx = 2;
			fifo_reg = S3C2410_UDC_EP2_FIFO_REG;
			break;

		case 3:
			idx = 3;
			fifo_reg = S3C2410_UDC_EP3_FIFO_REG;
			break;

		case 4:
			idx = 4;
			fifo_reg = S3C2410_UDC_EP4_FIFO_REG;
			break;

	}

	if (!req->req.length) {
		return 1;
	}

	buf = req->req.buf + req->req.actual;
	bufferspace = req->req.length - req->req.actual;
	if (!bufferspace)
	{
		dprintk(DEBUG_NORMAL, "read_fifo: Buffer full !!\n");
		return -1;
	}

	udc_writel(idx, S3C2410_UDC_INDEX_REG);

        fifo_count = fifo_count_out();
	dprintk(DEBUG_VERBOSE, "fifo_read fifo count : %d\n",fifo_count);

	if (fifo_count > ep->ep.maxpacket)
		avail = ep->ep.maxpacket;
	else
		avail = fifo_count;

	fifo_count=read_packet(fifo_reg,buf,req,avail);

	if (fifo_count < ep->ep.maxpacket) {
		is_last = 1;
		/* overflowed this request?  flush extra data */
		if (fifo_count != avail) {
			req->req.status = -EOVERFLOW;
		}
	} else {
		if (req->req.length == req->req.actual)
			is_last = 1;
		else
			is_last = 0;
	}

	udc_writel(idx, S3C2410_UDC_INDEX_REG);
	fifo_count = fifo_count_out();

	/* Only ep0 debug messages are interesting */
	if (!idx)
		dprintk(DEBUG_VERBOSE, "fifo_read fifo count : %d [last %d]\n",fifo_count,is_last);


	if (is_last) {
		if (!idx)
		{
			set_ep0_de_out(base_addr);
			ep->dev->ep0state=EP0_IDLE;
		}
		else
		{
			udc_writel(idx, S3C2410_UDC_INDEX_REG);
			ep_csr=udc_readl(S3C2410_UDC_OUT_CSR1_REG);
			udc_writel(idx, S3C2410_UDC_INDEX_REG);
			udc_writel(ep_csr&~S3C2410_UDC_OCSR1_PKTRDY,S3C2410_UDC_OUT_CSR1_REG);
		}
		done(ep, req, 0);
		if (!list_empty(&ep->queue))
		{
			is_last=0;
			req = container_of(ep->queue.next,
				struct s3c2410_request, queue);
		}
		else
			is_last=1;

	}
	else
	{
		if (!idx)
		{
			clear_ep0_opr(base_addr);
		}
		else
		{
			udc_writel(idx, S3C2410_UDC_INDEX_REG);
			ep_csr=udc_readl(S3C2410_UDC_OUT_CSR1_REG);
			udc_writel(idx, S3C2410_UDC_INDEX_REG);
			udc_writel(ep_csr&~S3C2410_UDC_OCSR1_PKTRDY,S3C2410_UDC_OUT_CSR1_REG);
		}
	}


	return is_last;
}


static int
read_fifo_crq(struct usb_ctrlrequest *crq)
{
	int bytes_read = 0;
	int fifo_count = 0;
	int i;


	unsigned char *pOut = (unsigned char*)crq;

	udc_writel(0, S3C2410_UDC_INDEX_REG);

	fifo_count = fifo_count_out();

	dprintk(DEBUG_NORMAL, "read_fifo_crq(): fifo_count=%d\n", fifo_count );

	fifo_count = sizeof(struct usb_ctrlrequest);
	while( fifo_count-- ) {
		i = 0;

		do {
			*pOut = (unsigned char)udc_readl(S3C2410_UDC_EP0_FIFO_REG);
			i++;
		} while((fifo_count_out() != fifo_count) && (i < 10));

		if ( i == 10 ) {
			dprintk(DEBUG_NORMAL, "read_fifo(): read failure\n");
		}

		pOut++;
		bytes_read++;
	}

	dprintk(DEBUG_VERBOSE, "read_fifo_crq: len=%d %02x:%02x {%x,%x,%x}\n",
			bytes_read, crq->bRequest, crq->bRequestType,
			crq->wValue, crq->wIndex, crq->wLength);

	return bytes_read;
}
static int s3c2410_get_status(struct s3c2410_udc *dev, struct usb_ctrlrequest  *crq)
{
	u16 status = 0;
	u8 ep_num = crq->wIndex & 0x7F;
	u8 is_in = crq->wIndex & USB_DIR_IN;

	switch(crq->bRequestType & USB_RECIP_MASK) {
		case USB_RECIP_INTERFACE:
			break;
		case USB_RECIP_DEVICE:
			status = dev->devstatus;
			break;
		case USB_RECIP_ENDPOINT:
			if (ep_num>4 || crq->wLength > 2)
				return 1;
			if (!ep_num) {
				udc_writel(0, S3C2410_UDC_INDEX_REG);
				status = udc_readl(S3C2410_UDC_IN_CSR1_REG);
				status = ( (status & S3C2410_UDC_EP0_CSR_SENDSTL) == S3C2410_UDC_EP0_CSR_SENDSTL);
			}
			else {
				udc_writel(ep_num, S3C2410_UDC_INDEX_REG);
				if (is_in) {
					status = udc_readl(S3C2410_UDC_IN_CSR1_REG);
					status = ( (status & S3C2410_UDC_ICSR1_SENTSTL) == S3C2410_UDC_ICSR1_SENTSTL);
				}
				else {
					status = udc_readl(S3C2410_UDC_OUT_CSR1_REG);
					status = ( (status & S3C2410_UDC_OCSR1_SENTSTL) == S3C2410_UDC_OCSR1_SENTSTL);
				}
			}

			break;
		default:
			return 1;
	}

	/* Seems to be needed to get it working. ouch :( */
	udelay(0x20);
	udc_writel(status&0xFF,S3C2410_UDC_EP0_FIFO_REG);
	udc_writel(status>>8,S3C2410_UDC_EP0_FIFO_REG);
	set_ep0_de_in(base_addr);

	return 0;
}
/*------------------------- usb state machine -------------------------------*/
static void handle_ep0(struct s3c2410_udc *dev)
{
	u32			ep0csr;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -