Hello,
I have a problem with a driver that control gadget serial linking with the USB device in a S3C2443 platform and I need some help. I work in a critical control system and I have to configurate it throw the USB device. The problem appear when I connect a Windows 7 PC with a USB 3.0 hub device to my system, if I connect the two ports of the Hub, one with the USB wire and the other for example with a optical mouse the kernel keep totally freeze, until I disconnect the USB wire and don’t respond to any command. In fact my system make a reset due to an external watch-dog.
I think the problem is in the S3C2443_udc.c driver that control the IRQ handlers and the endpoints of the USB device. I attach the source file for more information. I activate the Debug traces and I reproduce the problem explained before:
….
<7>s3c2443-udc: s3c24xx_udc_irq() High Speed interrupt
<7>s3c2443-udc: s3c24xx_udc_irq() UDC IRQ: stat 0x00000050 (0x00000010) | in 0x00000000 | out 0x00000000
<7>s3c2443-udc: s3c24xx_udc_irq() High Speed interrupt
<7>s3c2443-udc: s3c24xx_udc_irq() UDC IRQ: stat 0x00000050 (0x00000010) | in 0x00000000 | out 0x00000000
………….
This traces repeat over and over again until I plug-out the USB wire. I think the system enter in a infinite loop handling this interrupt. Could you help me? I need a patch for this issue or an explanation to make a patch. I need that my system never halt due to plug-in a USB wire.
Thanks in advance.
Code:
/* -*- linux-c -*-
*
* drivers/usb/gadget/s3c24xx_udc.c
*
* Samsung S3C on-chip full/high speed USB device controllers
*
* $Id: s3c-udc-hs.c,v 1.26 2007/02/22 09:45:04 ihlee215 Exp $*
*
* Copyright (C) 2006 for Samsung Electronics
*
* 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 "s3c2443_udc.h"
#include
#include
#include
/* @TODO: USB Device DMA support */
#define RX_DMA_MODE 0
#define TX_DMA_MODE 0
#if 0
#define DEBUG_S3C2443_UDC
#endif
#define pk_err(fmt, args...) printk(KERN_ERR "[ ERROR ] s3c2443-udc: " fmt, ## args)
#define pk_info(fmt, args...) printk(KERN_DEBUG "s3c2443-udc: " fmt, ## args)
#ifdef DEBUG_S3C2443_UDC
#define pk_dbg(fmt, args...) printk(KERN_DEBUG "s3c2443-udc: %s() " fmt, __func__ , \
## args)
#else
#define pk_dbg(fmt, args...) do { } while(0)
#endif
#if 0
#define S3C2443_UDC_DBG_OUT
#endif
#if defined(S3C2443_UDC_DBG_OUT)
#define pk_dbg_out(fmt, args...) printk(KERN_DEBUG "[OUT] " fmt, ## args)
#else
#define pk_dbg_out(fmt, args...) do { } while(0)
#endif /* S3C2443_UDC_DBG_OUT */
/*
* This macro enables the debug messages when the driver is going to access to the
* internal queue of the IN-endpoints
*/
#if 0
#define DEBUG_S3C2443_UDC_QUEUE
#endif
/* Some driver infos */
#define DRIVER_DESC "S3C2443 Dual-speed USB Device"
#define DRIVER_NAME "s3c2443_udc"
#define DRIVER_BUILD_TIME __TIME__
#define DRIVER_BUILD_DATE __DATE__
#define IOMEMSIZE(s) (s->end - s->start + 1)
/* Internal variables */
struct s3c24xx_udc *the_controller;
static const char driver_desc[] = DRIVER_DESC;
static const char ep0name[] = "ep0-control";
/* Max packet sizes */
static u32 ep0_fifo_size = 64;
static u32 ep_fifo_size = 512;
static u32 ep_fifo_size2 = 1024;
/* Internal functions */
static int s3c24xx_udc_ep_enable(struct usb_ep *ep,
const struct usb_endpoint_descriptor *);
static int s3c24xx_udc_ep_disable(struct usb_ep *ep);
static struct usb_request *s3c24xx_udc_alloc_request(struct usb_ep *ep, gfp_t gfp_flags);
static void s3c24xx_udc_free_request(struct usb_ep *ep, struct usb_request *);
static int s3c24xx_udc_queue(struct usb_ep *ep, struct usb_request *, gfp_t gfp_flags);
static int s3c24xx_udc_dequeue(struct usb_ep *ep, struct usb_request *);
static int s3c24xx_udc_set_halt(struct usb_ep *ep, int);
static int s3c24xx_udc_fifo_status(struct usb_ep *ep);
static void s3c24xx_udc_fifo_flush(struct usb_ep *ep);
static void s3c24xx_udc_ep0_kick(struct s3c24xx_udc *udc, struct s3c_ep *ep);
static void s3c24xx_handle_ep0(struct s3c24xx_udc *udc);
static void done(struct s3c_ep *ep, struct s3c_request *req, int status);
static void stop_activity(struct s3c24xx_udc *dev, struct usb_gadget_driver *driver);
static int s3c24xx_udc_enable(struct s3c24xx_udc *udc);
static void s3c24xx_udc_set_address(struct s3c24xx_udc *dev, unsigned char address);
static void reconfig_usbd(struct s3c24xx_udc *udc);
static void s3c24xx_ep0_setup(struct s3c24xx_udc *udc);
static int s3c24xx_udc_write_fifo(struct s3c_ep *ep, struct s3c_request *req);
static inline struct s3c24xx_udc *gadget_to_udc(struct usb_gadget *gadget)
{
return container_of(gadget, struct s3c24xx_udc, gadget);
}
static spinlock_t regs_lock = SPIN_LOCK_UNLOCKED;
static inline void s3c2443_print_err_packet_setup(int errcode,
struct usb_ctrlrequest *pctrl)
{
printk(KERN_DEBUG "[ ERROR ] s3c2443-udc: Err %i | bRequestType 0x%02x | "
"bRequest 0x%02x | wValue 0x%04x | wIndex 0x%04x | wLength %u
",
errcode, pctrl->bRequestType, pctrl->bRequest,
pctrl->wValue, pctrl->wIndex, pctrl->wLength);
}
/* Read access to one of the indexed registers */
static inline ulong usb_read(struct s3c24xx_udc *udc, ulong port, u8 ind)
{
ulong retval;
spin_lock(&regs_lock);
writel(ind, udc->base + S3C24XX_UDC_IR_REG);
retval = readl(udc->base + port);
spin_unlock(&regs_lock);
return retval;
}
/* Write access to one of the indexed registers */
static inline void usb_write(struct s3c24xx_udc *udc, ulong val, ulong port, u8 ind)
{
spin_lock(&regs_lock);
writel(ind, udc->base + S3C24XX_UDC_IR_REG);
writel(val, udc->base + port);
spin_unlock(&regs_lock);
}
static inline void usb_set(struct s3c24xx_udc *udc, ulong val, ulong port, u8 ind)
{
spin_lock(&regs_lock);
writel(ind, udc->base + S3C24XX_UDC_IR_REG);
writel(readl(udc->base + port) | val, udc->base + port);
spin_unlock(&regs_lock);
}
static inline void usb_clear(struct s3c24xx_udc *udc, ulong val, ulong port, u8 ind)
{
spin_lock(&regs_lock);
writel(ind, udc->base + S3C24XX_UDC_IR_REG);
writel(readl(udc->base + port) & ~val, udc->base + port);
spin_unlock(&regs_lock);
}
/* Return a value different than zero if the EP is enabled */
static inline int s3c24xx_ep_enabled(struct s3c24xx_udc *udc, int epnr)
{
ulong regval;
regval = readl(udc->base + S3C24XX_UDC_EIER_REG);
return (regval & (1 << epnr));
}
/* Enable/disable the interrupt of the passed EP-number */
static inline void s3c24xx_ep_irq_enable(struct s3c24xx_udc *udc, int epnr, int enable)
{
ulong eier;
eier = readl(udc->base + S3C24XX_UDC_EIER_REG);
if (enable)
eier |= (1 << epnr);
else
eier &= ~(1 << epnr);
writel(eier, udc->base + S3C24XX_UDC_EIER_REG);
}
static inline void s3c2443_udc_print_regs(char *marke, struct s3c24xx_udc *udc, int epnr)
{
struct regs_t {
char *name;
ulong addr;
};
int pos, old_epnr;
ulong regval;
static const struct regs_t regs[] = {
{ "EIR ", S3C24XX_UDC_EIR_REG },
{ "EIER ", S3C24XX_UDC_EIER_REG },
{ "EDR ", S3C24XX_UDC_EDR_REG },
{ "TR ", S3C24XX_UDC_TR_REG },
{ "SSR ", S3C24XX_UDC_SSR_REG },
{ "SCR ", S3C24XX_UDC_SCR_REG },
{ "EP0SR ", S3C24XX_UDC_EP0SR_REG },
{ "FCON ", S3C24XX_UDC_FIFO_CON_REG },
{ "FSTAT ", S3C24XX_UDC_FIFO_STATUS_REG },
{ "ESR ", S3C24XX_UDC_ESR_REG },
{ "ECR ", S3C24XX_UDC_ECR_REG },
{ "BRCR ", S3C24XX_UDC_BRCR_REG },
{ "BWCR ", S3C24XX_UDC_BWCR_REG },
};
/* First get a backup of the current EP number */
old_epnr = readl(udc->base + S3C24XX_UDC_IR_REG);
writel(epnr, udc->base + S3C24XX_UDC_IR_REG);
/* Now print all the registers */
printk(KERN_DEBUG "%s
", marke);
for (pos = 0; pos < ARRAY_SIZE(regs); pos++) {
regval = usb_read(udc, regs[pos].addr, epnr);
printk(KERN_DEBUG "%s: 0x%08lx
", regs[pos].name, regval);
}
writel(old_epnr, udc->base + S3C24XX_UDC_IR_REG);
}
static struct usb_ep_ops s3c24xx_ep_ops = {
.enable = s3c24xx_udc_ep_enable,
.disable = s3c24xx_udc_ep_disable,
.alloc_request = s3c24xx_udc_alloc_request,
.free_request = s3c24xx_udc_free_request,
.queue = s3c24xx_udc_queue,
.dequeue = s3c24xx_udc_dequeue,
.set_halt = s3c24xx_udc_set_halt,
.fifo_status = s3c24xx_udc_fifo_status,
.fifo_flush = s3c24xx_udc_fifo_flush,
};
/*
* Function for writing from the request buffer into the EP-FIFO
* The function updates the internal actual length of the USB-request for a possible
* next transfer of the same request.
* The return value is the number of remaining bytes in the request. If the return
* value is equal zero, then there is no more data to process in the request
* (Luis Galdos)
*/
static inline int s3c24xx_udc_write_packet(struct s3c_ep *ep, struct s3c_request *req)
{
u16 *buf;
int length, count;
u32 fifo = ep->fifo;
struct s3c24xx_udc *udc;
int max, remaining, epnr;
u8 *ptr;
/* @XXX: Need some sanity checks (Luis Galdos) */
udc = ep->dev;
max = ep->ep.maxpacket;
epnr = ep_index(ep);
/* Get the number of remaining bytes */
remaining = req->req.length - req->req.actual;
if (!remaining) {
pk_dbg("EP%i: Sending ZLP (actual: %i)
",
epnr, req->req.actual);
/* Send a frame with zero length */
/* usb_set(udc, S3C24XX_UDC_ECR_TZLS, S3C24XX_UDC_ECR_REG, epnr); */
usb_write(udc, 0, S3C24XX_UDC_BWCR_REG, epnr);
length = remaining;
goto exit_write_packet;
}
/* Use first a u8 pointer for obtaining the correct buffer address */
ptr = req->req.buf + req->req.actual;
buf = (u16 *)ptr;
prefetch(buf);
/* Only send the maximal allowed number of bytes */
length = min(remaining, max);
req->req.actual += length;
/* First write the number of bytes to transfer, and then fill the FIFO */
usb_write(udc, length, S3C24XX_UDC_BWCR_REG, epnr);
for (count = 0; count < length; count += 2)
writel(*buf++, udc->base + fifo);
/* Return the number of remaining bytes of the passed request */
exit_write_packet:
return (remaining - length);
}
/*
* Test function which returns the number of bytes written into the FIFO.
* This new function was implemented due an unknown issue registered with the
* Ethernet-gadget (the UDC send packets with size of 514 bytes!)
* (Luis Galdos)
*/
static inline int s3c24xx_udc_write_packet2(struct s3c_ep *ep, struct s3c_request *req)
{
u16 *buf;
int length, count;
u32 fifo = ep->fifo;
struct s3c24xx_udc *udc;
int max, remaining, epnr;
u8 *ptr;
udc = ep->dev;
max = ep->ep.maxpacket;
epnr = ep_index(ep);
/* Get the number of remaining bytes */
remaining = req->req.length - req->req.actual;
if (!remaining) {
pk_dbg("EP%i: Sending ZLP (actual: %i)
", epnr,
req->req.actual);
/*
* Send a frame with zero length.
* DONT use the TZLS control bit of the EP control register ECR.
*/
usb_write(udc, 0, S3C24XX_UDC_BWCR_REG, epnr);
length = 0;
goto exit_write_packet;
}
/* Use first an u8 pointer for obtaining the correct buffer address */
ptr = req->req.buf + req->req.actual;
buf = (u16 *)ptr;
prefetch(buf);
/* Only send the maximal allowed number of bytes */
length = min(remaining, max);
req->req.actual += length;
/* First write the number of bytes to transfer, and then fill the FIFO */
usb_write(udc, length, S3C24XX_UDC_BWCR_REG, epnr);
for (count = 0; count < length; count += 2)
writel(*buf++, udc->base + fifo);
/* Sanity check before writting into the FIFO */
#if defined(DEBUG_S3C2443_UDC_QUEUE)
{
ulong esr;
esr = usb_read(udc, S3C24XX_UDC_ESR_REG, ep_index(ep));
printk(KERN_DEBUG
"%p: len=%i, act=%i, ep=%02x, bwcr=0x%04x, esr=0x%04lx
",
req, req->req.length, req->req.actual, epnr, length, esr);
}
#endif
/* Return the number of remaining bytes of the passed request */
exit_write_packet:
return length;
}
/*
* Check the current state of the VBUS pin. If no VBUS pin was passed through the
* platform data, then assume the bus is always ON.
* (Luis Galdos)
*/
static inline int s3c2443_udc_vbus_state(struct s3c24xx_udc *udc)
{
int retval;
struct s3c2410_udc_mach_info *info;
info = udc->mach_info;
retval = 1;
if (info && info->vbus_pin) {
unsigned long iocfg;
/* @XXX: Do we really need to change to INPUT first? */
iocfg = s3c2410_gpio_getcfg(info->vbus_pin);
s3c2410_gpio_cfgpin(info->vbus_pin, S3C2410_GPIO_INPUT);
retval = s3c2410_gpio_getpin(info->vbus_pin);
s3c2410_gpio_cfgpin(info->vbus_pin, iocfg);
if (info->vbus_pin_inverted)
retval = !retval;
}
return retval;
}
/*
* Disable the controller by resetting the PHY for informing the USB-host
* that the device was disconnected
* (Luis Galdos)
*/
static void s3c24xx_udc_disable(struct s3c24xx_udc *udc)
{
ulong regval;
pk_dbg("UDC disable called
");
/* Disable the EP interrupts */
writel(0, udc->base + S3C24XX_UDC_EIER_REG);
writel(0xff, udc->base + S3C24XX_UDC_EIR_REG);
/* Clear all the status bits of the EP0 and flush it */
writel(S3C24XX_UDC_EP0SR_RSR | S3C24XX_UDC_EP0SR_TST |
S3C24XX_UDC_EP0SR_SHT | S3C24XX_UDC_EP0SR_LWO,
udc->base + S3C24XX_UDC_EP0SR_REG);
writel(0, udc->base + S3C24XX_UDC_EP0CR_REG);
/* Unset the function address */
s3c24xx_udc_set_address(udc, 0);
udc->ep0state = WAIT_FOR_SETUP;
udc->gadget.speed = USB_SPEED_UNKNOWN;
udc->usb_address = 0;
/* Clear all the status bits from the system status register */
regval = S3C24XX_UDC_INT_RESET | S3C24XX_UDC_INT_SUSPEND |
S3C24XX_UDC_INT_RESUME | S3C24XX_UDC_INT_SDE |
S3C24XX_UDC_SSR_TBM | S3C24XX_UDC_INT_VBUSON |
S3C24XX_UDC_SSR_VBUSOFF;
writel(regval, udc->base + S3C24XX_UDC_SSR_REG);
/* Reset the USB-function and the PHY */
writel(S3C2443_URSTCON_PHY | S3C2443_URSTCON_FUNC, S3C2443_URSTCON);
/* PHY power disable */
regval = readl(S3C2443_PWRCFG);
regval &= ~S3C2443_PWRCFG_USBPHY_ON;
writel(regval, S3C2443_PWRCFG);
}
/*
* Function for sending request data to the FIFO
* This function uses the EP-lock for avoiding the wrong queue order of the packets
* that are incoming from the Gadget-driver
* (Luis Galdos)
*/
static void s3c24xx_udc_epin_tasklet_func(unsigned long data)
{
struct s3c_ep *ep;
struct s3c_request *req;
int retval;
ulong esr;
struct s3c24xx_udc *udc;
ep = (struct s3c_ep *)data;
if (!ep) {
pk_err("Invalid EP pointer. Aborting %s
", __func__);
return;
}
spin_lock(&ep->lock);
udc = ep->dev;
esr = usb_read(udc, S3C24XX_UDC_ESR_REG, ep_index(ep));
/*
* Paranoic sanity check: If the FIFO has still a packet, then abort this
* tasklet and wait for the call from the interrupt handler (TPS)
*/
if (S3C24XX_UDC_ESR_PSIFNR(esr) == 2) {
pk_dbg("The FIFO seems to have still a packet
");
goto exit_unlock;
}
/* Check if there is a pending request for us */
if (list_empty(&ep->queue))
goto exit_unlock;
/* Get the next request from the queue of the endpoint */
req = list_entry(ep->queue.next, struct s3c_request, queue);
if (!req) {
pk_err("EP%i: NULL request pointer.
", ep_index(ep));
goto exit_unlock;
}
#if defined(DEBUG_S3C2443_UDC_QUEUE)
{
u8 ch1, ch2;
int len, act;
u8 *ptr = (u8 *)req->req.buf;
len = req->req.length;
act = req->req.actual;
ch1 = *ptr;
ch2 = *(ptr + len - 1);
printk(KERN_DEBUG "%p: act=%i, ep=%02x, 0x%02x ... 0x%02x
",
req, act, ep_index(ep), ch1, ch2);
}
#endif
retval = s3c24xx_udc_write_fifo(ep, req);
exit_unlock:
spin_unlock(&ep->lock);
}
/*
* Restart the UDC and the corresponding resources (tasklet, queues, etc.)
* (Luis Galdos)
*/
static void s3c24xx_udc_reinit(struct s3c24xx_udc *udc)
{
u32 i;
/* device/ep0 records init */
INIT_LIST_HEAD(&udc->gadget.ep_list);
INIT_LIST_HEAD(&udc->gadget.ep0->ep_list);
udc->ep0state = WAIT_FOR_SETUP;
/* basic endpoint records init */
for (i = 0; i < S3C_MAX_ENDPOINTS; i++) {
struct s3c_ep *ep = &udc->ep[i];
if (i != 0)
list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list);
ep->desc = 0;
ep->stopped = 0;
INIT_LIST_HEAD(&ep->queue);
ep->pio_irqs = 0;
}
/* the rest was statically initialized, and is read-only */
}
#define BYTES2MAXP(x) (x / 8)
#define MAXP2BYTES(x) (x * 8)
/*
* Until it's enabled, this UDC should be completely invisible
* to any USB host.
*/
static int s3c24xx_udc_enable(struct s3c24xx_udc *udc)
{
unsigned long regval;
pk_dbg("UDC enable called
");
/* First disable the HOST functionality! */
regval = __raw_readl(S3C2443_UCLKCON);
regval &= ~S3C2443_UCLKCON_HOST_ENABLE;
regval |= S3C2443_UCLKCON_THOST_DISABLE;
__raw_writel(regval, S3C2443_UCLKCON);
/* if reset by sleep wakeup, control the retention I/O cell */
if (__raw_readl(S3C2443_RSTSTAT) & 0x8)
__raw_writel(__raw_readl(S3C2443_RSTCON)|(1<<16), S3C2443_RSTCON);
/* PHY power enable */
regval = __raw_readl(S3C2443_PWRCFG);
regval |= S3C2443_PWRCFG_USBPHY_ON;
__raw_writel(regval, S3C2443_PWRCFG);
/*
* USB device 2.0 must reset like bellow,
* 1st phy reset and after at least 10us, func_reset & host reset
* phy reset can reset bellow registers.
*/
/* PHY 2.0 S/W reset */
regval = S3C2443_URSTCON_PHY;
__raw_writel(regval, S3C2443_URSTCON);
udelay(20);
__raw_writel(0x00, S3C2443_URSTCON);
/* Function reset, but DONT TOUCH THE HOST! */
regval = S3C2443_URSTCON_FUNC;
__raw_writel(regval, S3C2443_URSTCON);
__raw_writel(0x00, S3C2443_URSTCON);
/* 48Mhz, Oscillator, External X-tal, device */
regval = S3C2443_PHYCTRL_EXTCLK_OSCI;
__raw_writel(regval, S3C2443_PHYCTRL);
/*
* D+ pull up disable(VBUS detect), USB2.0 Function clock Enable,
* USB1.1 HOST disable, USB2.0 PHY test enable
*/
regval = __raw_readl(S3C2443_UCLKCON);
regval |= S3C2443_UCLKCON_FUNC_ENABLE;
__raw_writel(regval, S3C2443_UCLKCON);
reconfig_usbd(udc);
udc->gadget.speed = USB_SPEED_UNKNOWN;
/*
* So, now enable the pull up, USB2.0 Function clock Enable and
* USB2.0 PHY test enable
*/
regval = __raw_readl(S3C2443_UCLKCON);
regval |= S3C2443_UCLKCON_VBUS_PULLUP | S3C2443_UCLKCON_FUNC_ENABLE |
S3C2443_UCLKCON_TFUNC_ENABLE | S3C2443_UCLKCON_THOST_DISABLE;
__raw_writel(regval, S3C2443_UCLKCON);
return 0;
}
/*
* Function called from the Gadget-drivers for registering a new profile.
*/
int usb_gadget_register_driver(struct usb_gadget_driver *driver)
{
struct s3c24xx_udc *udc = the_controller;
int retval;
if (!driver)
return -EINVAL;
pk_dbg("Starting to register '%s'
", driver->driver.name);
if (driver->speed != USB_SPEED_FULL && driver->speed != USB_SPEED_HIGH) {
pk_err("Only Full and High speed supported.
");
return -EINVAL;
}
/*
* The 'unbind' function is not required when the Gadget driver is compiled
* as built-in (Luis Galdos)
*/
if (!driver->bind || !driver->disconnect || !driver->setup) {
pk_err("Missing function: Bind %p | Disconnect %p | Setup %p
",
driver->bind, driver->disconnect, driver->setup);
return -EINVAL;
}
if (!udc) {
pk_err("No UDC-controller probed? Aborting.
");
return -ENODEV;
}
if (udc->driver) {
pk_err("UDC already in use by '%s'
", udc->driver->driver.name);
return -EBUSY;
}
/* first hook up the driver ... */
udc->driver = driver;
udc->gadget.dev.driver = &driver->driver;
retval = device_add(&udc->gadget.dev);
if (retval) {
pk_err("Couldn't add the new Gadget device (%i)
", retval);
goto err_exit;
}
retval = driver->bind(&udc->gadget);
if (retval) {
pk_err("%s: bind to driver %s --> error %d
", udc->gadget.name,
driver->driver.name, retval);
goto err_del_device;
}
enable_irq(IRQ_USBD);
/*
* If a host was already detected, then only call the UDC enable function,
* otherwise check over the configured GPIO if a host is connected.
*/
if (udc->vbus)
s3c24xx_udc_enable(udc);
else {
struct s3c2410_udc_mach_info *info;
unsigned long state, iocfg;
info = udc->mach_info;
iocfg = s3c2410_gpio_getcfg(info->vbus_pin);
s3c2410_gpio_cfgpin(info->vbus_pin, S3C2410_GPIO_INPUT);
state = s3c2410_gpio_getpin(info->vbus_pin);
s3c2410_gpio_cfgpin(info->vbus_pin, iocfg);
if (info->vbus_pin_inverted)
state = !state;
if (state)
s3c24xx_udc_enable(udc);
}
pk_dbg("Gadget '%s' registered
", driver->driver.name);
return 0;
err_del_device:
device_del(&udc->gadget.dev);
err_exit:
udc->driver = NULL;
udc->gadget.dev.driver = NULL;
return retval;
}
EXPORT_SYMBOL(usb_gadget_register_driver);
/*
* Unregister entry point for the peripheral controller driver.
*/
int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
{
struct s3c24xx_udc *udc = the_controller;
unsigned long flags;
if (!udc)
return -ENODEV;
if (!driver || driver != udc->driver || !driver->unbind)
return -EINVAL;
spin_lock_irqsave(&udc->lock, flags);
udc->driver = NULL;
stop_activity(udc, driver);
spin_unlock_irqrestore(&udc->lock, flags);
driver->unbind(&udc->gadget);
device_del(&udc->gadget.dev);
disable_irq(IRQ_USBD);
pk_dbg("Unregistered gadget driver '%s'
", driver->driver.name);
/* Disable the pull-up for informing the host about the removed driver */
s3c24xx_udc_disable(udc);
return 0;
}
EXPORT_SYMBOL(usb_gadget_unregister_driver);
/*
* Write request to FIFO (max write == maxp size)
* Return: 0 = still running, 1 = completed, negative = errno
*/
static int s3c24xx_udc_write_fifo(struct s3c_ep *ep, struct s3c_request *req)
{
int max, count;
int is_last, is_short;
count = s3c24xx_udc_write_packet2(ep, req);
max = le16_to_cpu(ep->desc->wMaxPacketSize);
is_short = (count != max) ? (1) : (0);
/* If the packet is short, we dont need to send an additional ZLP */
if (is_short) {
pk_dbg("EP%i: Short packet
", ep_index(ep));
is_last = 1;
} else {
if (req->req.length != req->req.actual || req->req.zero)
is_last = 0;
else {
pk_dbg("EP%i: Clear ZLP
", ep_index(ep));
is_last = 1;
}
}
pk_dbg("TX EP%i: C %i | L %i - A %i | %c %c %c
",
ep_index(ep), count, req->req.length, req->req.actual,
is_last ? 'L':' ', is_short ? 'S':' ', req->req.zero ? 'Z':' ');
/* If this was the last packet, then call the done callback */
if (is_last) {
#if defined(DEBUG_S3C2443_UDC_QUEUE)
int len, act;
len = req->req.length;
act = req->req.actual;
printk(KERN_DEBUG "%p: len=%i, act=%i, ep=%02x [D]
",
req, len, act, ep_index(ep));
#endif
done(ep, req, 0);
}
return 0;
}
/*
* Read to request from FIFO (max read == bytes in fifo)
* Return: 0 = still running, 1 = completed, negative = errno
*/
static int s3c24xx_udc_read_fifo(struct s3c_ep *ep, struct s3c_request *req)
{
u32 csr;
u16 *buf;
unsigned bufferspace, count, count_bytes, is_short = 0, is_done = 0;
u32 fifo = ep->fifo;
struct s3c24xx_udc *udc;
udc = ep->dev;
csr = usb_read(udc, S3C24XX_UDC_ESR_REG, ep_index(ep));
/*
* If the FIFO is empty then return zero, so that a caller, like the queue-
* function, doesn't fail. Returning zero means that the request is not done
* and it can be added to the internal EP-request queue
* (Luis Galdos)
*/
if (!(csr & S3C24XX_UDC_ESR_RPS)) {
pk_dbg("EP%i: No packet to read.
", ep_index(ep));
return 0;
}
buf = req->req.buf + req->req.actual;
prefetchw(buf);
/* Calculate the current buffer space */
bufferspace = req->req.length - req->req.actual;
/* Read all bytes from this packet */
count = usb_read(udc, S3C24XX_UDC_BRCR_REG, ep_index(ep));
if (csr & S3C24XX_UDC_ESR_LWO)
count_bytes = count * 2 - 1;
else
count_bytes = count * 2;
/* Update the actual variable of the request */
req->req.actual += min(count_bytes, bufferspace);
is_short = (count_bytes < ep->ep.maxpacket);
is_done = (req->req.actual == req->req.length) ? 1 : 0;
/*
* G : Got
* A : Actual
* T : Total to get
*/
if (is_short || is_done) {
pk_dbg_out("EP%u: G %d | A %d | T %d [%c%c]
",
ep_index(ep), count_bytes, req->req.actual, req->req.length,
is_short ? 'S' : ' ', is_done ? 'D' : ' ');
}
while (likely(count-- != 0)) {
u16 byte = (u16)readl(udc->base + fifo);
/*
* If there is no more space in the request-buffer, then continue
* reading from the FIFO and return with the done value
* (Luis Galdos)
*/
if (unlikely(bufferspace == 0)) {
req->req.status = -EOVERFLOW;
is_short = 1;
} else {
*buf++ = byte;
bufferspace--;
}
}
/*
* If the complete FIFO-data passed into the request-buffer, then
* return one, otherwise skip the return
* (Luis Galdos)
*/
if (is_short || is_done) {
done(ep, req, 0);
return 1;
}
/* finished that packet. the next one may be waiting... */
return 0;
}
/*
* Retire a request from the internal EP-queue and call the complete
* function of the Gadget-request
* (Luis Galdos)
*/
static void done(struct s3c_ep *ep, struct s3c_request *req, int status)
{
unsigned int stopped = ep->stopped;
list_del_init(&req->queue);
/*
* If the queue is empty and the EP has the OUT direction, then disable
* the receive operation, otherwise we will lost some packets from
* the host.
*/
if (!ep_is_in(ep) && list_empty(&ep->queue)) {
ulong ecr;
struct s3c24xx_udc *udc;
udc = ep->dev;
ecr = usb_read(udc, S3C24XX_UDC_ECR_REG, ep_index(ep));
ecr |= S3C24XX_UDC_ECR_OUTPKTHLD;
usb_write(udc, ecr, S3C24XX_UDC_ECR_REG, ep_index(ep));
}
if (likely(req->req.status == -EINPROGRESS))
req->req.status = status;
else
status = req->req.status;
if (status && status != -ESHUTDOWN) {
pk_dbg("EP%i: done req %p | stat %d | actual %u | length %u
",
ep_index(ep),
&req->req, status, req->req.actual, req->req.length);
}
/* don't modify queue heads during completion callback */
ep->stopped = 1;
/*
* We must unlock the queue of the EP at this place, then the Gadget-driver
* probably will try to enqueue a new request by calling our queue-function.
* (Luis Galdos)
*/
/* spin_unlock(&ep->lock); */
/* spin_unlock(&ep->dev->lock); */
req->req.complete(&ep->ep, &req->req);
/* spin_lock(&ep->dev->lock); */
/* spin_lock(&ep->lock); */
ep->stopped = stopped;
}
/* Nuke/dequeue all the requested transfers */
void nuke(struct s3c_ep *ep, int status)
{
struct s3c_request *req;
pk_dbg("EP%i: Nuke function called
", ep_index(ep));
/* called with irqs blocked */
while (!list_empty(&ep->queue)) {
req = list_entry(ep->queue.next, struct s3c_request, queue);
done(ep, req, status);
}
}
/*
* This function handles the IN-operations of the endpoints different than zero
*/
static void s3c24xx_udc_in_epn(struct s3c24xx_udc *udc, u32 epnr)
{
ulong esr, handled;
struct s3c_ep *ep = &udc->ep[epnr];
handled = 0;
spin_lock(&ep->lock);
esr = usb_read(udc, S3C24XX_UDC_ESR_REG, epnr);
/* ACK the function stall condition */
if (esr & S3C24XX_UDC_ESR_FSC) {
pk_dbg("EP%i: Function stall
", epnr);
usb_set(udc, S3C24XX_UDC_ESR_FSC, S3C24XX_UDC_ESR_REG, epnr);
handled = 1;
}
/* The flush operation generates an interrupt too */
if (esr & S3C24XX_UDC_ESR_FFS) {
pk_dbg("EP%i: FIFO flush detected
", epnr);
usb_set(udc, S3C24XX_UDC_ESR_FFS, S3C24XX_UDC_ESR_REG, epnr);
handled = 1;
}
/* Underflow check */
if (esr & S3C24XX_UDC_ESR_FUDR) {
pk_dbg("EP%i: Underflow detected
", epnr);
usb_set(udc, S3C24XX_UDC_ESR_FUDR, S3C24XX_UDC_ESR_REG, epnr);
handled = 1;
}
/* Overflow check */
if (esr & S3C24XX_UDC_ESR_FOVF) {
pk_dbg("EP%i: Overflow detected
", epnr);
usb_set(udc, S3C24XX_UDC_ESR_FOVF, S3C24XX_UDC_ESR_REG, epnr);
handled = 1;
}
/* By successed transfer of a IN-packet then only schedule the tasklet */
if (esr & S3C24XX_UDC_ESR_TPS) {
usb_set(udc, S3C24XX_UDC_ESR_TPS, S3C24XX_UDC_ESR_REG, epnr);
tasklet_hi_schedule(&ep->in_tasklet);
handled = 1;
}
spin_unlock(&ep->lock);
if (!handled)
pk_info("EP%i: Unhandled IRQ (ESR 0x%04lx)
", epnr, esr);
}
/*
* This function is used for reading OUT-frames from the EP0. We can't use the same
* function for the SETUP-requests, then here we must pass the data to the
* higher Gadget-driver.
*/
static void s3c2443_udc_ep0_read(struct s3c24xx_udc *udc)
{
ulong ep0sr;
int bytes, count, bufferspace;
struct s3c_ep *ep;
struct s3c_request *req;
u16 *buf;
ep = &udc->ep[0];
spin_lock(&ep->lock);
/*
* @FIXME: Remove this delay. At this moment we need it for having a
* working RNDIS-support when connected to a WinXP host machine.
* (Luis Galdos)
*/
if (udc->ep0state == DATA_STATE_RECV)
udelay(100);
/* If there is nothing to read only return at this point */
ep0sr = readl(udc->base + S3C24XX_UDC_EP0SR_REG);
if (!(ep0sr & S3C24XX_UDC_EP0SR_RSR))
goto exit_unlock;
/* Check if we are waiting for a setup frame */
if (udc->ep0state == WAIT_FOR_SETUP) {
s3c24xx_ep0_setup(udc);
goto exit_unlock;
}
pk_dbg("Current state of EP0 is %i
", udc->ep0state);
/* Now get the number of bytes to read from the FIFO */
count = usb_read(udc, S3C24XX_UDC_BRCR_REG, ep_index(ep));
if (ep0sr & S3C24XX_UDC_EP0SR_LWO)
bytes = count * 2 - 1;
else
bytes = count * 2;
/* Check if we have a request for this data */
req = list_entry(ep->queue.next, struct s3c_request, queue);
if (!req) {
pk_err("Going to flush a EP0 frame
");
goto exit_ack;
}
buf = req->req.buf + req->req.actual;
prefetchw(buf);
bufferspace = req->req.length - req->req.actual;
req->req.actual += min(bytes, bufferspace);
pk_dbg("EP0 READ: %i bytes | space %i | req.len %i | reg.act %i
",
bytes, bufferspace, req->req.length, req->req.actual);
while (likely(count-- != 0)) {
u16 byte = (u16)readl(udc->base + ep->fifo);
*buf++ = byte;
}
/* If we are done with this request then call the corresponding function */
if (req->req.length == req->req.actual) {
udc->ep0state = WAIT_FOR_SETUP;
done(ep, req, 0);
}
exit_ack:
writel(S3C24XX_UDC_EP0_RX_SUCCESS, udc->base + S3C24XX_UDC_EP0SR_REG);
exit_unlock:
spin_unlock(&ep->lock);
}
/*
* The below function is called when data was received with an OUT-transaction
*/
static void s3c24xx_udc_out_epn(struct s3c24xx_udc *udc, u32 ep_idx)
{
struct s3c_ep *ep;
struct s3c_request *req;
ulong esr, epnr, handled;
ep = &udc->ep[ep_idx];
epnr = ep_index(ep);
if (epnr != ep_idx) {
pk_err("Invalid EP structure (%lu) or index (%u) passed
",
epnr, ep_idx);
return;
}
/* Read the status register of the EP */
handled = 0;
esr = usb_read(udc, S3C24XX_UDC_ESR_REG, epnr);
pk_dbg("EP%lu: Status reg 0x%08lx
", epnr, esr);
if (unlikely(!(ep->desc))) {
pk_err("No descriptor for EP%lu
", epnr);
return;
}
if (esr & S3C24XX_UDC_ESR_FSC) {
pk_dbg("EP%lu stall sent
", epnr);
usb_set(udc, S3C24XX_UDC_ESR_FSC, S3C24XX_UDC_