patch-2.2.8 linux/drivers/usb/ohci-hcd.c
Next file: linux/drivers/usb/ohci-hcd.h
Previous file: linux/drivers/usb/ohci-debug.c
Back to the patch index
Back to the overall index
- Lines: 1490
- Date:
Tue May 11 09:55:45 1999
- Orig file:
v2.2.7/linux/drivers/usb/ohci-hcd.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.2.7/linux/drivers/usb/ohci-hcd.c linux/drivers/usb/ohci-hcd.c
@@ -0,0 +1,1489 @@
+/*
+ * OHCI HCD (Host Controller Driver) for USB.
+ *
+ * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
+ *
+ * The OHCI HCD layer is a simple but nearly complete implementation of what the
+ * USB people would call a HCD for the OHCI.
+ * (ISO comming soon, Bulk disabled, INT u. CTRL transfers enabled)
+ * The layer on top of it, is for interfacing to the alternate-usb device-drivers.
+ *
+ * [ This is based on Linus' UHCI code and gregs OHCI fragments (0.03c source tree). ]
+ * [ Open Host Controller Interface driver for USB. ]
+ * [ (C) Copyright 1999 Linus Torvalds (uhci.c) ]
+ * [ (C) Copyright 1999 Gregory P. Smith <greg@electricrain.com> ]
+ * [ $Log: ohci.c,v $ ]
+ * [ Revision 1.1 1999/04/05 08:32:30 greg ]
+ *
+ *
+ * v2.1 1999/05/09 ep_addr correction, code clean up
+ * v2.0 1999/05/04
+ * virtual root hub is now an option,
+ * memory allocation based on kmalloc and kfree now, Bus error handling,
+ * INT and CTRL transfers enabled, Bulk included but disabled, ISO needs completion
+ *
+ * from Linus Torvalds (uhci.c) (APM not tested; hub, usb_device, bus and related stuff)
+ * from Greg Smith (ohci.c) (reset controller handling, hub)
+ *
+ * v1.0 1999/04/27 initial release
+ * ohci-hcd.c
+ */
+
+/* #define OHCI_DBG */ /* printk some debug information */
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/malloc.h>
+#include <linux/smp_lock.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+
+#include <asm/spinlock.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+
+#include "usb.h"
+#include "ohci-hcd.h"
+#include "inits.h"
+
+
+
+#ifdef CONFIG_APM
+#include <linux/apm_bios.h>
+static int handle_apm_event(apm_event_t event);
+static int apm_resume = 0;
+#endif
+
+
+
+static struct wait_queue *control_wakeup;
+static struct wait_queue *root_hub = NULL;
+
+static __u8 cc_to_status[16] = { /* mapping of the OHCI CC to the UHCI status codes; first guess */
+/* Activ, Stalled, Data Buffer Err, Babble Detected : NAK recvd, CRC/Timeout, Bitstuff, reservd */
+/* No Error */ 0x00,
+/* CRC Error */ 0x04,
+/* Bit Stuff */ 0x02,
+/* Data Togg */ 0x40,
+/* Stall */ 0x40,
+/* DevNotResp */ 0x04,
+/* PIDCheck */ 0x04,
+/* UnExpPID */ 0x40,
+/* DataOver */ 0x20,
+/* DataUnder */ 0x20,
+/* reservd */ 0x40,
+/* reservd */ 0x40,
+/* BufferOver */ 0x20,
+/* BuffUnder */ 0x20,
+/* Not Access */ 0x80,
+/* Not Access */ 0x80
+ };
+
+
+/********
+ **** Interface functions
+ ***********************************************/
+
+static int sohci_int_handler(void * ohci_in, unsigned int ep_addr, int ctrl_len, void * ctrl, void * data, int data_len, int status, __OHCI_BAG lw0, __OHCI_BAG lw1)
+{
+
+ struct ohci * ohci = ohci_in;
+ usb_device_irq handler=(void *) lw0;
+ void *dev_id = (void *) lw1;
+ int ret;
+
+ OHCI_DEBUG({ int i; printk("USB HC IRQ <<<: %x: data(%d):", ep_addr, data_len);)
+ OHCI_DEBUG( for(i=0; i < data_len; i++ ) printk(" %02x", ((__u8 *) data)[i]);)
+ OHCI_DEBUG( printk(" ret_status: %x\n", status); })
+
+ ret = handler(cc_to_status[status & 0xf], data, dev_id);
+ if(ret == 0) return 0; /* 0 .. do not requeue */
+ if(status > 0) return -1; /* error occured do not requeue ? */
+ ohci_trans_req(ohci, ep_addr, 0, NULL, data, 8, (__OHCI_BAG) handler, (__OHCI_BAG) dev_id); /* requeue int request */
+ return 0;
+}
+
+static int sohci_ctrl_handler(void * ohci_in, unsigned int ep_addr, int ctrl_len, void * ctrl, void * data, int data_len, int status, __OHCI_BAG lw0, __OHCI_BAG lw)
+{
+ *(int * )lw0 = status;
+ wake_up(&control_wakeup);
+
+ OHCI_DEBUG( { int i; printk("USB HC CTRL<<<: %x: ctrl(%d):", ep_addr, ctrl_len);)
+ OHCI_DEBUG( for(i=0; i < 8; i++ ) printk(" %02x", ((__u8 *) ctrl)[i]);)
+ OHCI_DEBUG( printk(" data(%d):", data_len);)
+ OHCI_DEBUG( for(i=0; i < data_len; i++ ) printk(" %02x", ((__u8 *) data)[i]);)
+ OHCI_DEBUG( printk(" ret_status: %x\n", status); })
+ return 0;
+}
+
+static int sohci_request_irq(struct usb_device *usb_dev, unsigned int pipe, usb_device_irq handler, int period, void *dev_id)
+{
+ struct ohci * ohci = usb_dev->bus->hcpriv;
+ union ep_addr_ ep_addr;
+
+ ep_addr.iep = 0;
+ ep_addr.bep.ep = ((pipe >> 15) & 0x0f) /* endpoint address */
+ | (pipe & 0x80) /* direction */
+ | (1 << 5); /* type = int*/
+ ep_addr.bep.fa = ((pipe >> 8) & 0x7f); /* device address */
+
+ OHCI_DEBUG( printk("USB HC IRQ >>>: %x: every %d ms\n", ep_addr.iep, period);)
+
+ usb_ohci_add_ep(ohci, ep_addr.iep, period, 1, sohci_int_handler, 1 << ((pipe & 0x03) + 3) , (pipe >> 26) & 0x01);
+
+ ohci_trans_req(ohci, ep_addr.iep, 0, NULL, ((struct ohci_device *) usb_dev->hcpriv)->data, 8, (__OHCI_BAG) handler, (__OHCI_BAG) dev_id);
+ return 0;
+}
+
+
+static int sohci_control_msg(struct usb_device *usb_dev, unsigned int pipe, void *cmd, void *data, int len)
+{
+ struct wait_queue wait = { current, NULL };
+ struct ohci * ohci = usb_dev->bus->hcpriv;
+ int status;
+ union ep_addr_ ep_addr;
+
+ ep_addr.iep = 0;
+ ep_addr.bep.ep = ((pipe >> 15) & 0x0f) /* endpoint address */
+ | (pipe & 0x80) /* direction */
+ | (1 << 6); /* type = ctrl*/
+ ep_addr.bep.fa = ((pipe >> 8) & 0x7f); /* device address */
+
+ status = 0xf; /* CC not Accessed */
+ OHCI_DEBUG( { int i; printk("USB HC CTRL>>>: %x: ctrl(%d):", ep_addr.iep, 8);)
+ OHCI_DEBUG( for(i=0; i < 8; i++ ) printk(" %02x", ((__u8 *) cmd)[i]);)
+ OHCI_DEBUG( printk(" data(%d):", len);)
+ OHCI_DEBUG( for(i=0; i < len; i++ ) printk(" %02x", ((__u8 *) data)[i]);)
+ OHCI_DEBUG( printk("\n"); })
+
+ usb_ohci_add_ep(ohci, ep_addr.iep, 0, 1, sohci_ctrl_handler, 1 << ((pipe & 0x03) + 3) , (pipe >> 26) & 0x01);
+
+ current->state = TASK_UNINTERRUPTIBLE;
+ add_wait_queue(&control_wakeup, &wait);
+
+ ohci_trans_req(ohci, ep_addr.iep, 8, cmd, data, len, (__OHCI_BAG) &status, 0);
+
+ schedule_timeout(HZ/10);
+
+ remove_wait_queue(&control_wakeup, &wait);
+
+ OHCI_DEBUG(printk("USB HC status::: %x\n", cc_to_status[status & 0x0f]);)
+
+ return cc_to_status[status & 0x0f];
+}
+
+
+static int sohci_usb_deallocate(struct usb_device *usb_dev) {
+ struct ohci_device *dev = usb_to_ohci(usb_dev);
+ union ep_addr_ ep_addr;
+
+ ep_addr.iep = 0;
+
+ OHCI_DEBUG(printk("USB HC dealloc %x\n", usb_dev->devnum);)
+
+ /* wait_ms(20); */
+
+ if(usb_dev->devnum >=0) {
+ ep_addr.bep.fa = usb_dev->devnum;
+ usb_ohci_rm_function(((struct ohci_device *)usb_dev->hcpriv)->ohci, ep_addr.iep);
+ }
+
+ USB_FREE(dev);
+ USB_FREE(usb_dev);
+
+ return 0;
+}
+
+static struct usb_device *sohci_usb_allocate(struct usb_device *parent) {
+
+ struct usb_device *usb_dev;
+ struct ohci_device *dev;
+
+
+ USB_ALLOC(usb_dev, sizeof(*usb_dev));
+ if (!usb_dev)
+ return NULL;
+
+ memset(usb_dev, 0, sizeof(*usb_dev));
+
+ USB_ALLOC(dev, sizeof(*dev));
+ if (!dev) {
+ USB_FREE(usb_dev);
+ return NULL;
+ }
+
+ /* Initialize "dev" */
+ memset(dev, 0, sizeof(*dev));
+
+ usb_dev->hcpriv = dev;
+ dev->usb = usb_dev;
+
+ usb_dev->parent = parent;
+
+ if (parent) {
+ usb_dev->bus = parent->bus;
+ dev->ohci = usb_to_ohci(parent)->ohci;
+ }
+ return usb_dev;
+}
+
+struct usb_operations sohci_device_operations = {
+ sohci_usb_allocate,
+ sohci_usb_deallocate,
+ sohci_control_msg,
+ sohci_request_irq,
+};
+
+
+/******
+ *** ED handling functions
+ ************************************/
+
+
+
+/*
+ * search for the right place to insert an interrupt ed into the int tree
+ * do some load ballancing
+ * */
+
+static int usb_ohci_int_ballance(struct ohci * ohci, int interval, int load) {
+
+ int i,j;
+
+ j = 0; /* search for the least loaded interrupt endpoint branch of all 32 branches */
+ for(i=0; i< 32; i++) if(ohci->ohci_int_load[j] > ohci->ohci_int_load[i]) j=i;
+
+ if(interval < 1) interval = 1;
+ if(interval > 32) interval = 32;
+ for(i= 0; ((interval >> i) > 1 ); interval &= (0xfffe << i++ )); /* interval = 2^int(ld(interval)) */
+
+
+ for(i=j%interval; i< 32; i+=interval) ohci->ohci_int_load[i] += load;
+ j = interval + (j % interval);
+
+ OHCI_DEBUG(printk("USB HC new int ed on pos : %x \n",j);)
+
+ return j;
+}
+
+/* get the ed from the endpoint / device adress */
+
+struct usb_ohci_ed * ohci_find_ep(struct ohci *ohci, unsigned int ep_addr_in) {
+
+union ep_addr_ ep_addr;
+struct usb_ohci_ed *tmp;
+unsigned int mask;
+
+mask = 0;
+ep_addr.iep = ep_addr_in;
+
+#ifdef VROOTHUB
+ if(ep_addr.bep.fa == ohci->root_hub_funct_addr) {
+ if((ep_addr.bep.ep & 0x0f) == 0)
+ return &ohci->ed_rh_ep0; /* root hub ep0 */
+ else
+ return &ohci->ed_rh_epi; /* root hub int ep */
+ }
+#endif
+
+ tmp = ohci->ed_func_ep0[ep_addr.bep.fa];
+ mask = ((((ep_addr.bep.ep >> 5) & 0x03)==2)?0x7f:0xff);
+ ep_addr.bep.ep &= mask; /* mask out direction of ctrl ep */
+
+ while (tmp != NULL) {
+ if (tmp->ep_addr.iep == ep_addr.iep)
+ return tmp;
+ tmp = tmp->ed_list;
+ }
+ return NULL;
+}
+
+spinlock_t usb_ed_lock = SPIN_LOCK_UNLOCKED;
+/* add a new endpoint ep_addr */
+struct usb_ohci_ed *usb_ohci_add_ep(struct ohci * ohci, unsigned int ep_addr_in, int interval, int load, f_handler handler, int ep_size, int speed) {
+
+ struct usb_ohci_ed * ed;
+ struct usb_ohci_td * td;
+ union ep_addr_ ep_addr;
+
+
+ int int_junk;
+
+ struct usb_ohci_ed *tmp;
+
+ ep_addr.iep = ep_addr_in ;
+ ep_addr.bep.ep &= ((((ep_addr.bep.ep >> 5) & 0x03)==2)?0x7f:0xff); /* mask out direction of ctrl ep */
+
+ spin_lock(&usb_ed_lock);
+
+ tmp = ohci_find_ep(ohci, ep_addr.iep);
+ if (tmp != NULL) {
+
+#ifdef VROOTHUB
+ if(ep_addr.bep.fa == ohci->root_hub_funct_addr) {
+ if((ep_addr.bep.ep & 0x0f) != 0) { /* root hub int ep */
+ ohci->ed_rh_epi.handler = handler;
+ ohci_init_rh_int_timer(ohci, interval);
+ }
+ else { /* root hub ep0 */
+ ohci->ed_rh_ep0.handler = handler;
+ }
+ }
+
+ else
+#endif
+
+ {
+ tmp->hw.info = ep_addr.bep.fa | ((ep_addr.bep.ep & 0xf) <<7)
+
+ | (((ep_addr.bep.ep & 0x60) == 0)? 0x8000 : 0)
+ | (speed << 13)
+ | ep_size <<16;
+
+ tmp->handler = handler;
+ }
+ spin_unlock(&usb_ed_lock);
+ return tmp; /* ed already in use */
+ }
+
+
+ OHCI_ALLOC(td, sizeof(td)); /* dummy td; end of td list for ed */
+ OHCI_ALLOC(ed, sizeof(ed));
+ td->prev_td = NULL;
+
+ ed->hw.tail_td = virt_to_bus(&td->hw);
+ ed->hw.head_td = ed->hw.tail_td;
+ ed->hw.info = ep_addr.bep.fa | ((ep_addr.bep.ep & 0xf) <<7)
+ /* | ((ep_addr.bep.port & 0x80)? 0x1000 : 0x0800 ) */
+ | (((ep_addr.bep.ep & 0x60) == 0)? 0x8000 : 0)
+ | (speed << 13)
+ | ep_size <<16;
+
+ ed->handler = handler;
+
+ switch((ep_addr.bep.ep >> 5) & 0x03) {
+ case CTRL:
+ ed->hw.next_ed = 0;
+ if(ohci->ed_controltail == NULL) {
+ writel(virt_to_bus(&ed->hw), &ohci->regs->ed_controlhead);
+ }
+ else {
+ ohci->ed_controltail->hw.next_ed = virt_to_bus(&ed->hw);
+ }
+ ed->ed_prev = ohci->ed_controltail;
+ ohci->ed_controltail = ed;
+ break;
+ case BULK:
+ ed->hw.next_ed = 0;
+ if(ohci->ed_bulktail == NULL) {
+ writel(virt_to_bus(&ed->hw), &ohci->regs->ed_bulkhead);
+ }
+ else {
+ ohci->ed_bulktail->hw.next_ed = virt_to_bus(&ed->hw);
+ }
+ ed->ed_prev = ohci->ed_bulktail;
+ ohci->ed_bulktail = ed;
+ break;
+ case INT:
+ int_junk = usb_ohci_int_ballance(ohci, interval, load);
+ ed->hw.next_ed = ohci->hc_area->ed[int_junk].next_ed;
+ ohci->hc_area->ed[int_junk].next_ed = virt_to_bus(&ed->hw);
+ ed->ed_prev = (struct usb_ohci_ed *) &ohci->hc_area->ed[int_junk];
+ break;
+ case ISO:
+ ed->hw.next_ed = 0;
+ ohci->ed_isotail->hw.next_ed = virt_to_bus(&ed->hw);
+ ed->ed_prev = ohci->ed_isotail;
+ ohci->ed_isotail = ed;
+ break;
+ }
+ ed->ep_addr = ep_addr;
+
+ /* Add it to the "hash"-table of known endpoint descriptors */
+
+ ed->ed_list = ohci->ed_func_ep0[ed->ep_addr.bep.fa];
+ ohci->ed_func_ep0[ed->ep_addr.bep.fa] = ed;
+
+ spin_unlock(&usb_ed_lock);
+
+ OHCI_DEBUG(printk("USB HC new ed %x: %x :", ep_addr.iep, (unsigned int ) ed); )
+ OHCI_DEBUG({ int i; for( i= 0; i<8 ;i++) printk(" %4x", ((unsigned int *) ed)[i]) ; printk("\n"); }; )
+ return 0;
+}
+
+/*****
+ * Request the removal of an endpoint
+ *
+ * put the ep on the rm_list and request a stop of the bulk or ctrl list
+ * real removal is done at the next start of frame hardware interrupt
+ */
+int usb_ohci_rm_ep(struct ohci * ohci, struct usb_ohci_ed *ed)
+{
+ unsigned int flags;
+ struct usb_ohci_ed *tmp;
+
+ OHCI_DEBUG(printk("USB HC remove ed %x: %x :\n", ed->ep_addr.iep, (unsigned int ) ed); )
+
+ spin_lock_irqsave(&usb_ed_lock, flags);
+
+ tmp = ohci->ed_func_ep0[ed->ep_addr.bep.fa];
+ if (tmp == NULL) {
+ spin_unlock_irqrestore(&usb_ed_lock, flags);
+ return 0;
+ }
+
+ if(tmp == ed) {
+ ohci->ed_func_ep0[ed->ep_addr.bep.fa] = ed->ed_list;
+ }
+ else {
+ while (tmp->ed_list != ed) {
+ if (tmp->ed_list == NULL) {
+ spin_unlock_irqrestore(&usb_ed_lock, flags);
+ return 0;
+ }
+ tmp = tmp->ed_list;
+ }
+ tmp->ed_list = ed->ed_list;
+ }
+ ed->ed_list = ohci->ed_rm_list;
+ ohci->ed_rm_list = ed;
+ ed->hw.info |= OHCI_ED_SKIP;
+
+ switch((ed->ep_addr.bep.ep >> 5) & 0x03) {
+ case CTRL:
+ writel_mask(~(0x01<<4), &ohci->regs->control); /* stop CTRL list */
+ break;
+ case BULK:
+ writel_mask(~(0x01<<5), &ohci->regs->control); /* stop BULK list */
+ break;
+ }
+
+
+ writel( OHCI_INTR_SF, &ohci->regs->intrenable); /* enable sof interrupt */
+
+ spin_unlock_irqrestore(&usb_ed_lock, flags);
+
+ return 1;
+}
+
+/* we have requested to stop the bulk or CTRL list,
+ * now we can remove the eds on the rm_list */
+
+static int ohci_rm_eds(struct ohci * ohci) {
+
+ unsigned int flags;
+ struct usb_ohci_ed *ed;
+ struct usb_ohci_ed *ed_tmp;
+ struct usb_ohci_td *td;
+ __u32 td_hw_tmp;
+ __u32 td_hw;
+
+ spin_lock_irqsave(&usb_ed_lock, flags);
+
+ ed = ohci->ed_rm_list;
+
+ while (ed != NULL) {
+
+ switch((ed->ep_addr.bep.ep >> 5) & 0x03) {
+ case CTRL:
+ if(ed->ed_prev == NULL) {
+ writel(ed->hw.next_ed, &ohci->regs->ed_controlhead);
+ }
+ else {
+ ed->ed_prev->hw.next_ed = ed->hw.next_ed;
+ }
+ if(ohci->ed_controltail == ed) {
+ ohci->ed_controltail = ed->ed_prev;
+ }
+ break;
+ case BULK:
+ if(ed->ed_prev == NULL) {
+ writel(ed->hw.next_ed, &ohci->regs->ed_bulkhead);
+ }
+ else {
+ ed->ed_prev->hw.next_ed = ed->hw.next_ed;
+ }
+ if(ohci->ed_bulktail == ed) {
+ ohci->ed_bulktail = ed->ed_prev;
+ }
+ break;
+ case INT:
+ ed->ed_prev->hw.next_ed = ed->hw.next_ed;
+ break;
+ case ISO:
+ ed->ed_prev->hw.next_ed = ed->hw.next_ed;
+ if(ohci->ed_isotail == ed) {
+ ohci->ed_isotail = ed->ed_prev;
+ }
+ break;
+ }
+
+ if(ed->hw.next_ed != 0) ((struct usb_ohci_ed *) bus_to_virt(ed->hw.next_ed))->ed_prev = ed->ed_prev;
+
+
+/* tds directly connected to ed */
+
+ td_hw = ed->hw.head_td & 0xfffffff0;
+ while(td_hw != 0) {
+ td = bus_to_virt(td_hw);
+ td_hw_tmp = td_hw;
+ td_hw = td->hw.next_td;
+ OHCI_FREE(td); /* free pending tds */
+ if(td_hw_tmp == ed->hw.tail_td) break;
+
+ }
+
+ /* mark TDs on the hc done list (if there are any) */
+ td_hw = readl(&ohci->regs->donehead) & 0xfffffff0;
+ while(td_hw != 0) {
+ td = bus_to_virt(td_hw);
+ td_hw = td->hw.next_td;
+ if(td->ep == ed) td->ep = 0;
+ }
+
+ /* mark TDs on the hcca done list (if there are any) */
+ td_hw = ohci->hc_area->hcca.done_head & 0xfffffff0 ;
+
+ while(td_hw != 0) {
+ td = bus_to_virt(td_hw);
+ td_hw = td->hw.next_td;
+ if(td->ep == ed) td->ep = 0;
+ }
+
+ ed_tmp = ed;
+ ed = ed->ed_list;
+ OHCI_FREE(ed_tmp); /* free ed */
+ }
+ writel(0, &ohci->regs->ed_controlcurrent); /* reset CTRL list */
+ writel(0, &ohci->regs->ed_bulkcurrent); /* reset BULK list */
+ writel_set((0x01<<4), &ohci->regs->control); /* start CTRL u. (BULK list) */
+
+ spin_unlock_irqrestore(&usb_ed_lock, flags);
+
+ ohci->ed_rm_list = NULL;
+ OHCI_DEBUG(printk("USB HC after rm ed control: %4x intrstat: %4x intrenable: %4x\n", readl(&ohci->regs->control),readl(&ohci->regs->intrstatus),readl(&ohci->regs->intrenable));)
+
+
+ return 0;
+}
+
+/* remove all endpoints of a function (device) */
+int usb_ohci_rm_function( struct ohci * ohci, unsigned int ep_addr_in)
+{
+ struct usb_ohci_ed *ed;
+ struct usb_ohci_ed *tmp;
+ union ep_addr_ ep_addr;
+
+
+
+ ep_addr.iep = ep_addr_in;
+
+ for(ed = ohci->ed_func_ep0[ep_addr.bep.fa]; ed != NULL;) {
+ tmp = ed;
+ ed = ed->ed_list;
+ usb_ohci_rm_ep(ohci, tmp);
+ }
+
+
+
+ return 1;
+}
+
+
+
+
+
+/******
+ *** TD handling functions
+ ************************************/
+
+
+#define FILL_TD(TD_PT, HANDLER, INFO, DATA, LEN, LW0, LW1) \
+ td_pt = (TD_PT); \
+ td_pt1 = (struct usb_ohci_td *) bus_to_virt(usb_ep->hw.tail_td); \
+ td_pt1->ep = usb_ep; \
+ td_pt1->handler = (HANDLER); \
+ td_pt1->buffer_start = (DATA); \
+ td_pt1->lw0 = (LW0); \
+ td_pt1->lw1 = (LW1); \
+ td_pt1->hw.info = (INFO); \
+ td_pt1->hw.cur_buf = virt_to_bus(DATA); \
+ td_pt1->hw.buf_end = td_pt1->hw.cur_buf + (LEN) - 1; \
+ td_pt1->hw.next_td = virt_to_bus(td_pt); \
+ usb_ep->hw.tail_td = virt_to_bus(td_pt); \
+ td_pt->prev_td = td_pt1; \
+ td_pt->hw.next_td = 0
+
+spinlock_t usb_req_lock = SPIN_LOCK_UNLOCKED;
+
+int ohci_trans_req(struct ohci * ohci, unsigned int ep_addr, int ctrl_len, void *ctrl, void * data, int data_len, __OHCI_BAG lw0, __OHCI_BAG lw1) {
+
+ int ed_type;
+ unsigned int flags;
+ struct usb_ohci_td *td_pt;
+ struct usb_ohci_td *td_pt1;
+ struct usb_ohci_td *td_pt_a1, *td_pt_a2, *td_pt_a3;
+ struct usb_ohci_ed *usb_ep;
+ f_handler handler;
+
+
+ td_pt_a1 =NULL;
+ td_pt_a2 =NULL;
+ td_pt_a3 =NULL;
+
+ usb_ep = ohci_find_ep(ohci, ep_addr);
+ if(usb_ep == NULL ) return -1; /* not known ep */
+
+ handler = usb_ep->handler;
+
+#ifdef VROOTHUB
+ if(usb_ep == &ohci->ed_rh_ep0) { /* root hub ep 0 control endpoint */
+ root_hub_control_msg(ohci, 8, ctrl, data, data_len, lw0, lw1, handler);
+ return 0;
+ }
+
+ if(usb_ep == &ohci->ed_rh_epi) { /* root hub interrupt endpoint */
+
+ root_hub_int_req(ohci, 8, ctrl, data, data_len, lw0, lw1, handler);
+ return 0;
+ }
+#endif
+ /* struct usb_ohci_ed * usb_ep = usb_ohci_add_ep(pipe, ohci, interval, 1); */
+
+ ed_type = ((((union ep_addr_)ep_addr).bep.ep >> 5) & 0x07);
+
+ switch(ed_type) {
+ case BULK_IN:
+ case BULK_OUT:
+ case INT_IN:
+ case INT_OUT:
+ OHCI_ALLOC(td_pt_a1, sizeof(td_pt_a1));
+ break;
+
+ case CTRL_IN:
+ case CTRL_OUT:
+ OHCI_ALLOC(td_pt_a1, sizeof(td_pt_a1));
+ OHCI_ALLOC(td_pt_a3, sizeof(td_pt_a3));
+ if(data_len > 0) {
+ OHCI_ALLOC(td_pt_a2, sizeof(td_pt_a2));
+ }
+ break;
+
+ case ISO_IN:
+ case ISO_OUT:
+
+ }
+
+ spin_lock_irqsave(&usb_req_lock, flags);
+
+ switch(ed_type) {
+ case BULK_IN:
+ FILL_TD( td_pt_a1, handler, TD_CC | TD_R | TD_DP_IN | TD_T_TOGGLE, data, data_len, lw0, lw1 );
+ writel( OHCI_BLF, &ohci->regs->cmdstatus); /* start bulk list */
+ break;
+
+ case BULK_OUT:
+ FILL_TD( td_pt_a1, handler, TD_CC | TD_DP_OUT | TD_T_TOGGLE, data, data_len, lw0, lw1 );
+ writel( OHCI_BLF, &ohci->regs->cmdstatus); /* start bulk list */
+ break;
+
+ case INT_IN:
+ FILL_TD( td_pt_a1, handler, TD_CC | TD_R | TD_DP_IN | TD_T_TOGGLE, data, data_len, lw0, lw1 );
+ break;
+
+ case INT_OUT:
+ FILL_TD( td_pt_a1, handler, TD_CC | TD_DP_OUT | TD_T_TOGGLE, data, data_len, lw0, lw1 );
+ break;
+
+ case CTRL_IN:
+ FILL_TD( td_pt_a1, NULL, TD_CC | TD_DP_SETUP | TD_T_DATA0, ctrl, ctrl_len, 0, 0 );
+ if(data_len > 0) {
+ FILL_TD( td_pt_a2, NULL, TD_CC | TD_R | TD_DP_IN | TD_T_DATA1, data, data_len, 0, 0 );
+ }
+ FILL_TD( td_pt_a3, handler, TD_CC | TD_DP_OUT | TD_T_DATA1, NULL, 0, lw0, lw1 );
+ writel( OHCI_CLF, &ohci->regs->cmdstatus); /* start Control list */
+ break;
+
+ case CTRL_OUT:
+ FILL_TD( td_pt_a1, NULL, TD_CC | TD_DP_SETUP | TD_T_DATA0, ctrl, ctrl_len, 0, 0 );
+ if(data_len > 0) {
+ FILL_TD( td_pt_a2, NULL, TD_CC | TD_R | TD_DP_OUT | TD_T_DATA1, data, data_len, 0, 0 );
+ }
+ FILL_TD( td_pt_a3, handler, TD_CC | TD_DP_IN | TD_T_DATA1, NULL, 0, lw0, lw1 );
+ writel( OHCI_CLF, &ohci->regs->cmdstatus); /* start Control list */
+ break;
+
+ case ISO_IN:
+ case ISO_OUT:
+ break;
+ }
+
+
+
+
+ td_pt1 = (struct usb_ohci_td *) bus_to_virt(usb_ep->hw.tail_td);
+
+
+ if(td_pt_a3 != NULL) td_pt_a3->prev_td = NULL;
+ else if (td_pt_a2 != NULL) td_pt_a2->prev_td = NULL;
+ else if (td_pt_a1 != NULL) td_pt_a1->prev_td = NULL;
+
+ spin_unlock_irqrestore(&usb_req_lock, flags);
+ return 0;
+}
+
+
+/******
+ *** Done List handling functions
+ ************************************/
+
+/* replies to the request have to be on a FIFO basis so
+ * we reverse the reversed done-list */
+
+static struct usb_ohci_td * ohci_reverse_done_list(struct ohci * ohci) {
+
+ __u32 td_list_hc;
+ struct usb_ohci_td * td_list = NULL;
+ struct usb_ohci_td * td_rev = NULL;
+
+ td_list_hc = ohci->hc_area->hcca.done_head & 0xfffffff0;
+ ohci->hc_area->hcca.done_head = 0;
+
+ while(td_list_hc) {
+
+ td_list = (struct usb_ohci_td *) bus_to_virt(td_list_hc);
+ td_list->next_dl_td = td_rev;
+
+ td_rev = td_list;
+ td_list_hc = td_list->hw.next_td & 0xfffffff0;
+ }
+ return td_list;
+}
+
+/* all done requests are replied here */
+static int usb_ohci_done_list(struct ohci * ohci) {
+
+ struct usb_ohci_td * td = NULL;
+ struct usb_ohci_td * td_list;
+ struct usb_ohci_td * td_list_next = NULL;
+ struct usb_ohci_td * td_err = NULL;
+ __u32 td_hw;
+
+
+ td_list = ohci_reverse_done_list(ohci);
+
+ while(td_list) {
+ td_list_next = td_list->next_dl_td;
+ td = td_list;
+
+ if(td->ep == NULL) { /* removed ep */
+ OHCI_FREE(td_list);
+ break;
+ }
+
+ /* the HC halts an ED if an error occurs; put all pendings TDs of an halted ED on the
+ * done list; they are marked with an 0xf CC_error code
+ */
+
+ if(TD_CC_GET(td_list->hw.info) != TD_CC_NOERROR) { /* on error move all pending tds of an ed into the done list */
+ printk("******* USB BUS error %x @ep %x\n", TD_CC_GET(td_list->hw.info), td_list->ep->ep_addr.iep);
+ td_err= td_list;
+ td_hw = td_list->ep->hw.head_td & 0xfffffff0;
+ while(td_hw != 0) {
+ if(td_hw == td_list->ep->hw.tail_td) break;
+ td = bus_to_virt(td_hw);
+ td_err->next_dl_td = td;
+ td_err= td;
+ td_hw = td->hw.next_td;
+ }
+ td_list->ep->hw.head_td = td_list->ep->hw.tail_td;
+ td->next_dl_td = td_list_next;
+ td_list_next = td_list->next_dl_td;
+
+ }
+ /* send the reply */
+ if(td_list->handler != NULL) {
+ if(td_list->prev_td == NULL) {
+ td_list->handler((void *) ohci,
+ td_list->ep->ep_addr.iep,
+ 0,
+ NULL,
+ td_list->buffer_start,
+ td_list->hw.buf_end-virt_to_bus(td_list->buffer_start)+1,
+ TD_CC_GET(td_list->hw.info),
+ td_list->lw0,
+ td_list->lw1);
+ OHCI_FREE(td_list);
+ }
+ else {
+ if(td_list->prev_td->prev_td == NULL) { /* cntrl 2 Transactions dataless */
+ td_list->handler((void *) ohci,
+ td_list->ep->ep_addr.iep,
+ td_list->prev_td->hw.buf_end-virt_to_bus(td_list->prev_td->buffer_start)+1,
+ td_list->prev_td->buffer_start,
+ NULL,
+ 0,
+ (TD_CC_GET(td_list->prev_td->hw.info) > 0) ? TD_CC_GET(td_list->prev_td->hw.info) : TD_CC_GET(td_list->hw.info),
+ td_list->lw0,
+ td_list->lw1);
+ OHCI_FREE(td_list->prev_td);
+ OHCI_FREE(td_list);
+ }
+ else { /* cntrl 3 Transactions */
+ td_list->handler((void *) ohci,
+ td_list->ep->ep_addr.iep,
+ td_list->prev_td->prev_td->hw.buf_end-virt_to_bus(td_list->prev_td->prev_td->buffer_start)+1,
+ td_list->prev_td->prev_td->buffer_start,
+ td_list->prev_td->buffer_start,
+ td_list->prev_td->hw.buf_end-virt_to_bus(td_list->prev_td->buffer_start)+1,
+ (TD_CC_GET(td_list->prev_td->prev_td->hw.info) > 0) ? TD_CC_GET(td_list->prev_td->prev_td->hw.info)
+ : (TD_CC_GET(td_list->prev_td->hw.info) > 0) ? TD_CC_GET(td_list->prev_td->hw.info) : TD_CC_GET(td_list->hw.info),
+ td_list->lw0,
+ td_list->lw1);
+ OHCI_FREE(td_list->prev_td->prev_td);
+ OHCI_FREE(td_list->prev_td);
+ OHCI_FREE(td_list);
+
+ }
+ }
+
+ }
+ td_list = td_list_next;
+ }
+ return 0;
+}
+
+
+
+/******
+ *** HC functions
+ ************************************/
+
+
+
+void reset_hc(struct ohci *ohci) {
+ int retries = 5;
+ int timeout = 30;
+ int fminterval;
+
+ if(readl(&ohci->regs->control) & 0x100) { /* SMM owns the HC */
+ writel(0x08, &ohci->regs->cmdstatus); /* request ownership */
+ printk("USB HC TakeOver from SMM\n");
+ do {
+ wait_ms(100);
+ if(--retries) {
+ printk("USB HC TakeOver timed out!\n");
+ break;
+ }
+ }
+ while(readl(&ohci->regs->control) & 0x100);
+ }
+
+ writel((1<<31), &ohci->regs->intrdisable); /* Disable HC interrupts */
+ OHCI_DEBUG(printk("USB HC reset_hc: %x ; retries: %d\n", readl(&ohci->regs->control), 5-retries);)
+ fminterval = readl(&ohci->regs->fminterval) & 0x3fff;
+ writel(1, &ohci->regs->cmdstatus); /* HC Reset */
+ while ((readl(&ohci->regs->cmdstatus) & 0x01) != 0) { /* 10us Reset */
+ if (--timeout == 0) {
+ printk("USB HC reset timed out!\n");
+ return;
+ }
+ udelay(1);
+ }
+ /* set the timing */
+ fminterval |= (((fminterval -210) * 6)/7)<<16;
+ writel(fminterval, &ohci->regs->fminterval);
+ writel(((fminterval&0x3fff)*9)/10, &ohci->regs->periodicstart);
+}
+
+
+/*
+ * Reset and start an OHCI controller
+ */
+void start_hc(struct ohci *ohci)
+{
+ /* int fminterval; */
+ unsigned int mask;
+ /* fminterval = readl(&ohci->regs->fminterval) & 0x3fff;
+ reset_hc(ohci); */
+
+
+ writel(virt_to_bus(&ohci->hc_area->hcca), &ohci->regs->hcca); /* a reset clears this */
+
+ /* Choose the interrupts we care about now, others later on demand */
+ mask = OHCI_INTR_MIE | OHCI_INTR_WDH;
+ /* | OHCI_INTR_SO | OHCI_INTR_UE |OHCI_INTR_RHSC |OHCI_INTR_SF|
+ OHCI_INTR_FNO */
+
+
+
+ writel((0x00), &ohci->regs->control); /* USB Reset BUS */
+ wait_ms(10);
+
+ writel((0x97), &ohci->regs->control); /* USB Operational */
+
+ writel( 0x10000, &ohci->regs->roothub.status); /* root hub power on */
+ wait_ms(50);
+
+ OHCI_DEBUG(printk("USB HC rstart_hc_operational: %x\n", readl(&ohci->regs->control)); )
+ OHCI_DEBUG(printk("USB HC roothubstata: %x \n", readl( &(ohci->regs->roothub.a) )); )
+ OHCI_DEBUG(printk("USB HC roothubstatb: %x \n", readl( &(ohci->regs->roothub.b) )); )
+ OHCI_DEBUG(printk("USB HC roothubstatu: %x \n", readl( &(ohci->regs->roothub.status) )); )
+ OHCI_DEBUG(printk("USB HC roothubstat1: %x \n", readl( &(ohci->regs->roothub.portstatus[0]) )); )
+ OHCI_DEBUG(printk("USB HC roothubstat2: %x \n", readl( &(ohci->regs->roothub.portstatus[1]) )); )
+
+ /* control_wakeup = NULL; */
+ writel(mask, &ohci->regs->intrenable);
+ writel(mask, &ohci->regs->intrstatus);
+
+#ifdef VROOTHUB
+ {
+
+ struct usb_device * usb_dev;
+ struct ohci_device *dev;
+
+ usb_dev = sohci_usb_allocate(ohci->root_hub->usb);
+ dev = usb_dev->hcpriv;
+
+ dev->ohci = ohci;
+
+ usb_connect(usb_dev);
+
+ ohci->root_hub->usb->children[0] = usb_dev;
+
+ usb_new_device(usb_dev);
+ }
+#endif
+
+
+
+}
+
+
+
+
+static void ohci_interrupt(int irq, void *__ohci, struct pt_regs *r)
+{
+ struct ohci *ohci = __ohci;
+ struct ohci_regs *regs = ohci->regs;
+
+ int ints;
+
+
+ if((ohci->hc_area->hcca.done_head != 0) && !(ohci->hc_area->hcca.done_head & 0x01)) {
+ ints = OHCI_INTR_WDH;
+ }
+ else {
+ if((ints = (readl(®s->intrstatus) & readl(®s->intrenable))) == 0)
+ return;
+ }
+
+ ohci->intrstatus |= ints;
+ OHCI_DEBUG(printk("USB HC interrupt: %x (%x) \n", ints, readl(&ohci->regs->intrstatus));)
+
+ /* ints &= ~(OHCI_INTR_WDH); WH Bit will be set by done list subroutine */
+ /* if(ints & OHCI_INTR_FNO) {
+ writel(OHCI_INTR_FNO, ®s->intrstatus);
+ if (waitqueue_active(&ohci_tasks)) wake_up(&ohci_tasks);
+ } */
+
+ if(ints & OHCI_INTR_WDH) {
+ writel(OHCI_INTR_WDH, ®s->intrdisable);
+ ohci->intrstatus &= (~OHCI_INTR_WDH);
+ usb_ohci_done_list(ohci); /* prepare out channel list */
+ writel(OHCI_INTR_WDH, &ohci->regs->intrstatus);
+ writel(OHCI_INTR_WDH, &ohci->regs->intrenable);
+
+ }
+
+ if(ints & OHCI_INTR_SF) {
+ writel(OHCI_INTR_SF, ®s->intrdisable);
+ writel(OHCI_INTR_SF, &ohci->regs->intrstatus);
+ ohci->intrstatus &= (~OHCI_INTR_SF);
+ if(ohci->ed_rm_list != NULL) {
+ ohci_rm_eds(ohci);
+ }
+ }
+#ifndef VROOTHUB
+ if(ints & OHCI_INTR_RHSC) {
+ writel(OHCI_INTR_RHSC, ®s->intrdisable);
+ writel(OHCI_INTR_RHSC, &ohci->regs->intrstatus);
+ wake_up(&root_hub);
+
+ }
+ #endif
+
+ writel(OHCI_INTR_MIE, ®s->intrenable);
+
+}
+
+#ifndef VROOTHUB
+/*
+ * This gets called if the connect status on the root
+ * hub (and the root hub only) changes.
+ */
+static void ohci_connect_change(struct ohci *ohci, unsigned int port_nr)
+{
+ struct usb_device *usb_dev;
+ struct ohci_device *dev;
+ OHCI_DEBUG(printk("uhci_connect_change: called for %d stat %x\n", port_nr,readl(&ohci->regs->roothub.portstatus[port_nr]) );)
+
+ /*
+ * Even if the status says we're connected,
+ * the fact that the status bits changed may
+ * that we got disconnected and then reconnected.
+ *
+ * So start off by getting rid of any old devices..
+ */
+ usb_disconnect(&ohci->root_hub->usb->children[port_nr]);
+
+ if(!(readl(&ohci->regs->roothub.portstatus[port_nr]) & RH_PS_CCS)) {
+ writel(RH_PS_CCS, &ohci->regs->roothub.portstatus[port_nr]);
+ return; /* nothing connected */
+ }
+ /*
+ * Ok, we got a new connection. Allocate a device to it,
+ * and find out what it wants to do..
+ */
+ usb_dev = sohci_usb_allocate(ohci->root_hub->usb);
+ dev = usb_dev->hcpriv;
+ dev->ohci = ohci;
+ usb_connect(dev->usb);
+ ohci->root_hub->usb->children[port_nr] = usb_dev;
+ wait_ms(200); /* wait for powerup */
+ /* reset port/device */
+ writel(RH_PS_PRS, &ohci->regs->roothub.portstatus[port_nr]); /* reset port */
+ while(!(readl( &ohci->regs->roothub.portstatus[port_nr]) & RH_PS_PRSC)) wait_ms(10); /* reset active ? */
+ writel(RH_PS_PES, &ohci->regs->roothub.portstatus[port_nr]); /* enable port */
+ wait_ms(10);
+ /* Get speed information */
+ usb_dev->slow = (readl( &ohci->regs->roothub.portstatus[port_nr]) & RH_PS_LSDA) ? 1 : 0;
+
+ /*
+ * Ok, all the stuff specific to the root hub has been done.
+ * The rest is generic for any new USB attach, regardless of
+ * hub type.
+ */
+ usb_new_device(usb_dev);
+}
+#endif
+
+
+
+/*
+ * Allocate the resources required for running an OHCI controller.
+ * Host controller interrupts must not be running while calling this
+ * function.
+ *
+ * The mem_base parameter must be the usable -virtual- address of the
+ * host controller's memory mapped I/O registers.
+ *
+ * This is where OHCI triumphs over UHCI, because good is dumb.
+ * Note how much simpler this function is than in uhci.c.
+ *
+ * OHCI hardware takes care of most of the scheduling of different
+ * transfer types with the correct prioritization for us.
+ */
+
+
+static struct ohci *alloc_ohci(void* mem_base)
+{
+ int i,j;
+ struct ohci *ohci;
+ struct ohci_hc_area *hc_area;
+ struct usb_bus *bus;
+ struct ohci_device *dev;
+ struct usb_device *usb;
+
+ /*
+ * Here we allocate some dummy EDs as well as the
+ * OHCI host controller communications area. The HCCA is just
+ * a nice pool of memory with pointers to endpoint descriptors
+ * for the different interrupts.
+ *
+ * The first page of memory contains the HCCA and ohci structure
+ */
+ hc_area = (struct ohci_hc_area *) __get_free_pages(GFP_KERNEL, 1);
+ if (!hc_area)
+ return NULL;
+ memset(hc_area, 0, sizeof(*hc_area));
+ ohci = &hc_area->ohci;
+ ohci->irq = -1;
+ ohci->regs = mem_base;
+
+ ohci->hc_area = hc_area;
+ /* Tell the controller where the HCCA is */
+ writel(virt_to_bus(&hc_area->hcca), &ohci->regs->hcca);
+
+
+ /*
+ * Initialize the ED polling "tree", full tree;
+ * dummy eds ed[i] (hc should skip them)
+ * i == 0 is the end of the iso list;
+ * 1 is the 1ms node,
+ * 2,3 2ms nodes,
+ * 4,5,6,7 4ms nodes,
+ * 8 ... 15 8ms nodes,
+ * 16 ... 31 16ms nodes,
+ * 32 ... 63 32ms nodes
+ * Sequenzes:
+ * 32-16- 8-4-2-1-0
+ * 33-17- 9-5-3-1-0
+ * 34-18-10-6-2-1-0
+ * 35-19-11-7-3-1-0
+ * 36-20-12-4-2-1-0
+ * 37-21-13-5-3-1-0
+ * 38-22-14-6-2-1-0
+ * 39-23-15-7-3-1-0
+ * 40-24- 8-4-2-1-0
+ * 41-25- 9-5-3-1-0
+ * 42-26-10-6-2-1-0
+ * : :
+ * 63-31-15-7-3-1-0
+ */
+ hc_area->ed[ED_ISO].info |= OHCI_ED_SKIP; /* place holder, so skip it */
+ hc_area->ed[ED_ISO].next_ed = 0x0000; /* end of iso list */
+
+ hc_area->ed[1].next_ed = virt_to_bus(&(hc_area->ed[ED_ISO]));
+ hc_area->ed[1].info |= OHCI_ED_SKIP; /* place holder, skip it */
+
+ j=1;
+ for (i = 2; i < (NUM_INTS * 2); i++) {
+ if (i >= NUM_INTS)
+ hc_area->hcca.int_table[i - NUM_INTS] = virt_to_bus(&(hc_area->ed[i]));
+
+ if( i == j*4) j *= 2;
+ hc_area->ed[i].next_ed = virt_to_bus(&(hc_area->ed[j+ i%j]));
+ hc_area->ed[i].info |= OHCI_ED_SKIP; /* place holder, skip it */
+ }
+
+
+ /*
+ * for load ballancing of the interrupt branches
+ */
+ for (i = 0; i < NUM_INTS; i++) ohci->ohci_int_load[i] = 0;
+
+ /*
+ * Store the end of control and bulk list eds. So, we know where we can add
+ * elements to these lists.
+ */
+ ohci->ed_isotail = (struct usb_ohci_ed *) &(hc_area->ed[ED_ISO]);
+ ohci->ed_controltail = NULL;
+ ohci->ed_bulktail = NULL;
+
+ /*
+ * Tell the controller where the control and bulk lists are
+ * The lists are empty now.
+ */
+ writel(0, &ohci->regs->ed_controlhead);
+ writel(0, &ohci->regs->ed_bulkhead);
+
+
+ USB_ALLOC(bus, sizeof(*bus));
+ if (!bus)
+ return NULL;
+
+ memset(bus, 0, sizeof(*bus));
+
+ ohci->bus = bus;
+ bus->hcpriv = (void *) ohci;
+ bus->op = &sohci_device_operations;
+
+
+ usb = sohci_usb_allocate(NULL);
+ if (!usb)
+ return NULL;
+
+ dev = ohci->root_hub = usb_to_ohci(usb);
+
+ usb->bus = bus;
+ /* bus->root_hub = ohci_to_usb(ohci->root_hub); */
+ dev->ohci = ohci;
+
+ /* Initialize the root hub */
+
+ usb->maxchild = readl(&ohci->regs->roothub.a) & 0xff;
+ usb_init_root_hub(usb);
+
+ return ohci;
+}
+
+
+/*
+ * De-allocate all resources..
+ */
+
+static void release_ohci(struct ohci *ohci)
+{
+ int i;
+ union ep_addr_ ep_addr;
+ ep_addr.iep = 0;
+
+ OHCI_DEBUG(printk("USB HC release ohci \n");)
+
+ if (ohci->irq >= 0) {
+ free_irq(ohci->irq, ohci);
+ ohci->irq = -1;
+ }
+
+ /* stop hc */
+ writel(OHCI_USB_SUSPEND, &ohci->regs->control);
+
+ /* deallocate all EDs and TDs */
+ for(i=0; i < 128; i ++) {
+ ep_addr.bep.fa = i;
+ usb_ohci_rm_function(ohci, ep_addr.iep);
+ }
+ ohci_rm_eds(ohci); /* remove eds */
+
+ /* disconnect all devices */
+ if(ohci->root_hub)
+ for(i = 0; i < ohci->root_hub->usb->maxchild; i++)
+ usb_disconnect(ohci->root_hub->usb->children + i);
+
+ USB_FREE(ohci->root_hub->usb);
+ USB_FREE(ohci->root_hub);
+ USB_FREE(ohci->bus);
+
+ /* unmap the IO address space */
+ iounmap(ohci->regs);
+
+
+ free_pages((unsigned int) ohci->hc_area, 1);
+
+}
+
+
+void cleanup_drivers(void);
+
+static int ohci_roothub_thread(void * __ohci)
+{
+ struct ohci *ohci = (struct ohci *)__ohci;
+ lock_kernel();
+
+ /*
+ * This thread doesn't need any user-level access,
+ * so get rid of all our resources..
+ */
+ printk("ohci_roothub_thread at %p\n", &ohci_roothub_thread);
+ exit_mm(current);
+ exit_files(current);
+ exit_fs(current);
+
+
+ strcpy(current->comm, "root-hub");
+
+
+ start_hc(ohci);
+ writel( 0x10000, &ohci->regs->roothub.status);
+ wait_ms(50); /* root hub power on */
+ do {
+#ifdef CONFIG_APM
+ if (apm_resume) {
+ apm_resume = 0;
+ start_hc(ohci);
+ continue;
+ }
+#endif
+
+ OHCI_DEBUG(printk("USB RH tasks: int: %x\n", ohci->intrstatus); )
+#ifndef VROOTHUB
+ /* if (ohci->intrstatus & OHCI_INTR_RHSC) */
+ {
+ int port_nr;
+ for(port_nr=0; port_nr< ohci->root_hub->usb->maxchild; port_nr++)
+ if(readl(&ohci->regs->roothub.portstatus[port_nr]) & (RH_PS_CSC | RH_PS_PRSC)) {
+ ohci_connect_change(ohci, port_nr);
+ writel(0xffff0000, &ohci->regs->roothub.portstatus[port_nr]);
+ }
+ ohci->intrstatus &= ~(OHCI_INTR_RHSC);
+ writel(OHCI_INTR_RHSC, &ohci->regs->intrenable);
+ }
+#endif
+
+ interruptible_sleep_on(&root_hub);
+
+ } while (!signal_pending(current));
+
+#ifdef VROOTHUB
+ ohci_del_rh_int_timer(ohci);
+#endif
+
+
+ cleanup_drivers();
+ /* reset_hc(ohci); */
+
+ release_ohci(ohci);
+ MOD_DEC_USE_COUNT;
+
+ printk("ohci_control_thread exiting\n");
+
+ return 0;
+}
+
+
+
+
+/*
+ * Increment the module usage count, start the control thread and
+ * return success.
+ */
+static int found_ohci(int irq, void* mem_base)
+{
+ int retval;
+ struct ohci *ohci;
+ OHCI_DEBUG(printk("USB HC found ohci: irq= %d membase= %x \n", irq, (int)mem_base);)
+ /* Allocate the running OHCI structures */
+ ohci = alloc_ohci(mem_base);
+ if (!ohci) {
+ return -ENOMEM;
+ }
+
+ reset_hc(ohci);
+
+ retval = -EBUSY;
+ if (request_irq(irq, ohci_interrupt, SA_SHIRQ, "ohci-usb", ohci) == 0) {
+ int pid;
+
+ MOD_INC_USE_COUNT;
+ ohci->irq = irq;
+
+ pid = kernel_thread(ohci_roothub_thread, ohci, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
+ if (pid >= 0)
+ return 0;
+
+
+ MOD_DEC_USE_COUNT;
+ retval = pid;
+ }
+
+ release_ohci(ohci);
+ return retval;
+}
+
+static int start_ohci(struct pci_dev *dev)
+{
+ unsigned int mem_base = dev->base_address[0];
+
+ /* If its OHCI, its memory */
+ if (mem_base & PCI_BASE_ADDRESS_SPACE_IO)
+ return -ENODEV;
+
+ /* Get the memory address and map it for IO */
+ mem_base &= PCI_BASE_ADDRESS_MEM_MASK;
+
+ /*
+ * FIXME ioremap_nocache isn't implemented on all CPUs (such
+ * as the Alpha) [?] What should I use instead...
+ *
+ * The iounmap() is done on in release_ohci.
+ */
+ mem_base = (unsigned int) ioremap_nocache(mem_base, 4096);
+
+ if (!mem_base) {
+ printk("Error mapping OHCI memory\n");
+ return -EFAULT;
+ }
+
+ return found_ohci(dev->irq, (void *) mem_base);
+}
+
+
+
+#ifdef CONFIG_APM
+static int handle_apm_event(apm_event_t event)
+{
+ static int down = 0;
+
+ switch (event) {
+ case APM_SYS_SUSPEND:
+ case APM_USER_SUSPEND:
+ if (down) {
+ printk(KERN_DEBUG "ohci: received extra suspend event\n");
+ break;
+ }
+ down = 1;
+ break;
+ case APM_NORMAL_RESUME:
+ case APM_CRITICAL_RESUME:
+ if (!down) {
+ printk(KERN_DEBUG "ohci: received bogus resume event\n");
+ break;
+ }
+ down = 0;
+ if (waitqueue_active(&root_hub)) {
+ apm_resume = 1;
+ wake_up(&root_hub);
+ }
+ break;
+ }
+ return 0;
+}
+#endif
+
+
+ int usb_mouse_init(void);
+#ifdef MODULE
+
+void cleanup_module(void)
+{
+#ifdef CONFIG_APM
+ apm_unregister_callback(&handle_apm_event);
+#endif
+}
+
+#define ohci_hcd_init init_module
+
+#endif
+
+#define PCI_CLASS_SERIAL_USB_OHCI 0x0C0310
+#define PCI_CLASS_SERIAL_USB_OHCI_PG 0x10
+
+
+int ohci_hcd_init(void)
+{
+ int retval;
+ struct pci_dev *dev = NULL;
+
+ retval = -ENODEV;
+
+ dev = NULL;
+ while((dev = pci_find_class(PCI_CLASS_SERIAL_USB_OHCI, dev))) { /* OHCI */
+ retval = start_ohci(dev);
+ if (retval < 0) break;
+
+
+#ifdef CONFIG_USB_MOUSE
+ usb_mouse_init();
+#endif
+#ifdef CONFIG_USB_KBD
+ usb_kbd_init();
+#endif
+ hub_init();
+#ifdef CONFIG_USB_AUDIO
+ usb_audio_init();
+#endif
+#ifdef CONFIG_APM
+ apm_register_callback(&handle_apm_event);
+#endif
+
+ return 0;
+ }
+ return retval;
+}
+
+void cleanup_drivers(void)
+{
+ hub_cleanup();
+#ifdef CONFIG_USB_MOUSE
+ usb_mouse_cleanup();
+#endif
+}
+
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)