X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=src%2Fusb-ohci.c;h=9107db2bcd2af68e76fc7ceb9656d7dd4d1c73c6;hb=refs%2Fheads%2Fcoreboot;hp=2d33fa98c2c5149521bc5faae7e92655ad888188;hpb=991eaff3f618d8018b2956b19bb47b0dff58a1d7;p=seabios.git diff --git a/src/usb-ohci.c b/src/usb-ohci.c index 2d33fa9..9107db2 100644 --- a/src/usb-ohci.c +++ b/src/usb-ohci.c @@ -14,145 +14,169 @@ #define FIT (1 << 31) +struct usb_ohci_s { + struct usb_s usb; + struct ohci_regs *regs; +}; + + +/**************************************************************** + * Root hub + ****************************************************************/ + +// Check if device attached to port +static int +ohci_hub_detect(struct usbhub_s *hub, u32 port) +{ + struct usb_ohci_s *cntl = container_of(hub->cntl, struct usb_ohci_s, usb); + u32 sts = readl(&cntl->regs->roothub_portstatus[port]); + if (!(sts & RH_PS_CCS)) + // No device. + return -1; + + // XXX - need to wait for USB_TIME_ATTDB if just powered up? + + return 0; +} + +// Disable port +static void +ohci_hub_disconnect(struct usbhub_s *hub, u32 port) +{ + struct usb_ohci_s *cntl = container_of(hub->cntl, struct usb_ohci_s, usb); + writel(&cntl->regs->roothub_portstatus[port], RH_PS_CCS|RH_PS_LSDA); +} + +// Reset device on port static int -start_ohci(struct usb_s *cntl, struct ohci_hcca *hcca) +ohci_hub_reset(struct usbhub_s *hub, u32 port) { - u32 oldfminterval = readl(&cntl->ohci.regs->fminterval); - u32 oldrwc = readl(&cntl->ohci.regs->control) & OHCI_CTRL_RWC; + struct usb_ohci_s *cntl = container_of(hub->cntl, struct usb_ohci_s, usb); + writel(&cntl->regs->roothub_portstatus[port], RH_PS_PRS); + u32 sts; + u64 end = calc_future_tsc(USB_TIME_DRSTR * 2); + for (;;) { + sts = readl(&cntl->regs->roothub_portstatus[port]); + if (!(sts & RH_PS_PRS)) + // XXX - need to ensure USB_TIME_DRSTR time in reset? + break; + if (check_tsc(end)) { + // Timeout. + warn_timeout(); + ohci_hub_disconnect(hub, port); + return -1; + } + yield(); + } + + if ((sts & (RH_PS_CCS|RH_PS_PES)) != (RH_PS_CCS|RH_PS_PES)) + // Device no longer present + return -1; + + return !!(sts & RH_PS_LSDA); +} + +static struct usbhub_op_s ohci_HubOp = { + .detect = ohci_hub_detect, + .reset = ohci_hub_reset, + .disconnect = ohci_hub_disconnect, +}; + +// Find any devices connected to the root hub. +static int +check_ohci_ports(struct usb_ohci_s *cntl) +{ + ASSERT32FLAT(); + // Turn on power for all devices on roothub. + u32 rha = readl(&cntl->regs->roothub_a); + rha &= ~(RH_A_PSM | RH_A_OCPM); + writel(&cntl->regs->roothub_status, RH_HS_LPSC); + writel(&cntl->regs->roothub_b, RH_B_PPCM); + msleep((rha >> 24) * 2); + // XXX - need to sleep for USB_TIME_SIGATT if just powered up? + + struct usbhub_s hub; + memset(&hub, 0, sizeof(hub)); + hub.cntl = &cntl->usb; + hub.portcount = rha & RH_A_NDP; + hub.op = &ohci_HubOp; + usb_enumerate(&hub); + return hub.devcount; +} + + +/**************************************************************** + * Setup + ****************************************************************/ + +static int +start_ohci(struct usb_ohci_s *cntl, struct ohci_hcca *hcca) +{ + u32 oldfminterval = readl(&cntl->regs->fminterval); + u32 oldrwc = readl(&cntl->regs->control) & OHCI_CTRL_RWC; // XXX - check if already running? // Do reset - writel(&cntl->ohci.regs->control, OHCI_USB_RESET | oldrwc); - readl(&cntl->ohci.regs->control); // flush writes - msleep(50); + writel(&cntl->regs->control, OHCI_USB_RESET | oldrwc); + readl(&cntl->regs->control); // flush writes + msleep(USB_TIME_DRSTR); // Do software init (min 10us, max 2ms) u64 end = calc_future_tsc_usec(10); - writel(&cntl->ohci.regs->cmdstatus, OHCI_HCR); + writel(&cntl->regs->cmdstatus, OHCI_HCR); for (;;) { - u32 status = readl(&cntl->ohci.regs->cmdstatus); + u32 status = readl(&cntl->regs->cmdstatus); if (! status & OHCI_HCR) break; - if (check_time(end)) { - dprintf(1, "Timeout on ohci software reset\n"); + if (check_tsc(end)) { + warn_timeout(); return -1; } } // Init memory - writel(&cntl->ohci.regs->ed_controlhead, (u32)cntl->ohci.control_ed); - writel(&cntl->ohci.regs->ed_bulkhead, 0); - writel(&cntl->ohci.regs->hcca, (u32)hcca); + writel(&cntl->regs->ed_controlhead, 0); + writel(&cntl->regs->ed_bulkhead, 0); + writel(&cntl->regs->hcca, (u32)hcca); // Init fminterval u32 fi = oldfminterval & 0x3fff; - writel(&cntl->ohci.regs->fminterval + writel(&cntl->regs->fminterval , (((oldfminterval & FIT) ^ FIT) | fi | (((6 * (fi - 210)) / 7) << 16))); - writel(&cntl->ohci.regs->periodicstart, ((9 * fi) / 10) & 0x3fff); - readl(&cntl->ohci.regs->control); // flush writes + writel(&cntl->regs->periodicstart, ((9 * fi) / 10) & 0x3fff); + readl(&cntl->regs->control); // flush writes // XXX - verify that fminterval was setup correctly. // Go into operational state - writel(&cntl->ohci.regs->control + writel(&cntl->regs->control , (OHCI_CTRL_CBSR | OHCI_CTRL_CLE | OHCI_CTRL_PLE | OHCI_USB_OPER | oldrwc)); - readl(&cntl->ohci.regs->control); // flush writes + readl(&cntl->regs->control); // flush writes return 0; } static void -stop_ohci(struct usb_s *cntl) +stop_ohci(struct usb_ohci_s *cntl) { - u32 oldrwc = readl(&cntl->ohci.regs->control) & OHCI_CTRL_RWC; - writel(&cntl->ohci.regs->control, oldrwc); - readl(&cntl->ohci.regs->control); // flush writes + u32 oldrwc = readl(&cntl->regs->control) & OHCI_CTRL_RWC; + writel(&cntl->regs->control, oldrwc); + readl(&cntl->regs->control); // flush writes } -// Find any devices connected to the root hub. -static int -check_ohci_ports(struct usb_s *cntl) -{ - // Turn on power for all devices on roothub. - u32 rha = readl(&cntl->ohci.regs->roothub_a); - rha &= ~(RH_A_PSM | RH_A_OCPM); - writel(&cntl->ohci.regs->roothub_status, RH_HS_LPSC); - writel(&cntl->ohci.regs->roothub_b, RH_B_PPCM); - msleep((rha >> 24) * 2); - - // Count and reset connected devices - int ports = rha & RH_A_NDP; - int totalcount = 0; - int i; - for (i=0; iohci.regs->roothub_portstatus[i]) & RH_PS_CCS) { - writel(&cntl->ohci.regs->roothub_portstatus[i], RH_PS_PRS); - totalcount++; - } - if (!totalcount) - // No devices connected - goto shutdown; - - msleep(60); // XXX - should poll instead of using timer. - - totalcount = 0; - for (i=0; iohci.regs->roothub_portstatus[i]); - if ((sts & (RH_PS_CCS|RH_PS_PES)) == (RH_PS_CCS|RH_PS_PES)) { - int count = configure_usb_device(cntl, !!(sts & RH_PS_LSDA)); - if (! count) - // Shutdown port - writel(&cntl->ohci.regs->roothub_portstatus[i] - , RH_PS_CCS|RH_PS_LSDA); - totalcount += count; - } - } - if (!totalcount) - goto shutdown; - - return totalcount; - -shutdown: - // Turn off power to all ports - writel(&cntl->ohci.regs->roothub_status, RH_HS_LPS); - return 0; -} - -void -ohci_init(void *data) +static void +configure_ohci(void *data) { - if (! CONFIG_USB_OHCI) - return; - struct usb_s *cntl = data; - - // XXX - don't call pci_config_XXX from a thread - cntl->type = USB_TYPE_OHCI; - u32 baseaddr = pci_config_readl(cntl->bdf, PCI_BASE_ADDRESS_0); - cntl->ohci.regs = (void*)(baseaddr & PCI_BASE_ADDRESS_MEM_MASK); - - dprintf(3, "OHCI init on dev %02x:%02x.%x (regs=%p)\n" - , pci_bdf_to_bus(cntl->bdf), pci_bdf_to_dev(cntl->bdf) - , pci_bdf_to_fn(cntl->bdf), cntl->ohci.regs); - - // Enable bus mastering and memory access. - pci_config_maskw(cntl->bdf, PCI_COMMAND - , 0, PCI_COMMAND_MASTER|PCI_COMMAND_MEMORY); - - // XXX - check for and disable SMM control? - - // Disable interrupts - writel(&cntl->ohci.regs->intrdisable, ~0); - writel(&cntl->ohci.regs->intrstatus, ~0); + struct usb_ohci_s *cntl = data; // Allocate memory struct ohci_hcca *hcca = memalign_high(256, sizeof(*hcca)); struct ohci_ed *intr_ed = malloc_high(sizeof(*intr_ed)); - struct ohci_ed *control_ed = malloc_high(sizeof(*control_ed)); - if (!hcca || !intr_ed || !control_ed) { - dprintf(1, "No ram for ohci init\n"); + if (!hcca || !intr_ed) { + warn_noalloc(); goto free; } memset(hcca, 0, sizeof(*hcca)); @@ -161,15 +185,13 @@ ohci_init(void *data) int i; for (i=0; iint_table); i++) hcca->int_table[i] = (u32)intr_ed; - memset(control_ed, 0, sizeof(*control_ed)); - control_ed->hwINFO = ED_SKIP; - cntl->ohci.control_ed = control_ed; int ret = start_ohci(cntl, hcca); if (ret) goto err; int count = check_ohci_ports(cntl); + free_pipe(cntl->usb.defaultpipe); if (! count) goto err; return; @@ -179,9 +201,49 @@ err: free: free(hcca); free(intr_ed); - free(control_ed); } +void +ohci_init(struct pci_device *pci, int busid) +{ + if (! CONFIG_USB_OHCI) + return; + struct usb_ohci_s *cntl = malloc_tmphigh(sizeof(*cntl)); + if (!cntl) { + warn_noalloc(); + return; + } + memset(cntl, 0, sizeof(*cntl)); + cntl->usb.busid = busid; + cntl->usb.pci = pci; + cntl->usb.type = USB_TYPE_OHCI; + + u16 bdf = pci->bdf; + u32 baseaddr = pci_config_readl(bdf, PCI_BASE_ADDRESS_0); + cntl->regs = (void*)(baseaddr & PCI_BASE_ADDRESS_MEM_MASK); + + dprintf(1, "OHCI init on dev %02x:%02x.%x (regs=%p)\n" + , pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf) + , pci_bdf_to_fn(bdf), cntl->regs); + + // Enable bus mastering and memory access. + pci_config_maskw(bdf, PCI_COMMAND + , 0, PCI_COMMAND_MASTER|PCI_COMMAND_MEMORY); + + // XXX - check for and disable SMM control? + + // Disable interrupts + writel(&cntl->regs->intrdisable, ~0); + writel(&cntl->regs->intrstatus, ~0); + + run_thread(configure_ohci, cntl); +} + + +/**************************************************************** + * End point communication + ****************************************************************/ + static int wait_ed(struct ohci_ed *ed) { @@ -190,89 +252,213 @@ wait_ed(struct ohci_ed *ed) for (;;) { if (ed->hwHeadP == ed->hwTailP) return 0; - if (check_time(end)) { - dprintf(1, "Timeout on wait_ed %p\n", ed); + if (check_tsc(end)) { + warn_timeout(); return -1; } yield(); } } +// Wait for next USB frame to start - for ensuring safe memory release. +static void +ohci_waittick(struct usb_ohci_s *cntl) +{ + barrier(); + struct ohci_hcca *hcca = (void*)cntl->regs->hcca; + u32 startframe = hcca->frame_no; + u64 end = calc_future_tsc(1000 * 5); + for (;;) { + if (hcca->frame_no != startframe) + break; + if (check_tsc(end)) { + warn_timeout(); + return; + } + yield(); + } +} + +static void +signal_freelist(struct usb_ohci_s *cntl) +{ + u32 v = readl(&cntl->regs->control); + if (v & OHCI_CTRL_CLE) { + writel(&cntl->regs->control, v & ~(OHCI_CTRL_CLE|OHCI_CTRL_BLE)); + ohci_waittick(cntl); + writel(&cntl->regs->ed_controlcurrent, 0); + writel(&cntl->regs->ed_bulkcurrent, 0); + writel(&cntl->regs->control, v); + } else { + ohci_waittick(cntl); + } +} + +struct ohci_pipe { + struct ohci_ed ed; + struct usb_pipe pipe; + void *data; + int count; + struct ohci_td *tds; +}; + +void +ohci_free_pipe(struct usb_pipe *p) +{ + if (! CONFIG_USB_OHCI) + return; + dprintf(7, "ohci_free_pipe %p\n", p); + struct ohci_pipe *pipe = container_of(p, struct ohci_pipe, pipe); + struct usb_ohci_s *cntl = container_of( + pipe->pipe.cntl, struct usb_ohci_s, usb); + + u32 *pos = &cntl->regs->ed_controlhead; + for (;;) { + struct ohci_ed *next = (void*)*pos; + if (!next) { + // Not found?! Exit without freeing. + warn_internalerror(); + return; + } + if (next == &pipe->ed) { + *pos = next->hwNextED; + signal_freelist(cntl); + free(pipe); + return; + } + pos = &next->hwNextED; + } +} + +struct usb_pipe * +ohci_alloc_control_pipe(struct usb_pipe *dummy) +{ + if (! CONFIG_USB_OHCI) + return NULL; + struct usb_ohci_s *cntl = container_of( + dummy->cntl, struct usb_ohci_s, usb); + dprintf(7, "ohci_alloc_control_pipe %p\n", &cntl->usb); + + // Allocate a queue head. + struct ohci_pipe *pipe = malloc_tmphigh(sizeof(*pipe)); + if (!pipe) { + warn_noalloc(); + return NULL; + } + memset(pipe, 0, sizeof(*pipe)); + memcpy(&pipe->pipe, dummy, sizeof(pipe->pipe)); + pipe->ed.hwINFO = ED_SKIP; + + // Add queue head to controller list. + pipe->ed.hwNextED = cntl->regs->ed_controlhead; + barrier(); + cntl->regs->ed_controlhead = (u32)&pipe->ed; + return &pipe->pipe; +} + int -ohci_control(u32 endp, int dir, const void *cmd, int cmdsize +ohci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize , void *data, int datasize) { if (! CONFIG_USB_OHCI) return -1; - - dprintf(5, "ohci_control %x\n", endp); - struct usb_s *cntl = endp2cntl(endp); - int maxpacket = endp2maxsize(endp); - int lowspeed = endp2speed(endp); - int devaddr = endp2devaddr(endp) | (endp2ep(endp) << 7); + dprintf(5, "ohci_control %p\n", p); + if (datasize > 4096) { + // XXX - should support larger sizes. + warn_noalloc(); + return -1; + } + struct ohci_pipe *pipe = container_of(p, struct ohci_pipe, pipe); + struct usb_ohci_s *cntl = container_of( + pipe->pipe.cntl, struct usb_ohci_s, usb); + int maxpacket = pipe->pipe.maxpacket; + int lowspeed = pipe->pipe.speed; + int devaddr = pipe->pipe.devaddr | (pipe->pipe.ep << 7); // Setup transfer descriptors struct ohci_td *tds = malloc_tmphigh(sizeof(*tds) * 3); - tds[0].hwINFO = TD_DP_SETUP | TD_T_DATA0 | TD_CC; - tds[0].hwCBP = (u32)cmd; - tds[0].hwNextTD = (u32)&tds[1]; - tds[0].hwBE = (u32)cmd + cmdsize - 1; - tds[1].hwINFO = (dir ? TD_DP_IN : TD_DP_OUT) | TD_T_DATA1 | TD_CC; - tds[1].hwCBP = datasize ? (u32)data : 0; - tds[1].hwNextTD = (u32)&tds[2]; - tds[1].hwBE = (u32)data + datasize - 1; - tds[2].hwINFO = (dir ? TD_DP_OUT : TD_DP_IN) | TD_T_DATA1 | TD_CC; - tds[2].hwCBP = 0; - tds[2].hwNextTD = (u32)&tds[3]; - tds[2].hwBE = 0; + if (!tds) { + warn_noalloc(); + return -1; + } + struct ohci_td *td = tds; + td->hwINFO = TD_DP_SETUP | TD_T_DATA0 | TD_CC; + td->hwCBP = (u32)cmd; + td->hwNextTD = (u32)&td[1]; + td->hwBE = (u32)cmd + cmdsize - 1; + td++; + if (datasize) { + td->hwINFO = (dir ? TD_DP_IN : TD_DP_OUT) | TD_T_DATA1 | TD_CC; + td->hwCBP = (u32)data; + td->hwNextTD = (u32)&td[1]; + td->hwBE = (u32)data + datasize - 1; + td++; + } + td->hwINFO = (dir ? TD_DP_OUT : TD_DP_IN) | TD_T_DATA1 | TD_CC; + td->hwCBP = 0; + td->hwNextTD = (u32)&td[1]; + td->hwBE = 0; + td++; // Transfer data - struct ohci_ed *ed = cntl->ohci.control_ed; - ed->hwINFO = ED_SKIP; + pipe->ed.hwINFO = ED_SKIP; barrier(); - ed->hwHeadP = (u32)&tds[0]; - ed->hwTailP = (u32)&tds[3]; + pipe->ed.hwHeadP = (u32)tds; + pipe->ed.hwTailP = (u32)td; barrier(); - ed->hwINFO = devaddr | (maxpacket << 16) | (lowspeed ? ED_LOWSPEED : 0); - writel(&cntl->ohci.regs->cmdstatus, OHCI_CLF); + pipe->ed.hwINFO = devaddr | (maxpacket << 16) | (lowspeed ? ED_LOWSPEED : 0); + writel(&cntl->regs->cmdstatus, OHCI_CLF); - int ret = wait_ed(ed); - ed->hwINFO = ED_SKIP; + int ret = wait_ed(&pipe->ed); + pipe->ed.hwINFO = ED_SKIP; if (ret) - usleep(1); // XXX - in case controller still accessing tds + ohci_waittick(cntl); free(tds); return ret; } -struct ohci_pipe { - struct ohci_ed ed; - struct usb_pipe pipe; - void *data; - int count; - struct ohci_td *tds; -}; +struct usb_pipe * +ohci_alloc_bulk_pipe(struct usb_pipe *dummy) +{ + if (! CONFIG_USB_OHCI) + return NULL; + dprintf(1, "OHCI Bulk transfers not supported.\n"); + return NULL; +} + +int +ohci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize) +{ + return -1; +} struct usb_pipe * -ohci_alloc_intr_pipe(u32 endp, int frameexp) +ohci_alloc_intr_pipe(struct usb_pipe *dummy, int frameexp) { if (! CONFIG_USB_OHCI) return NULL; + struct usb_ohci_s *cntl = container_of( + dummy->cntl, struct usb_ohci_s, usb); + dprintf(7, "ohci_alloc_intr_pipe %p %d\n", &cntl->usb, frameexp); - dprintf(7, "ohci_alloc_intr_pipe %x %d\n", endp, frameexp); if (frameexp > 5) frameexp = 5; - struct usb_s *cntl = endp2cntl(endp); - int maxpacket = endp2maxsize(endp); - int lowspeed = endp2speed(endp); - int devaddr = endp2devaddr(endp) | (endp2ep(endp) << 7); + int maxpacket = dummy->maxpacket; + int lowspeed = dummy->speed; + int devaddr = dummy->devaddr | (dummy->ep << 7); // Determine number of entries needed for 2 timer ticks. int ms = 1<pipe, dummy, sizeof(pipe->pipe)); + pipe->data = data; + pipe->count = count; + pipe->tds = tds; struct ohci_ed *ed = &pipe->ed; ed->hwHeadP = (u32)&tds[0]; @@ -289,7 +475,7 @@ ohci_alloc_intr_pipe(u32 endp, int frameexp) // Add to interrupt schedule. barrier(); - struct ohci_hcca *hcca = (void*)cntl->ohci.regs->hcca; + struct ohci_hcca *hcca = (void*)cntl->regs->hcca; if (frameexp == 0) { // Add to existing interrupt entry. struct ohci_ed *intr_ed = (void*)hcca->int_table[0]; @@ -302,10 +488,6 @@ ohci_alloc_intr_pipe(u32 endp, int frameexp) hcca->int_table[i] = (u32)ed; } - pipe->data = data; - pipe->count = count; - pipe->tds = tds; - pipe->pipe.endp = endp; return &pipe->pipe; err: @@ -316,17 +498,17 @@ err: } int -ohci_poll_intr(struct usb_pipe *pipe, void *data) +ohci_poll_intr(struct usb_pipe *p, void *data) { ASSERT16(); if (! CONFIG_USB_OHCI) return -1; - struct ohci_pipe *p = container_of(pipe, struct ohci_pipe, pipe); - struct ohci_td *tds = GET_FLATPTR(p->tds); - struct ohci_td *head = (void*)GET_FLATPTR(p->ed.hwHeadP); - struct ohci_td *tail = (void*)GET_FLATPTR(p->ed.hwTailP); - int count = GET_FLATPTR(p->count); + struct ohci_pipe *pipe = container_of(p, struct ohci_pipe, pipe); + struct ohci_td *tds = GET_FLATPTR(pipe->tds); + struct ohci_td *head = (void*)(GET_FLATPTR(pipe->ed.hwHeadP) & ~(ED_C|ED_H)); + struct ohci_td *tail = (void*)GET_FLATPTR(pipe->ed.hwTailP); + int count = GET_FLATPTR(pipe->count); int pos = (tail - tds + 1) % count; struct ohci_td *next = &tds[pos]; if (head == next) @@ -335,9 +517,8 @@ ohci_poll_intr(struct usb_pipe *pipe, void *data) // XXX - check for errors. // Copy data. - u32 endp = GET_FLATPTR(p->pipe.endp); - int maxpacket = endp2maxsize(endp); - void *pipedata = GET_FLATPTR(p->data); + int maxpacket = GET_FLATPTR(pipe->pipe.maxpacket); + void *pipedata = GET_FLATPTR(pipe->data); void *intrdata = pipedata + maxpacket * pos; memcpy_far(GET_SEG(SS), data , FLATPTR_TO_SEG(intrdata), (void*)FLATPTR_TO_OFFSET(intrdata) @@ -349,8 +530,8 @@ ohci_poll_intr(struct usb_pipe *pipe, void *data) SET_FLATPTR(tail->hwCBP, (u32)intrdata); SET_FLATPTR(tail->hwNextTD, (u32)next); SET_FLATPTR(tail->hwBE, (u32)intrdata + maxpacket - 1); - - SET_FLATPTR(p->ed.hwTailP, (u32)next); + barrier(); + SET_FLATPTR(pipe->ed.hwTailP, (u32)next); return 0; }