aboutsummaryrefslogtreecommitdiff
path: root/payloads/libpayload/drivers/usb/ohci.c
diff options
context:
space:
mode:
Diffstat (limited to 'payloads/libpayload/drivers/usb/ohci.c')
-rw-r--r--payloads/libpayload/drivers/usb/ohci.c472
1 files changed, 472 insertions, 0 deletions
diff --git a/payloads/libpayload/drivers/usb/ohci.c b/payloads/libpayload/drivers/usb/ohci.c
new file mode 100644
index 0000000000..94c19454cb
--- /dev/null
+++ b/payloads/libpayload/drivers/usb/ohci.c
@@ -0,0 +1,472 @@
+/*
+ * This file is 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
+
+#include <arch/virtual.h>
+#include <usb/usb.h>
+#include "ohci_private.h"
+#include "ohci.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_reset (hci_t *controller)
+{
+}
+
+#ifdef USB_DEBUG
+/* Section 4.3.3 */
+static const char *completion_codes[] = {
+ "No error",
+ "CRC",
+ "Bit stuffing",
+ "Data toggle mismatch",
+ "Stall",
+ "Device not responding",
+ "PID check failure",
+ "Unexpected PID",
+ "Data overrun",
+ "Data underrun",
+ "--- (10)",
+ "--- (11)",
+ "Buffer overrun",
+ "Buffer underrun",
+ "Not accessed (14)",
+ "Not accessed (15)"
+};
+
+/* Section 4.3.1.2 */
+static const char *direction[] = {
+ "SETUP",
+ "OUT",
+ "IN",
+ "reserved / from TD"
+};
+#endif
+
+hci_t *
+ohci_init (pcidev_t addr)
+{
+ int i;
+
+ hci_t *controller = new_controller ();
+
+ if (!controller)
+ usb_fatal("Could not create USB controller instance.\n");
+
+ controller->instance = malloc (sizeof (ohci_t));
+ if(!controller->instance)
+ usb_fatal("Not enough memory creating USB controller instance.\n");
+
+ controller->start = ohci_start;
+ controller->stop = ohci_stop;
+ controller->reset = ohci_reset;
+ controller->shutdown = ohci_shutdown;
+ controller->bulk = ohci_bulk;
+ controller->control = ohci_control;
+ 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->bus_address = addr;
+ controller->reg_base = pci_read_config32 (controller->bus_address, 0x10); // OHCI mandates MMIO, so bit 0 is clear
+ OHCI_INST (controller)->opreg = (opreg_t*)phys_to_virt(controller->reg_base);
+ printf("OHCI Version %x.%x\n", (OHCI_INST (controller)->opreg->HcRevision >> 4) & 0xf, OHCI_INST (controller)->opreg->HcRevision & 0xf);
+
+ if ((OHCI_INST (controller)->opreg->HcControl & HostControllerFunctionalStateMask) == USBReset) {
+ /* cold boot */
+ OHCI_INST (controller)->opreg->HcControl &= ~RemoteWakeupConnected;
+ OHCI_INST (controller)->opreg->HcFmInterval = (11999 * FrameInterval) | ((((11999 - 210)*6)/7) * FSLargestDataPacket);
+ /* TODO: right value for PowerOnToPowerGoodTime ? */
+ OHCI_INST (controller)->opreg->HcRhDescriptorA = NoPowerSwitching | NoOverCurrentProtection | (10 * PowerOnToPowerGoodTime);
+ OHCI_INST (controller)->opreg->HcRhDescriptorB = (0 * DeviceRemovable);
+ udelay(100); /* TODO: reset asserting according to USB spec */
+ } else if ((OHCI_INST (controller)->opreg->HcControl & HostControllerFunctionalStateMask) != USBOperational) {
+ OHCI_INST (controller)->opreg->HcControl = (OHCI_INST (controller)->opreg->HcControl & ~HostControllerFunctionalStateMask) | USBResume;
+ udelay(100); /* TODO: resume time according to USB spec */
+ }
+ int interval = OHCI_INST (controller)->opreg->HcFmInterval;
+
+ td_t *periodic_td = memalign(sizeof(*periodic_td), sizeof(*periodic_td));
+ memset((void*)periodic_td, 0, sizeof(*periodic_td));
+ for (i=0; i<32; i++) OHCI_INST (controller)->hcca->HccaInterruptTable[i] = virt_to_phys(periodic_td);
+ /* TODO: build HCCA data structures */
+
+ OHCI_INST (controller)->opreg->HcCommandStatus = 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;
+ OHCI_INST (controller)->hcca = memalign(256, 256);
+ memset((void*)OHCI_INST (controller)->hcca, 0, 256);
+
+ OHCI_INST (controller)->opreg->HcHCCA = virt_to_phys(OHCI_INST (controller)->hcca);
+ OHCI_INST (controller)->opreg->HcControl &= ~IsochronousEnable; // unused by this driver
+ OHCI_INST (controller)->opreg->HcControl |= BulkListEnable; // always enabled. OHCI still sleeps on BulkListFilled
+ OHCI_INST (controller)->opreg->HcControl |= ControlListEnable; // dito
+ OHCI_INST (controller)->opreg->HcControl |= PeriodicListEnable; // FIXME: setup interrupt data structures and enable all the time
+ // disable everything, contrary to what OHCI spec says in 5.1.1.4, as we don't need IRQs
+ OHCI_INST (controller)->opreg->HcInterruptEnable = 1<<31;
+ OHCI_INST (controller)->opreg->HcInterruptDisable = ~(1<<31);
+ OHCI_INST (controller)->opreg->HcInterruptStatus = ~0;
+ OHCI_INST (controller)->opreg->HcPeriodicStart = (((OHCI_INST (controller)->opreg->HcFmInterval & FrameIntervalMask) / 10) * 9);
+ OHCI_INST (controller)->opreg->HcControl = (OHCI_INST (controller)->opreg->HcControl & ~HostControllerFunctionalStateMask) | USBOperational;
+
+ mdelay(100);
+
+ controller->devices[0]->controller = controller;
+ controller->devices[0]->init = ohci_rh_init;
+ controller->devices[0]->init (controller->devices[0]);
+ ohci_reset (controller);
+ return controller;
+}
+
+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);
+ 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 void
+dump_td(td_t *cur, int level)
+{
+#ifdef USB_DEBUG
+ static const char *spaces=" ";
+ const char *spc=spaces+(10-level);
+#endif
+ debug("%std at %x (%s), condition code: %s\n", spc, cur, direction[cur->direction], completion_codes[cur->condition_code & 0xf]);
+ debug("%s toggle: %x\n", spc, cur->toggle);
+}
+
+static int
+wait_for_ed(usbdev_t *dev, ed_t *head)
+{
+ td_t *cur;
+
+ /* wait for results */
+ while (((head->head_pointer & ~3) != head->tail_pointer) &&
+ !(head->head_pointer & 1) &&
+ ((((td_t*)phys_to_virt(head->head_pointer & ~3))->condition_code & 0xf)>=0xe)) {
+ debug("intst: %x; ctrl: %x; cmdst: %x; head: %x -> %x, tail: %x, condition: %x\n",
+ OHCI_INST(dev->controller)->opreg->HcInterruptStatus,
+ OHCI_INST(dev->controller)->opreg->HcControl,
+ OHCI_INST(dev->controller)->opreg->HcCommandStatus,
+ head->head_pointer,
+ ((td_t*)phys_to_virt(head->head_pointer & ~3))->next_td,
+ head->tail_pointer,
+ ((td_t*)phys_to_virt(head->head_pointer & ~3))->condition_code);
+ mdelay(1);
+ }
+ if (OHCI_INST(dev->controller)->opreg->HcInterruptStatus & WritebackDoneHead) {
+ debug("done queue:\n");
+ debug("%x, %x\n", OHCI_INST(dev->controller)->hcca->HccaDoneHead, phys_to_virt(OHCI_INST(dev->controller)->hcca->HccaDoneHead));
+ if ((OHCI_INST(dev->controller)->hcca->HccaDoneHead & ~1) == 0) {
+ debug("HcInterruptStatus %x\n", OHCI_INST(dev->controller)->opreg->HcInterruptStatus);
+ }
+ td_t *done_queue = NULL;
+ td_t *done_head = (td_t*)phys_to_virt(OHCI_INST(dev->controller)->hcca->HccaDoneHead);
+ OHCI_INST(dev->controller)->opreg->HcInterruptStatus = WritebackDoneHead;
+ while (1) {
+ td_t *oldnext = (td_t*)phys_to_virt(done_head->next_td);
+ if (oldnext == done_queue) break; /* last element refers to second to last, ie. endless loop */
+ if (oldnext == phys_to_virt(0)) break; /* last element of done list == first element of real list */
+ debug("head is %x, pointing to %x. requeueing to %x\n", done_head, oldnext, done_queue);
+ done_head->next_td = (u32)done_queue;
+ done_queue = done_head;
+ done_head = oldnext;
+ }
+ for (cur = done_queue; cur != 0; cur = (td_t*)cur->next_td) {
+ dump_td(cur, 1);
+ }
+ }
+
+ if (head->head_pointer & 1) {
+ debug("HALTED!\n");
+ return 1;
+ }
+ return 0;
+}
+
+static int
+ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen,
+ unsigned char *data)
+{
+ int i;
+
+ 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);
+ int td_count = (pages+1)/2;
+
+ td_t *tds = memalign(sizeof(td_t), (td_count+3)*sizeof(td_t));
+ memset((void*)tds, 0, (td_count+3)*sizeof(td_t));
+
+ for (i=0; i < td_count + 3; i++) {
+ tds[i].next_td = virt_to_phys(&tds[i+1]);
+ }
+ tds[td_count + 3].next_td = 0;
+
+ tds[0].direction = OHCI_SETUP;
+ tds[0].toggle_from_td = 1;
+ tds[0].toggle = 0;
+ tds[0].error_count = 0;
+ tds[0].delay_interrupt = 7;
+ tds[0].condition_code = 0xf;
+ tds[0].current_buffer_pointer = virt_to_phys(devreq);
+ tds[0].buffer_end = virt_to_phys(devreq + drlen - 1);
+
+ cur = &tds[0];
+
+ while (pages > 0) {
+ cur++;
+ cur->direction = (dir==IN)?OHCI_IN:OHCI_OUT;
+ cur->toggle_from_td = 0;
+ cur->toggle = 1;
+ cur->error_count = 0;
+ cur->delay_interrupt = 7;
+ cur->condition_code = 0xf;
+ cur->current_buffer_pointer = 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 = 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 = virt_to_phys(data + second_page_size - 1);
+ dalen -= second_page_size;
+ data += second_page_size;
+ }
+ }
+
+ cur++;
+ cur->direction = (dir==IN)?OHCI_OUT:OHCI_IN;
+ cur->toggle_from_td = 1;
+ cur->toggle = 1;
+ cur->error_count = 0;
+ cur->delay_interrupt = 7;
+ cur->condition_code = 0xf;
+ cur->current_buffer_pointer = 0;
+ cur->buffer_end = 0;
+
+ /* final dummy TD */
+ cur++;
+
+ /* Data structures */
+ ed_t *head = memalign(sizeof(ed_t), sizeof(ed_t));
+ memset((void*)head, 0, sizeof(*head));
+ head->function_address = dev->address;
+ head->endpoint_number = 0;
+ head->direction = OHCI_FROM_TD;
+ head->lowspeed = dev->speed;
+ head->format = 0;
+ head->maximum_packet_size = dev->endpoints[0].maxpacketsize;
+ head->tail_pointer = virt_to_phys(cur);
+ head->head_pointer = virt_to_phys(tds);
+ head->halted = 0;
+ head->toggle = 0;
+
+ debug("doing control transfer with %x. first_td at %x\n", head->function_address, virt_to_phys(tds));
+
+ /* activate schedule */
+ OHCI_INST(dev->controller)->opreg->HcControlHeadED = virt_to_phys(head);
+ OHCI_INST(dev->controller)->opreg->HcCommandStatus = ControlListFilled;
+
+ int failure = wait_for_ed(dev, head);
+
+ /* free memory */
+ free((void*)tds);
+ free((void*)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;
+ debug("bulk: %x bytes from %x, finalize: %x, maxpacketsize: %x\n", dalen, data, finalize, ep->maxpacketsize);
+
+ 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);
+ int td_count = (pages+1)/2;
+
+ if (finalize && ((dalen % ep->maxpacketsize) == 0)) {
+ td_count++;
+ }
+
+ td_t *tds = memalign(sizeof(td_t), (td_count+1)*sizeof(td_t));
+ memset((void*)tds, 0, (td_count+1)*sizeof(td_t));
+
+ for (i=0; i < td_count; i++) {
+ tds[i].next_td = virt_to_phys(&tds[i+1]);
+ }
+
+ for (cur = tds; cur->next_td != 0; cur++) {
+ cur->toggle_from_td = 0;
+ cur->error_count = 0;
+ cur->delay_interrupt = 7;
+ cur->condition_code = 0xf;
+ cur->direction = (ep->direction==IN)?OHCI_IN:OHCI_OUT;
+ 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 = 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 = virt_to_phys(data + second_page_size - 1);
+ dalen -= second_page_size;
+ data += second_page_size;
+ }
+ }
+
+ /* Data structures */
+ ed_t *head = memalign(sizeof(ed_t), sizeof(ed_t));
+ memset((void*)head, 0, sizeof(*head));
+ head->function_address = ep->dev->address;
+ head->endpoint_number = ep->endpoint & 0xf;
+ head->direction = (ep->direction==IN)?OHCI_IN:OHCI_OUT;
+ head->lowspeed = ep->dev->speed;
+ head->format = 0;
+ head->maximum_packet_size = ep->maxpacketsize;
+ head->tail_pointer = virt_to_phys(cur);
+ head->head_pointer = virt_to_phys(tds);
+ head->halted = 0;
+ head->toggle = ep->toggle;
+
+ debug("doing bulk transfer with %x(%x). first_td at %x, last %x\n", head->function_address, head->endpoint_number, virt_to_phys(tds), virt_to_phys(cur));
+
+ /* activate schedule */
+ OHCI_INST(ep->dev->controller)->opreg->HcBulkHeadED = virt_to_phys(head);
+ OHCI_INST(ep->dev->controller)->opreg->HcCommandStatus = BulkListFilled;
+
+ int failure = wait_for_ed(ep->dev, head);
+
+ ep->toggle = head->toggle;
+
+ /* free memory */
+ free((void*)tds);
+ free((void*)head);
+
+ if (failure) {
+ /* try cleanup */
+ clear_stall(ep);
+ }
+
+ return failure;
+}
+
+/* create and hook-up an intr queue into device schedule */
+static void*
+ohci_create_intr_queue (endpoint_t *ep, int reqsize, int reqcount, int reqtiming)
+{
+ return NULL;
+}
+
+/* remove queue from device schedule, dropping all data that came in */
+static void
+ohci_destroy_intr_queue (endpoint_t *ep, void *q_)
+{
+}
+
+/* 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 *q_)
+{
+ return NULL;
+}
+