/* * OHCI HCD (Host Controller Driver) for USB. * * (C) Copyright 1999 Roman Weissgaerber * * 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 ] * [ $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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usb.h" #include "ohci-hcd.h" #ifdef CONFIG_APM #include static int handle_apm_event(apm_event_t event); static int apm_resume = 0; #endif static DECLARE_WAIT_QUEUE_HEAD(bulk_wakeup); static DECLARE_WAIT_QUEUE_HEAD(control_wakeup); static DECLARE_WAIT_QUEUE_HEAD(root_hub); 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, data_len, 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_bulk_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(&bulk_wakeup); OHCI_DEBUG( { int i; printk("USB HC BULK<<<: %x:", ep_addr, ctrl_len);) 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) { DECLARE_WAITQUEUE(wait, current); 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_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len) { DECLARE_WAITQUEUE(wait, current); 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 */ | (11 << 5); /* type = bulk*/ ep_addr.bep.fa = ((pipe >> 8) & 0x7f); /* device address */ status = 0xf; /* CC not Accessed */ OHCI_DEBUG( { int i; printk("USB HC BULK>>>: %x:", ep_addr.iep);) 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_bulk_handler, 1 << ((pipe & 0x03) + 3) , (pipe >> 26) & 0x01); current->state = TASK_UNINTERRUPTIBLE; add_wait_queue(&bulk_wakeup, &wait); ohci_trans_req(ohci, ep_addr.iep, 0, NULL, data, len, (__OHCI_BAG) &status, 0); schedule_timeout(HZ/10); remove_wait_queue(&bulk_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_bulk_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((0x03<<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; int port_nr; /* 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 */ if(readl(&ohci->regs->roothub.a) & 0x100) /* global power on */ writel( 0x10000, &ohci->regs->roothub.status); /* root hub power on */ else { /* port power on */ for(port_nr=0; port_nr < (ohci->regs->roothub.a & 0xff); port_nr++) writel(0x100, &ohci->regs->roothub.portstatus[port_nr]); } wait_ms(50); writel((0x00), &ohci->regs->control); /* USB Reset BUS */ wait_ms(10); writel((0xB7), &ohci->regs->control); /* USB Operational */ 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; struct ohci_device *tmp_root_hub= usb_to_ohci(ohci->bus->root_hub); usb_dev = sohci_usb_allocate(tmp_root_hub->usb); dev = usb_dev->hcpriv; dev->ohci = ohci; usb_connect(usb_dev); tmp_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; struct ohci_device *tmp_root_hub=usb_to_ohci(ohci->bus->root_hub); 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(&tmp_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(tmp_root_hub->usb); dev = usb_dev->hcpriv; dev->ohci = ohci; usb_connect(dev->usb); tmp_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 = usb_to_ohci(usb); usb->bus = bus; bus->root_hub = usb; 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; struct ohci_device *tmp_root_hub=usb_to_ohci(ohci->bus->root_hub); 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->bus->root_hub) for(i = 0; i < tmp_root_hub->usb->maxchild; i++) usb_disconnect(tmp_root_hub->usb->children + i); usb_deregister_bus(ohci->bus); USB_FREE(tmp_root_hub->usb); USB_FREE(tmp_root_hub); USB_FREE(ohci->bus); /* unmap the IO address space */ iounmap(ohci->regs); free_pages((unsigned int) ohci->hc_area, 1); } 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 */ usb_register_bus(ohci->bus); 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 #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_APM apm_register_callback(&handle_apm_event); #endif return 0; } return retval; } #ifdef MODULE int init_module(void){ return ohci_hcd_init(); } void cleanup_module(void) { # ifdef CONFIG_APM apm_unregister_callback(&handle_apm_event); # endif } #endif //MODULE