www.pudn.com > Linux2410_usb.rar > hc_isp116x.c
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------* * isp1161 USB HCD for Linux Version 0.8 (9/3/2001) * * Roman Weissgaerber weissg@vienna.at (C) 2001 * *-------------------------------------------------------------------------* * 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#include #include #include #include #include #include #include #include #include #include #include #include #include "hc_isp116x.h" static int hc_verbose = 1; static int hc_error_verbose = 0; static int urb_debug = 0; static int irq = 25; static unsigned long hcport = 0xf9080000; MODULE_PARM(hc_verbose,"i"); MODULE_PARM_DESC(hc_verbose,"verbose startup messages, default is 1 (yes)"); MODULE_PARM(hc_error_verbose,"i"); MODULE_PARM_DESC(hc_error_verbose,"verbose error/warning messages, default is 0 (no)"); MODULE_PARM(urb_debug,"i"); MODULE_PARM_DESC(urb_debug,"debug urb messages, default is 0 (no)"); MODULE_PARM(irq,"i"); MODULE_PARM_DESC(irq,"IRQ 3, 10 (default)"); MODULE_PARM(hcport,"i"); MODULE_PARM_DESC(hcport,"ISP116x PORT 0x290"); /*-------------------------------------------------------------------------* * Following from hc_simple.c Version 0.8 (9/3/2001) * Roman Weissgaerber weissg@vienna.at (C) 2001 *-------------------------------------------------------------------------*/ /* main lock for urb access */ static spinlock_t usb_urb_lock = SPIN_LOCK_UNLOCKED; /*-------------------------------------------------------------------------*/ #ifdef CONFIG_ARCTIC2 /* MVL-CEE */ #include static int hc_isp_suspend(struct device * dev, u32 state, u32 level); static int hc_isp_resume(struct device * dev, u32 level); static int hc_isp_scale(struct bus_op_point * op, u32 level); static struct device_driver hc_isp_driver_ldm = { name: "USB_PH_ISP1161", devclass: NULL, probe: NULL, suspend: hc_isp_suspend, resume: hc_isp_resume, scale: hc_isp_scale, remove: NULL, constraints: NULL, }; static struct device hc_isp_device_ldm = { name: "Philips ISP1161 USB ", bus_id: "hc_isp", driver: NULL, power_state: DPM_POWER_OFF, }; static void hc_isp_ldm_register(void) { extern void ebc_driver_register(struct device_driver *driver); extern void ebc_device_register(struct device *device); ebc_driver_register(&hc_isp_driver_ldm); ebc_device_register(&hc_isp_device_ldm); } static void hc_isp_ldm_unregister(void) { extern void ebc_driver_unregister(struct device_driver *driver); extern void ebc_device_unregister(struct device *device); ebc_driver_unregister(&hc_isp_driver_ldm); ebc_device_unregister(&hc_isp_device_ldm); } static int hc_isp_scale(struct bus_op_point * op, u32 level) { /* linux-pm-TODO */ return 0; } /* * TODO: get rid of pm_hci. */ static hci_t *pm_hci = NULL; static int hc_reset (hci_t * hci); static int hc_start (hci_t * hci); static int hc_alloc_trans_buffer (hci_t * hci); static void hc_isp116x_intregdump(hci_t * hci) { printk("HcInterruptStatus=%x HcInterruptEnable=%x\n", READ_REG32 (hci, HcInterruptStatus), READ_REG32 (hci, HcInterruptEnable)); printk("HcuPInterrupt=%x HcuPInterruptEnable=%x\n", READ_REG16 (hci, HcuPInterrupt), READ_REG16 (hci, HcuPInterruptEnable)); printk("HcHardwareConfiguration=%x\n", READ_REG16 (hci,HcHardwareConfiguration)); } static int hc_isp_suspend(struct device * dev, u32 state, u32 level) { switch(level) { case SUSPEND_POWER_DOWN: WRITE_REG16 (pm_hci, 0, HcDMAConfiguration); WRITE_REG16 (pm_hci, 0, HcuPInterruptEnable); WRITE_REG16(pm_hci, READ_REG16(pm_hci, HcHardwareConfiguration) & ~InterruptPinEnable, HcHardwareConfiguration); hc_release_hci(pm_hci); break; } return 0; } static int hc_isp_resume(struct device * dev, u32 level) { switch(level) { case RESUME_POWER_ON: /* * Turn off sleep mode. */ hc_found_hci(hcport, irq, 0); break; } return 0; } #endif /* MVL-CEE */ /*-------------------------------------------------------------------------*/ /* URB HCD API function layer * * * */ /*-------------------------------------------------------------------------*/ /* set actual length to 0 and set status * queue URB * */ static inline int hcs_urb_queue (hci_t * hci, struct urb * urb) { int i; if (usb_pipeisoc (urb->pipe)) { for (i = 0; i < urb->number_of_packets; i++) { urb->iso_frame_desc[i].actual_length = 0; urb->iso_frame_desc[i].status = -EXDEV; } } urb->status = USB_ST_URB_PENDING; urb->actual_length = 0; urb->error_count = 0; qu_queue_urb (hci, urb); return 0; } /*-------------------------------------------------------------------------*/ /* return path (complete - callback) of URB API * also handles resubmition of intr URBs * */ static int hcs_return_urb (hci_t * hci, struct urb * urb, int resub_ok) { struct usb_device * dev = urb->dev; int resubmit = 0; if (urb_debug) urb_print (urb, "RET", usb_pipeout (urb->pipe)); resubmit = urb->interval && resub_ok; urb->dev = urb->hcpriv = NULL; if (urb->complete) urb->complete (urb); /* call complete */ if (resubmit) { /* requeue the URB */ urb->dev = dev; hcs_urb_queue (hci, urb); } return 0; } /*-------------------------------------------------------------------------*/ /* got a transfer request * */ static int hci_submit_urb (struct urb * urb) { hci_t * hci; unsigned int pipe = urb->pipe; unsigned long flags; int ret; if (!urb->dev || !urb->dev->bus || urb->hcpriv) return -EINVAL; if (usb_endpoint_halted (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe))) return -EPIPE; hci = (hci_t *) urb->dev->bus->hcpriv; /* a request to the virtual root hub */ if (usb_pipedevice (pipe) == hci->rh.devnum) { return rh_submit_urb (urb); } if (urb_debug) urb_print (urb, "SUB", usb_pipein (pipe)); /* queue the URB to its endpoint-queue */ spin_lock_irqsave (&usb_urb_lock, flags); ret = hcs_urb_queue (hci, urb); spin_unlock_irqrestore (&usb_urb_lock, flags); return ret; } /*-------------------------------------------------------------------------*/ /* unlink URB: mark the URB to unlink * */ static int hci_unlink_urb (struct urb * urb) { unsigned long flags; hci_t * hci; DECLARE_WAITQUEUE (wait, current); void * comp = NULL; if (!urb) /* just to be sure */ return -EINVAL; if (!urb->dev || !urb->dev->bus) return -ENODEV; hci = (hci_t *) urb->dev->bus->hcpriv; /* a request to the virtual root hub */ if (usb_pipedevice (urb->pipe) == hci->rh.devnum) { return rh_unlink_urb (urb); } if (urb_debug) urb_print (urb, "UNLINK", 1); spin_lock_irqsave (&usb_urb_lock, flags); /* HACK: It seems the HID driver sometimes tries to unlink * URBs which have never been submitted - and hence whose * urb_list field has never been initialized. This causes * crashes on unplug */ if ( (urb->urb_list.prev && urb->urb_list.next) && !list_empty (&urb->urb_list)) { /* URB active? */ if (urb->transfer_flags & (USB_ASYNC_UNLINK | USB_TIMEOUT_KILLED)) { /* asynchron with callback */ list_del (&urb->urb_list); /* relink the urb to the del list */ list_add (&urb->urb_list, &hci->del_list); comp = urb->complete; urb->complete = NULL; spin_unlock_irqrestore (&usb_urb_lock, flags); } else { /* synchron without callback */ add_wait_queue (&hci->waitq, &wait); set_current_state (TASK_UNINTERRUPTIBLE); list_del (&urb->urb_list); /* relink the urb to the del list */ list_add (&urb->urb_list, &hci->del_list); spin_unlock_irqrestore (&usb_urb_lock, flags); schedule_timeout(HZ/50); if (!list_empty (&urb->urb_list)) list_del (&urb->urb_list); urb->complete = comp; urb->hcpriv = NULL; remove_wait_queue (&hci->waitq, &wait); } } else { /* hcd does not own URB but we keep the driver happy anyway */ spin_unlock_irqrestore (&usb_urb_lock, flags); if (urb->complete && (urb->transfer_flags & USB_ASYNC_UNLINK)) { urb->status = -ENOENT; urb->actual_length = 0; urb->complete (urb); urb->status = 0; } else { urb->status = -ENOENT; } } return 0; } /*-------------------------------------------------------------------------*/ /* allocate private data space for a usb device * */ static int hci_alloc_dev (struct usb_device * usb_dev) { struct hci_device * dev; int i; dev = kmalloc (sizeof (*dev), GFP_KERNEL); if (!dev) return -ENOMEM; memset (dev, 0, sizeof (*dev)); for (i = 0; i < 32; i++) { INIT_LIST_HEAD (&(dev->urb_queue [i])); dev->pipe_head [i] = NULL; } usb_dev->hcpriv = dev; if (hc_verbose) printk ("USB HC dev alloc %d bytes\n", sizeof (*dev)); return 0; } /*-------------------------------------------------------------------------*/ /* free private data space of usb device * */ static int hci_free_dev (struct usb_device * usb_dev) { if (hc_verbose) printk ("USB HC dev free\n"); if (usb_dev->hcpriv) kfree (usb_dev->hcpriv); usb_dev->hcpriv = NULL; return 0; } /*-------------------------------------------------------------------------*/ /* tell us the current USB frame number * */ static int hci_get_current_frame_number (struct usb_device *usb_dev) { hci_t * hci = usb_dev->bus->hcpriv; int frame_no; frame_no = GET_FRAME_NUMBER (hci); return frame_no; } /*-------------------------------------------------------------------------*/ /* make a list of all io-functions * */ static struct usb_operations hci_device_operations = { hci_alloc_dev, hci_free_dev, hci_get_current_frame_number, hci_submit_urb, hci_unlink_urb }; /*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/ /* URB queueing: * * For each type of transfer (INTR, BULK, ISO, CTRL) there is a list of * active URBs. * (hci->intr_list, hci->bulk_list, hci->iso_list, hci->ctrl_list) * For every endpoint the head URB of the queued URBs is linked to one of * those lists. * * The rest of the queued URBs of an endpoint are linked into a * private URB list for each endpoint. (hci_dev->urb_queue [endpoint_io]) * hci_dev->pipe_head [endpoint_io] .. points to the head URB which is * in one of the active URB lists. * * The index of an endpoint consists of its number and its direction. * * The state of an intr and iso URB is 0. * For ctrl URBs the states are US_CTRL_SETUP, US_CTRL_DATA, US_CTRL_ACK * Bulk URBs states are US_BULK and US_BULK0 (with 0-len packet) * * * * */ static inline int qu_pipeindex (__u32 pipe) { return (usb_pipeendpoint (pipe) << 1) | (usb_pipecontrol (pipe) ? 0 : usb_pipeout (pipe)); } static inline void qu_seturbstate (struct urb * urb, int state) { urb->pipe &= ~0x3; urb->pipe |= state & 0x3; } static inline int qu_urbstate (struct urb * urb) { return urb->pipe & 0x3; } /*-------------------------------------------------------------------------*/ static inline void qu_queue_active_urb (hci_t * hci, struct urb * urb) { int urb_state = 0; switch (usb_pipetype (urb->pipe)) { case PIPE_CONTROL: list_add (&urb->urb_list, &hci->ctrl_list); urb_state = US_CTRL_SETUP; break; case PIPE_BULK: list_add (&urb->urb_list, &hci->bulk_list); if (urb->transfer_flags & USB_ZERO_PACKET) urb_state = US_BULK0; break; case PIPE_INTERRUPT: urb->start_frame = (hci->frame_no + urb->interval) & 0x7ff; list_add (&urb->urb_list, &hci->intr_list); break; case PIPE_ISOCHRONOUS: list_add (&urb->urb_list, &hci->iso_list); break; } qu_seturbstate (urb, urb_state); } static int qu_queue_urb (hci_t * hci, struct urb * urb) { int index = qu_pipeindex (urb->pipe); struct hci_device * hci_dev = usb_to_hci (urb->dev); if (hci_dev->pipe_head [index]) { __list_add (&urb->urb_list, hci_dev->urb_queue [index].prev, &(hci_dev->urb_queue [index])); } else { hci_dev->pipe_head [index] = urb; qu_queue_active_urb (hci, urb); } return 0; } /*-------------------------------------------------------------------------*/ /* return path (after transfer) * remove URB from queue and return it to the api layer * */ static void qu_return_urb (hci_t * hci, struct urb * urb, int resub_ok) { struct hci_device * hci_dev = usb_to_hci (urb->dev); int index = qu_pipeindex (urb->pipe); list_del (&urb->urb_list); INIT_LIST_HEAD (&urb->urb_list); if (!list_empty (&(hci_dev->urb_queue [index]))) { urb = list_entry (hci_dev->urb_queue [index].next, struct urb, urb_list); list_del (&urb->urb_list); INIT_LIST_HEAD (&urb->urb_list); hci_dev->pipe_head [index] = urb; qu_queue_active_urb (hci, urb); } else { hci_dev->pipe_head [index] = NULL; } hcs_return_urb (hci, urb, resub_ok); } /*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/ /* SCHEDULE operations * * * * */ static int sh_scan_urb_list (hci_t * hci, struct list_head * list_lh) { struct list_head * lh; struct urb * urb; list_for_each (lh, list_lh) { urb = list_entry (lh, struct urb, urb_list); if (!usb_pipeint (urb->pipe) || (((hci->frame_no - urb->start_frame) & 0x7ff) >= urb->interval)) { if (!sh_add_packet (hci, urb)) return 0; else urb->start_frame = hci->frame_no; } } return 1; } /*-------------------------------------------------------------------------*/ /* scan lists of active URBs and construct a schedule for a frame * */ static int sh_schedule_trans (hci_t * hci) { int units_left; hci->trans = 0; units_left = sh_scan_urb_list (hci, &hci->intr_list); if (units_left) { /* add CTRL transfers */ units_left = sh_scan_urb_list (hci, &hci->ctrl_list); } if (units_left) { /* add BULK transfers */ sh_scan_urb_list (hci, &hci->bulk_list); } return 0; } /*-------------------------------------------------------------------------*/ /* add some parts of the active URB to the schedule * */ static int sh_add_packet (hci_t * hci, struct urb * urb) { __u8 * data = NULL; int len = 0; int toggle = 0; int maxps = usb_maxpacket (urb->dev, urb->pipe, usb_pipeout (urb->pipe)); int endpoint = usb_pipeendpoint (urb->pipe); int address = usb_pipedevice (urb->pipe); int slow = usb_pipeslow (urb->pipe); int out = usb_pipeout (urb->pipe); int pid = 0; int ret; int i; int iso = 0; dbg("transfer_pa: addr: %d, ep:%d, out:%x in:%x\n", address, endpoint, urb->dev->toggle[1], urb->dev->toggle[0]); if (maxps == 0) maxps = 8; /* calculate len, toggle bit and add the transaction */ switch (usb_pipetype (urb->pipe)) { case PIPE_ISOCHRONOUS: iso = 1; i = hci->frame_no - urb->start_frame; data = urb->transfer_buffer + urb->iso_frame_desc[i].offset; len = urb->iso_frame_desc[i].length; break; case PIPE_BULK: /* BULK and BULK0 */ case PIPE_INTERRUPT: pid = out ? PID_OUT : PID_IN; len = urb->transfer_buffer_length - urb->actual_length; data = urb->transfer_buffer + urb->actual_length; toggle = usb_gettoggle (urb->dev, endpoint, out); break; case PIPE_CONTROL: switch (qu_urbstate (urb)) { case US_CTRL_SETUP: len = 8; pid = PID_SETUP; data = urb->setup_packet; toggle = 0; break; case US_CTRL_DATA: if (urb->transfer_buffer_length != 0) { pid = out ? PID_OUT : PID_IN; len = urb->transfer_buffer_length - urb->actual_length; data = urb->transfer_buffer + urb->actual_length; toggle = (urb->actual_length & maxps) ? 0 : 1; break; } else /* correct state and fall through */ qu_seturbstate (urb, US_CTRL_ACK); case US_CTRL_ACK: len = 0; /* reply in opposite direction */ pid = !out ? PID_OUT : PID_IN; toggle = 1; break; } } ret = hc_add_trans (hci, len, data, toggle, maxps, slow, endpoint, address, pid, iso); if (ret) { hci->urb_array [hci->trans] = urb; hci->trans ++; if (usb_pipetype (urb->pipe) == PIPE_INTERRUPT) urb->start_frame = hci->frame_no; } return ret; } /*-------------------------------------------------------------------------*/ /* parse the done_list * */ static int sh_done_list (hci_t * hci) { int actbytes; int active = 0; void * data = NULL; void * data_urb = NULL; int cc; int maxps; int toggle; struct urb * urb; int urb_state; int ret = 1; /* -1 parse abbort, 1 parse ok, 0 last element */ int trans = 0; for (trans = 0; trans < hci->trans; trans++) { urb = hci->urb_array [trans]; if (urb->dev == 0) { printk("hc_isp1116x: urb on done list has no device.\n"); continue; } urb_state = qu_urbstate (urb); toggle = usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe)); ret = hc_parse_trans (hci, &actbytes, &data, &cc, &toggle); maxps = usb_maxpacket (urb->dev, urb->pipe, usb_pipeout (urb->pipe)); if (maxps == 0) maxps = 8; if (cc != TD_NOTACCESSED) { active = (urb_state != US_CTRL_SETUP) && (actbytes && !(actbytes & (maxps - 1)) && (urb->transfer_buffer_length != urb->actual_length + actbytes)); data_urb = urb->transfer_buffer + urb->actual_length; if (!(urb->transfer_flags & USB_DISABLE_SPD) && cc == TD_DATAUNDERRUN) cc = 0; if (cc) { /* last packet has an error */ if (hc_error_verbose) printk("hc USB %d error %d in frame %d ep %d out %d addr %d\n", urb->error_count, cc, hci->frame_no, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe), usb_pipedevice (urb->pipe)); if (++urb->error_count > 3 || cc == TD_CC_STALL) { active = 0; urb_state = 0; /* return */ } else { // actbytes = actbytes & ~(maxps - 1); actbytes = 0; active = 1; } } else if (urb_state != US_CTRL_SETUP) { /* no error */ if (!usb_pipeout (urb->pipe) && actbytes && data_urb != data) memcpy (data_urb, data, actbytes); urb->actual_length += actbytes; usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), usb_pipeout (urb->pipe), !toggle); // (!actbytes || !((actbytes - 1) & maxps) ? !toggle : toggle)); } if (!active) { if (!(urb_state--)) { urb->status = cc_to_error [cc]; qu_return_urb (hci, urb, 1); } else { qu_seturbstate (urb, urb_state); } } } } return ret; } /*-------------------------------------------------------------------------*/ /* scan the delete list (qu_return_urb removes URB from list) * */ static void sh_del_list (hci_t * hci) { struct list_head * lh = &hci->del_list; struct urb * urb; if (!list_empty (lh)) { do { lh = lh->next; urb = list_entry (lh, struct urb, urb_list); qu_return_urb (hci, urb, 0); } while (!list_empty (lh)); wake_up (&hci->waitq); } return; } /*------------------------------------------------------------------------- * ISP116x virtual root hub * From hc_isp116x_rh.c by Roman Weissgaerber weissg@vienna.at *-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------* * Virtual Root Hub *-------------------------------------------------------------------------*/ /* Device descriptor */ static __u8 root_hub_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 root_hub_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 */ 0x02, /* __u16 ep_wMaxPacketSize; ((MAX_ROOT_PORTS + 1) / 8 */ 0x00, 0xff /* __u8 ep_bInterval; 255 ms */ }; /* Hub class-specific descriptor is constructed dynamically */ /*-------------------------------------------------------------------------*/ /* prepare Interrupt pipe data; HUB INTERRUPT ENDPOINT */ static int rh_send_irq (hci_t * hci, void * rh_data, int rh_len) { int num_ports; int i; int ret; int len; u8 data[8]; num_ports = READ_REG32 (hci, HcRhDescriptorA) & RH_A_NDP; data[0] = (READ_REG32 (hci, HcRhStatus) & (RH_HS_LPSC | RH_HS_OCIC)) ? 1 : 0; ret = data[0]; for (i = 0; i < num_ports; i++) { u32 reg = READ_REG32(hci, HcRhPortStatus + i); data[(i + 1) / 8] |= ((reg & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC | RH_PS_OCIC | RH_PS_PRSC)) ? 1 : 0) << ((i + 1) % 8); ret += data[(i + 1) / 8] ; } len = i/8 + 1; if (ret > 0) { memcpy (rh_data, data, min(len, min_t(int,rh_len, sizeof(data)))); return len; } return 0; } /*-------------------------------------------------------------------------*/ /* Virtual Root Hub INTs are polled by this timer every "interval" ms */ static void rh_int_timer_do (unsigned long ptr) { int len; struct urb * urb = (struct urb *) ptr; hci_t * hci = urb->dev->bus->hcpriv; #if 0 /* cleanup? */ if (hci->disabled) // return; /* ignore timers firing during PM suspend, etc */ if ((hci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER) goto out; #endif if(hci->rh.send) { len = rh_send_irq (hci, urb->transfer_buffer, urb->transfer_buffer_length); if (len > 0) { urb->actual_length = len; if (urb_debug == 2) urb_print (urb, "RET-t(rh)", usb_pipeout (urb->pipe)); if (urb->complete) urb->complete (urb); } } // out: rh_init_int_timer (urb); } /*-------------------------------------------------------------------------*/ /* Root Hub INTs are polled by this timer */ static int rh_init_int_timer (struct urb * urb) { hci_t * hci = urb->dev->bus->hcpriv; hci->rh.interval = urb->interval; init_timer (&hci->rh.rh_int_timer); hci->rh.rh_int_timer.function = rh_int_timer_do; hci->rh.rh_int_timer.data = (unsigned long) urb; hci->rh.rh_int_timer.expires = jiffies + (HZ * (urb->interval < 30? 30: urb->interval)) / 1000; add_timer (&hci->rh.rh_int_timer); return 0; } /*-------------------------------------------------------------------------*/ #define OK(x) len = (x); break #define WR_RH_STAT(x) WRITE_REG32 (hci, (x), HcRhStatus) #define WR_RH_PORTSTAT(x) WRITE_REG32 (hci, (x), HcRhPortStatus + wIndex - 1) #define RD_RH_STAT READ_REG32 (hci, HcRhStatus) #define RD_RH_PORTSTAT READ_REG32 (hci, HcRhPortStatus + wIndex - 1) /* request to virtual root hub */ static int rh_submit_urb (struct urb * urb) { struct usb_device * usb_dev = urb->dev; hci_t * hci = usb_dev->bus->hcpriv; unsigned int pipe = urb->pipe; struct usb_ctrlrequest * cmd = (struct usb_ctrlrequest *) urb->setup_packet; void * data = urb->transfer_buffer; int leni = urb->transfer_buffer_length; int len = 0; int status = TD_CC_NOERROR; __u32 datab[4]; __u8 * data_buf = (__u8 *) datab; __u16 bmRType_bReq; __u16 wValue; __u16 wIndex; __u16 wLength; if (usb_pipeint(pipe)) { hci->rh.urb = urb; hci->rh.send = 1; hci->rh.interval = urb->interval; rh_init_int_timer(urb); urb->status = cc_to_error [TD_CC_NOERROR]; 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); dbg ("rh_submit_urb, req = %d(%x) len=%d", bmRType_bReq, bmRType_bReq, wLength); switch (bmRType_bReq) { /* Request Destination: without flags: Device, RH_INTERFACE: interface, RH_ENDPOINT: endpoint, RH_CLASS means HUB here, RH_OTHER | RH_CLASS almost ever means HUB_PORT here */ case RH_GET_STATUS: *(__u16 *) data_buf = cpu_to_le16 (1); OK (2); case RH_GET_STATUS | RH_INTERFACE: *(__u16 *) data_buf = cpu_to_le16 (0); OK (2); case RH_GET_STATUS | RH_ENDPOINT: *(__u16 *) data_buf = cpu_to_le16 (0); OK (2); case RH_GET_STATUS | RH_CLASS: *(__u32 *) data_buf = cpu_to_le32 ( RD_RH_STAT & ~(RH_HS_CRWE | RH_HS_DRWE)); OK (4); case RH_GET_STATUS | RH_OTHER | RH_CLASS: *(__u32 *) data_buf = cpu_to_le32 (RD_RH_PORTSTAT); OK (4); case RH_CLEAR_FEATURE | RH_ENDPOINT: switch (wValue) { case (RH_ENDPOINT_STALL): OK (0); } break; case RH_CLEAR_FEATURE | RH_CLASS: switch (wValue) { case RH_C_HUB_LOCAL_POWER: OK(0); case (RH_C_HUB_OVER_CURRENT): WR_RH_STAT(RH_HS_OCIC); OK (0); } break; case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS: switch (wValue) { case (RH_PORT_ENABLE): WR_RH_PORTSTAT (RH_PS_CCS ); OK (0); case (RH_PORT_SUSPEND): WR_RH_PORTSTAT (RH_PS_POCI); OK (0); case (RH_PORT_POWER): WR_RH_PORTSTAT (RH_PS_LSDA); OK (0); case (RH_C_PORT_CONNECTION): WR_RH_PORTSTAT (RH_PS_CSC ); OK (0); case (RH_C_PORT_ENABLE): WR_RH_PORTSTAT (RH_PS_PESC); OK (0); case (RH_C_PORT_SUSPEND): WR_RH_PORTSTAT (RH_PS_PSSC); OK (0); case (RH_C_PORT_OVER_CURRENT): WR_RH_PORTSTAT (RH_PS_OCIC); OK (0); case (RH_C_PORT_RESET): WR_RH_PORTSTAT (RH_PS_PRSC); OK (0); } break; case RH_SET_FEATURE | RH_OTHER | RH_CLASS: switch (wValue) { case (RH_PORT_SUSPEND): WR_RH_PORTSTAT (RH_PS_PSS ); OK (0); case (RH_PORT_RESET): /* BUG IN HUB CODE *********/ if (RD_RH_PORTSTAT & RH_PS_CCS) WR_RH_PORTSTAT (RH_PS_PRS); OK (0); case (RH_PORT_POWER): WR_RH_PORTSTAT (RH_PS_PPS ); OK (0); case (RH_PORT_ENABLE): /* BUG IN HUB CODE *********/ if (RD_RH_PORTSTAT & RH_PS_CCS) WR_RH_PORTSTAT (RH_PS_PES ); OK (0); } break; case RH_SET_ADDRESS: hci->rh.devnum = wValue; OK(0); case RH_GET_DESCRIPTOR: switch ((wValue & 0xff00) >> 8) { case (0x01): /* device descriptor */ len = min (leni, min_t(int, sizeof (root_hub_dev_des), wLength)); data_buf = root_hub_dev_des; OK(len); case (0x02): /* configuration descriptor */ len = min (leni, min_t(int, sizeof (root_hub_config_des), wLength)); data_buf = root_hub_config_des; OK(len); case (0x03): /* string descriptors */ len = usb_root_hub_string (wValue & 0xff, (int)(long) 0, "ISP116x", data, wLength); if (len > 0) { data_buf = data; OK (min (leni, len)); } // else fallthrough default: status = TD_CC_STALL; } break; case RH_GET_DESCRIPTOR | RH_CLASS: { __u32 temp = READ_REG32 (hci, HcRhDescriptorA); data_buf [0] = 9; // min length; data_buf [1] = 0x29; data_buf [2] = temp & RH_A_NDP; data_buf [3] = 0; if (temp & RH_A_PSM) /* per-port power switching? */ data_buf [3] |= 0x1; if (temp & RH_A_NOCP) /* no overcurrent reporting? */ data_buf [3] |= 0x10; else if (temp & RH_A_OCPM) /* per-port overcurrent reporting? */ data_buf [3] |= 0x8; datab [1] = 0; data_buf [5] = (temp & RH_A_POTPGT) >> 24; temp = READ_REG32 (hci, HcRhDescriptorB); data_buf [7] = temp & RH_B_DR; if (data_buf [2] < 7) { data_buf [8] = 0xff; } else { data_buf [0] += 2; data_buf [8] = (temp & RH_B_DR) >> 8; data_buf [10] = data_buf [9] = 0xff; } len = min (leni, min_t(int, data_buf [0], wLength)); OK (len); } case RH_GET_CONFIGURATION: *(__u8 *) data_buf = 0x01; OK (1); case RH_SET_CONFIGURATION: WR_RH_STAT (0x10000); OK (0); default: dbg ("unsupported root hub command"); status = TD_CC_STALL; } len = min(len, leni); if (data != data_buf) memcpy (data, data_buf, len); urb->actual_length = len; urb->status = cc_to_error [status]; // hci_return_urb (hci, urb, NULL); urb->hcpriv = NULL; urb->dev = NULL; if (urb->complete) urb->complete (urb); return 0; } /*-------------------------------------------------------------------------*/ static int rh_unlink_urb (struct urb * urb) { hci_t * hci = urb->dev->bus->hcpriv; if (hci->rh.urb == urb) { hci->rh.send = 0; del_timer (&hci->rh.rh_int_timer); hci->rh.urb = NULL; urb->hcpriv = NULL; 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; } /*-------------------------------------------------------------------------*/ /* connect the virtual root hub */ static int rh_connect_rh (hci_t * hci) { struct usb_device * usb_dev; hci->rh.devnum = 0; usb_dev = usb_alloc_dev (NULL, hci->bus); if (!usb_dev) return -ENOMEM; hci->bus->root_hub = usb_dev; usb_connect (usb_dev); if (usb_new_device (usb_dev) != 0) { usb_free_dev (usb_dev); return -ENODEV; } return 0; } /* * --------------------------------------------------------------------------- * ISP116x driver */ static inline void REG_DELAY(void) { /* The sepcs of the ISP1611 controller require a 300ns delay * between writing the command register and writing another. * The following is overkill, but will do until I come up with * a better way. */ udelay(1); } static inline u32 READ_REG32 (hci_t * hci, int regindex) { hcipriv_t * hp = &hci->hp; u32 val16, val; writew (regindex, hp->hcvirtual + 2); REG_DELAY(); val16 = readw (hp->hcvirtual); val = val16; val16 = readw (hp->hcvirtual); val |= val16 << 16; return val; } static inline u16 READ_REG16 (hci_t * hci, int regindex) { hcipriv_t * hp = &hci->hp; writew (regindex, hp->hcvirtual + 2); REG_DELAY(); return readw (hp->hcvirtual); } static inline void READ_REGn16 (hci_t * hci, int regindex, int length, __u8 * buffer) { hcipriv_t * hp = &hci->hp; int i; unsigned int val = 0; writew (regindex, hp->hcvirtual + 2); REG_DELAY(); for (i = 0; i < length; i += 2) { val = readw (hp->hcvirtual); buffer [i] = val; buffer [i+1] = val >> 8; } if (length & 1) { val = readw (hp->hcvirtual); buffer [length - 1] = val; } } static inline void WRITE_REG32 (hci_t * hci, u32 value, int regindex) { hcipriv_t * hp = &hci->hp; writew (regindex | 0x80, hp->hcvirtual + 2); REG_DELAY(); writew (value, hp->hcvirtual); writew (value >> 16, hp->hcvirtual); } static inline void WRITE_REG16 (hci_t * hci, u16 value, int regindex) { hcipriv_t * hp = &hci->hp; writew (regindex | 0x80, hp->hcvirtual + 2); REG_DELAY(); writew (value, hp->hcvirtual); } static inline void WRITE_REGn16 (hci_t * hci, int regindex, int length, __u8 * buffer) { hcipriv_t * hp = &hci->hp; int i; writew (regindex | 0x80, hp->hcvirtual + 2); REG_DELAY(); for (i = 0; i < length; i+=2) { writew (buffer [i] + (buffer [i+1] << 8), hp->hcvirtual); } if (length & 1) { writew (buffer [length - 1], hp->hcvirtual); } } /*-------------------------------------------------------------------------*/ /* tl functions */ static inline void hc_mark_last_trans (hci_t * hci) { hcipriv_t * hp = &hci->hp; __u8 * ptd = hp->tl; if (hp->tlp > 0) *(ptd + hp->tl_last) |= (1 << 3); } static inline int hc_add_trans (hci_t * hci, int len, void * data, int toggle, int maxps, int slow, int endpoint, int address, int pid, int format) { hcipriv_t * hp = &hci->hp; int last = 0; __u8 * ptd = hp->tl; /* just send 4 packets of each kind at a frame, * other URBs also want some bandwitdh, and a NACK is cheaper * with less packets */ if (len > maxps * 1) len = maxps * 1; if (hp->units_left < len + 8) return 0; else hp->units_left -= len + 8; ptd += hp->tlp; hp->tl_last = hp->tlp + 3; ptd [0] = 0; ptd [1] = (toggle << 2) | (1 << 3) | (0xf << 4); ptd [2] = maxps; ptd [3] = ((maxps >> 8) & 0x3) | (slow << 2) | (last << 3) | (endpoint << 4); ptd [4] = len; ptd [5] = ((len >> 8) & 0x3) | (pid << 2); ptd [6] = address | (format << 7); ptd [7] = 0; memcpy (ptd + 8, data, len); hp->tlp += ((len + 8 + 3) & ~0x3); return 1; } static inline int hc_parse_trans (hci_t * hci, int * actbytes, void ** data, int * cc, int * toggle) { hcipriv_t * hp = &hci->hp; int last = 0; int totbytes; __u8 *ptd = hp->tl + hp->tlp; *cc = (ptd [1] >> 4) & 0xf; last = (ptd [3] >> 3) & 0x1; *actbytes = ((ptd [1] & 0x3) << 8) | ptd [0]; totbytes = ((ptd [5] & 0x3) << 8) | ptd [4]; *data = ptd + 8; *toggle = !(ptd [1] >> 2 & 1); hp->tlp += ((totbytes + 8 + 3) & ~0x3); return !last; } /*-------------------------------------------------------------------------*/ /* static void hc_start_list (hci_t * hci, ed_t * ed) { hcipriv_t * hp = hci->hcipriv; int units_left = hp->atl_buffer_len; switch (usb_pipetype (ed->pipe)) { case PIPE_CONTROL: case PIPE_BULK: case PIPE_INTERRUPT: if (hp->atl_len == 0) { hp->tlp = 0; sh_schedule_trans (hci, TR_INTR | TR_CTRL | TR_BULK, &units_left); mark_last_trans (hci); hp->atl_len = hp->tlp; if (hp->atl_len > 0) { WRITE_REG16 (hci, hp->atl_len, HcTransferCounter); WRITE_REGn16 (hci, HcATLBufferPort, hp->atl_len, hp->tl); } } } } */ /* an interrupt happens */ static void hc_interrupt (int irq, void * __hci, struct pt_regs * r) { hci_t * hci = __hci; hcipriv_t * hp = &hci->hp; int ints_uP, ints = 0, bstat = 0; if ((ints_uP = (READ_REG16 (hci, HcuPInterrupt) & READ_REG16 (hci, HcuPInterruptEnable))) == 0) { return; } WRITE_REG16 (hci, ints_uP, HcuPInterrupt); if ((ints_uP & OPR_Reg) && (ints = (READ_REG32 (hci, HcInterruptStatus)))) { if (ints & OHCI_INTR_SO) { dbg("USB Schedule overrun"); WRITE_REG32 (hci, OHCI_INTR_SO, HcInterruptEnable); } if (ints & OHCI_INTR_SF) { WRITE_REG32 (hci, OHCI_INTR_SF, HcInterruptEnable); } WRITE_REG32 (hci, ints, HcInterruptStatus); WRITE_REG32 (hci, OHCI_INTR_MIE, HcInterruptEnable); } bstat = READ_REG16 (hci, HcBufferStatus); if (ints_uP & SOFITLInt) { } hci->frame_no = GET_FRAME_NUMBER (hci); #ifdef DEBUG if (hci->frame_no == 0) printk("*** INTERRUPT frame %u intuP %x ints %x bstat %x \n", hci->frame_no, ints_uP, ints, bstat); #endif if (ints_uP & ATLInt) { dbg("int2 %x %x bstat %x\n", ints_uP, ints, bstat); if ((bstat & ATLBufferFull) && (bstat & ATLBufferDone)) { if (hp->atl_len > 0) { READ_REGn16 (hci, HcATLBufferPort, hp->atl_len, hp->tl); hp->tlp = 0; sh_done_list (hci); } hp->tlp = 0; hci->trans = 0; } } sh_del_list (hci); if (hci->trans == 0 && !(bstat & ATLBufferFull)) { hp->units_left = hp->atl_buffer_len; hp->tlp = 0; sh_schedule_trans (hci); hc_mark_last_trans (hci); hp->atl_len = hp->tlp; if (hp->atl_len > 0) { WRITE_REG16 (hci, hp->atl_len, HcTransferCounter); WRITE_REGn16 (hci, HcATLBufferPort, hp->atl_len, hp->tl); } } } /*-------------------------------------------------------------------------* * HC functions *-------------------------------------------------------------------------*/ /* reset the HC and BUS */ static int hc_reset (hci_t * hci) { int timeout = 30; /* Disable HC interrupts */ WRITE_REG32 (hci, OHCI_INTR_MIE, HcInterruptDisable); dbg ("USB HC reset_hc usb-: ctrl = 0x%x ;", READ_REG32 (hci, HcControl)); /* Reset USB (needed by some controllers) */ WRITE_REG32 (hci, 0, HcControl); WRITE_REG16 (hci, 0xf6, HcSoftwareReset); /* HC Reset requires max 10 us delay */ WRITE_REG32 (hci, OHCI_HCR, HcCommandStatus); while ((READ_REG32 (hci, HcCommandStatus) & OHCI_HCR) != 0) { if (--timeout == 0) { err ("USB HC reset timed out!"); return -1; } udelay (1); } return 0; } /*-------------------------------------------------------------------------*/ /* Start an host controller, set the BUS operational * enable interrupts * connect the virtual root hub */ static int hc_alloc_trans_buffer (hci_t * hci) { hcipriv_t * hp = &hci->hp; int maxlen; hp->itl0_len = 0; hp->itl1_len = 0; hp->atl_len = 0; hp->itl_buffer_len = 1024; hp->atl_buffer_len = 4096 - 2 * hp->itl_buffer_len; /* 2048 */ WRITE_REG16 (hci, hp->itl_buffer_len, HcITLBufferLength); WRITE_REG16 (hci, hp->atl_buffer_len, HcATLBufferLength); WRITE_REG16 (hci, InterruptPinEnable | InterruptPinTrigger | InterruptOutputPolarity | DataBusWidth16 | AnalogOCEnable, HcHardwareConfiguration); WRITE_REG16 (hci, 0, HcDMAConfiguration); maxlen = (hp->itl_buffer_len > hp->atl_buffer_len) ? hp->itl_buffer_len : hp->atl_buffer_len; if (!hp->tl) hp->tl = kmalloc (maxlen, GFP_KERNEL); if (!hp->tl) return -ENOMEM; memset (hp->tl, 0, maxlen); return 0; } static int hc_start (hci_t * hci) { hcipriv_t * hp = &hci->hp; __u32 mask; unsigned int fminterval; fminterval = 0x2edf; fminterval |= ((((fminterval - 210) * 6) / 7) << 16); WRITE_REG32 (hci, fminterval, HcFmInterval); WRITE_REG32 (hci, 0x628, HcLSThreshold); /* start controller operations */ hp->hc_control = OHCI_USB_OPER; WRITE_REG32 (hci, hp->hc_control, HcControl); /* Choose the interrupts we care about now, others later on demand */ mask = OHCI_INTR_MIE | OHCI_INTR_SO | OHCI_INTR_SF; WRITE_REG32 (hci, mask, HcInterruptEnable); WRITE_REG32 (hci, mask, HcInterruptStatus); mask = SOFITLInt | ATLInt | OPR_Reg; WRITE_REG16 (hci, mask, HcuPInterrupt); WRITE_REG16 (hci, mask, HcuPInterruptEnable); #ifdef OHCI_USE_NPS WRITE_REG32 ((READ_REG32 (hci, HcRhDescriptorA) | RH_A_NPS) & ~RH_A_PSM, HcRhDescriptorA); WRITE_REG32 (hci, RH_HS_LPSC, HcRhStatus); #endif /* OHCI_USE_NPS */ // POTPGT delay is bits 24-31, in 2 ms units. mdelay ((READ_REG32 (hci, HcRhDescriptorA) >> 23) & 0x1fe); rh_connect_rh (hci); return 0; } /*-------------------------------------------------------------------------*/ /* allocate HCI */ static hci_t * __devinit hc_alloc_hci (void) { hci_t * hci; hcipriv_t * hp; struct usb_bus * bus; hci = (hci_t *) kmalloc (sizeof (hci_t), GFP_KERNEL); if (!hci) return NULL; memset (hci, 0, sizeof (hci_t)); hp = &hci->hp; hp->irq = -1; hp->hcport = 0; hp->hcvirtual = 0; INIT_LIST_HEAD (&hci->hci_hcd_list); list_add (&hci->hci_hcd_list, &hci_hcd_list); init_waitqueue_head (&hci->waitq); INIT_LIST_HEAD (&hci->ctrl_list); INIT_LIST_HEAD (&hci->bulk_list); INIT_LIST_HEAD (&hci->iso_list); INIT_LIST_HEAD (&hci->intr_list); INIT_LIST_HEAD (&hci->del_list); bus = usb_alloc_bus (&hci_device_operations); if (!bus) { kfree (hci); return NULL; } hci->bus = bus; bus->bus_name = "isp116x"; bus->hcpriv = (void *) hci; return hci; } /*-------------------------------------------------------------------------*/ /* De-allocate all resources.. */ static void hc_release_hci (hci_t * hci) { hcipriv_t * hp = &hci->hp; /* disconnect all devices */ if (hci->bus->root_hub) usb_disconnect (&hci->bus->root_hub); if (hp->hcport > 0) { WRITE_REG16 (hci, 0, HcHardwareConfiguration); WRITE_REG16 (hci, 0, HcDMAConfiguration); WRITE_REG16 (hci, 0, HcuPInterruptEnable); } hc_reset (hci); if (hp->tl) kfree (hp->tl); if (hp->hcvirtual) { iounmap(hp->hcvirtual); hp->hcvirtual = 0; } if (hp->hcport > 0) { release_mem_region (hp->hcport, 4); hp->hcport = 0; } if (hp->irq >= 0) { free_irq (hp->irq, hci); hp->irq = -1; } usb_deregister_bus (hci->bus); usb_free_bus (hci->bus); list_del (&hci->hci_hcd_list); INIT_LIST_HEAD (&hci->hci_hcd_list); kfree (hci); } /*-------------------------------------------------------------------------*/ /* Increment the module usage count, start the control thread and * return success. */ static int __devinit hc_found_hci (int addr, int irq, int dma) { hci_t * hci; hcipriv_t * hp; printk("hc_found_hci: addr=%x\n", addr); hci = hc_alloc_hci (); if (!hci) { return -ENOMEM; } hp = &hci->hp; if (!request_mem_region (addr, 4, "ISP116x USB HOST")) { err ("request address %d-%d failed", addr, addr+4); hc_release_hci (hci); return -EBUSY; } hp->hcport = addr; hp->hcvirtual = ioremap(addr, 4); if (! hp->hcvirtual) { err("ioremap() failed"); hc_release_hci(hci); return -EBUSY; } if ((READ_REG16(hci, HcChipID) & 0xff00) != 0x6100) { hc_release_hci (hci); return -ENODEV; } if (hc_reset (hci) < 0) { hc_release_hci (hci); return -ENODEV; } if (hc_alloc_trans_buffer (hci)) { hc_release_hci (hci); return -ENOMEM; } printk(KERN_INFO __FILE__ ": USB ISP116x at %x IRQ %d Rev. %x ChipID: %x\n", addr, irq, READ_REG32(hci, HcRevision), READ_REG16(hci, HcChipID)); /* FIXME this is a second HC reset; why?? */ WRITE_REG32 (hci, hp->hc_control = OHCI_USB_RESET, HcControl); wait_ms (1000); usb_register_bus (hci->bus); if (request_irq (irq, hc_interrupt, 0, "ISP116x", hci) != 0) { err ("request interrupt %d failed", irq); hc_release_hci (hci); return -EBUSY; } hp->irq = irq; if (hc_start (hci) < 0) { err ("can't start usb-%x", addr); hc_release_hci (hci); return -EBUSY; } pm_hci = hci; return 0; } /*-------------------------------------------------------------------------*/ static int __init hci_hcd_init (void) { int ret; ret = hc_found_hci(hcport, irq, 0); #ifdef CONFIG_ARCTIC2 /* MVL-CEE */ hc_isp_ldm_register(); #endif /* MVL-CEE */ return ret; } /*-------------------------------------------------------------------------*/ static void __exit hci_hcd_cleanup (void) { struct list_head * hci_l; hci_t * hci; for (hci_l = hci_hcd_list.next; hci_l != &hci_hcd_list;) { hci = list_entry (hci_l, hci_t, hci_hcd_list); hci_l = hci_l->next; hc_release_hci(hci); } #ifdef CONFIG_ARCTIC2 /* MVL-CEE */ hc_isp_ldm_unregister(); #endif /* MVL-CEE */ } module_init (hci_hcd_init); module_exit (hci_hcd_cleanup); MODULE_AUTHOR ("Roman Weissgaerber "); MODULE_DESCRIPTION ("USB ISP116x Host Controller Driver");