ChangeSet 1.1074.1.15, 2003/07/09 22:17:36-07:00, yinah@couragetech.com.cn [PATCH] USB: patch for sl811 usb host controller driver Ok, I have get rid of the typedefs and make it build on 2.4.21. I'm not try to merge this with the existing sl811 driver, I modifed most of it for support isochronous transfer mode, especially the structrue and the urb schedule part, so it's almost a new one. Some one need this feature can use this one, others can still use the existing. So may be it makes sense to have two different drivers. Note: I wrote it on 2.4.18, at our board which use SA1110, and I test it with usb mouse, usb moving disk and a web camera which use ov511 chipset. drivers/usb/host/Config.in | 1 drivers/usb/host/Makefile | 1 drivers/usb/host/sl811.c | 2749 +++++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/sl811.h | 177 ++ 4 files changed, 2928 insertions(+) diff -Nru a/drivers/usb/host/Config.in b/drivers/usb/host/Config.in --- a/drivers/usb/host/Config.in Mon Jul 14 10:04:12 2003 +++ b/drivers/usb/host/Config.in Mon Jul 14 10:04:12 2003 @@ -12,4 +12,5 @@ define_bool CONFIG_USB_UHCI_ALT n fi dep_tristate ' OHCI (Compaq, iMacs, OPTi, SiS, ALi, ...) support' CONFIG_USB_OHCI $CONFIG_USB +dep_tristate ' SL811HS Alternate (support isochornous mode)' CONFIG_USB_SL811HS_ALT $CONFIG_USB diff -Nru a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile --- a/drivers/usb/host/Makefile Mon Jul 14 10:04:12 2003 +++ b/drivers/usb/host/Makefile Mon Jul 14 10:04:12 2003 @@ -9,6 +9,7 @@ obj-$(CONFIG_USB_UHCI_ALT) += uhci.o obj-$(CONFIG_USB_UHCI) += usb-uhci.o obj-$(CONFIG_USB_OHCI) += usb-ohci.o +obj-$(CONFIG_USB_SL811HS_ALT) += sl811.o # Extract lists of the multi-part drivers. # The 'int-*' lists are the intermediate files used to build the multi's. diff -Nru a/drivers/usb/host/sl811.c b/drivers/usb/host/sl811.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/host/sl811.c Mon Jul 14 10:04:12 2003 @@ -0,0 +1,2749 @@ +/* + * SL811 Host Controller Interface driver for USB. + * + * Copyright (c) 2003/06, Courage Co., Ltd. + * + * Based on: + * 1.uhci.c by Linus Torvalds, Johannes Erdfelt, Randy Dunlap, + * Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber, + * Adam Richter, Gregory P. Smith; + 2.Original SL811 driver (hc_sl811.o) by Pei Liu + * + * It's now support isochronous mode and more effective than hc_sl811.o + * + * To do: + * 1.Modify the timeout part, it's some messy + * 2.Use usb-a and usb-b set in Ping-Pong mode + + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hcd.h" +#include "hub.h" +#include "sl811.h" + +#define DRIVER_VERSION "v0.28" +#define DRIVER_AUTHOR "Yin Aihua " +#define DRIVER_DESC "Sl811 USB Host Controller Driver" + +static int sl811_addr_io = 0xf100000e; +static int sl811_data_io = 0xf100000f; +static int sl811_irq = 44; + +static LIST_HEAD(sl811_hcd_list); + +/* + * 0, normal prompt and information + * 1, error should not occur in normal + * 2, error maybe occur in normal + * 3, useful and detail debug information + * 4, function level enter and level inforamtion + * 5, endless information will output because of timer function or interrupt + */ +static int debug = 0; + +MODULE_PARM(sl811_addr_io,"i"); +MODULE_PARM_DESC(sl811_addr_io,"sl811 address io port 0xf100000e"); +MODULE_PARM(sl811_data_io,"i"); +MODULE_PARM_DESC(sl811_data_io,"sl811 data io port 0xf100000f"); +MODULE_PARM(sl811_irq,"i"); +MODULE_PARM_DESC(sl811_irq,"sl811 irq 44(default)"); +MODULE_PARM(debug,"i"); +MODULE_PARM_DESC(debug,"debug level"); + +static void sl811_rh_int_timer_do(unsigned long ptr); +static void sl811_transfer_done(struct sl811_hc *hc, int sof); + +/* + * Read a byte of data from the SL811H/SL11H + */ +static __u8 sl811_read(struct sl811_hc *hc, __u8 offset) +{ + __u8 data; + + writeb(offset, hc->addr_io); + wmb(); + data = readb(hc->data_io); + rmb(); + + return data; +} + +/* + * Write a byte of data to the SL811H/SL11H + */ +static void sl811_write(struct sl811_hc *hc, __u8 offset, __u8 data) +{ + writeb(offset, hc->addr_io); + writeb(data, hc->data_io); + wmb(); +} + +/* + * Read consecutive bytes of data from the SL811H/SL11H buffer + */ +static void sl811_read_buf(struct sl811_hc *hc, __u8 offset, __u8 *buf, __u8 size) +{ + writeb(offset, hc->addr_io); + wmb(); + while (size--) { + *buf++ = readb(hc->data_io); + rmb(); + } +} + +/* + * Write consecutive bytes of data to the SL811H/SL11H buffer + */ +static void sl811_write_buf(struct sl811_hc *hc, __u8 offset, __u8 *buf, __u8 size) +{ + writeb(offset, hc->addr_io); + wmb(); + while (size--) { + writeb(*buf, hc->data_io); + wmb(); + buf++; + } +} + +/* + * This routine test the Read/Write functionality of SL811HS registers + */ +static int sl811_reg_test(struct sl811_hc *hc) +{ + int i, data, result = 0; + __u8 buf[256]; + + for (i = 0x10; i < 256; i++) { + /* save the original buffer */ + buf[i] = sl811_read(hc, i); + + /* Write the new data to the buffer */ + sl811_write(hc, i, i); + } + + /* compare the written data */ + for (i = 0x10; i < 256; i++) { + data = sl811_read(hc, i); + if (data != i) { + PDEBUG(1, "Pattern test failed!! value = 0x%x, s/b 0x%x", data, i); + result = -1; + } + } + + /* restore the data */ + for (i = 0x10; i < 256; i++) + sl811_write(hc, i, buf[i]); + + return result; +} + +/* + * Display all SL811HS register values + */ +static void sl811_reg_show(struct sl811_hc *hc) +{ + int i; + + for (i = 0; i < 256; i++) + PDEBUG(4, "offset %d: 0x%x", i, sl811_read(hc, i)); +} + +/* + * This function enables SL811HS interrupts + */ +static void sl811_enable_interrupt(struct sl811_hc *hc) +{ + PDEBUG(4, "enter"); + sl811_write(hc, SL811_INTR, SL811_INTR_DONE_A | SL811_INTR_SOF | SL811_INTR_INSRMV); +} + +/* + * This function disables SL811HS interrupts + */ +static void sl811_disable_interrupt(struct sl811_hc *hc) +{ + PDEBUG(4, "enter"); + // Disable all other interrupt except for insert/remove. + sl811_write(hc, SL811_INTR, SL811_INTR_INSRMV); +} + +/* + * SL811 Virtual Root Hub + */ + +/* Device descriptor */ +static __u8 sl811_rh_dev_des[] = +{ + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x10, /* __u16 bcdUSB; v1.1 */ + 0x01, + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x00, /* __u8 bDeviceProtocol; */ + 0x08, /* __u8 bMaxPacketSize0; 8 Bytes */ + 0x00, /* __u16 idVendor; */ + 0x00, + 0x00, /* __u16 idProduct; */ + 0x00, + 0x00, /* __u16 bcdDevice; */ + 0x00, + 0x00, /* __u8 iManufacturer; */ + 0x02, /* __u8 iProduct; */ + 0x01, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; + +/* Configuration descriptor */ +static __u8 sl811_rh_config_des[] = +{ + 0x09, /* __u8 bLength; */ + 0x02, /* __u8 bDescriptorType; Configuration */ + 0x19, /* __u16 wTotalLength; */ + 0x00, + 0x01, /* __u8 bNumInterfaces; */ + 0x01, /* __u8 bConfigurationValue; */ + 0x00, /* __u8 iConfiguration; */ + 0x40, /* __u8 bmAttributes; + Bit 7: Bus-powered, 6: Self-powered, 5 Remote-wakwup, + 4..0: resvd */ + 0x00, /* __u8 MaxPower; */ + + /* interface */ + 0x09, /* __u8 if_bLength; */ + 0x04, /* __u8 if_bDescriptorType; Interface */ + 0x00, /* __u8 if_bInterfaceNumber; */ + 0x00, /* __u8 if_bAlternateSetting; */ + 0x01, /* __u8 if_bNumEndpoints; */ + 0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */ + 0x00, /* __u8 if_bInterfaceSubClass; */ + 0x00, /* __u8 if_bInterfaceProtocol; */ + 0x00, /* __u8 if_iInterface; */ + + /* endpoint */ + 0x07, /* __u8 ep_bLength; */ + 0x05, /* __u8 ep_bDescriptorType; Endpoint */ + 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* __u8 ep_bmAttributes; Interrupt */ + 0x08, /* __u16 ep_wMaxPacketSize; */ + 0x00, + 0xff /* __u8 ep_bInterval; 255 ms */ +}; + +/* root hub class descriptor*/ +static __u8 sl811_rh_hub_des[] = +{ + 0x09, /* __u8 bLength; */ + 0x29, /* __u8 bDescriptorType; Hub-descriptor */ + 0x01, /* __u8 bNbrPorts; */ + 0x00, /* __u16 wHubCharacteristics; */ + 0x00, + 0x50, /* __u8 bPwrOn2pwrGood; 2ms */ + 0x00, /* __u8 bHubContrCurrent; 0 mA */ + 0xfc, /* __u8 DeviceRemovable; *** 7 Ports max *** */ + 0xff /* __u8 PortPwrCtrlMask; *** 7 ports max *** */ +}; + +/* + * This function examine the port change in the virtual root hub. HUB INTERRUPT ENDPOINT. + */ +static int sl811_rh_send_irq(struct sl811_hc *hc, __u8 *rh_change, int rh_len) +{ + __u8 data = 0; + + PDEBUG(5, "enter"); + + /* + * Right now, It is assume the power is good and no changes and only one port. + */ + if (hc->rh_status.wPortChange & (USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE)) { + data = 1<<1; + *(__u8 *)rh_change = data; + return 1; + } else + return 0; +} + +/* + * This function creates a timer that act as interrupt pipe in the virtual hub. + * + * Note: The virtual root hub's interrupt pipe are polled by the timer + * every "interval" ms + */ +static void sl811_rh_init_int_timer(struct urb * urb) +{ + struct sl811_hc *hc = urb->dev->bus->hcpriv; + hc->rh.interval = urb->interval; + + init_timer(&hc->rh.rh_int_timer); + hc->rh.rh_int_timer.function = sl811_rh_int_timer_do; + hc->rh.rh_int_timer.data = (unsigned long)urb; + hc->rh.rh_int_timer.expires = jiffies + + (HZ * (urb->interval < 30? 30: urb->interval)) / 1000; + add_timer (&hc->rh.rh_int_timer); +} + +/* + * This function is called when the timer expires. It gets the the port + * change data and pass along to the upper protocol. + */ +static void sl811_rh_int_timer_do(unsigned long ptr) +{ + int len; + struct urb *urb = (struct urb *)ptr; + struct sl811_hc *hc = urb->dev->bus->hcpriv; + PDEBUG (5, "enter"); + + if(hc->rh.send) { + len = sl811_rh_send_irq(hc, urb->transfer_buffer, + urb->transfer_buffer_length); + if (len > 0) { + urb->actual_length = len; + if (urb->complete) + urb->complete(urb); + } + } + +#ifdef SL811_TIMEOUT + +{ + struct list_head *head, *tmp; + struct sl811_urb_priv *urbp; + struct urb *u; + int i; + static int timeout_count = 0; + +// check time out every second + if (++timeout_count > 4) { + int max_scan = hc->active_urbs; + timeout_count = 0; + for (i = 0; i < 6; ++i) { + head = &hc->urb_list[i]; + tmp = head->next; + while (tmp != head && max_scan--) { + u = list_entry(tmp, struct urb, urb_list); + urbp = (struct sl811_urb_priv *)u->hcpriv; + tmp = tmp->next; + // Check if the URB timed out + if (u->timeout && time_after_eq(jiffies, urbp->inserttime + u->timeout)) { + PDEBUG(3, "urb = %p time out, we kill it", urb); + u->transfer_flags |= USB_TIMEOUT_KILLED; + } + } + } + } +} + +#endif + // re-activate the timer + sl811_rh_init_int_timer(urb); +} + +/* helper macro */ +#define OK(x) len = (x); break + +/* + * This function handles all USB request to the the virtual root hub + */ +static int sl811_rh_submit_urb(struct urb *urb) +{ + struct usb_device *usb_dev = urb->dev; + struct sl811_hc *hc = usb_dev->bus->hcpriv; + struct usb_ctrlrequest *cmd = (struct usb_ctrlrequest *)urb->setup_packet; + void *data = urb->transfer_buffer; + int buf_len = urb->transfer_buffer_length; + unsigned int pipe = urb->pipe; + __u8 data_buf[16]; + __u8 *bufp = data_buf; + int len = 0; + int status = 0; + + __u16 bmRType_bReq; + __u16 wValue; + __u16 wIndex; + __u16 wLength; + + if (usb_pipeint(pipe)) { + hc->rh.urb = urb; + hc->rh.send = 1; + hc->rh.interval = urb->interval; + sl811_rh_init_int_timer(urb); + urb->status = 0; + + return 0; + } + + bmRType_bReq = cmd->bRequestType | (cmd->bRequest << 8); + wValue = le16_to_cpu (cmd->wValue); + wIndex = le16_to_cpu (cmd->wIndex); + wLength = le16_to_cpu (cmd->wLength); + + PDEBUG(5, "submit rh urb, req = %d(%x) len=%d", bmRType_bReq, bmRType_bReq, wLength); + + /* Request Destination: + without flags: Device, + USB_RECIP_INTERFACE: interface, + USB_RECIP_ENDPOINT: endpoint, + USB_TYPE_CLASS means HUB here, + USB_RECIP_OTHER | USB_TYPE_CLASS almost ever means HUB_PORT here + */ + switch (bmRType_bReq) { + case RH_GET_STATUS: + *(__u16 *)bufp = cpu_to_le16(1); + OK(2); + + case RH_GET_STATUS | USB_RECIP_INTERFACE: + *(__u16 *)bufp = cpu_to_le16(0); + OK(2); + + case RH_GET_STATUS | USB_RECIP_ENDPOINT: + *(__u16 *)bufp = cpu_to_le16(0); + OK(2); + + case RH_GET_STATUS | USB_TYPE_CLASS: + *(__u32 *)bufp = cpu_to_le32(0); + OK(4); + + case RH_GET_STATUS | USB_RECIP_OTHER | USB_TYPE_CLASS: + *(__u32 *)bufp = cpu_to_le32(hc->rh_status.wPortChange<<16 | hc->rh_status.wPortStatus); + OK(4); + + case RH_CLEAR_FEATURE | USB_RECIP_ENDPOINT: + switch (wValue) { + case 1: + OK(0); + } + break; + + case RH_CLEAR_FEATURE | USB_TYPE_CLASS: + switch (wValue) { + case C_HUB_LOCAL_POWER: + OK(0); + + case C_HUB_OVER_CURRENT: + OK(0); + } + break; + + case RH_CLEAR_FEATURE | USB_RECIP_OTHER | USB_TYPE_CLASS: + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + hc->rh_status.wPortStatus &= ~USB_PORT_STAT_ENABLE; + OK(0); + + case USB_PORT_FEAT_SUSPEND: + hc->rh_status.wPortStatus &= ~USB_PORT_STAT_SUSPEND; + OK(0); + + case USB_PORT_FEAT_POWER: + hc->rh_status.wPortStatus &= ~USB_PORT_STAT_POWER; + OK(0); + + case USB_PORT_FEAT_C_CONNECTION: + hc->rh_status.wPortChange &= ~USB_PORT_STAT_C_CONNECTION; + OK(0); + + case USB_PORT_FEAT_C_ENABLE: + hc->rh_status.wPortChange &= ~USB_PORT_STAT_C_ENABLE; + OK(0); + + case USB_PORT_FEAT_C_SUSPEND: + hc->rh_status.wPortChange &= ~USB_PORT_STAT_C_SUSPEND; + OK(0); + + case USB_PORT_FEAT_C_OVER_CURRENT: + hc->rh_status.wPortChange &= ~USB_PORT_STAT_C_OVERCURRENT; + OK(0); + + case USB_PORT_FEAT_C_RESET: + hc->rh_status.wPortChange &= ~USB_PORT_STAT_C_RESET; + OK(0); + } + break; + + case RH_SET_FEATURE | USB_RECIP_OTHER | USB_TYPE_CLASS: + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + hc->rh_status.wPortStatus |= USB_PORT_STAT_SUSPEND; + OK(0); + + case USB_PORT_FEAT_RESET: + hc->rh_status.wPortStatus |= USB_PORT_STAT_RESET; + hc->rh_status.wPortChange = 0; + hc->rh_status.wPortChange |= USB_PORT_STAT_C_RESET; + hc->rh_status.wPortStatus &= ~USB_PORT_STAT_RESET; + hc->rh_status.wPortStatus |= USB_PORT_STAT_ENABLE; + OK(0); + + case USB_PORT_FEAT_POWER: + hc->rh_status.wPortStatus |= USB_PORT_STAT_POWER; + OK(0); + + case USB_PORT_FEAT_ENABLE: + hc->rh_status.wPortStatus |= USB_PORT_STAT_ENABLE; + OK(0); + } + break; + + case RH_SET_ADDRESS: + hc->rh.devnum = wValue; + OK(0); + + case RH_GET_DESCRIPTOR: + switch ((wValue & 0xff00) >> 8) { + case USB_DT_DEVICE: + len = sizeof(sl811_rh_dev_des); + bufp = sl811_rh_dev_des; + OK(len); + + case USB_DT_CONFIG: + len = sizeof(sl811_rh_config_des); + bufp = sl811_rh_config_des; + OK(len); + + case USB_DT_STRING: + len = usb_root_hub_string(wValue & 0xff, (int)(long)0, "SL811HS", data, wLength); + if (len > 0) { + bufp = data; + OK(len); + } + + default: + status = -EPIPE; + } + break; + + case RH_GET_DESCRIPTOR | USB_TYPE_CLASS: + len = sizeof(sl811_rh_hub_des); + bufp = sl811_rh_hub_des; + OK(len); + + case RH_GET_CONFIGURATION: + bufp[0] = 0x01; + OK(1); + + case RH_SET_CONFIGURATION: + OK(0); + + default: + PDEBUG(1, "unsupported root hub command"); + status = -EPIPE; + } + + len = min(len, buf_len); + if (data != bufp) + memcpy(data, bufp, len); + urb->actual_length = len; + urb->status = status; + + PDEBUG(5, "len = %d, status = %d", len, status); + + urb->hcpriv = NULL; + urb->dev = NULL; + if (urb->complete) + urb->complete(urb); + + return 0; +} + +/* + * This function unlinks the URB + */ +static int sl811_rh_unlink_urb(struct urb *urb) +{ + struct sl811_hc *hc = urb->dev->bus->hcpriv; + + PDEBUG(5, "enter"); + + if (hc->rh.urb == urb) { + hc->rh.send = 0; + del_timer(&hc->rh.rh_int_timer); + hc->rh.urb = NULL; + urb->hcpriv = NULL; + usb_dec_dev_use(urb->dev); + urb->dev = NULL; + if (urb->transfer_flags & USB_ASYNC_UNLINK) { + urb->status = -ECONNRESET; + if (urb->complete) + urb->complete(urb); + } else + urb->status = -ENOENT; + } + + return 0; +} + +/* + * This function connect the virtual root hub to the USB stack + */ +static int sl811_connect_rh(struct sl811_hc * hc) +{ + struct usb_device *usb_dev; + + hc->rh.devnum = 0; + usb_dev = usb_alloc_dev(NULL, hc->bus); + if (!usb_dev) + return -ENOMEM; + + hc->bus->root_hub = usb_dev; + usb_connect(usb_dev); + + if (usb_new_device(usb_dev)) { + usb_free_dev(usb_dev); + return -ENODEV; + } + + PDEBUG(5, "leave success"); + + return 0; +} + +/* + * This function allocates private data space for the usb device + */ +static int sl811_alloc_dev_priv(struct usb_device *usb_dev) +{ + return 0; +} + +/* + * This function de-allocates private data space for the usb devic + */ +static int sl811_free_dev_priv (struct usb_device *usb_dev) +{ + return 0; +} + +/* + * This function allocates private data space for the urb + */ +static struct sl811_urb_priv* sl811_alloc_urb_priv(struct urb *urb) +{ + struct sl811_urb_priv *urbp; + + urbp = kmalloc(sizeof(*urbp), GFP_KERNEL); + if (!urbp) + return NULL; + + memset(urbp, 0, sizeof(*urbp)); + + INIT_LIST_HEAD(&urbp->td_list); + + urbp->urb = urb; + urb->hcpriv = urbp; + + return urbp; +} + +/* + * This function free private data space for the urb + */ +static void sl811_free_urb_priv(struct urb *urb) +{ + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td; + struct list_head *head, *tmp; + + if (!urbp) + return ; + + head = &urbp->td_list; + tmp = head->next; + + while (tmp != head) { + td = list_entry(tmp, struct sl811_td, td_list); + tmp = tmp->next; + kfree(td); + } + + kfree(urbp); + urb->hcpriv = NULL; + + return ; +} + +/* + * This function calculate the bus time need by this td. + * Fix me! Can this use usb_calc_bus_time()? + */ +static void sl811_calc_td_time(struct sl811_td *td) +{ +#if 1 + int time; + int len = td->len; + struct sl811_hc *hc = td->urb->dev->bus->hcpriv; + + if (hc->rh_status.wPortStatus & USB_PORT_STAT_LOW_SPEED) + time = 8*8*len + 1024; + else { + if (td->ctrl & SL811_USB_CTRL_PREAMBLE) + time = 8*8*len + 2048; + else + time = 8*len + 256; + } + + time += 2*10 * len; + + td->bustime = time; + +#else + + unsigned long tmp; + int time; + int low_speed = usb_pipeslow(td->urb->pipe); + int input_dir = usb_pipein(td->urb->pipe); + int bytecount = td->len; + int isoc = usb_pipeisoc(td->urb->pipe); + + if (low_speed) { /* no isoc. here */ + if (input_dir) { + tmp = (67667L * (31L + 10L * BitTime (bytecount))) / 1000L; + time = (64060L + (2 * BW_HUB_LS_SETUP) + BW_HOST_DELAY + tmp); + } else { + tmp = (66700L * (31L + 10L * BitTime (bytecount))) / 1000L; + time = (64107L + (2 * BW_HUB_LS_SETUP) + BW_HOST_DELAY + tmp); + } + } else if (!isoc){ /* for full-speed: */ + tmp = (8354L * (31L + 10L * BitTime (bytecount))) / 1000L; + time = (9107L + BW_HOST_DELAY + tmp); + } else { /* for isoc: */ + tmp = (8354L * (31L + 10L * BitTime (bytecount))) / 1000L; + time = (((input_dir) ? 7268L : 6265L) + BW_HOST_DELAY + tmp); + } + + td->bustime = time / 84; + +#endif +} + +/* + * This function calculate the remainder bus time in current frame. + */ +static inline int sl811_calc_bus_remainder(struct sl811_hc *hc) +{ + return (sl811_read(hc, SL811_SOFCNTDIV) * 64); +} + +/* + * This function allocates td for the urb + */ +static struct sl811_td* sl811_alloc_td(struct urb *urb) +{ + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td; + + td = kmalloc(sizeof (*td), GFP_KERNEL); + if (!td) + return NULL; + + memset(td, 0, sizeof(*td)); + + INIT_LIST_HEAD(&td->td_list); + + td->urb = urb; + list_add_tail(&td->td_list, &urbp->td_list); + + return td; +} + +/* + * Fill the td. + */ +static inline void sl811_fill_td(struct sl811_td *td, __u8 ctrl, __u8 addr, __u8 len, __u8 pidep, __u8 dev, __u8 *buf) +{ + td->ctrl = ctrl; + td->addr = addr; + td->len = len; + td->pidep = pidep; + td->dev = dev; + td->buf = buf; + td->left = len; + td->errcnt = 3; +} + +/* + * Fill the td. + */ +static inline void sl811_reset_td(struct sl811_td *td) +{ + td->status = 0; + td->left = td->len; + td->done = 0; + td->errcnt = 3; + td->nakcnt = 0; + td->td_status = 0; +} + +static void sl811_print_td(int level, struct sl811_td *td) +{ + PDEBUG(level, "td = %p, ctrl = %x, addr = %x, len = %x, pidep = %x\n + dev = %x, status = %x, left = %x, errcnt = %x, done = %x\n + buf = %p, bustime = %d, td_status = %d\n", + td, td->ctrl, td->addr, td->len, td->pidep, + td->dev, td->status, td->left, td->errcnt, td->done, + td->buf, td->bustime, td->td_status); +} + +/* + * Isochronous transfers + */ +static int sl811_submit_isochronous(struct urb *urb) +{ + __u8 dev = usb_pipedevice(urb->pipe); + __u8 pidep = PIDEP(usb_packetid(urb->pipe), usb_pipeendpoint(urb->pipe)); + __u8 ctrl = 0; + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td = NULL; + int i; + + PDEBUG(4, "enter, urb = %p, urbp = %p", urb, urbp); + + /* Can't have low speed bulk transfers */ + if (usb_pipeslow(urb->pipe)) { + PDEBUG(1, "error, urb = %p, low speed device", urb); + return -EINVAL; + } + + if (usb_pipeout(urb->pipe)) + ctrl |= SL811_USB_CTRL_DIR_OUT; + + ctrl |= SL811_USB_CTRL_ARM | SL811_USB_CTRL_ENABLE | SL811_USB_CTRL_ISO; + + for (i = 0; i < urb->number_of_packets; i++) { + urb->iso_frame_desc[i].actual_length = 0; + urb->iso_frame_desc[i].status = -EXDEV; + + td = sl811_alloc_td(urb); + if (!td) + return -ENOMEM; + + sl811_fill_td(td, ctrl, SL811_DATA_START, + urb->iso_frame_desc[i].length, + pidep, dev, + urb->transfer_buffer + urb->iso_frame_desc[i].offset); + sl811_calc_td_time(td); + if (urbp->cur_td == NULL) + urbp->cur_td = urbp->first_td = td; + } + + urbp->last_td = td; + + PDEBUG(4, "leave success"); + +/* +// for debug + { + struct list_head *head, *tmp; + struct sl811_td *td; + int i = 0; + head = &urbp->td_list; + tmp = head->next; + + if (list_empty(&urbp->td_list)) { + PDEBUG(1, "bug!!! td list is empty!"); + return -ENODEV; + } + + while (tmp != head) { + ++i; + td = list_entry(tmp, struct sl811_td, td_list); + PDEBUG(2, "td = %p, i = %d", td, i); + tmp = tmp->next; + } + } +*/ + return 0; +} + +/* + * Reset isochronous transfers + */ +static void sl811_reset_isochronous(struct urb *urb) +{ + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td = NULL; + struct list_head *head, *tmp; + int i; + + PDEBUG(4, "enter, urb = %p", urb); + + for (i = 0; i < urb->number_of_packets; i++) { + urb->iso_frame_desc[i].actual_length = 0; + urb->iso_frame_desc[i].status = -EXDEV; + } + + head = &urbp->td_list; + tmp = head->next; + while (tmp != head) { + td = list_entry(tmp, struct sl811_td, td_list); + tmp = tmp->next; + sl811_reset_td(td); + } + + urbp->cur_td = urbp->first_td; + + urb->status = -EINPROGRESS; + urb->actual_length = 0; + urb->error_count = 0; +} + +/* + * Result the iso urb. + */ +static void sl811_result_isochronous(struct urb *urb) +{ + struct list_head *tmp, *head; + struct sl811_urb_priv *urbp = urb->hcpriv; + int status = 0; + struct sl811_td *td; + int i; + + PDEBUG(4, "enter, urb = %p", urb); + + urb->actual_length = 0; + + i = 0; + head = &urbp->td_list; + tmp = head->next; + while (tmp != head) { + td = list_entry(tmp, struct sl811_td, td_list); + tmp = tmp->next; + + if (!td->done) { + if (urbp->unlink) + urb->status = -ENOENT; + else { + PDEBUG(1, "we should not get here!"); + urb->status = -EXDEV; + } + return ; + } + if (td->td_status) { + status = td->td_status; + urb->error_count++; + PDEBUG(1, "error: td = %p, td status = %d", td, td->td_status); + } + + urb->iso_frame_desc[i].actual_length = td->len - td->left; + urb->actual_length += td->len - td->left; + urb->iso_frame_desc[i].status = td->td_status; + ++i; + if (td->left) + PDEBUG(3, "short packet, td = %p, len = %d, left = %d", td, td->len, td->left); + } + + urb->status = status; +/* +// for debug + PDEBUG(2, "iso urb complete, len = %d, status =%d ", urb->actual_length, urb->status); +*/ + PDEBUG(4, "leave success"); +} + +/* + * Interrupt transfers + */ +static int sl811_submit_interrupt(struct urb *urb) +{ + int maxsze = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); + int len = urb->transfer_buffer_length; + __u8 *data = urb->transfer_buffer; + __u8 dev = usb_pipedevice(urb->pipe); + __u8 pidep = PIDEP(usb_packetid(urb->pipe), usb_pipeendpoint(urb->pipe)); + __u8 ctrl = 0; + struct sl811_hc *hc = urb->dev->bus->hcpriv; + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td = NULL; + + PDEBUG(4, "enter, urb = %p", urb); + + if (len > maxsze) { + PDEBUG(1, "length is big than max packet size, len = %d, max packet = %d", len, maxsze); + return -EINVAL; + } + if (usb_pipeslow(urb->pipe) && !(hc->rh_status.wPortStatus & USB_PORT_STAT_LOW_SPEED)) + ctrl |= SL811_USB_CTRL_PREAMBLE; + + ctrl |= SL811_USB_CTRL_ARM | SL811_USB_CTRL_ENABLE; + if (usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe))) + ctrl |= SL811_USB_CTRL_TOGGLE_1; + usb_dotoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe)); + td = sl811_alloc_td(urb); + if (!td) + return -ENOMEM; + + sl811_fill_td(td, ctrl, SL811_DATA_START, len, pidep, dev, data); + sl811_calc_td_time(td); + urbp->cur_td = urbp->first_td = urbp->last_td = td; + urbp->interval = 0; + + PDEBUG(4, "leave success"); + + return 0; +} + +/* + * Reset interrupt transfers + */ +static void sl811_reset_interrupt(struct urb *urb) +{ + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td = urbp->cur_td; + + PDEBUG(4, "enter, interval = %d", urb->interval); + + td->ctrl &= ~SL811_USB_CTRL_TOGGLE_1; + if (usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe))) + td->ctrl |= SL811_USB_CTRL_TOGGLE_1; + usb_dotoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe)); + + sl811_reset_td(td); + + urbp->interval = urb->interval; + + urb->status = -EINPROGRESS; + urb->actual_length = 0; +} + +/* + * Result the interrupt urb. + */ +static void sl811_result_interrupt(struct urb *urb) +{ + struct list_head *tmp; + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td; + int toggle; + + PDEBUG(4, "enter, urb = %p", urb); + + urb->actual_length = 0; + + tmp = &urbp->td_list; + tmp = tmp->next; + td = list_entry(tmp, struct sl811_td, td_list); + + // success. + if (td->done && td->td_status == 0) { + urb->actual_length += td->len - td->left; + urb->status = 0; + return ; + } + // tranfer is done but fail, reset the toggle. + else if (td->done && td->td_status) { + urb->status = td->td_status; +reset_toggle: + toggle = (td->ctrl & SL811_USB_CTRL_TOGGLE_1) ? 1 : 0; + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe), toggle); + PDEBUG(3, "error: td = %p, td status = %d", td, td->td_status); + return ; + } + // unlink, and not do transfer yet + else if (td->done == 0 && urbp->unlink && td->td_status == 0) { + urb->status = -ENOENT; + PDEBUG(3, "unlink and not transfer!"); + return ; + } + // unlink, and transfer not complete yet. + else if (td->done == 0 && urbp->unlink && td->td_status) { + urb->status = -ENOENT; + PDEBUG(3, "unlink and not complete!"); + goto reset_toggle; + } + // must be bug!!! + else {// (td->done == 0 && urbp->unlink == 0) + PDEBUG(1, "we should not get here!"); + urb->status = -EPIPE; + return ; + } +} + +/* + * Control transfers + */ +static int sl811_submit_control(struct urb *urb) +{ + int maxsze = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); + int len = urb->transfer_buffer_length; + __u8 *data = urb->transfer_buffer; + __u8 dev = usb_pipedevice(urb->pipe); + __u8 pidep = 0; + __u8 ctrl = 0; + struct sl811_hc *hc = urb->dev->bus->hcpriv; + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td = NULL; + + PDEBUG(4, "enter, urb = %p", urb); + + if (usb_pipeslow(urb->pipe) && !(hc->rh_status.wPortStatus & USB_PORT_STAT_LOW_SPEED)) + ctrl |= SL811_USB_CTRL_PREAMBLE; + + /* Build SETUP TD */ + pidep = PIDEP(USB_PID_SETUP, usb_pipeendpoint(urb->pipe)); + ctrl |= SL811_USB_CTRL_ARM | SL811_USB_CTRL_ENABLE | SL811_USB_CTRL_DIR_OUT; + td = sl811_alloc_td(urb); + if (!td) + return -ENOMEM; + + sl811_fill_td(td, ctrl, SL811_DATA_START, 8, pidep, dev, urb->setup_packet); + sl811_calc_td_time(td); + + urbp->cur_td = urbp->first_td = td; + + /* + * If direction is "send", change the frame from SETUP (0x2D) + * to OUT (0xE1). Else change it from SETUP to IN (0x69). + */ + pidep = PIDEP(usb_packetid(urb->pipe), usb_pipeendpoint(urb->pipe)); + if (usb_pipeout(urb->pipe)) + ctrl |= SL811_USB_CTRL_DIR_OUT; + else + ctrl &= ~SL811_USB_CTRL_DIR_OUT; + + /* Build the DATA TD's */ + while (len > 0) { + int pktsze = len; + + if (pktsze > maxsze) + pktsze = maxsze; + + /* Alternate Data0/1 (start with Data1) */ + ctrl ^= SL811_USB_CTRL_TOGGLE_1; + + td = sl811_alloc_td(urb); + if (!td) + return -ENOMEM; + + sl811_fill_td(td, ctrl, SL811_DATA_START, pktsze, pidep, dev, data); + sl811_calc_td_time(td); + + data += pktsze; + len -= pktsze; + } + + /* Build the final TD for control status */ + td = sl811_alloc_td(urb); + if (!td) + return -ENOMEM; + + /* It's IN if the pipe is an output pipe or we're not expecting data back */ + if (usb_pipeout(urb->pipe) || !urb->transfer_buffer_length) { + pidep = PIDEP(USB_PID_IN, usb_pipeendpoint(urb->pipe)); + ctrl &= ~SL811_USB_CTRL_DIR_OUT; + } else { + pidep = PIDEP(USB_PID_OUT, usb_pipeendpoint(urb->pipe)); + ctrl |= SL811_USB_CTRL_DIR_OUT; + } + + /* End in Data1 */ + ctrl |= SL811_USB_CTRL_TOGGLE_1; + + sl811_fill_td(td, ctrl, SL811_DATA_START, 0, pidep, dev, 0); + sl811_calc_td_time(td); + urbp->last_td = td; +/* +// for debug + { + struct list_head *head, *tmp; + struct sl811_td *td; + int i = 0; + head = &urbp->td_list; + tmp = head->next; + + if (list_empty(&urbp->td_list)) { + PDEBUG(1, "bug!!! td list is empty!"); + return -ENODEV; + } + + while (tmp != head) { + ++i; + td = list_entry(tmp, struct sl811_td, td_list); + PDEBUG(3, "td = %p, i = %d", td, i); + tmp = tmp->next; + } + } +*/ + PDEBUG(4, "leave success"); + + return 0; +} + +/* + * Result the control urb. + */ +static void sl811_result_control(struct urb *urb) +{ + struct list_head *tmp, *head; + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td; + + PDEBUG(4, "enter, urb = %p", urb); + + if (list_empty(&urbp->td_list)) { + PDEBUG(1, "td list is empty"); + return ; + } + + head = &urbp->td_list; + + tmp = head->next; + td = list_entry(tmp, struct sl811_td, td_list); + + /* The first TD is the SETUP phase, check the status, but skip the count */ + if (!td->done) { + PDEBUG(3, "setup phase error, td = %p, done = %d", td, td->done); + goto err_done; + } + if (td->td_status) { + PDEBUG(3, "setup phase error, td = %p, td status = %d", td, td->td_status); + goto err_status; + } + + urb->actual_length = 0; + + /* The rest of the TD's (but the last) are data */ + tmp = tmp->next; + while (tmp != head && tmp->next != head) { + td = list_entry(tmp, struct sl811_td, td_list); + tmp = tmp->next; + if (!td->done) { + PDEBUG(3, "data phase error, td = %p, done = %d", td, td->done); + goto err_done; + } + if (td->td_status) { + PDEBUG(3, "data phase error, td = %p, td status = %d", td, td->td_status); + goto err_status; + } + + urb->actual_length += td->len - td->left; + // short packet. + if (td->left) { + PDEBUG(3, "data phase short packet, td = %p, count = %d", td, td->len - td->left); + break; + } + } + + /* The last td is status phase */ + td = urbp->last_td; + if (!td->done) { + PDEBUG(3, "status phase error, td = %p, done = %d", td, td->done); + goto err_done; + } + if (td->td_status) { + PDEBUG(3, "status phase error, td = %p, td status = %d", td, td->td_status); + goto err_status; + } + + PDEBUG(4, "leave success"); + + urb->status = 0; + return ; + +err_done: + if (urbp->unlink) + urb->status = -ENOENT; + else { + PDEBUG(1, "we should not get here! td = %p", td); + urb->status = -EPIPE; + } + return ; + +err_status: + urb->status = td->td_status; + return ; +} + +/* + * Bulk transfers + */ +static int sl811_submit_bulk(struct urb *urb) +{ + int maxsze = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); + int len = urb->transfer_buffer_length; + __u8 *data = urb->transfer_buffer; + __u8 dev = usb_pipedevice(urb->pipe); + __u8 pidep = PIDEP(usb_packetid(urb->pipe), usb_pipeendpoint(urb->pipe)); + __u8 ctrl = 0; + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td = NULL; + + PDEBUG(4, "enter, urb = %p", urb); + + if (len < 0) { + PDEBUG(1, "error, urb = %p, len = %d", urb, len); + return -EINVAL; + } + + /* Can't have low speed bulk transfers */ + if (usb_pipeslow(urb->pipe)) { + PDEBUG(1, "error, urb = %p, low speed device", urb); + return -EINVAL; + } + + if (usb_pipeout(urb->pipe)) + ctrl |= SL811_USB_CTRL_DIR_OUT; + + ctrl |= SL811_USB_CTRL_ARM | SL811_USB_CTRL_ENABLE; + + /* Build the DATA TD's */ + do { /* Allow zero length packets */ + int pktsze = len; + + if (pktsze > maxsze) + pktsze = maxsze; + + td = sl811_alloc_td(urb); + if (!td) + return -ENOMEM; + + /* Alternate Data0/1 (start with Data1) */ + ctrl &= ~SL811_USB_CTRL_TOGGLE_1; + if (usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe))) + ctrl |= SL811_USB_CTRL_TOGGLE_1; + usb_dotoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe)); + + sl811_fill_td(td, ctrl, SL811_DATA_START, pktsze, pidep, dev, data); + sl811_calc_td_time(td); + + if (urbp->cur_td == NULL) + urbp->cur_td = urbp->first_td = td; + + data += pktsze; + len -= maxsze; + } while (len > 0); + + /* + * USB_ZERO_PACKET means adding a 0-length packet, if + * direction is OUT and the transfer_length was an + * exact multiple of maxsze, hence + * (len = transfer_length - N * maxsze) == 0 + * however, if transfer_length == 0, the zero packet + * was already prepared above. + */ + if (usb_pipeout(urb->pipe) && (urb->transfer_flags & USB_ZERO_PACKET) && + !len && urb->transfer_buffer_length) { + + td = sl811_alloc_td(urb); + if (!td) + return -ENOMEM; + + /* Alternate Data0/1 (start with Data1) */ + ctrl &= ~SL811_USB_CTRL_TOGGLE_1; + if (usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe))) + ctrl |= SL811_USB_CTRL_TOGGLE_1; + usb_dotoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe)); + + sl811_fill_td(td, ctrl, SL811_DATA_START, 0, pidep, dev, 0); + sl811_calc_td_time(td); + } + + urbp->last_td = td; + + PDEBUG(4, "leave success"); + + return 0; +} + +/* + * Reset bulk transfers + */ +static int sl811_reset_bulk(struct urb *urb) +{ + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td; + struct list_head *head, *tmp; + + PDEBUG(4, "enter, urb = %p", urb); + + + head = &urbp->td_list; + tmp = head->next; + + while (tmp != head) { + td = list_entry(tmp, struct sl811_td, td_list); + + /* Alternate Data0/1 (start with Data1) */ + td->ctrl &= ~SL811_USB_CTRL_TOGGLE_1; + if (usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe))) + td->ctrl |= SL811_USB_CTRL_TOGGLE_1; + usb_dotoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe)); + + sl811_reset_td(td); + } + + urb->status = -EINPROGRESS; + urb->actual_length = 0; + urbp->cur_td = urbp->first_td; + + PDEBUG(4, "leave success"); + + return 0; +} + +/* + * Result the bulk urb. + */ +static void sl811_result_bulk(struct urb *urb) +{ + struct list_head *tmp, *head; + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td = NULL; + int toggle; + + PDEBUG(4, "enter, urb = %p", urb); + + urb->actual_length = 0; + + head = &urbp->td_list; + tmp = head->next; + while (tmp != head) { + td = list_entry(tmp, struct sl811_td, td_list); + tmp = tmp->next; + + // success. + if (td->done && td->td_status == 0) { + urb->actual_length += td->len - td->left; + + // short packet + if (td->left) { + urb->status = 0; + PDEBUG(3, "short packet, td = %p, count = %d", td, td->len - td->left); + goto reset_toggle; + } + } + // tranfer is done but fail, reset the toggle. + else if (td->done && td->td_status) { + urb->status = td->td_status; + PDEBUG(3, "error: td = %p, td status = %d", td, td->td_status); + goto reset_toggle; + } + // unlink, and not do transfer yet + else if (td->done == 0 && urbp->unlink && td->td_status == 0) { + urb->status = -ENOENT; + PDEBUG(3, "unlink and not transfer!"); + return ; + } + // unlink, and transfer not complete yet. + else if (td->done == 0 && urbp->unlink && td->td_status) { + PDEBUG(3, "unlink and not complete!"); + urb->status = -ENOENT; + goto reset_toggle; + } + // must be bug!!! + else {// (td->done == 0 && urbp->unlink == 0) + urb->status = -EPIPE; + PDEBUG(1, "we should not get here!"); + return ; + } + } + + PDEBUG(4, "leave success"); + urb->status = 0; + return ; + +reset_toggle: + toggle = (td->ctrl & SL811_USB_CTRL_TOGGLE_1) ? 1 : 0; + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), usb_pipeout(urb->pipe), toggle); +} + +/* + * Find the first urb have the same dev and endpoint. + */ +static inline int sl811_find_same_urb(struct list_head *head, struct urb *urb) +{ + struct list_head *tmp; + struct urb *u; + + if (!head || !urb) + return 0; + + tmp = head->next; + + while (tmp != head) { + u = list_entry(tmp, struct urb, urb_list); + if (u == urb) + return 1; + tmp = tmp->next; + } + + return 0; +} + +/* + * Find the first urb have the same dev and endpoint. + */ +static inline struct urb* sl811_find_same_devep(struct list_head *head, struct urb *urb) +{ + struct list_head *tmp; + struct urb *u; + + if (!head || !urb) + return NULL; + + tmp = head->next; + + while (tmp != head) { + u = list_entry(tmp, struct urb, urb_list); + if ((usb_pipe_endpdev(u->pipe)) == (usb_pipe_endpdev(urb->pipe))) + return u; + tmp = tmp->next; + } + + return NULL; +} + +/* + * This function is called by the USB core API when an URB is available to + * process. + */ +static int sl811_submit_urb(struct urb *urb) +{ + struct sl811_hc *hc = urb->dev->bus->hcpriv; + unsigned int pipe = urb->pipe; + struct list_head *head = NULL; + unsigned long flags; + int bustime; + int ret = 0; + + if (!urb) { + PDEBUG(1, "urb is null"); + return -EINVAL; + } + + if (urb->hcpriv) { + PDEBUG(1, "urbp is not null, urb = %p, urbp = %p", urb, urb->hcpriv); + return -EINVAL; + } + + if (!urb->dev || !urb->dev->bus || !hc) { + PDEBUG(1, "dev or bus or hc is null"); + return -ENODEV; + } + + if (usb_endpoint_halted(urb->dev, usb_pipeendpoint(pipe), usb_pipeout(pipe))) { + PDEBUG(2, "sl811_submit_urb: endpoint_halted"); + return -EPIPE; + } + + if (usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)) > SL811_DATA_LIMIT) { + printk(KERN_ERR "Packet size is big for SL811, should < %d!\n", SL811_DATA_LIMIT); + return -EINVAL; + } + + /* a request to the virtual root hub */ + if (usb_pipedevice(pipe) == hc->rh.devnum) + return sl811_rh_submit_urb(urb); + + spin_lock_irqsave(&hc->hc_lock, flags); + spin_lock(&urb->lock); + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + head = &hc->iso_list; + break; + case PIPE_INTERRUPT: + head = &hc->intr_list; + break; + case PIPE_CONTROL: + head = &hc->ctrl_list; + break; + case PIPE_BULK: + head = &hc->bulk_list; + break; + } + + if (sl811_find_same_devep(head, urb)) { + list_add(&urb->urb_list, &hc->wait_list); + PDEBUG(4, "add to wait list"); + goto out_unlock; + } + + if (!sl811_alloc_urb_priv(urb)) { + ret = -ENOMEM; + goto out_unlock; + } + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + if (urb->number_of_packets <= 0) { + ret = -EINVAL; + break; + } + bustime = usb_check_bandwidth(urb->dev, urb); + if (bustime < 0) { + ret = bustime; + break; + } + if (!(ret = sl811_submit_isochronous(urb))) + usb_claim_bandwidth(urb->dev, urb, bustime, 1); + break; + case PIPE_INTERRUPT: + bustime = usb_check_bandwidth(urb->dev, urb); + if (bustime < 0) + ret = bustime; + else if (!(ret = sl811_submit_interrupt(urb))) + usb_claim_bandwidth(urb->dev, urb, bustime, 0); + break; + case PIPE_CONTROL: + ret = sl811_submit_control(urb); + break; + case PIPE_BULK: + ret = sl811_submit_bulk(urb); + break; + } + + if (!ret) { + ((struct sl811_urb_priv *)urb->hcpriv)->inserttime = jiffies; + list_add(&urb->urb_list, head); + PDEBUG(4, "add to type list"); + urb->status = -EINPROGRESS; + if (++hc->active_urbs == 1) + sl811_enable_interrupt(hc); + goto out_unlock; + } else { + PDEBUG(2, "submit urb fail! error = %d", ret); + sl811_free_urb_priv(urb); + } + +out_unlock: + spin_unlock(&urb->lock); + spin_unlock_irqrestore(&hc->hc_lock, flags); + + return ret; +} + +/* + * Submit the urb the wait list. + */ +static int sl811_submit_urb_with_lock(struct urb *urb) +{ + struct sl811_hc *hc = urb->dev->bus->hcpriv; + struct list_head *head = NULL; + int bustime; + int ret = 0; + + spin_lock(&urb->lock); + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + head = &hc->iso_list; + break; + case PIPE_INTERRUPT: + head = &hc->intr_list; + break; + case PIPE_CONTROL: + head = &hc->ctrl_list; + break; + case PIPE_BULK: + head = &hc->bulk_list; + break; + } + + if (!sl811_alloc_urb_priv(urb)) { + ret = -ENOMEM; + goto out_unlock; + } + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + if (urb->number_of_packets <= 0) { + ret = -EINVAL; + break; + } + bustime = usb_check_bandwidth(urb->dev, urb); + if (bustime < 0) { + ret = bustime; + break; + } + if (!(ret = sl811_submit_isochronous(urb))) + usb_claim_bandwidth(urb->dev, urb, bustime, 1); + break; + case PIPE_INTERRUPT: + bustime = usb_check_bandwidth(urb->dev, urb); + if (bustime < 0) + ret = bustime; + else if (!(ret = sl811_submit_interrupt(urb))) + usb_claim_bandwidth(urb->dev, urb, bustime, 0); + break; + case PIPE_CONTROL: + ret = sl811_submit_control(urb); + break; + case PIPE_BULK: + ret = sl811_submit_bulk(urb); + break; + } + + if (ret == 0) { + ((struct sl811_urb_priv *)urb->hcpriv)->inserttime = jiffies; + list_add(&urb->urb_list, head); + PDEBUG(4, "add to type list"); + urb->status = -EINPROGRESS; + if (++hc->active_urbs == 1) + sl811_enable_interrupt(hc); + goto out_unlock; + } else { + PDEBUG(2, "submit urb fail! error = %d", ret); + sl811_free_urb_priv(urb); + } + +out_unlock: + spin_unlock(&urb->lock); + + return ret; +} + +/* + * Reset the urb + */ +static void sl811_reset_urb(struct urb *urb) +{ + struct sl811_urb_priv *urbp = urb->hcpriv; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + sl811_reset_isochronous(urb); + break; + case PIPE_INTERRUPT: + sl811_reset_interrupt(urb); + break; + case PIPE_CONTROL: + return; + case PIPE_BULK: + sl811_reset_bulk(urb); + break; + } + urbp->inserttime = jiffies; +} + +/* + * Return the result of a transfer + */ +static void sl811_result_urb(struct urb *urb) +{ + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_hc *hc = urb->dev->bus->hcpriv; + struct list_head *head = NULL; + struct urb *u = NULL; + int reset = 0; + int ring = 0; + + if (urb->status != -EINPROGRESS) { + PDEBUG(1, "urb status is not EINPROGRESS!"); + return ; + } + + spin_lock(&urb->lock); + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + head = &hc->iso_list; + sl811_result_isochronous(urb); + + // if the urb is not unlink and is in a urb "ring", we reset it + if (!urbp->unlink && urb->next) + ring = 1; + break; + case PIPE_INTERRUPT: + head = &hc->intr_list; + sl811_result_interrupt(urb); + + // if the urb is not unlink and not "once" query, we reset. + if (!urbp->unlink && urb->interval) + reset = 1; + break; + case PIPE_CONTROL: + head = &hc->ctrl_list; + sl811_result_control(urb); + break; + case PIPE_BULK: + head = &hc->bulk_list; + sl811_result_bulk(urb); + + // if the urb is not unlink and is in a urb "ring", we reset it + if (!urbp->unlink && urb->next) + ring = 1; + break; + } + + PDEBUG(4, "result urb status = %d", urb->status); + + if (ring && urb->next == urb) + reset = 1; + + if (!reset) { + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + usb_release_bandwidth(urb->dev, urb, 1); + break; + case PIPE_INTERRUPT: + usb_release_bandwidth(urb->dev, urb, 0); + break; + } + sl811_free_urb_priv(urb); + } + + spin_unlock(&urb->lock); + + if (urb->complete) + urb->complete(urb); + + if (reset) { + spin_lock(&urb->lock); + sl811_reset_urb(urb); + if (usb_pipeint(urb->pipe)) + list_add(&urb->urb_list, &hc->idle_intr_list); + else + list_add(&urb->urb_list, head); + spin_unlock(&urb->lock); + } else { + if (--hc->active_urbs <= 0) { + hc->active_urbs = 0; + sl811_disable_interrupt(hc); + } + + if (ring) + u = urb->next; + else + u = sl811_find_same_devep(&hc->wait_list, urb); + + if (u) { + if (!list_empty(&u->urb_list)) + list_del(&u->urb_list); + if (sl811_submit_urb_with_lock(u)) + list_add(&u->urb_list, &hc->wait_list); + } + } +} + + +#ifdef SL811_TIMEOUT + +/* + * Unlink the urb from the urb list + */ +static int sl811_unlink_urb(struct urb *urb) +{ + unsigned long flags; + struct sl811_hc *hc; + struct sl811_urb_priv *urbp; + int call = 0; + int schedule = 0; + int count = 0; + + if (!urb) { + PDEBUG(1, "urb is null"); + return -EINVAL; + } + + if (!urb->dev || !urb->dev->bus) { + PDEBUG(1, "dev or bus is null"); + return -ENODEV; + } + + hc = urb->dev->bus->hcpriv; + urbp = urb->hcpriv; + + /* a request to the virtual root hub */ + if (usb_pipedevice(urb->pipe) == hc->rh.devnum) + return sl811_rh_unlink_urb(urb); + + spin_lock_irqsave(&hc->hc_lock, flags); + spin_lock(&urb->lock); + + // in wait list + if (sl811_find_same_urb(&hc->wait_list, urb)) { + PDEBUG(4, "unlink urb in wait list"); + list_del_init(&urb->urb_list); + urb->status = -ENOENT; + call = 1; + goto out; + } + + // in intr idle list. + if (sl811_find_same_urb(&hc->idle_intr_list, urb)) { + PDEBUG(4, "unlink urb in idle intr list"); + list_del_init(&urb->urb_list); + urb->status = -ENOENT; + sl811_free_urb_priv(urb); + usb_release_bandwidth(urb->dev, urb, 0); + if (--hc->active_urbs <= 0) { + hc->active_urbs = 0; + sl811_disable_interrupt(hc); + } + call = 1; + goto out; + } + + if (urb->status == -EINPROGRESS) { + PDEBUG(3, "urb is still in progress"); + urbp->unlink = 1; + +re_unlink: + // Is it in progress? + urbp = urb->hcpriv; + if (urbp && hc->cur_td == urbp->cur_td) { + ++count; + if (sl811_read(hc, 0) & SL811_USB_CTRL_ARM) { + PDEBUG(3, "unlink: cur td is still in progress! count = %d", count); +re_schedule: + schedule = 1; + spin_unlock(&urb->lock); + spin_unlock_irqrestore(&hc->hc_lock, flags); + schedule_timeout(HZ/50); + spin_lock_irqsave(&hc->hc_lock, flags); + spin_lock(&urb->lock); + } else { + PDEBUG(3, "unlink: lost of interrupt? do parse! count = %d", count); + spin_unlock(&urb->lock); + sl811_transfer_done(hc, 0); + spin_lock(&urb->lock); + } + goto re_unlink; + } + + if (list_empty(&urb->urb_list)) { + PDEBUG(3, "unlink: list empty!"); + goto out; + } + + if (urb->transfer_flags & USB_TIMEOUT_KILLED) { + PDEBUG(3, "unlink: time out killed"); + // it is timeout killed by us + goto result; + } else if (urb->transfer_flags & USB_ASYNC_UNLINK) { + // we do nothing, just let it be processing later + PDEBUG(3, "unlink async, do nothing"); + goto out; + } else { + // synchron without callback + PDEBUG(3, "unlink synchron, we wait the urb complete or timeout"); + if (schedule == 0) { + PDEBUG(3, "goto re_schedule"); + goto re_schedule; + } else { + PDEBUG(3, "already scheduled"); + goto result; + } + } + } else if (!list_empty(&urb->urb_list)) { + PDEBUG(1, "urb = %p, status = %d is in a list, why?", urb, urb->status); + //list_del_init(&urb->urb_list); + //call = 1; + } + +out: + spin_unlock(&urb->lock); + spin_unlock_irqrestore(&hc->hc_lock, flags); + + if (call && urb->complete) + urb->complete(urb); + + return 0; + +result: + spin_unlock(&urb->lock); + + list_del_init(&urb->urb_list); + sl811_result_urb(urb); + + spin_unlock_irqrestore(&hc->hc_lock, flags); + + return 0; +} + +#else + +/* + * Unlink the urb from the urb list + */ +static int sl811_unlink_urb(struct urb *urb) +{ + unsigned long flags; + struct sl811_hc *hc; + struct sl811_urb_priv *urbp; + int call = 0; + + if (!urb) { + PDEBUG(1, "urb is null"); + return -EINVAL; + } + + if (!urb->dev || !urb->dev->bus) { + PDEBUG(1, "dev or bus is null"); + return -ENODEV; + } + + hc = urb->dev->bus->hcpriv; + urbp = urb->hcpriv; + + /* a request to the virtual root hub */ + if (usb_pipedevice(urb->pipe) == hc->rh.devnum) + return sl811_rh_unlink_urb(urb); + + spin_lock_irqsave(&hc->hc_lock, flags); + spin_lock(&urb->lock); + + // in wait list + if (sl811_find_same_urb(&hc->wait_list, urb)) { + PDEBUG(2, "unlink urb in wait list"); + list_del_init(&urb->urb_list); + urb->status = -ENOENT; + call = 1; + goto out; + } + + if (urb->status == -EINPROGRESS) { + PDEBUG(2, "urb is still in progress"); + urbp->unlink = 1; + + // Is it in progress? + urbp = urb->hcpriv; + if (urbp && hc->cur_td == urbp->cur_td) { + // simple, let it out + PDEBUG(2, "unlink: cur td is still in progress!"); + hc->cur_td = NULL; + } + + goto result; + } else if (!list_empty(&urb->urb_list)) { + PDEBUG(1, "urb = %p, status = %d is in a list, why?", urb, urb->status); + list_del_init(&urb->urb_list); + if (urbp) + goto result; + else + call = 1; + } + +out: + spin_unlock(&urb->lock); + spin_unlock_irqrestore(&hc->hc_lock, flags); + + if (call && urb->complete) + urb->complete(urb); + + return 0; + +result: + spin_unlock(&urb->lock); + + list_del_init(&urb->urb_list); + sl811_result_urb(urb); + + spin_unlock_irqrestore(&hc->hc_lock, flags); + + return 0; +} + +#endif + +static int sl811_get_current_frame_number(struct usb_device *usb_dev) +{ + return ((struct sl811_hc *)(usb_dev->bus->hcpriv))->frame_number; +} + +static struct usb_operations sl811_device_operations = +{ + sl811_alloc_dev_priv, + sl811_free_dev_priv, + sl811_get_current_frame_number, + sl811_submit_urb, + sl811_unlink_urb +}; + +/* + * This functions transmit a td. + */ +static inline void sl811_trans_cur_td(struct sl811_hc *hc, struct sl811_td *td) +{ + sl811_print_td(4, td); + sl811_write_buf(hc, SL811_ADDR_A, &td->addr, 4); + if (td->len && (td->ctrl & SL811_USB_CTRL_DIR_OUT)) + sl811_write_buf(hc, td->addr, td->buf, td->len); + + sl811_write(hc, SL811_CTRL_A, td->ctrl); +} + + +/* + * This function checks the status of the transmitted or received packet + * and copy the data from the SL811HS register into a buffer. + */ +static void sl811_parse_cur_td(struct sl811_hc *hc, struct sl811_td *td) +{ + struct urb *urb = td->urb; +#ifdef SL811_DEBUG + int dev = usb_pipedevice(td->urb->pipe); + int ep = usb_pipeendpoint(td->urb->pipe); +#endif + + sl811_read_buf(hc, SL811_STS_A, &td->status, 2); + + if (td->status & SL811_USB_STS_ACK) { + td->done = 1; + +/* if ((td->ctrl & SL811_USB_CTRL_TOGGLE_1) != (td->status & SL811_USB_STS_TOGGLE_1)) { + PDEBUG(2, "dev %d endpoint %d unexpect data toggle!", dev, ep); + td->td_status = -EILSEQ; + } +*/ + if (!(td->ctrl & SL811_USB_CTRL_DIR_OUT) && td->len > 0) + sl811_read_buf(hc, td->addr, td->buf, td->len - td->left); + + if (td->left && (urb->transfer_flags & USB_DISABLE_SPD)) { + PDEBUG(2, "dev %d endpoint %d unexpect short packet! td = %p", dev, ep, td); + td->td_status = -EREMOTEIO; + } else + td->td_status = 0; + } else if (td->status & SL811_USB_STS_STALL) { + PDEBUG(2, "dev %d endpoint %d halt, td = %p", dev, ep, td); + td->td_status = -EPIPE; + if (urb->dev) + usb_endpoint_halt(td->urb->dev, usb_pipeendpoint(td->urb->pipe), usb_pipeout(td->urb->pipe)); + td->done = 1; + } else if (td->status & SL811_USB_STS_OVERFLOW) { + PDEBUG(1, "dev %d endpoint %d overflow, sl811 only support packet less than %d", dev, ep, SL811_DATA_LIMIT); + td->td_status = -EOVERFLOW; + td->done = 1; + } else if (td->status & SL811_USB_STS_TIMEOUT ) { + PDEBUG(2, "dev %d endpoint %d timeout, td = %p", dev, ep, td); + td->td_status = -ETIMEDOUT; + if (--td->errcnt == 0) + td->done = 1; + } else if (td->status & SL811_USB_STS_ERROR) { + PDEBUG(2, "dev %d endpoint %d error, td = %p", dev, ep, td); + td->td_status = -EILSEQ; + if (--td->errcnt == 0) + td->done = 1; + } else if (td->status & SL811_USB_STS_NAK) { + ++td->nakcnt; + PDEBUG(3, "dev %d endpoint %d nak, td = %p, count = %d", dev, ep, td, td->nakcnt); + td->td_status = -EINPROGRESS; + if (!usb_pipeslow(td->urb->pipe) && td->nakcnt > 1024) { + PDEBUG(2, "too many naks, td = %p, count = %d", td, td->nakcnt); + td->td_status = -ETIMEDOUT; + td->done = 1; + } + } + + sl811_print_td(4, td); +} + +/* + * This function checks the status of current urb. + */ +static int sl811_parse_cur_urb(struct urb *urb) +{ + struct sl811_urb_priv *urbp = urb->hcpriv; + struct sl811_td *td = urbp->cur_td; + struct list_head *tmp; + + sl811_print_td(5, td); + + // this td not done yet. + if (!td->done) + return 0; + + // the last ld, so the urb is done. + if (td == urbp->last_td) { + PDEBUG(4, "urb = %p is done success", td->urb); + if (usb_pipeisoc(td->urb->pipe)) + PDEBUG(4, "ISO URB DONE, td = %p", td); + return 1; + } + + // iso transfer, we always advance to next td + if (usb_pipeisoc(td->urb->pipe)) { + tmp = &td->td_list; + tmp = tmp->next; + urbp->cur_td = list_entry(tmp, struct sl811_td, td_list); + PDEBUG(4, "ISO NEXT, td = %p", urbp->cur_td); + return 0; + } + + // some error occur, so the urb is done. + if (td->td_status) { + PDEBUG(3, "urb = %p is done error, td status is = %d", td->urb, td->td_status); + return 1; + } + + // short packet. + if (td->left) { + if (usb_pipecontrol(td->urb->pipe)) { + // control packet, we advance to the last td + PDEBUG(3, "ctrl short packet, advance to last td"); + urbp->cur_td = urbp->last_td; + return 0; + } else { + // interrut and bulk packet, urb is over. + PDEBUG(3, "bulk or intr short packet, urb is over"); + return 1; + } + } + + // we advance to next td. + tmp = &td->td_list; + tmp = tmp->next; + urbp->cur_td = list_entry(tmp, struct sl811_td, td_list); +#ifdef SL811_DEBUG + PDEBUG(4, "advance to the next td, urb = %p, td = %p", urb, urbp->cur_td); + sl811_print_td(5, urbp->cur_td); + if (td == urbp->cur_td) + PDEBUG(1, "bug!!!"); +#endif + return 0; +} + +/* + * Find the next td to transfer. + */ +static inline struct sl811_td* sl811_schedule_next_td(struct urb *urb, struct sl811_td *cur_td) +{ + struct sl811_urb_priv *urbp = urb->hcpriv; + + PDEBUG(4, "urb at %p, cur td at %p", urb, cur_td); + + // iso don't schedule the td in the same frame. + if (usb_pipeisoc(cur_td->urb->pipe)) + return NULL; + + // cur td is not complete + if (!cur_td->done) + return NULL; + + // here, urbp->cur_td is already the next td; + return urbp->cur_td; +} + +/* + * Scan the list to find a active urb + */ +static inline struct urb* sl811_get_list_next_urb(struct sl811_hc *hc, struct list_head *next) +{ + struct urb *urb; + int i; + + if (list_empty(next)) + return NULL; + + if (next == hc->cur_list) + return NULL; + + for (i = 0; i < 4; ++i) + if (next == &hc->urb_list[i]) + return NULL; + + urb = list_entry(next, struct urb, urb_list); + PDEBUG(4, "next urb in list is at %p", urb); + + return urb; +} + +/* + * Find the next td to transfer. + */ +static struct sl811_td* sl811_schedule_next_urb(struct sl811_hc *hc, struct list_head *next) +{ + struct urb *urb = NULL; + int back_loop = 1; + struct list_head *old_list = hc->cur_list; + + // try to get next urb in the same list. + if (next) { + urb = sl811_get_list_next_urb(hc, next); + if (!urb) + ++hc->cur_list; + } + + // try other list. + if (!urb) { +re_loop: + // try all the list. + while (hc->cur_list < &hc->urb_list[4]) { + if ((urb = sl811_get_list_next_urb(hc, hc->cur_list->next))) + return ((struct sl811_urb_priv *)urb->hcpriv)->cur_td; + ++hc->cur_list; + } + // the last list is try + if (back_loop && (old_list >= &hc->ctrl_list)) { + hc->cur_list = &hc->ctrl_list; + back_loop = 0; + goto re_loop; + } + } + + if (hc->cur_list > &hc->urb_list[3]) + hc->cur_list = &hc->ctrl_list; + + return NULL; +} + +/* + * This function process the transfer rusult. + */ +static void sl811_transfer_done(struct sl811_hc *hc, int sof) +{ + struct sl811_td *cur_td = hc->cur_td, *next_td = NULL; + struct urb *cur_urb = NULL; + struct list_head *next = NULL; + int done; + + PDEBUG(5, "enter"); + + if (cur_td == NULL) { + PDEBUG(1, "in done interrupt, but td is null, be already parsed?"); + return ; + } + + cur_urb = cur_td->urb; + hc->cur_td = NULL; + next = &cur_urb->urb_list; + next = next->next; + + spin_lock(&cur_urb->lock); + sl811_parse_cur_td(hc, cur_td); + done = sl811_parse_cur_urb(cur_urb); + spin_unlock(&cur_urb->lock); + + if (done) { + list_del_init(&cur_urb->urb_list); + cur_td = NULL; + sl811_result_urb(cur_urb); + } + + if (sof) + return ; + + if (!done) { + next_td = sl811_schedule_next_td(cur_urb, cur_td); + if (next_td && next_td != cur_td && (sl811_calc_bus_remainder(hc) > next_td->bustime)) { + hc->cur_td = next_td; + PDEBUG(5, "ADD TD"); + sl811_trans_cur_td(hc, next_td); + return ; + } + } + + while (1) { + next_td = sl811_schedule_next_urb(hc, next); + if (!next_td) + return; + if (next_td == cur_td) + return; + next = &next_td->urb->urb_list; + next = next->next; + if (sl811_calc_bus_remainder(hc) > next_td->bustime) { + hc->cur_td = next_td; + PDEBUG(5, "ADD TD"); + sl811_trans_cur_td(hc, next_td); + return ; + } + } +} + +/* + * + */ +static void inline sl811_dec_intr_interval(struct sl811_hc *hc) +{ + struct list_head *head, *tmp; + struct urb *urb; + struct sl811_urb_priv *urbp; + + if (list_empty(&hc->idle_intr_list)) + return ; + + head = &hc->idle_intr_list; + tmp = head->next; + + while (tmp != head) { + urb = list_entry(tmp, struct urb, urb_list); + tmp = tmp->next; + spin_lock(&urb->lock); + urbp = urb->hcpriv; + if (--urbp->interval == 0) { + list_del(&urb->urb_list); + list_add(&urb->urb_list, &hc->intr_list); + PDEBUG(4, "intr urb active"); + } + spin_unlock(&urb->lock); + } +} + +/* + * The sof interrupt is happen. + */ +static void sl811_start_sof(struct sl811_hc *hc) +{ + struct sl811_td *next_td; +#ifdef SL811_DEBUG + static struct sl811_td *repeat_td = NULL; + static repeat_cnt = 1; +#endif + if (++hc->frame_number > 1024) + hc->frame_number = 0; + + if (hc->active_urbs == 0) + return ; + + sl811_dec_intr_interval(hc); + + if (hc->cur_td) { + if (sl811_read(hc, 0) & SL811_USB_CTRL_ARM) { +#ifdef SL811_DEBUG + if (repeat_td == hc->cur_td) + ++repeat_cnt; + else { + if (repeat_cnt >= 2) + PDEBUG(2, "cur td = %p repeat %d", hc->cur_td, repeat_cnt); + repeat_cnt = 1; + repeat_td = hc->cur_td; + } +#endif + return ; + } else { + PDEBUG(2, "lost of interrupt in sof? do parse!"); + sl811_transfer_done(hc, 1); + + // let this frame idle + return; + } + } + + hc->cur_list = &hc->iso_list; + + if (hc->active_urbs == 0) + return ; + + next_td = sl811_schedule_next_urb(hc, NULL); + if (!next_td) { +#ifdef SL811_DEBUG + if (list_empty(&hc->idle_intr_list)) + PDEBUG(2, "not schedule a td, why? urbs = %d", hc->active_urbs); +#endif + return; + } + if (sl811_calc_bus_remainder(hc) > next_td->bustime) { + hc->cur_td = next_td; + sl811_trans_cur_td(hc, next_td); + } else + PDEBUG(2, "bus time if not enough, why?"); +} + +/* + * This function resets SL811HS controller and detects the speed of + * the connecting device + * + * Return: 0 = no device attached; 1 = USB device attached + */ +static int sl811_hc_reset(struct sl811_hc *hc) +{ + int status ; + + sl811_write(hc, SL811_CTRL2, SL811_CTL2_HOST | SL811_12M_HI); + sl811_write(hc, SL811_CTRL1, SL811_CTRL1_RESET); + + mdelay(20); + + // Disable hardware SOF generation. + sl811_write(hc, SL811_CTRL1, 0); + mdelay(2); + sl811_write(hc, SL811_INTRSTS, 0xff); + status = sl811_read(hc, SL811_INTRSTS); + + if (status & SL811_INTR_NOTPRESENT) { + // Device is not present + PDEBUG(0, "Device not present"); + hc->rh_status.wPortStatus &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE); + hc->rh_status.wPortChange |= USB_PORT_STAT_C_CONNECTION; + sl811_write(hc, SL811_INTR, SL811_INTR_INSRMV); + return 0; + } + + // Send SOF to address 0, endpoint 0. + sl811_write(hc, SL811_LEN_B, 0); + sl811_write(hc, SL811_PIDEP_B, PIDEP(USB_PID_SOF, 0)); + sl811_write(hc, SL811_DEV_B, 0x00); + sl811_write (hc, SL811_SOFLOW, SL811_12M_HI); + + if (status & SL811_INTR_SPEED_FULL) { + /* full speed device connect directly to root hub */ + PDEBUG (0, "Full speed Device attached"); + + sl811_write(hc, SL811_CTRL1, SL811_CTRL1_RESET); + mdelay(20); + sl811_write(hc, SL811_CTRL2, SL811_CTL2_HOST | SL811_12M_HI); + sl811_write(hc, SL811_CTRL1, SL811_CTRL1_SOF); + + /* start the SOF or EOP */ + sl811_write(hc, SL811_CTRL_B, SL811_USB_CTRL_ARM); + hc->rh_status.wPortStatus |= USB_PORT_STAT_CONNECTION; + hc->rh_status.wPortStatus &= ~USB_PORT_STAT_LOW_SPEED; + mdelay(2); + sl811_write (hc, SL811_INTRSTS, 0xff); + } else { + /* slow speed device connect directly to root-hub */ + PDEBUG(0, "Low speed Device attached"); + + sl811_write(hc, SL811_CTRL1, SL811_CTRL1_RESET); + mdelay(20); + sl811_write(hc, SL811_CTRL2, SL811_CTL2_HOST | SL811_CTL2_DSWAP | SL811_12M_HI); + sl811_write(hc, SL811_CTRL1, SL811_CTRL1_SPEED_LOW | SL811_CTRL1_SOF); + + /* start the SOF or EOP */ + sl811_write(hc, SL811_CTRL_B, SL811_USB_CTRL_ARM); + hc->rh_status.wPortStatus |= USB_PORT_STAT_CONNECTION | USB_PORT_STAT_LOW_SPEED; + mdelay(2); + sl811_write(hc, SL811_INTRSTS, 0xff); + } + + hc->rh_status.wPortChange |= USB_PORT_STAT_C_CONNECTION; + sl811_write(hc, SL811_INTR, SL811_INTR_INSRMV); + + return 1; +} + +/* + * Interrupt service routine. + */ +static void sl811_interrupt(int irq, void *__hc, struct pt_regs * r) +{ + __u8 status; + struct sl811_hc *hc = __hc; + + status = sl811_read(hc, SL811_INTRSTS); + if (status == 0) + return ; + + sl811_write(hc, SL811_INTRSTS, 0xff); + + if (status & SL811_INTR_INSRMV) { + sl811_write(hc, SL811_INTR, 0); + sl811_write(hc, SL811_CTRL1, 0); + // wait for device stable + mdelay(100); + sl811_hc_reset(hc); + return ; + } + + spin_lock(&hc->hc_lock); + + if (status & SL811_INTR_DONE_A) { + if (status & SL811_INTR_SOF) { + sl811_transfer_done(hc, 1); + PDEBUG(4, "sof in done!"); + sl811_start_sof(hc); + } else + sl811_transfer_done(hc, 0); + } else if (status & SL811_INTR_SOF) + sl811_start_sof(hc); + + spin_unlock(&hc->hc_lock); + + return ; +} + +/* + * This function allocates all data structure and store in the + * private data structure. + * + * Return value : data structure for the host controller + */ +static struct sl811_hc* __devinit sl811_alloc_hc(void) +{ + struct sl811_hc *hc; + struct usb_bus *bus; + int i; + + PDEBUG(5, "enter"); + + hc = (struct sl811_hc *)kmalloc(sizeof(struct sl811_hc), GFP_KERNEL); + if (!hc) + return NULL; + + memset(hc, 0, sizeof(struct sl811_hc)); + + hc->rh_status.wPortStatus = USB_PORT_STAT_POWER; + hc->rh_status.wPortChange = 0; + + hc->active_urbs = 0; + INIT_LIST_HEAD(&hc->hc_hcd_list); + list_add(&hc->hc_hcd_list, &sl811_hcd_list); + + init_waitqueue_head(&hc->waitq); + + for (i = 0; i < 6; ++i) + INIT_LIST_HEAD(&hc->urb_list[i]); + + hc->cur_list = &hc->iso_list; + + bus = usb_alloc_bus(&sl811_device_operations); + if (!bus) { + kfree (hc); + return NULL; + } + + hc->bus = bus; + bus->hcpriv = hc; + + return hc; +} + +/* + * This function De-allocate all resources + */ +static void sl811_release_hc(struct sl811_hc *hc) +{ + PDEBUG(5, "enter"); + + /* disconnect all devices */ + if (hc->bus->root_hub) + usb_disconnect(&hc->bus->root_hub); + + if (hc->addr_io) + release_region(hc->addr_io, 1); + + if (hc->data_io) + release_region(hc->data_io, 1); + + if (hc->irq) + free_irq(hc->irq, hc); + + usb_deregister_bus(hc->bus); + usb_free_bus(hc->bus); + + list_del(&hc->hc_hcd_list); + INIT_LIST_HEAD(&hc->hc_hcd_list); + + kfree (hc); +} + +/* + * This function is board specific. It sets up the interrupt to + * be an edge trigger and trigger on the rising edge + */ +static void sl811_init_irq(void) +{ + GPDR &= ~(1<<23); + set_GPIO_IRQ_edge(1<<23, GPIO_RISING_EDGE); +} + +/* + * This function request IO memory regions, request IRQ, and + * allocate all other resources. + * + * Input: addr_io = first IO address + * data_io = second IO address + * irq = interrupt number + * + * Return: 0 = success or error condition + */ +static int __devinit sl811_found_hc(int addr_io, int data_io, int irq) +{ + struct sl811_hc *hc; + + PDEBUG(5, "enter"); + + hc = sl811_alloc_hc(); + if (!hc) + return -ENOMEM; + + sl811_init_irq(); + + if (!request_region(addr_io, 1, "SL811 USB HOST")) { + PDEBUG(1, "request address %d failed", addr_io); + sl811_release_hc(hc); + return -EBUSY; + } + hc->addr_io = addr_io; + + if (!request_region(data_io, 1, "SL811 USB HOST")) { + PDEBUG(1, "request address %d failed", data_io); + sl811_release_hc (hc); + return -EBUSY; + } + hc->data_io = data_io; + + usb_register_bus(hc->bus); + + if (request_irq(irq, sl811_interrupt, 0, "SL811", hc)) { + PDEBUG(1, "request interrupt %d failed", irq); + sl811_release_hc(hc); + return -EBUSY; + } + hc->irq = irq; + + printk(KERN_INFO __FILE__ ": USB SL811 at %x, addr2 = %x, IRQ %d\n", + addr_io, data_io, irq); + + if (sl811_reg_test(hc)) { + PDEBUG(1, "SL811 register test failed!"); + sl811_release_hc(hc); + return -ENODEV; + } + + sl811_hc_reset(hc); + sl811_connect_rh(hc); + + return 0; +} + +/* + * This is an init function, and it is the first function being called + * + * Return: 0 = success or error condition + */ +static int __init sl811_hcd_init(void) +{ + int ret; + + PDEBUG(5, "etner"); + + info(DRIVER_VERSION " : " DRIVER_DESC); + + ret = sl811_found_hc(sl811_addr_io, sl811_data_io, sl811_irq); + + return ret; +} + +/* + * This is a cleanup function, and it is called when module is unloaded. + */ +static void __exit sl811_hcd_cleanup(void) +{ + struct list_head *list = sl811_hcd_list.next; + struct sl811_hc *hc; + + PDEBUG(5, "enter"); + + for (; list != &sl811_hcd_list; ) { + hc = list_entry(list, struct sl811_hc, hc_hcd_list); + list = list->next; + sl811_release_hc(hc); + } +} + +module_init(sl811_hcd_init); +module_exit(sl811_hcd_cleanup); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); diff -Nru a/drivers/usb/host/sl811.h b/drivers/usb/host/sl811.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/host/sl811.h Mon Jul 14 10:04:12 2003 @@ -0,0 +1,177 @@ +#ifndef __LINUX_SL811_H +#define __LINUX_SL811_H + +//#define SL811_DEBUG + +#ifdef SL811_DEBUG + #define PDEBUG(level, fmt, args...) \ + if (debug >= (level)) info("[%s:%d] " fmt, \ + __PRETTY_FUNCTION__, __LINE__ , ## args) +#else + #define PDEBUG(level, fmt, args...) do {} while(0) +#endif + +//#define SL811_TIMEOUT + +/* Sl811 host control register */ +#define SL811_CTRL_A 0x00 +#define SL811_ADDR_A 0x01 +#define SL811_LEN_A 0x02 +#define SL811_STS_A 0x03 /* read */ +#define SL811_PIDEP_A 0x03 /* write */ +#define SL811_CNT_A 0x04 /* read */ +#define SL811_DEV_A 0x04 /* write */ +#define SL811_CTRL1 0x05 +#define SL811_INTR 0x06 +#define SL811_CTRL_B 0x08 +#define SL811_ADDR_B 0x09 +#define SL811_LEN_B 0x0A +#define SL811_STS_B 0x0B /* read */ +#define SL811_PIDEP_B 0x0B /* write */ +#define SL811_CNT_B 0x0C /* read */ +#define SL811_DEV_B 0x0C /* write */ +#define SL811_INTRSTS 0x0D /* write clears bitwise */ +#define SL811_HWREV 0x0E /* read */ +#define SL811_SOFLOW 0x0E /* write */ +#define SL811_SOFCNTDIV 0x0F /* read */ +#define SL811_CTRL2 0x0F /* write */ + +/* USB control register bits (addr 0x00 and addr 0x08) */ +#define SL811_USB_CTRL_ARM 0x01 +#define SL811_USB_CTRL_ENABLE 0x02 +#define SL811_USB_CTRL_DIR_OUT 0x04 +#define SL811_USB_CTRL_ISO 0x10 +#define SL811_USB_CTRL_SOF 0x20 +#define SL811_USB_CTRL_TOGGLE_1 0x40 +#define SL811_USB_CTRL_PREAMBLE 0x80 + +/* USB status register bits (addr 0x03 and addr 0x0B) */ +#define SL811_USB_STS_ACK 0x01 +#define SL811_USB_STS_ERROR 0x02 +#define SL811_USB_STS_TIMEOUT 0x04 +#define SL811_USB_STS_TOGGLE_1 0x08 +#define SL811_USB_STS_SETUP 0x10 +#define SL811_USB_STS_OVERFLOW 0x20 +#define SL811_USB_STS_NAK 0x40 +#define SL811_USB_STS_STALL 0x80 + +/* Control register 1 bits (addr 0x05) */ +#define SL811_CTRL1_SOF 0x01 +#define SL811_CTRL1_RESET 0x08 +#define SL811_CTRL1_JKSTATE 0x10 +#define SL811_CTRL1_SPEED_LOW 0x20 +#define SL811_CTRL1_SUSPEND 0x40 + +/* Interrut enable (addr 0x06) and interrupt status register bits (addr 0x0D) */ +#define SL811_INTR_DONE_A 0x01 +#define SL811_INTR_DONE_B 0x02 +#define SL811_INTR_SOF 0x10 +#define SL811_INTR_INSRMV 0x20 +#define SL811_INTR_DETECT 0x40 +#define SL811_INTR_NOTPRESENT 0x40 +#define SL811_INTR_SPEED_FULL 0x80 /* only in status reg */ + +/* HW rev and SOF lo register bits (addr 0x0E) */ +#define SL811_HWR_HWREV 0xF0 + +/* SOF counter and control reg 2 (addr 0x0F) */ +#define SL811_CTL2_SOFHI 0x3F +#define SL811_CTL2_DSWAP 0x40 +#define SL811_CTL2_HOST 0x80 + +/* Set up for 1-ms SOF time. */ +#define SL811_12M_LOW 0xE0 +#define SL811_12M_HI 0x2E + +#define SL811_DATA_START 0x10 +#define SL811_DATA_LIMIT 240 + + +/* Requests: bRequest << 8 | bmRequestType */ +#define RH_GET_STATUS 0x0080 +#define RH_CLEAR_FEATURE 0x0100 +#define RH_SET_FEATURE 0x0300 +#define RH_SET_ADDRESS 0x0500 +#define RH_GET_DESCRIPTOR 0x0680 +#define RH_SET_DESCRIPTOR 0x0700 +#define RH_GET_CONFIGURATION 0x0880 +#define RH_SET_CONFIGURATION 0x0900 +#define RH_GET_STATE 0x0280 +#define RH_GET_INTERFACE 0x0A80 +#define RH_SET_INTERFACE 0x0B00 +#define RH_SYNC_FRAME 0x0C80 + + +#define PIDEP(pid, ep) (((pid) & 0x0f) << 4 | (ep)) + +/* Virtual Root HUB */ +struct virt_root_hub { + int devnum; /* Address of Root Hub endpoint */ + void *urb; /* interrupt URB of root hub */ + int send; /* active flag */ + int interval; /* intervall of roothub interrupt transfers */ + struct timer_list rh_int_timer; /* intervall timer for rh interrupt EP */ +}; + +struct sl811_td { + /* hardware */ + __u8 ctrl; /* control register */ + + /* write */ + __u8 addr; /* base adrress register */ + __u8 len; /* base length register */ + __u8 pidep; /* PId and endpoint register */ + __u8 dev; /* device address register */ + + /* read */ + __u8 status; /* status register */ + __u8 left; /* transfer count register */ + + /* software */ + __u8 errcnt; /* error count, begin with 3 */ + __u8 done; /* is this td tranfer done */ + __u8 *buf; /* point to data buffer for tranfer */ + int bustime; /* the bus time need by this td */ + int td_status; /* the status of this td */ + int nakcnt; /* number of naks */ + struct urb *urb; /* the urb this td belongs to */ + struct list_head td_list; /* link to a list of the urb */ +}; + +struct sl811_urb_priv { + struct urb *urb; /* the urb this priv beloings to */ + struct list_head td_list; /* list of all the td of this urb */ + struct sl811_td *cur_td; /* current td is in processing or it will be */ + struct sl811_td *first_td; /* the first td of this urb */ + struct sl811_td *last_td; /* the last td of this urb */ + int interval; /* the query time value for intr urb */ + int unlink; /* is the this urb unlinked */ + unsigned long inserttime; /* the time when insert to list */ +}; + +struct sl811_hc { + spinlock_t hc_lock; /* Lock for this structure */ + + int irq; /* IRQ number this hc use */ + int addr_io; /* I/O address line address */ + int data_io; /* I/O data line address */ + struct virt_root_hub rh; /* root hub */ + struct usb_port_status rh_status;/* root hub port status */ + struct list_head urb_list[6]; /* set of urbs, the order is iso,intr,ctrl,bulk,inactive intr, wait */ + struct list_head *cur_list; /* the current list is in process */ + wait_queue_head_t waitq; /* deletion of URBs and devices needs a waitqueue */ + struct sl811_td *cur_td; /* point to the td is in process */ + struct list_head hc_hcd_list; /* list of all hci_hcd */ + struct usb_bus *bus; /* our bus */ + int active_urbs; /* total number of active usbs */ + int frame_number; /* the current frame number, we do't use it, any one need it? */ +}; + +#define iso_list urb_list[0] /* set of isoc urbs */ +#define intr_list urb_list[1] /* ordered (tree) set of int urbs */ +#define ctrl_list urb_list[2] /* set of ctrl urbs */ +#define bulk_list urb_list[3] /* set of bulk urbs */ +#define idle_intr_list urb_list[4] /* set of intr urbs in its idle time*/ +#define wait_list urb_list[5] /* set of wait urbs */ + +#endif