| /* |
| * Driver for USB OHCI ported from CoreBoot |
| * |
| * Copyright (C) 2014 BALATON Zoltan |
| * |
| * This file was part of the libpayload project. |
| * |
| * Copyright (C) 2010 Patrick Georgi |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| //#define USB_DEBUG_ED |
| |
| #include "config.h" |
| #include <asm/io.h> |
| #include <libopenbios/ofmem.h> |
| #include "timer.h" |
| #include "drivers/pci.h" |
| #include "pci.h" |
| #include <drivers/usb.h> |
| #include "usbohci_private.h" |
| #include "usbohci.h" |
| |
| static void ohci_start (hci_t *controller); |
| static void ohci_stop (hci_t *controller); |
| static void ohci_reset (hci_t *controller); |
| static void ohci_shutdown (hci_t *controller); |
| static int ohci_bulk (endpoint_t *ep, int size, u8 *data, int finalize); |
| static int ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, |
| int dalen, u8 *data); |
| static void* ohci_create_intr_queue (endpoint_t *ep, int reqsize, int reqcount, int reqtiming); |
| static void ohci_destroy_intr_queue (endpoint_t *ep, void *queue); |
| static u8* ohci_poll_intr_queue (void *queue); |
| static void ohci_process_done_queue(ohci_t *ohci, int spew_debug); |
| |
| #ifdef USB_DEBUG_ED |
| static void |
| dump_td (td_t *cur) |
| { |
| usb_debug("+---------------------------------------------------+\n"); |
| if (((__le32_to_cpu(cur->config) & (3UL << 19)) >> 19) == 0) |
| usb_debug("|..[SETUP]..........................................|\n"); |
| else if (((__le32_to_cpu(cur->config) & (3UL << 8)) >> 8) == 2) |
| usb_debug("|..[IN].............................................|\n"); |
| else if (((__le32_to_cpu(cur->config) & (3UL << 8)) >> 8) == 1) |
| usb_debug("|..[OUT]............................................|\n"); |
| else |
| usb_debug("|..[]...............................................|\n"); |
| usb_debug("|:|============ OHCI TD at [0x%08lx] ==========|:|\n", virt_to_phys(cur)); |
| usb_debug("|:| ERRORS = [%ld] | CONFIG = [0x%08x] | |:|\n", |
| 3 - ((__le32_to_cpu(cur->config) & (3UL << 26)) >> 26), __le32_to_cpu(cur->config)); |
| usb_debug("|:+-----------------------------------------------+:|\n"); |
| usb_debug("|:| C | Condition Code | [%02ld] |:|\n", |
| (__le32_to_cpu(cur->config) & (0xFUL << 28)) >> 28); |
| usb_debug("|:| O | Direction/PID | [%ld] |:|\n", |
| (__le32_to_cpu(cur->config) & (3UL << 19)) >> 19); |
| usb_debug("|:| N | Buffer Rounding | [%ld] |:|\n", |
| (__le32_to_cpu(cur->config) & (1UL << 18)) >> 18); |
| usb_debug("|:| F | Delay Intterrupt | [%ld] |:|\n", |
| (__le32_to_cpu(cur->config) & (7UL << 21)) >> 21); |
| usb_debug("|:| I | Data Toggle | [%ld] |:|\n", |
| (__le32_to_cpu(cur->config) & (3UL << 24)) >> 24); |
| usb_debug("|:| G | Error Count | [%ld] |:|\n", |
| (__le32_to_cpu(cur->config) & (3UL << 26)) >> 26); |
| usb_debug("|:+-----------------------------------------------+:|\n"); |
| usb_debug("|:| Current Buffer Pointer [0x%08x] |:|\n", __le32_to_cpu(cur->current_buffer_pointer)); |
| usb_debug("|:+-----------------------------------------------+:|\n"); |
| usb_debug("|:| Next TD [0x%08x] |:|\n", __le32_to_cpu(cur->next_td)); |
| usb_debug("|:+-----------------------------------------------+:|\n"); |
| usb_debug("|:| Current Buffer End [0x%08x] |:|\n", __le32_to_cpu(cur->buffer_end)); |
| usb_debug("|:|-----------------------------------------------|:|\n"); |
| usb_debug("|...................................................|\n"); |
| usb_debug("+---------------------------------------------------+\n"); |
| } |
| |
| static void |
| dump_ed (ed_t *cur) |
| { |
| td_t *tmp_td = NULL; |
| usb_debug("+===================================================+\n"); |
| usb_debug("| ############# OHCI ED at [0x%08lx] ########### |\n", virt_to_phys(cur)); |
| usb_debug("+---------------------------------------------------+\n"); |
| usb_debug("| Next Endpoint Descriptor [0x%08lx] |\n", __le32_to_cpu(cur->next_ed) & ~0xFUL); |
| usb_debug("+---------------------------------------------------+\n"); |
| usb_debug("| | @ 0x%08x : |\n", __le32_to_cpu(cur->config)); |
| usb_debug("| C | Maximum Packet Length | [%04ld] |\n", |
| ((__le32_to_cpu(cur->config) & (0x3fffUL << 16)) >> 16)); |
| usb_debug("| O | Function Address | [%04d] |\n", |
| __le32_to_cpu(cur->config) & 0x7F); |
| usb_debug("| N | Endpoint Number | [%02ld] |\n", |
| (__le32_to_cpu(cur->config) & (0xFUL << 7)) >> 7); |
| usb_debug("| F | Endpoint Direction | [%ld] |\n", |
| ((__le32_to_cpu(cur->config) & (3UL << 11)) >> 11)); |
| usb_debug("| I | Endpoint Speed | [%ld] |\n", |
| ((__le32_to_cpu(cur->config) & (1UL << 13)) >> 13)); |
| usb_debug("| G | Skip | [%ld] |\n", |
| ((__le32_to_cpu(cur->config) & (1UL << 14)) >> 14)); |
| usb_debug("| | Format | [%ld] |\n", |
| ((__le32_to_cpu(cur->config) & (1UL << 15)) >> 15)); |
| usb_debug("+---------------------------------------------------+\n"); |
| usb_debug("| TD Queue Tail Pointer [0x%08lx] |\n", |
| __le32_to_cpu(cur->tail_pointer) & ~0xFUL); |
| usb_debug("+---------------------------------------------------+\n"); |
| usb_debug("| TD Queue Head Pointer [0x%08lx] |\n", |
| __le32_to_cpu(cur->head_pointer) & ~0xFUL); |
| usb_debug("| CarryToggleBit [%d] Halted [%d] |\n", |
| (u16)(__le32_to_cpu(cur->head_pointer) & 0x2UL)>>1, (u16)(__le32_to_cpu(cur->head_pointer) & 0x1UL)); |
| |
| tmp_td = (td_t *)phys_to_virt((__le32_to_cpu(cur->head_pointer) & ~0xFUL)); |
| if ((__le32_to_cpu(cur->head_pointer) & ~0xFUL) != (__le32_to_cpu(cur->tail_pointer) & ~0xFUL)) { |
| usb_debug("|:::::::::::::::::: OHCI TD CHAIN ::::::::::::::::::|\n"); |
| while (virt_to_phys(tmp_td) != (__le32_to_cpu(cur->tail_pointer) & ~0xFUL)) |
| { |
| dump_td(tmp_td); |
| tmp_td = (td_t *)phys_to_virt((__le32_to_cpu(tmp_td->next_td) & ~0xFUL)); |
| } |
| usb_debug("|:::::::::::::::: EOF OHCI TD CHAIN ::::::::::::::::|\n"); |
| usb_debug("+---------------------------------------------------+\n"); |
| } else { |
| usb_debug("+---------------------------------------------------+\n"); |
| } |
| } |
| #endif |
| |
| static void |
| ohci_reset (hci_t *controller) |
| { |
| if (controller == NULL) |
| return; |
| |
| OHCI_INST(controller)->opreg->HcCommandStatus = __cpu_to_le32(HostControllerReset); |
| mdelay(2); /* wait 2ms */ |
| OHCI_INST(controller)->opreg->HcControl = 0; |
| mdelay(10); /* wait 10ms */ |
| } |
| |
| static void |
| ohci_reinit (hci_t *controller) |
| { |
| } |
| |
| hci_t * |
| ohci_init (void *bar) |
| { |
| int i; |
| |
| hci_t *controller = new_controller (); |
| |
| if (!controller) { |
| printk("Could not create USB controller instance.\n"); |
| return NULL; |
| } |
| |
| controller->instance = malloc (sizeof (ohci_t)); |
| if(!controller->instance) { |
| printk("Not enough memory creating USB controller instance.\n"); |
| return NULL; |
| } |
| |
| controller->type = OHCI; |
| |
| controller->start = ohci_start; |
| controller->stop = ohci_stop; |
| controller->reset = ohci_reset; |
| controller->init = ohci_reinit; |
| controller->shutdown = ohci_shutdown; |
| controller->bulk = ohci_bulk; |
| controller->control = ohci_control; |
| controller->set_address = generic_set_address; |
| controller->finish_device_config = NULL; |
| controller->destroy_device = NULL; |
| controller->create_intr_queue = ohci_create_intr_queue; |
| controller->destroy_intr_queue = ohci_destroy_intr_queue; |
| controller->poll_intr_queue = ohci_poll_intr_queue; |
| for (i = 0; i < 128; i++) { |
| controller->devices[i] = 0; |
| } |
| init_device_entry (controller, 0); |
| OHCI_INST (controller)->roothub = controller->devices[0]; |
| |
| controller->reg_base = (u32)(unsigned long)bar; |
| OHCI_INST (controller)->opreg = (opreg_t*)phys_to_virt(controller->reg_base); |
| usb_debug("OHCI Version %x.%x\n", |
| (READ_OPREG(OHCI_INST(controller), HcRevision) >> 4) & 0xf, |
| READ_OPREG(OHCI_INST(controller), HcRevision) & 0xf); |
| |
| if ((READ_OPREG(OHCI_INST(controller), HcControl) & HostControllerFunctionalStateMask) == USBReset) { |
| /* cold boot */ |
| OHCI_INST (controller)->opreg->HcControl &= __cpu_to_le32(~RemoteWakeupConnected); |
| OHCI_INST (controller)->opreg->HcFmInterval = |
| __cpu_to_le32((11999 * FrameInterval) | ((((11999 - 210)*6)/7) * FSLargestDataPacket)); |
| /* TODO: right value for PowerOnToPowerGoodTime ? */ |
| OHCI_INST (controller)->opreg->HcRhDescriptorA = |
| __cpu_to_le32(NoPowerSwitching | NoOverCurrentProtection | (10 * PowerOnToPowerGoodTime)); |
| OHCI_INST (controller)->opreg->HcRhDescriptorB = __cpu_to_le32(0 * DeviceRemovable); |
| udelay(100); /* TODO: reset asserting according to USB spec */ |
| } else if ((READ_OPREG(OHCI_INST(controller), HcControl) & HostControllerFunctionalStateMask) != USBOperational) { |
| OHCI_INST (controller)->opreg->HcControl = |
| __cpu_to_le32((READ_OPREG(OHCI_INST(controller), HcControl) & ~HostControllerFunctionalStateMask) |
| | USBResume); |
| udelay(100); /* TODO: resume time according to USB spec */ |
| } |
| int interval = OHCI_INST (controller)->opreg->HcFmInterval; |
| |
| OHCI_INST (controller)->opreg->HcCommandStatus = __cpu_to_le32(HostControllerReset); |
| udelay (10); /* at most 10us for reset to complete. State must be set to Operational within 2ms (5.1.1.4) */ |
| OHCI_INST (controller)->opreg->HcFmInterval = interval; |
| ofmem_posix_memalign((void **)&(OHCI_INST (controller)->hcca), 256, 256); |
| memset((void*)OHCI_INST (controller)->hcca, 0, 256); |
| |
| usb_debug("HCCA addr %p\n", OHCI_INST(controller)->hcca); |
| /* Initialize interrupt table. */ |
| u32 *const intr_table = OHCI_INST(controller)->hcca->HccaInterruptTable; |
| ed_t *const periodic_ed; |
| ofmem_posix_memalign((void **)&periodic_ed, sizeof(ed_t), sizeof(ed_t)); |
| memset((void *)periodic_ed, 0, sizeof(*periodic_ed)); |
| for (i = 0; i < 32; ++i) |
| intr_table[i] = __cpu_to_le32(virt_to_phys(periodic_ed)); |
| OHCI_INST (controller)->periodic_ed = periodic_ed; |
| |
| OHCI_INST (controller)->opreg->HcHCCA = __cpu_to_le32(virt_to_phys(OHCI_INST(controller)->hcca)); |
| /* Make sure periodic schedule is enabled. */ |
| OHCI_INST (controller)->opreg->HcControl |= __cpu_to_le32(PeriodicListEnable); |
| OHCI_INST (controller)->opreg->HcControl &= __cpu_to_le32(~IsochronousEnable); // unused by this driver |
| // disable everything, contrary to what OHCI spec says in 5.1.1.4, as we don't need IRQs |
| OHCI_INST (controller)->opreg->HcInterruptEnable = __cpu_to_le32(1<<31); |
| OHCI_INST (controller)->opreg->HcInterruptDisable = __cpu_to_le32(~(1<<31)); |
| OHCI_INST (controller)->opreg->HcInterruptStatus = __cpu_to_le32(~0); |
| OHCI_INST (controller)->opreg->HcPeriodicStart = |
| __cpu_to_le32((READ_OPREG(OHCI_INST(controller), HcFmInterval) & FrameIntervalMask) / 10 * 9); |
| OHCI_INST (controller)->opreg->HcControl = __cpu_to_le32((READ_OPREG(OHCI_INST(controller), HcControl) |
| & ~HostControllerFunctionalStateMask) | USBOperational); |
| |
| mdelay(100); |
| |
| controller->devices[0]->controller = controller; |
| controller->devices[0]->init = ohci_rh_init; |
| controller->devices[0]->init (controller->devices[0]); |
| return controller; |
| } |
| |
| hci_t * |
| ohci_pci_init (pci_addr addr) |
| { |
| u32 reg_base; |
| uint16_t cmd; |
| |
| cmd = pci_config_read16(addr, PCI_COMMAND); |
| cmd |= PCI_COMMAND_BUS_MASTER; |
| pci_config_write16(addr, PCI_COMMAND, cmd); |
| |
| /* regarding OHCI spec, Appendix A, BAR_OHCI register description, Table A-4 |
| * BASE ADDRESS only [31-12] bits. All other usually 0, but not all. |
| * OHCI mandates MMIO, so bit 0 is clear */ |
| reg_base = pci_config_read32 (addr, PCI_BASE_ADDR_0) & 0xfffff000; |
| |
| return ohci_init((void *)(unsigned long)reg_base); |
| } |
| |
| static void |
| ohci_shutdown (hci_t *controller) |
| { |
| if (controller == 0) |
| return; |
| detach_controller (controller); |
| ohci_stop(controller); |
| OHCI_INST (controller)->roothub->destroy (OHCI_INST (controller)-> |
| roothub); |
| controller->reset (controller); |
| free ((void *)OHCI_INST (controller)->periodic_ed); |
| free (OHCI_INST (controller)); |
| free (controller); |
| } |
| |
| static void |
| ohci_start (hci_t *controller) |
| { |
| // TODO: turn on all operation of OHCI, but assume that it's initialized. |
| } |
| |
| static void |
| ohci_stop (hci_t *controller) |
| { |
| // TODO: turn off all operation of OHCI |
| } |
| |
| static int |
| wait_for_ed(usbdev_t *dev, ed_t *head, int pages) |
| { |
| usb_debug("Waiting for %d pages on dev %p with head %p\n", pages, dev, head); |
| /* wait for results */ |
| /* TOTEST: how long to wait? |
| * give 2s per TD (2 pages) plus another 2s for now |
| */ |
| int timeout = pages*1000 + 2000; |
| while (((__le32_to_cpu(head->head_pointer) & ~3) != __le32_to_cpu(head->tail_pointer)) && |
| !(__le32_to_cpu(head->head_pointer) & 1) && |
| ((__le32_to_cpu((((td_t*)phys_to_virt(__le32_to_cpu(head->head_pointer) & ~3)))->config) |
| & TD_CC_MASK) >= TD_CC_NOACCESS) && timeout--) { |
| /* don't log every ms */ |
| if (!(timeout % 100)) |
| usb_debug("intst: %x; ctrl: %x; cmdst: %x; head: %x -> %x, tail: %x, condition: %x\n", |
| READ_OPREG(OHCI_INST(dev->controller), HcInterruptStatus), |
| READ_OPREG(OHCI_INST(dev->controller), HcControl), |
| READ_OPREG(OHCI_INST(dev->controller), HcCommandStatus), |
| __le32_to_cpu(head->head_pointer), |
| __le32_to_cpu(((td_t*)phys_to_virt(__le32_to_cpu(head->head_pointer) & ~3))->next_td), |
| __le32_to_cpu(head->tail_pointer), |
| (__le32_to_cpu(((td_t*)phys_to_virt(__le32_to_cpu(head->head_pointer) & ~3))->config) & TD_CC_MASK) >> TD_CC_SHIFT); |
| mdelay(1); |
| } |
| if (timeout < 0) |
| usb_debug("Error: ohci: endpoint " |
| "descriptor processing timed out.\n"); |
| /* Clear the done queue. */ |
| ohci_process_done_queue(OHCI_INST(dev->controller), 1); |
| |
| if (__le32_to_cpu(head->head_pointer) & 1) { |
| usb_debug("HALTED!\n"); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static void |
| ohci_free_ed (ed_t *const head) |
| { |
| /* In case the transfer canceled, we have to free unprocessed TDs. */ |
| while ((__le32_to_cpu(head->head_pointer) & ~0x3) != __le32_to_cpu(head->tail_pointer)) { |
| /* Save current TD pointer. */ |
| td_t *const cur_td = |
| (td_t*)phys_to_virt(__le32_to_cpu(head->head_pointer) & ~0x3); |
| /* Advance head pointer. */ |
| head->head_pointer = cur_td->next_td; |
| /* Free current TD. */ |
| free((void *)cur_td); |
| } |
| |
| /* Always free the dummy TD */ |
| if ((__le32_to_cpu(head->head_pointer) & ~0x3) == __le32_to_cpu(head->tail_pointer)) |
| free(phys_to_virt(__le32_to_cpu(head->head_pointer) & ~0x3)); |
| /* and the ED. */ |
| free((void *)head); |
| } |
| |
| static int |
| ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen, |
| unsigned char *data) |
| { |
| td_t *cur; |
| |
| // pages are specified as 4K in OHCI, so don't use getpagesize() |
| int first_page = (unsigned long)data / 4096; |
| int last_page = (unsigned long)(data+dalen-1)/4096; |
| if (last_page < first_page) last_page = first_page; |
| int pages = (dalen==0)?0:(last_page - first_page + 1); |
| |
| /* First TD. */ |
| td_t *const first_td; |
| ofmem_posix_memalign((void **)&first_td, sizeof(td_t), sizeof(td_t)); |
| memset((void *)first_td, 0, sizeof(*first_td)); |
| cur = first_td; |
| |
| cur->config = __cpu_to_le32(TD_DIRECTION_SETUP | |
| TD_DELAY_INTERRUPT_NOINTR | |
| TD_TOGGLE_FROM_TD | |
| TD_TOGGLE_DATA0 | |
| TD_CC_NOACCESS); |
| cur->current_buffer_pointer = __cpu_to_le32(virt_to_phys(devreq)); |
| cur->buffer_end = __cpu_to_le32(virt_to_phys((char *)devreq + drlen - 1)); |
| |
| while (pages > 0) { |
| /* One more TD. */ |
| td_t *const next; |
| ofmem_posix_memalign((void **)&next, sizeof(td_t), sizeof(td_t)); |
| memset((void *)next, 0, sizeof(*next)); |
| /* Linked to the previous. */ |
| cur->next_td = __cpu_to_le32(virt_to_phys(next)); |
| /* Advance to the new TD. */ |
| cur = next; |
| |
| cur->config = __cpu_to_le32((dir == IN ? TD_DIRECTION_IN : TD_DIRECTION_OUT) | |
| TD_DELAY_INTERRUPT_NOINTR | |
| TD_TOGGLE_FROM_ED | |
| TD_CC_NOACCESS); |
| cur->current_buffer_pointer = __cpu_to_le32(virt_to_phys(data)); |
| pages--; |
| int consumed = (4096 - ((unsigned long)data % 4096)); |
| if (consumed >= dalen) { |
| // end of data is within same page |
| cur->buffer_end = __cpu_to_le32(virt_to_phys(data + dalen - 1)); |
| dalen = 0; |
| /* assert(pages == 0); */ |
| } else { |
| dalen -= consumed; |
| data += consumed; |
| pages--; |
| int second_page_size = dalen; |
| if (dalen > 4096) { |
| second_page_size = 4096; |
| } |
| cur->buffer_end = __cpu_to_le32(virt_to_phys(data + second_page_size - 1)); |
| dalen -= second_page_size; |
| data += second_page_size; |
| } |
| } |
| |
| /* One more TD. */ |
| td_t *const next_td; |
| ofmem_posix_memalign((void **)&next_td, sizeof(td_t), sizeof(td_t)); |
| memset((void *)next_td, 0, sizeof(*next_td)); |
| /* Linked to the previous. */ |
| cur->next_td = __cpu_to_le32(virt_to_phys(next_td)); |
| /* Advance to the new TD. */ |
| cur = next_td; |
| cur->config = __cpu_to_le32((dir == IN ? TD_DIRECTION_OUT : TD_DIRECTION_IN) | |
| TD_DELAY_INTERRUPT_ZERO | /* Write done head after this TD. */ |
| TD_TOGGLE_FROM_TD | |
| TD_TOGGLE_DATA1 | |
| TD_CC_NOACCESS); |
| cur->current_buffer_pointer = 0; |
| cur->buffer_end = 0; |
| |
| /* Final dummy TD. */ |
| td_t *const final_td; |
| ofmem_posix_memalign((void **)&final_td, sizeof(td_t), sizeof(td_t)); |
| memset((void *)final_td, 0, sizeof(*final_td)); |
| /* Linked to the previous. */ |
| cur->next_td = __cpu_to_le32(virt_to_phys(final_td)); |
| |
| /* Data structures */ |
| ed_t *head; |
| ofmem_posix_memalign((void **)&head, sizeof(ed_t), sizeof(ed_t)); |
| memset((void*)head, 0, sizeof(*head)); |
| head->config = __cpu_to_le32((dev->address << ED_FUNC_SHIFT) | |
| (0 << ED_EP_SHIFT) | |
| (OHCI_FROM_TD << ED_DIR_SHIFT) | |
| (dev->speed?ED_LOWSPEED:0) | |
| (dev->endpoints[0].maxpacketsize << ED_MPS_SHIFT)); |
| head->tail_pointer = __cpu_to_le32(virt_to_phys(final_td)); |
| head->head_pointer = __cpu_to_le32(virt_to_phys(first_td)); |
| |
| usb_debug("ohci_control(): doing transfer with %x. first_td at %x\n", |
| __le32_to_cpu(head->config) & ED_FUNC_MASK, __le32_to_cpu(head->head_pointer)); |
| #ifdef USB_DEBUG_ED |
| dump_ed(head); |
| #endif |
| |
| /* activate schedule */ |
| OHCI_INST(dev->controller)->opreg->HcControlHeadED = __cpu_to_le32(virt_to_phys(head)); |
| OHCI_INST(dev->controller)->opreg->HcControl |= __cpu_to_le32(ControlListEnable); |
| OHCI_INST(dev->controller)->opreg->HcCommandStatus = __cpu_to_le32(ControlListFilled); |
| |
| int failure = wait_for_ed(dev, head, |
| (dalen==0)?0:(last_page - first_page + 1)); |
| /* Wait some frames before and one after disabling list access. */ |
| mdelay(4); |
| OHCI_INST(dev->controller)->opreg->HcControl &= __cpu_to_le32(~ControlListEnable); |
| mdelay(1); |
| |
| /* free memory */ |
| ohci_free_ed(head); |
| |
| return failure; |
| } |
| |
| /* finalize == 1: if data is of packet aligned size, add a zero length packet */ |
| static int |
| ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) |
| { |
| int i; |
| usb_debug("bulk: %x bytes from %p, finalize: %x, maxpacketsize: %x\n", dalen, data, finalize, ep->maxpacketsize); |
| |
| td_t *cur, *next; |
| |
| // pages are specified as 4K in OHCI, so don't use getpagesize() |
| int first_page = (unsigned long)data / 4096; |
| int last_page = (unsigned long)(data+dalen-1)/4096; |
| if (last_page < first_page) last_page = first_page; |
| int pages = (dalen==0)?0:(last_page - first_page + 1); |
| int td_count = (pages+1)/2; |
| |
| if (finalize && ((dalen % ep->maxpacketsize) == 0)) { |
| td_count++; |
| } |
| |
| /* First TD. */ |
| td_t *const first_td; |
| ofmem_posix_memalign((void **)&first_td, sizeof(td_t), sizeof(td_t)); |
| memset((void *)first_td, 0, sizeof(*first_td)); |
| cur = next = first_td; |
| |
| for (i = 0; i < td_count; ++i) { |
| /* Advance to next TD. */ |
| cur = next; |
| cur->config = __cpu_to_le32((ep->direction == IN ? TD_DIRECTION_IN : TD_DIRECTION_OUT) | |
| TD_DELAY_INTERRUPT_NOINTR | |
| TD_TOGGLE_FROM_ED | |
| TD_CC_NOACCESS); |
| cur->current_buffer_pointer = __cpu_to_le32(virt_to_phys(data)); |
| pages--; |
| if (dalen == 0) { |
| /* magic TD for empty packet transfer */ |
| cur->current_buffer_pointer = 0; |
| cur->buffer_end = 0; |
| /* assert((pages == 0) && finalize); */ |
| } |
| int consumed = (4096 - ((unsigned long)data % 4096)); |
| if (consumed >= dalen) { |
| // end of data is within same page |
| cur->buffer_end = __cpu_to_le32(virt_to_phys(data + dalen - 1)); |
| dalen = 0; |
| /* assert(pages == finalize); */ |
| } else { |
| dalen -= consumed; |
| data += consumed; |
| pages--; |
| int second_page_size = dalen; |
| if (dalen > 4096) { |
| second_page_size = 4096; |
| } |
| cur->buffer_end = __cpu_to_le32(virt_to_phys(data + second_page_size - 1)); |
| dalen -= second_page_size; |
| data += second_page_size; |
| } |
| /* One more TD. */ |
| ofmem_posix_memalign((void **)&next, sizeof(td_t), sizeof(td_t)); |
| memset((void *)next, 0, sizeof(*next)); |
| /* Linked to the previous. */ |
| cur->next_td = __cpu_to_le32(virt_to_phys(next)); |
| } |
| |
| /* Write done head after last TD. */ |
| cur->config &= __cpu_to_le32(~TD_DELAY_INTERRUPT_MASK); |
| /* Advance to final, dummy TD. */ |
| cur = next; |
| |
| /* Data structures */ |
| ed_t *head; |
| ofmem_posix_memalign((void **)&head, sizeof(ed_t), sizeof(ed_t)); |
| memset((void*)head, 0, sizeof(*head)); |
| head->config = __cpu_to_le32((ep->dev->address << ED_FUNC_SHIFT) | |
| ((ep->endpoint & 0xf) << ED_EP_SHIFT) | |
| (((ep->direction==IN)?OHCI_IN:OHCI_OUT) << ED_DIR_SHIFT) | |
| (ep->dev->speed?ED_LOWSPEED:0) | |
| (ep->maxpacketsize << ED_MPS_SHIFT)); |
| head->tail_pointer = __cpu_to_le32(virt_to_phys(cur)); |
| head->head_pointer = __cpu_to_le32(virt_to_phys(first_td) | (ep->toggle?ED_TOGGLE:0)); |
| |
| usb_debug("doing bulk transfer with %x(%x). first_td at %lx, last %lx\n", |
| __le32_to_cpu(head->config) & ED_FUNC_MASK, |
| (__le32_to_cpu(head->config) & ED_EP_MASK) >> ED_EP_SHIFT, |
| virt_to_phys(first_td), virt_to_phys(cur)); |
| |
| /* activate schedule */ |
| OHCI_INST(ep->dev->controller)->opreg->HcBulkHeadED = __cpu_to_le32(virt_to_phys(head)); |
| OHCI_INST(ep->dev->controller)->opreg->HcControl |= __cpu_to_le32(BulkListEnable); |
| OHCI_INST(ep->dev->controller)->opreg->HcCommandStatus = __cpu_to_le32(BulkListFilled); |
| |
| int failure = wait_for_ed(ep->dev, head, |
| (dalen==0)?0:(last_page - first_page + 1)); |
| /* Wait some frames before and one after disabling list access. */ |
| mdelay(4); |
| OHCI_INST(ep->dev->controller)->opreg->HcControl &= __cpu_to_le32(~BulkListEnable); |
| mdelay(1); |
| |
| ep->toggle = __le32_to_cpu(head->head_pointer) & ED_TOGGLE; |
| |
| /* free memory */ |
| ohci_free_ed(head); |
| |
| if (failure) { |
| /* try cleanup */ |
| clear_stall(ep); |
| } |
| |
| return failure; |
| } |
| |
| |
| struct _intr_queue; |
| |
| struct _intrq_td { |
| volatile td_t td; |
| u8 *data; |
| struct _intrq_td *next; |
| struct _intr_queue *intrq; |
| }; |
| |
| struct _intr_queue { |
| volatile ed_t ed; |
| struct _intrq_td *head; |
| struct _intrq_td *tail; |
| u8 *data; |
| int reqsize; |
| endpoint_t *endp; |
| unsigned int remaining_tds; |
| int destroy; |
| }; |
| |
| typedef struct _intrq_td intrq_td_t; |
| typedef struct _intr_queue intr_queue_t; |
| |
| #define INTRQ_TD_FROM_TD(x) ((intrq_td_t *)x) |
| |
| static void |
| ohci_fill_intrq_td(intrq_td_t *const td, intr_queue_t *const intrq, |
| u8 *const data) |
| { |
| memset(td, 0, sizeof(*td)); |
| td->td.config = __cpu_to_le32(TD_QUEUETYPE_INTR | |
| (intrq->endp->direction == IN ? TD_DIRECTION_IN : TD_DIRECTION_OUT) | |
| TD_DELAY_INTERRUPT_ZERO | |
| TD_TOGGLE_FROM_ED | |
| TD_CC_NOACCESS); |
| td->td.current_buffer_pointer = __cpu_to_le32(virt_to_phys(data)); |
| td->td.buffer_end = __cpu_to_le32(virt_to_phys(data) + intrq->reqsize - 1); |
| td->intrq = intrq; |
| td->data = data; |
| } |
| |
| /* create and hook-up an intr queue into device schedule */ |
| static void * |
| ohci_create_intr_queue(endpoint_t *const ep, const int reqsize, |
| const int reqcount, const int reqtiming) |
| { |
| int i; |
| intrq_td_t *first_td = NULL, *last_td = NULL; |
| |
| if (reqsize > 4096) |
| return NULL; |
| |
| intr_queue_t *const intrq; |
| ofmem_posix_memalign((void **)&intrq, sizeof(intrq->ed), sizeof(*intrq)); |
| memset(intrq, 0, sizeof(*intrq)); |
| intrq->data = (u8 *)malloc(reqcount * reqsize); |
| intrq->reqsize = reqsize; |
| intrq->endp = ep; |
| |
| /* Create #reqcount TDs. */ |
| u8 *cur_data = intrq->data; |
| for (i = 0; i < reqcount; ++i) { |
| intrq_td_t *const td; |
| ofmem_posix_memalign((void **)&td, sizeof(td->td), sizeof(*td)); |
| ++intrq->remaining_tds; |
| ohci_fill_intrq_td(td, intrq, cur_data); |
| cur_data += reqsize; |
| if (!first_td) |
| first_td = td; |
| else |
| last_td->td.next_td = __cpu_to_le32(virt_to_phys(&td->td)); |
| last_td = td; |
| } |
| |
| /* Create last, dummy TD. */ |
| intrq_td_t *dummy_td; |
| ofmem_posix_memalign((void **)&dummy_td, sizeof(dummy_td->td), sizeof(*dummy_td)); |
| memset(dummy_td, 0, sizeof(*dummy_td)); |
| dummy_td->intrq = intrq; |
| if (last_td) |
| last_td->td.next_td = __cpu_to_le32(virt_to_phys(&dummy_td->td)); |
| last_td = dummy_td; |
| |
| /* Initialize ED. */ |
| intrq->ed.config = __cpu_to_le32((ep->dev->address << ED_FUNC_SHIFT) | |
| ((ep->endpoint & 0xf) << ED_EP_SHIFT) | |
| (((ep->direction == IN) ? OHCI_IN : OHCI_OUT) << ED_DIR_SHIFT) | |
| (ep->dev->speed ? ED_LOWSPEED : 0) | |
| (ep->maxpacketsize << ED_MPS_SHIFT)); |
| intrq->ed.tail_pointer = __cpu_to_le32(virt_to_phys(last_td)); |
| intrq->ed.head_pointer = __cpu_to_le32(virt_to_phys(first_td) | (ep->toggle ? ED_TOGGLE : 0)); |
| |
| #ifdef USB_DEBUG_ED |
| dump_ed(&intrq->ed); |
| #endif |
| /* Insert ED into periodic table. */ |
| int nothing_placed = 1; |
| ohci_t *const ohci = OHCI_INST(ep->dev->controller); |
| u32 *const intr_table = ohci->hcca->HccaInterruptTable; |
| const u32 dummy_ptr = __cpu_to_le32(virt_to_phys(ohci->periodic_ed)); |
| for (i = 0; i < 32; i += reqtiming) { |
| /* Advance to the next free position. */ |
| while ((i < 32) && (intr_table[i] != dummy_ptr)) ++i; |
| if (i < 32) { |
| usb_debug("Placed endpoint %lx to %d\n", virt_to_phys(&intrq->ed), i); |
| intr_table[i] = __cpu_to_le32(virt_to_phys(&intrq->ed)); |
| nothing_placed = 0; |
| } |
| } |
| if (nothing_placed) { |
| usb_debug("Error: Failed to place ohci interrupt endpoint " |
| "descriptor into periodic table: no space left\n"); |
| ohci_destroy_intr_queue(ep, intrq); |
| return NULL; |
| } |
| |
| return intrq; |
| } |
| |
| /* remove queue from device schedule, dropping all data that came in */ |
| static void |
| ohci_destroy_intr_queue(endpoint_t *const ep, void *const q_) |
| { |
| intr_queue_t *const intrq = (intr_queue_t *)q_; |
| |
| int i; |
| |
| /* Remove interrupt queue from periodic table. */ |
| ohci_t *const ohci = OHCI_INST(ep->dev->controller); |
| u32 *const intr_table = ohci->hcca->HccaInterruptTable; |
| for (i=0; i < 32; ++i) { |
| if (intr_table[i] == __cpu_to_le32(virt_to_phys(intrq))) |
| intr_table[i] = __cpu_to_le32(virt_to_phys(ohci->periodic_ed)); |
| } |
| /* Wait for frame to finish. */ |
| mdelay(1); |
| |
| /* Free unprocessed TDs. */ |
| while ((__le32_to_cpu(intrq->ed.head_pointer) & ~0x3) != __le32_to_cpu(intrq->ed.tail_pointer)) { |
| td_t *const cur_td = (td_t *)phys_to_virt(__le32_to_cpu(intrq->ed.head_pointer) & ~0x3); |
| intrq->ed.head_pointer = cur_td->next_td; |
| free(INTRQ_TD_FROM_TD(cur_td)); |
| --intrq->remaining_tds; |
| } |
| /* Free final, dummy TD. */ |
| free(phys_to_virt(__le32_to_cpu(intrq->ed.head_pointer) & ~0x3)); |
| /* Free data buffer. */ |
| free(intrq->data); |
| |
| /* Free TDs already fetched from the done queue. */ |
| ohci_process_done_queue(ohci, 1); |
| while (intrq->head) { |
| intrq_td_t *const cur_td = (intrq_td_t *const )__le32_to_cpu(intrq->head); |
| intrq->head = intrq->head->next; |
| free(cur_td); |
| --intrq->remaining_tds; |
| } |
| |
| /* Mark interrupt queue to be destroyed. |
| ohci_process_done_queue() will free the remaining TDs |
| and finish the interrupt queue off once all TDs are gone. */ |
| intrq->destroy = 1; |
| |
| /* Save data toggle. */ |
| ep->toggle = __le32_to_cpu(intrq->ed.head_pointer) & ED_TOGGLE; |
| } |
| |
| /* read one intr-packet from queue, if available. extend the queue for new input. |
| return NULL if nothing new available. |
| Recommended use: while (data=poll_intr_queue(q)) process(data); |
| */ |
| static u8 * |
| ohci_poll_intr_queue(void *const q_) |
| { |
| intr_queue_t *const intrq = (intr_queue_t *)q_; |
| |
| u8 *data = NULL; |
| |
| /* Process done queue first, then check if we have work to do. */ |
| ohci_process_done_queue(OHCI_INST(intrq->endp->dev->controller), 0); |
| |
| if (intrq->head) { |
| /* Save pointer to processed TD and advance. */ |
| intrq_td_t *const cur_td = intrq->head; |
| intrq->head = cur_td->next; |
| |
| /* Return data buffer of this TD. */ |
| data = cur_td->data; |
| |
| /* Requeue this TD (i.e. copy to dummy and requeue as dummy). */ |
| intrq_td_t *const dummy_td = |
| INTRQ_TD_FROM_TD(phys_to_virt(__le32_to_cpu(intrq->ed.tail_pointer))); |
| ohci_fill_intrq_td(dummy_td, intrq, data); |
| /* Reset all but intrq pointer (i.e. init as dummy). */ |
| memset(cur_td, 0, sizeof(*cur_td)); |
| cur_td->intrq = intrq; |
| /* Insert into interrupt queue as dummy. */ |
| dummy_td->td.next_td = __le32_to_cpu(virt_to_phys(&cur_td->td)); |
| intrq->ed.tail_pointer = __le32_to_cpu(virt_to_phys(&cur_td->td)); |
| } |
| |
| return data; |
| } |
| |
| static void |
| ohci_process_done_queue(ohci_t *const ohci, const int spew_debug) |
| { |
| int i, j; |
| |
| /* Temporary queue of interrupt queue TDs (to reverse order). */ |
| intrq_td_t *temp_tdq = NULL; |
| |
| /* Check if done head has been written. */ |
| if (!(READ_OPREG(ohci, HcInterruptStatus) & WritebackDoneHead)) |
| return; |
| /* Fetch current done head. |
| Lsb is only interesting for hw interrupts. */ |
| u32 phys_done_queue = __le32_to_cpu(ohci->hcca->HccaDoneHead) & ~1; |
| /* Tell host controller, he may overwrite the done head pointer. */ |
| ohci->opreg->HcInterruptStatus = __cpu_to_le32(WritebackDoneHead); |
| |
| i = 0; |
| /* Process done queue (it's in reversed order). */ |
| while (phys_done_queue) { |
| td_t *const done_td = (td_t *)phys_to_virt(phys_done_queue); |
| |
| /* Advance pointer to next TD. */ |
| phys_done_queue = __le32_to_cpu(done_td->next_td); |
| |
| switch (__le32_to_cpu(done_td->config) & TD_QUEUETYPE_MASK) { |
| case TD_QUEUETYPE_ASYNC: |
| /* Free processed async TDs. */ |
| free((void *)done_td); |
| break; |
| case TD_QUEUETYPE_INTR: { |
| intrq_td_t *const td = INTRQ_TD_FROM_TD(done_td); |
| intr_queue_t *const intrq = td->intrq; |
| /* Check if the corresponding interrupt |
| queue is still beeing processed. */ |
| if (intrq->destroy) { |
| /* Free this TD, and */ |
| free(td); |
| --intrq->remaining_tds; |
| /* the interrupt queue if it has no more TDs. */ |
| if (!intrq->remaining_tds) |
| free(intrq); |
| usb_debug("Freed TD from orphaned interrupt " |
| "queue, %d TDs remain.\n", |
| intrq->remaining_tds); |
| } else { |
| /* Save done TD to be processed. */ |
| td->next = temp_tdq; |
| temp_tdq = td; |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| ++i; |
| } |
| if (spew_debug) |
| usb_debug("Processed %d done TDs.\n", i); |
| |
| j = 0; |
| /* Process interrupt queue TDs in right order. */ |
| while (temp_tdq) { |
| /* Save pointer of current TD and advance. */ |
| intrq_td_t *const cur_td = temp_tdq; |
| temp_tdq = temp_tdq->next; |
| |
| /* The interrupt queue for the current TD. */ |
| intr_queue_t *const intrq = cur_td->intrq; |
| /* Append to interrupt queue. */ |
| if (!intrq->head) { |
| /* First element. */ |
| intrq->head = intrq->tail = cur_td; |
| } else { |
| /* Insert at tail. */ |
| intrq->tail->next = cur_td; |
| intrq->tail = cur_td; |
| } |
| /* It's always the last element. */ |
| cur_td->next = NULL; |
| ++j; |
| } |
| if (spew_debug) |
| usb_debug("processed %d done tds, %d intr tds thereof.\n", i, j); |
| } |
| |
| int ob_usb_ohci_init (const char *path, uint32_t addr) |
| { |
| hci_t *ctrl; |
| int i; |
| |
| usb_debug("ohci_init: %s addr = %x\n", path, addr); |
| ctrl = ohci_pci_init(addr); |
| if (!ctrl) |
| return 0; |
| |
| /* Init ports */ |
| usb_poll(); |
| |
| /* Look for a keyboard */ |
| for (i = 0; i < 128; i++) { |
| if (ctrl->devices[i] && ctrl->devices[i]->configuration) { |
| configuration_descriptor_t *cd; |
| interface_descriptor_t *intf; |
| |
| cd = (configuration_descriptor_t *)ctrl->devices[i]->configuration; |
| intf = (interface_descriptor_t *)(ctrl->devices[i]->configuration + cd->bLength); |
| usb_debug("Device at port %d is class %d\n", i, intf->bInterfaceClass); |
| if (intf->bInterfaceClass == hid_device && |
| intf->bInterfaceSubClass == hid_subclass_boot && |
| intf->bInterfaceProtocol == hid_boot_proto_keyboard ) { |
| break; |
| } |
| } |
| } |
| if ( i < 128 ) |
| ob_usb_hid_add_keyboard(path); |
| |
| return 1; |
| } |