summaryrefslogtreecommitdiff
path: root/payloads/libpayload/drivers/usb/uhci.c
diff options
context:
space:
mode:
Diffstat (limited to 'payloads/libpayload/drivers/usb/uhci.c')
-rw-r--r--payloads/libpayload/drivers/usb/uhci.c507
1 files changed, 507 insertions, 0 deletions
diff --git a/payloads/libpayload/drivers/usb/uhci.c b/payloads/libpayload/drivers/usb/uhci.c
new file mode 100644
index 0000000000..a558db842c
--- /dev/null
+++ b/payloads/libpayload/drivers/usb/uhci.c
@@ -0,0 +1,507 @@
+/*
+ * This file is part of the libpayload project.
+ *
+ * Copyright (C) 2008 coresystems GmbH
+ *
+ * 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.
+ */
+
+#include "usb.h"
+#include "uhci.h"
+#include <arch/virtual.h>
+
+static void uhci_start (hci_t *controller);
+static void uhci_stop (hci_t *controller);
+static void uhci_reset (hci_t *controller);
+static void uhci_shutdown (hci_t *controller);
+static int uhci_packet (usbdev_t *dev, int endp, int pid, int toggle,
+ int length, u8 *data);
+static int uhci_bulk (endpoint_t *ep, int size, u8 *data, int finalize);
+static int uhci_control (usbdev_t *dev, pid_t dir, int drlen, void *devreq,
+ int dalen, u8 *data);
+
+#if 0
+/* dump uhci */
+static void
+uhci_dump (hci_t *controller)
+{
+ printf ("dump:\nUSBCMD: %x\n", uhci_reg_read16 (controller, USBCMD));
+ printf ("USBSTS: %x\n", uhci_reg_read16 (controller, USBSTS));
+ printf ("USBINTR: %x\n", uhci_reg_read16 (controller, USBINTR));
+ printf ("FRNUM: %x\n", uhci_reg_read16 (controller, FRNUM));
+ printf ("FLBASEADD: %x\n", uhci_reg_read32 (controller, FLBASEADD));
+ printf ("SOFMOD: %x\n", uhci_reg_read8 (controller, SOFMOD));
+ printf ("PORTSC1: %x\n", uhci_reg_read16 (controller, PORTSC1));
+ printf ("PORTSC2: %x\n", uhci_reg_read16 (controller, PORTSC2));
+}
+#endif
+
+static void
+td_dump (td_t *td)
+{
+ printf ("%x packet (at %lx) to %x.%x failed\n", td->pid,
+ virt_to_phys (td), td->dev_addr, td->endp);
+ printf ("td (counter at %x) returns: ", td->counter);
+ printf (" bitstuff err: %x, ", td->status_bitstuff_err);
+ printf (" CRC err: %x, ", td->status_crc_err);
+ printf (" NAK rcvd: %x, ", td->status_nakrcvd);
+ printf (" Babble: %x, ", td->status_babble);
+ printf (" Data Buffer err: %x, ", td->status_databuf_err);
+ printf (" Stalled: %x, ", td->status_stalled);
+ printf (" Active: %x\n", td->status_active);
+ if (td->status_babble)
+ printf (" Babble because of %s\n",
+ td->status_bitstuff_err ? "host" : "device");
+ if (td->status_active)
+ printf (" still active - timeout?\n");
+}
+
+static void
+uhci_reset (hci_t *controller)
+{
+ /* reset */
+ uhci_reg_write16 (controller, USBCMD, 4);
+ mdelay (50);
+ uhci_reg_write16 (controller, USBCMD, 0);
+ mdelay (10);
+ uhci_reg_write16 (controller, USBCMD, 2);
+ while ((uhci_reg_read16 (controller, USBCMD) & 2) != 0)
+ mdelay (1);
+
+ uhci_reg_write32 (controller, FLBASEADD,
+ (u32) virt_to_phys (UHCI_INST (controller)->
+ framelistptr));
+ //printf ("framelist at %p\n",UHCI_INST(controller)->framelistptr);
+
+ /* disable irqs */
+ uhci_reg_write16 (controller, USBINTR, 0);
+
+ /* reset framelist index */
+ uhci_reg_write16 (controller, FRNUM, 0);
+
+ uhci_reg_mask16 (controller, USBCMD, ~0, 0xc0); // max packets, configure flag
+
+ uhci_start (controller);
+}
+
+hci_t *
+uhci_init (pcidev_t addr)
+{
+ int i;
+ hci_t *controller = new_controller ();
+
+ controller->instance = malloc (sizeof (uhci_t));
+ controller->start = uhci_start;
+ controller->stop = uhci_stop;
+ controller->reset = uhci_reset;
+ controller->shutdown = uhci_shutdown;
+ controller->packet = uhci_packet;
+ controller->bulk = uhci_bulk;
+ controller->control = uhci_control;
+ UHCI_INST (controller)->roothub = &(controller->devices[0]);
+
+ controller->bus_address = addr;
+ controller->reg_base = pci_read_config32 (controller->bus_address, 0x20) & ~1; /* ~1 clears the register type indicator that is set to 1 for IO space */
+
+ /* kill legacy support handler */
+ uhci_stop (controller);
+ mdelay (1);
+ uhci_reg_write16 (controller, USBSTS, 0x3f);
+ pci_write_config32 (controller->bus_address, 0xc0, 0x8f00);
+
+ UHCI_INST (controller)->framelistptr = memalign (0x1000, 1024 * sizeof (flistp_t *)); /* 4kb aligned to 4kb */
+ memset (UHCI_INST (controller)->framelistptr, 0,
+ 1024 * sizeof (flistp_t));
+
+ UHCI_INST (controller)->qh_intr = memalign (16, sizeof (qh_t));
+ UHCI_INST (controller)->qh_data = memalign (16, sizeof (qh_t));
+ UHCI_INST (controller)->qh_last = memalign (16, sizeof (qh_t));
+
+ UHCI_INST (controller)->qh_intr->headlinkptr.ptr =
+ virt_to_phys (UHCI_INST (controller)->qh_data);
+ UHCI_INST (controller)->qh_intr->headlinkptr.queue_head = 1;
+ UHCI_INST (controller)->qh_intr->elementlinkptr.ptr = 0;
+ UHCI_INST (controller)->qh_intr->elementlinkptr.terminate = 1;
+
+ UHCI_INST (controller)->qh_data->headlinkptr.ptr =
+ virt_to_phys (UHCI_INST (controller)->qh_last);
+ UHCI_INST (controller)->qh_data->headlinkptr.queue_head = 1;
+ UHCI_INST (controller)->qh_data->elementlinkptr.ptr = 0;
+ UHCI_INST (controller)->qh_data->elementlinkptr.terminate = 1;
+
+ UHCI_INST (controller)->qh_last->headlinkptr.ptr = 0;
+ UHCI_INST (controller)->qh_last->headlinkptr.terminate = 1;
+ UHCI_INST (controller)->qh_last->elementlinkptr.ptr = 0;
+ UHCI_INST (controller)->qh_last->elementlinkptr.terminate = 1;
+
+ for (i = 0; i < 1024; i++) {
+ UHCI_INST (controller)->framelistptr[i].ptr =
+ virt_to_phys (UHCI_INST (controller)->qh_intr);
+ UHCI_INST (controller)->framelistptr[i].terminate = 0;
+ UHCI_INST (controller)->framelistptr[i].queue_head = 1;
+ }
+ for (i = 1; i < 128; i++) {
+ init_device_entry (controller, i);
+ }
+ controller->devices[0].controller = controller;
+ controller->devices[0].init = uhci_rh_init;
+ controller->devices[0].init (&controller->devices[0]);
+ uhci_reset (controller);
+ return controller;
+}
+
+static void
+uhci_shutdown (hci_t *controller)
+{
+ if (controller == 0)
+ return;
+ detach_controller (controller);
+ UHCI_INST (controller)->roothub->destroy (UHCI_INST (controller)->
+ roothub);
+ uhci_reg_mask16 (controller, USBCMD, 0, 0); // stop work
+ free (UHCI_INST (controller)->framelistptr);
+ free (UHCI_INST (controller)->qh_intr);
+ free (UHCI_INST (controller)->qh_data);
+ free (UHCI_INST (controller)->qh_last);
+ free (UHCI_INST (controller));
+ free (controller);
+}
+
+static void
+uhci_start (hci_t *controller)
+{
+ uhci_reg_mask16 (controller, USBCMD, ~0, 1); // start work on schedule
+}
+
+static void
+uhci_stop (hci_t *controller)
+{
+ uhci_reg_mask16 (controller, USBCMD, ~1, 0); // stop work on schedule
+}
+
+#define GET_TD(x) ((void*)(((unsigned int)(x))&~0xf))
+
+static td_t *
+wait_for_completed_qh (hci_t *controller, qh_t *qh)
+{
+ int timeout = 1000; /* max 30 ms. */
+ void *current = GET_TD (qh->elementlinkptr.ptr);
+ while ((qh->elementlinkptr.terminate == 0) && (timeout-- > 0)) {
+ if (current != GET_TD (qh->elementlinkptr.ptr)) {
+ current = GET_TD (qh->elementlinkptr.ptr);
+ timeout = 1000;
+ }
+ uhci_reg_mask16 (controller, USBSTS, ~0, 0); // clear resettable registers
+ udelay (30);
+ }
+ return (GET_TD (qh->elementlinkptr.ptr) ==
+ 0) ? 0 : GET_TD (phys_to_virt (qh->elementlinkptr.ptr));
+}
+
+static void
+wait_for_completed_td (hci_t *controller, td_t *td)
+{
+ int timeout = 10000;
+ while ((td->status_active == 1)
+ && ((uhci_reg_read16 (controller, USBSTS) & 2) == 0)
+ && (timeout-- > 0)) {
+ uhci_reg_mask16 (controller, USBSTS, ~0, 0); // clear resettable registers
+ udelay (10);
+ }
+}
+
+static int
+maxlen (int size)
+{
+ return (size - 1) & 0x7ff;
+}
+
+static int
+min (int a, int b)
+{
+ if (a < b)
+ return a;
+ else
+ return b;
+}
+
+static int
+uhci_control (usbdev_t *dev, pid_t dir, int drlen, void *devreq, int dalen,
+ unsigned char *data)
+{
+ int endp = 0; /* this is control: always 0 */
+ int mlen = dev->endpoints[0].maxpacketsize;
+ int count = (2 + (dalen + mlen - 1) / mlen);
+ unsigned short req = ((unsigned short *) devreq)[0];
+ int i;
+ td_t *tds = memalign (16, sizeof (td_t) * count);
+ memset (tds, 0, sizeof (td_t) * count);
+ count--; /* to compensate for 0-indexed array */
+ for (i = 0; i < count; i++) {
+ tds[i].ptr = virt_to_phys (&tds[i + 1]);
+ tds[i].depth_first = 1;
+ tds[i].terminate = 0;
+ }
+ tds[count].ptr = 0;
+ tds[count].depth_first = 1;
+ tds[count].terminate = 1;
+
+ tds[0].pid = SETUP;
+ tds[0].dev_addr = dev->address;
+ tds[0].endp = endp;
+ tds[0].maxlen = maxlen (drlen);
+ tds[0].counter = 3;
+ tds[0].data_toggle = 0;
+ tds[0].lowspeed = dev->lowspeed;
+ tds[0].bufptr = virt_to_phys (devreq);
+ tds[0].status_active = 1;
+
+ int toggle = 1;
+ for (i = 1; i < count; i++) {
+ tds[i].pid = dir;
+ tds[i].dev_addr = dev->address;
+ tds[i].endp = endp;
+ tds[i].maxlen = maxlen (min (mlen, dalen));
+ tds[i].counter = 3;
+ tds[i].data_toggle = toggle;
+ tds[i].lowspeed = dev->lowspeed;
+ tds[i].bufptr = virt_to_phys (data);
+ tds[i].status_active = 1;
+ toggle ^= 1;
+ dalen -= mlen;
+ data += mlen;
+ }
+
+ tds[count].pid = (dir == OUT) ? IN : OUT;
+ tds[count].dev_addr = dev->address;
+ tds[count].endp = endp;
+ tds[count].maxlen = maxlen (0);
+ tds[count].counter = 0; /* as per linux 2.4.10 */
+ tds[count].data_toggle = 1;
+ tds[count].lowspeed = dev->lowspeed, tds[count].bufptr = 0;
+ tds[count].status_active = 1;
+ UHCI_INST (dev->controller)->qh_data->elementlinkptr.ptr =
+ virt_to_phys (tds);
+ UHCI_INST (dev->controller)->qh_data->elementlinkptr.queue_head = 0;
+ UHCI_INST (dev->controller)->qh_data->elementlinkptr.terminate = 0;
+ td_t *td = wait_for_completed_qh (dev->controller,
+ UHCI_INST (dev->controller)->
+ qh_data);
+ int result;
+ if (td == 0) {
+ result = 0;
+ } else {
+ printf ("control packet, req %x\n", req);
+ td_dump (td);
+ result = 1;
+ }
+ free (tds);
+ return result;
+}
+
+static int
+uhci_packet (usbdev_t *dev, int endp, int pid, int toggle, int length,
+ unsigned char *data)
+{
+ static td_t *td = 0;
+ if (td == 0)
+ td = memalign (16, sizeof (td_t));
+
+ memset (td, 0, sizeof (td_t));
+ td->ptr = 0;
+ td->terminate = 1;
+ td->queue_head = 0;
+
+ td->pid = pid;
+ td->dev_addr = dev->address;
+ td->endp = endp & 0xf;
+ td->maxlen = maxlen (length);
+ if (pid == SETUP)
+ td->counter = 3;
+ else
+ td->counter = 0;
+ td->data_toggle = toggle & 1;
+ td->lowspeed = dev->lowspeed;
+ td->bufptr = virt_to_phys (data);
+
+ td->status_active = 1;
+
+ UHCI_INST (dev->controller)->qh_data->elementlinkptr.ptr =
+ virt_to_phys (td);
+ UHCI_INST (dev->controller)->qh_data->elementlinkptr.queue_head = 0;
+ UHCI_INST (dev->controller)->qh_data->elementlinkptr.terminate = 0;
+ wait_for_completed_td (dev->controller, td);
+ if ((td->status & 0x7f) == 0) {
+ //printf("successfully sent a %x packet to %x.%x\n",pid, dev->address,endp);
+ // success
+ return 0;
+ } else {
+ td_dump (td);
+ return 1;
+ }
+}
+
+static td_t *
+create_schedule (int numpackets)
+{
+ if (numpackets == 0)
+ return 0;
+ td_t *tds = memalign (16, sizeof (td_t) * numpackets);
+ memset (tds, 0, sizeof (td_t) * numpackets);
+ int i;
+ for (i = 0; i < numpackets; i++) {
+ tds[i].ptr = virt_to_phys (&tds[i + 1]);
+ tds[i].terminate = 0;
+ tds[i].queue_head = 0;
+ tds[i].depth_first = 1;
+ }
+ tds[numpackets - 1].ptr = 0;
+ tds[numpackets - 1].terminate = 1;
+ tds[numpackets - 1].queue_head = 0;
+ tds[numpackets - 1].depth_first = 0;
+ return tds;
+}
+
+static void
+fill_schedule (td_t *td, endpoint_t *ep, int length, unsigned char *data,
+ int *toggle)
+{
+ td->pid = ep->direction;
+ td->dev_addr = ep->dev->address;
+ td->endp = ep->endpoint & 0xf;
+ td->maxlen = maxlen (length);
+ if (ep->direction == SETUP)
+ td->counter = 3;
+ else
+ td->counter = 0;
+ td->data_toggle = *toggle & 1;
+ td->lowspeed = ep->dev->lowspeed;
+ td->bufptr = virt_to_phys (data);
+
+ td->status_active = 1;
+ *toggle ^= 1;
+}
+
+static int
+run_schedule (usbdev_t *dev, td_t *td)
+{
+ UHCI_INST (dev->controller)->qh_data->elementlinkptr.ptr =
+ virt_to_phys (td);
+ UHCI_INST (dev->controller)->qh_data->elementlinkptr.queue_head = 0;
+ UHCI_INST (dev->controller)->qh_data->elementlinkptr.terminate = 0;
+ td = wait_for_completed_qh (dev->controller,
+ UHCI_INST (dev->controller)->qh_data);
+ if (td == 0) {
+ return 0;
+ } else {
+ td_dump (td);
+ return 1;
+ }
+}
+
+/* finalize == 1: if data is of packet aligned size, add a zero length packet */
+static int
+uhci_bulk (endpoint_t *ep, int size, u8 *data, int finalize)
+{
+ int maxpsize = ep->maxpacketsize;
+ if (maxpsize == 0)
+ fatal ("MaxPacketSize == 0!!!");
+ int numpackets = (size + maxpsize - 1 + finalize) / maxpsize;
+ if (numpackets == 0)
+ return 0;
+ td_t *tds = create_schedule (numpackets);
+ int i = 0, toggle = ep->toggle;
+ while ((size > 0) || ((size == 0) && (finalize != 0))) {
+ fill_schedule (&tds[i], ep, min (size, maxpsize), data,
+ &toggle);
+ i++;
+ data += maxpsize;
+ size -= maxpsize;
+ }
+ if (run_schedule (ep->dev, tds) == 1) {
+ clear_stall (ep);
+ free (tds);
+ return 1;
+ }
+ ep->toggle = toggle;
+ free (tds);
+ return 0;
+}
+
+void
+uhci_reg_write32 (hci_t *ctrl, usbreg reg, u32 value)
+{
+ outl (value, ctrl->reg_base + reg);
+}
+
+u32
+uhci_reg_read32 (hci_t *ctrl, usbreg reg)
+{
+ return inl (ctrl->reg_base + reg);
+}
+
+void
+uhci_reg_write16 (hci_t *ctrl, usbreg reg, u16 value)
+{
+ outw (value, ctrl->reg_base + reg);
+}
+
+u16
+uhci_reg_read16 (hci_t *ctrl, usbreg reg)
+{
+ return inw (ctrl->reg_base + reg);
+}
+
+void
+uhci_reg_write8 (hci_t *ctrl, usbreg reg, u8 value)
+{
+ outb (value, ctrl->reg_base + reg);
+}
+
+u8
+uhci_reg_read8 (hci_t *ctrl, usbreg reg)
+{
+ return inb (ctrl->reg_base + reg);
+}
+
+void
+uhci_reg_mask32 (hci_t *ctrl, usbreg reg, u32 andmask, u32 ormask)
+{
+ uhci_reg_write32 (ctrl, reg,
+ (uhci_reg_read32 (ctrl, reg) & andmask) | ormask);
+}
+
+void
+uhci_reg_mask16 (hci_t *ctrl, usbreg reg, u16 andmask, u16 ormask)
+{
+ uhci_reg_write16 (ctrl, reg,
+ (uhci_reg_read16 (ctrl, reg) & andmask) | ormask);
+}
+
+void
+uhci_reg_mask8 (hci_t *ctrl, usbreg reg, u8 andmask, u8 ormask)
+{
+ uhci_reg_write8 (ctrl, reg,
+ (uhci_reg_read8 (ctrl, reg) & andmask) | ormask);
+}