📄 s3c2410_udc.c
字号:
/*
* 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 + -