[coreboot] [PATCH] USB: Add OHCI support

Daniel Mack daniel at caiaq.de
Mon Oct 5 04:44:47 CEST 2009


This is the first version of an OHCI stack for libpayload. It does not
currently implement INTERRUPT and ISOCRONOUS transfers.

CONTROL and BULK messages work fine, though. Successfully tested on an
ALIX.2D board and the USB mass storage device driver.

This stack was originally taken from U-Boot, but heavily cleaned up and
adopted to the libpayload USB stack API.

It is not tested for endianess but currently works on LE machines only.

Thanks to Leandro Dorilex for his initial work on this.

Signed-off-by: Daniel Mack <daniel at caiaq.de>
Thanks-to: Leandro Dorilex <ldorileo at gmail.com>
---
 drivers/Makefile.inc  |    3 +
 drivers/usb/ohci.c    | 1296 +++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/ohci.h    |  320 ++++++++++++
 drivers/usb/ohci_rh.c |  150 ++++++
 drivers/usb/ohci_rh.h |   44 ++
 drivers/usb/usbinit.c |    3 +-
 6 files changed, 1815 insertions(+), 1 deletions(-)
 create mode 100644 drivers/usb/ohci.c
 create mode 100644 drivers/usb/ohci.h
 create mode 100644 drivers/usb/ohci_rh.c
 create mode 100644 drivers/usb/ohci_rh.h

diff --git a/drivers/Makefile.inc b/drivers/Makefile.inc
index a9ece92..3eeec11 100644
--- a/drivers/Makefile.inc
+++ b/drivers/Makefile.inc
@@ -55,6 +55,9 @@ TARGETS-$(CONFIG_USB) += drivers/usb/usb_dev.o
 TARGETS-$(CONFIG_USB_HUB) += drivers/usb/usbhub.o
 TARGETS-$(CONFIG_USB_UHCI) += drivers/usb/uhci.o
 TARGETS-$(CONFIG_USB_UHCI) += drivers/usb/uhci_rh.o
+TARGETS-$(CONFIG_USB_OHCI) += drivers/usb/ohci.o
+TARGETS-$(CONFIG_USB_OHCI) += drivers/usb/ohci_rh.o
+TARGETS-$(CONFIG_USB_OHCI) += drivers/usb/ohci_dbg.o
 TARGETS-$(CONFIG_USB_HID) += drivers/usb/usbhid.o
 TARGETS-$(CONFIG_USB_MSC) += drivers/usb/usbmsc.o
 
diff --git a/drivers/usb/ohci.c b/drivers/usb/ohci.c
new file mode 100644
index 0000000..161acb4
--- /dev/null
+++ b/drivers/usb/ohci.c
@@ -0,0 +1,1296 @@
+/*
+ * URB OHCI HCD (Host Controller Driver) for USB.
+ *
+ * Implementation taken from U-Boot sources,
+ * copyright (c) 1999-2007
+ *
+ *   Zhang Wei, Freescale Semiconductor, Inc. <wei.zhang at freescale.com>
+ *   Gary Jennejohn, DENX Software Engineering <garyj at denx.de>
+ *   Roman Weissgaerber <weissg at vienna.at>
+ *   David Brownell
+ *
+ * Port to libpayload by Daniel Mack <daniel at caiaq.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <libpayload-config.h>
+
+#include <usb/usb.h>
+#include <arch/virtual.h>
+#include <arch/endian.h>
+
+#include "ohci.h"
+#include "ohci_rh.h"
+
+static int cc_to_error[16] = {
+/* mapping of the OHCI CC status to error codes */
+	/* No  Error    */       0,
+	/* CRC Error    */       USB_ST_CRC_ERR,
+	/* Bit Stuff    */       USB_ST_BIT_ERR,
+	/* Data Togg    */       USB_ST_CRC_ERR,
+	/* Stall	*/       USB_ST_STALLED,
+	/* DevNotResp   */       -1,
+	/* PIDCheck     */       USB_ST_BIT_ERR,
+	/* UnExpPID     */       USB_ST_BIT_ERR,
+	/* DataOver     */       USB_ST_BUF_ERR,
+	/* DataUnder    */       USB_ST_BUF_ERR,
+	/* reservd      */       -1,
+	/* reservd      */       -1,
+	/* BufferOver   */       USB_ST_BUF_ERR,
+	/* BuffUnder    */       USB_ST_BUF_ERR,
+	/* Not Access   */       -1,
+	/* Not Access   */       -1
+};
+
+static const char *cc_to_string[16] = {
+	"No Error",
+	"CRC: Last data packet from endpoint contained a CRC error.",
+	"BITSTUFFING: Last data packet from endpoint contained a bit "
+		     "stuffing violation",
+	"DATATOGGLEMISMATCH: Last packet from endpoint had data toggle PID "
+		     "that did not match the expected value.",
+	"STALL: TD was moved to the Done Queue because the endpoint returned"
+		     " a STALL PID",
+	"DEVICENOTRESPONDING: Device did not respond to token (IN) or did "
+		     "not provide a handshake (OUT)",
+	"PIDCHECKFAILURE: Check bits on PID from endpoint failed on data PID "
+		     "(IN) or handshake (OUT)",
+	"UNEXPECTEDPID: Receive PID was not valid when encountered or PID "
+		     "value is not defined.",
+	"DATAOVERRUN: The amount of data returned by the endpoint exceeded "
+		     "either the size of the maximum data packet allowed "
+		     "from the endpoint (found in MaximumPacketSize field "
+		     "of ED) or the remaining buffer size.",
+	"DATAUNDERRUN: The endpoint returned less than MaximumPacketSize "
+		     "and that amount was not sufficient to fill the "
+		     "specified buffer",
+	"reserved1",
+	"reserved2",
+	"BUFFEROVERRUN: During an IN, HC received data from endpoint faster "
+		     "than it could be written to system memory",
+	"BUFFERUNDERRUN: During an OUT, HC could not retrieve data from "
+		     "system memory fast enough to keep up with data USB "
+		     "data rate.",
+	"NOT ACCESSED: This code is set by software before the TD is placed "
+		     "on a list to be processed by the HC.(1)",
+	"NOT ACCESSED: This code is set by software before the TD is placed "
+		     "on a list to be processed by the HC.(2)",
+};
+
+/*-------------------------------------------------------------------------*
+ * URB support functions
+ *-------------------------------------------------------------------------*/
+
+/* free HCD-private data associated with this URB */
+
+static void urb_free_priv(urb_priv_t *urb)
+{
+	int i;
+
+	for (i = 0; i < urb->length; i++)
+		if (urb->td[i]) {
+			urb->td[i]->usb_dev = NULL;
+			urb->td[i] = NULL;
+		}
+
+	free(urb);
+}
+
+/*-------------------------------------------------------------------------*
+ * TD handling functions
+ *-------------------------------------------------------------------------*/
+
+static inline ohci_td_t *
+td_alloc (ohci_t *ohci, usbdev_t *usb_dev)
+{
+	int i;
+
+	for (i = 0; i < NUM_TDS; i++) {
+		ohci_td_t *td = ohci->td[i];
+
+		if (td->usb_dev == NULL) {
+			td->usb_dev = usb_dev;
+			return td;
+		}
+	}
+
+	return NULL;
+}
+
+static inline void
+ed_free (ohci_ed_t *ed)
+{
+	ed->usb_dev = NULL;
+}
+
+/* enqueue next TD for this URB (OHCI spec 5.2.8.2) */
+
+static void td_fill(ohci_t *ohci, u32 info,
+			u8 *data, int len,
+			usbdev_t *dev, int index, urb_priv_t *urb_priv)
+{
+	ohci_td_t *td, *td_pt;
+
+	if (index > urb_priv->length) {
+		err("%s: index > length\n", __func__);
+		return;
+	}
+
+	if (len == 0)
+		data = NULL;
+
+	/* use this td as the next dummy */
+	td_pt = urb_priv->td[index];
+	td_pt->hwNextTD = 0;
+	td_pt->hwBE = 0;
+
+	/* fill the old dummy TD */
+	td = urb_priv->td[index] = phys_to_virt(urb_priv->ed->hwTailP & ~0xf);
+
+	td->ed = urb_priv->ed;
+	td->next_dl_td = NULL;
+	td->index = index;
+	td->data = data;
+	td->transfer_len = len;
+
+#ifdef OHCI_FILL_TRACE
+	if (ep->type == CONTROL || (ep->type == BULK && ep->direction == OUT)) {
+		int i;
+
+		for (i = 0; i < len; i++)
+			dbg("td->data[%d] %#2x\n", i, data[i]);
+	}
+#endif
+
+	td->hwINFO = le32_to_cpu(info);
+	td->hwCBP = data ? virt_to_phys(data) : 0;
+	td->hwBE = data ? virt_to_phys(data + len - 1) : 0;
+	td->hwNextTD = virt_to_phys(td_pt);
+
+#if 0
+	if (ep->type == BULK)
+		dbg("%s :%d - (ed %p/%08x) filling td @%p, len %d, hwCBP %08x - hwBE %08x\n",
+			__func__, __LINE__, td->ed, virt_to_phys(td->ed), td, len, td->hwCBP, td->hwBE);
+#endif
+
+	/* append to queue */
+	td->ed->hwTailP = td->hwNextTD;
+}
+
+static void td_submit_job(urb_priv_t *urb, dev_req_t *setup)
+{
+	endpoint_t *ep = urb->ep;
+	usbdev_t *dev = ep->dev;
+	ohci_t *ohci = OHCI_INST(dev->controller);
+	int data_len = urb->transfer_buffer_length;
+	u8 *data;
+	u32 cnt = 0, info = 0, toggle = 0;
+	dev_req_t *dr = setup;
+
+	/* OHCI handles the DATA-toggles itself, we just use the USB-toggle
+	 * bits for reseting */
+	if (ep->toggle) {
+		toggle = TD_T_TOGGLE;
+	} else {
+		toggle = TD_T_DATA0;
+		ep->toggle = 1;
+	}
+
+	urb->td_cnt = 0;
+	data = data_len ? urb->transfer_buffer : NULL;
+
+	switch (ep->type) {
+	case BULK:
+		info = (ep->direction == OUT) ?
+			TD_CC | TD_DP_OUT :
+			TD_CC | TD_DP_IN ;
+
+		while (data_len > 4096) {
+			td_fill(ohci, info | (cnt ? TD_T_TOGGLE : toggle),
+				data, 4096, dev, cnt, urb);
+			data += 4096;
+			data_len -= 4096;
+			cnt++;
+		}
+
+		/* avoid ED halt on final TD short read */
+		if (ep->direction == IN)
+			info |= TD_R;
+
+		td_fill(ohci, info | (cnt ? TD_T_TOGGLE : toggle),
+			data, data_len, dev, cnt, urb);
+
+		cnt++;
+
+		if (!ohci->sleeping) {
+			/* start bulk list */
+			writel(OHCI_BLF, &ohci->regs->cmdstatus);
+		}
+
+		break;
+
+	case CONTROL:
+		/* Setup phase */
+		info = TD_CC | TD_DP_SETUP | TD_T_DATA0;
+		td_fill(ohci, info, (u8 *) setup, 8, dev, cnt++, urb);
+
+		/* Optional Data phase */
+		if (data_len > 0) {
+			info = (dr->data_dir == device_to_host) ?
+				TD_CC | TD_R | TD_DP_IN  | TD_T_DATA1 :
+				TD_CC | TD_R | TD_DP_OUT | TD_T_DATA1;
+			/* NOTE:  mishandles transfers >8K, some >4K */
+			td_fill(ohci, info, data, data_len, dev, cnt++, urb);
+		}
+
+		/* Status phase */
+		info = (dr->data_dir == host_to_device || data_len == 0) ?
+			TD_CC | TD_DP_IN  | TD_T_DATA1 :
+			TD_CC | TD_DP_OUT | TD_T_DATA1;
+		td_fill(ohci, info, NULL, 0, dev, cnt++, urb);
+
+		if (!ohci->sleeping)
+			/* start Control list */
+			writel(OHCI_CLF, &ohci->regs->cmdstatus);
+
+		break;
+
+	case INTERRUPT:
+		info = (ep->direction == OUT) ?
+			TD_CC | TD_DP_OUT | toggle :
+			TD_CC | TD_DP_IN  | TD_R | toggle;
+		td_fill(ohci, info, data, data_len, dev, cnt++, urb);
+		break;
+
+	case ISOCHRONOUS:
+		err("%s: iso transfers not implemented\n", __func__);
+		break;
+	}
+
+	if (urb->length != cnt)
+		dbg("%s: TD LENGTH %d != CNT %d", __func__, urb->length, cnt);
+}
+
+/*-------------------------------------------------------------------------*
+ * ED handling functions
+ *-------------------------------------------------------------------------*/
+
+/* search for the right branch to insert an interrupt ed into the int tree
+ * do some load ballancing;
+ * returns the branch and
+ * sets the interval to interval = 2^integer (ld (interval)) */
+
+static int ep_int_balance(ohci_t *ohci, int interval, int load)
+{
+	int i, branch = 0;
+
+	/* search for the least loaded interrupt endpoint
+	 * branch of all 32 branches
+	 */
+	for (i = 0; i < 32; i++)
+		if (ohci->ohci_int_load[branch] > ohci->ohci_int_load[i])
+			branch = i;
+
+	branch %= interval;
+	for (i = branch; i < 32; i += interval)
+		ohci->ohci_int_load[i] += load;
+
+	return branch;
+}
+
+/*  2^int( ld (inter)) */
+
+static int ep_2_n_interval(int inter)
+{
+	int i;
+	for (i = 0; ((inter >> i) > 1) && (i < 5); i++);
+	return 1 << i;
+}
+
+/* the int tree is a binary tree
+ * in order to process it sequentially the indexes of the branches have to
+ * be mapped the mapping reverses the bits of a word of num_bits length */
+static int ep_rev(int num_bits, int word)
+{
+	int i, wout = 0;
+
+	for (i = 0; i < num_bits; i++)
+		wout |= (((word >> i) & 1) << (num_bits - i - 1));
+
+	return wout;
+}
+
+/* link an ed into one of the HC chains */
+static int ep_link(ohci_t *ohci, ohci_ed_t *edi)
+{
+	ohci_ed_t *ed = edi;
+	ohci_ed_t *ed_p;
+	int int_branch;
+	int i;
+	int inter;
+	int interval;
+	int load;
+
+	ed->state = ED_OPER;
+	ed->int_interval = 0;
+
+	switch (ed->type) {
+	case CONTROL:
+		ed->hwNextED = 0;
+		if (ohci->ed_controltail == NULL)
+			writel(virt_to_phys(ed), &ohci->regs->ed_controlhead);
+		else
+			ohci->ed_controltail->hwNextED = virt_to_phys(ed);
+
+		ed->ed_prev = ohci->ed_controltail;
+		if (!ohci->ed_rm_list[0] && !ohci->ed_rm_list[1] &&
+			!ohci->ed_controltail && !ohci->sleeping) {
+			ohci->hc_control |= OHCI_CTRL_CLE;
+			writel(ohci->hc_control, &ohci->regs->control);
+		}
+		ohci->ed_controltail = edi;
+		break;
+
+	case BULK:
+		ed->hwNextED = 0;
+		if (ohci->ed_bulktail == NULL)
+			writel(virt_to_phys(ed), &ohci->regs->ed_bulkhead);
+		else
+			ohci->ed_bulktail->hwNextED = virt_to_phys(ed);
+		ed->ed_prev = ohci->ed_bulktail;
+		if (!ohci->ed_bulktail && !ohci->ed_rm_list[0] &&
+			!ohci->ed_rm_list[1] && !ohci->sleeping) {
+			ohci->hc_control |= OHCI_CTRL_BLE;
+			writel(ohci->hc_control, &ohci->regs->control);
+		}
+		ohci->ed_bulktail = edi;
+		break;
+
+	case INTERRUPT:
+		load = ed->int_load;
+		interval = ep_2_n_interval(ed->int_period);
+		ed->int_interval = interval;
+		int_branch = ep_int_balance(ohci, interval, load);
+		ed->int_branch = int_branch;
+
+		for (i = 0; i < ep_rev(6, interval); i += inter) {
+			volatile u32 *int_table = ohci->hcca->int_table;
+			inter = 1;
+
+			for (ed_p = phys_to_virt(int_table[ep_rev(5, i) + int_branch]);
+				ed_p && ed_p->int_interval >= interval;
+				ed_p = phys_to_virt(ed_p->hwNextED & ~0xf))
+					inter = ep_rev(6, ed_p->int_interval);
+
+			ed->hwNextED = virt_to_phys(ed_p);
+			//FIXME - have a look at the original implementation
+			//*ed_p = virt_to_phys(ed);
+		}
+
+		break;
+	}
+
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* scan the periodic table to find and unlink this ED */
+static void periodic_unlink(struct ohci *ohci, ohci_ed_t *ed,
+			    unsigned index, unsigned period)
+{
+	for (; index < NUM_INTS; index += period) {
+		ohci_ed_t *ed_p = phys_to_virt(&ohci->hcca->int_table[index]);
+
+		/* ED might have been unlinked through another path */
+		while (ed_p) {
+			if (ed_p == ed) {
+				ed_p = phys_to_virt(ed->hwNextED & ~0xf);
+				break;
+			}
+			ed_p = phys_to_virt(ed_p->hwNextED & ~0xf);
+		}
+	}
+}
+
+/* unlink an ed from one of the HC chains.
+ * just the link to the ed is unlinked.
+ * the link from the ed still points to another operational ed or 0
+ * so the HC can eventually finish the processing of the unlinked ed */
+
+static int ep_unlink(ohci_t *ohci, ohci_ed_t *edi)
+{
+	ohci_ed_t *ed = edi;
+	int i;
+
+	ed->hwINFO |= cpu_to_le32(OHCI_ED_SKIP);
+
+	switch (ed->type) {
+	case CONTROL:
+		if (ed->ed_prev == NULL) {
+			if (!ed->hwNextED) {
+				ohci->hc_control &= ~OHCI_CTRL_CLE;
+				writel(ohci->hc_control, &ohci->regs->control);
+			}
+			writel(ed->hwNextED, &ohci->regs->ed_controlhead);
+		} else {
+			ed->ed_prev->hwNextED = ed->hwNextED & ~0xf;
+		}
+
+		if (ohci->ed_controltail == ed) {
+			ohci->ed_controltail = ed->ed_prev;
+		} else {
+			ohci_ed_t *next = phys_to_virt(ed->hwNextED & ~0xf);
+
+			if (next)
+				next->ed_prev = ed->ed_prev;
+		}
+		break;
+
+	case BULK:
+		if (ed->ed_prev == NULL) {
+			if (!ed->hwNextED) {
+				ohci->hc_control &= ~OHCI_CTRL_BLE;
+				writel(ohci->hc_control, &ohci->regs->control);
+			}
+			writel(ed->hwNextED, &ohci->regs->ed_bulkhead);
+		} else {
+			ed->ed_prev->hwNextED = ed->hwNextED & ~0xf;
+		}
+		if (ohci->ed_bulktail == ed) {
+			ohci->ed_bulktail = ed->ed_prev;
+		} else {
+			ohci_ed_t *next = phys_to_virt(ed->hwNextED & ~0xf);
+
+			if (next)
+				next->ed_prev = ed->ed_prev;
+		}
+		break;
+
+	case INTERRUPT:
+		periodic_unlink(ohci, ed, 0, 1);
+		for (i = ed->int_branch; i < 32; i += ed->int_interval)
+			ohci->ohci_int_load[i] -= ed->int_load;
+		break;
+	}
+
+	ed->state = cpu_to_le32(ED_UNLINK);
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* add/reinit an endpoint; this should be done once at the
+ * usb_set_configuration command, but the USB stack is a little bit
+ * stateless so we do it at every transaction if the state of the ed
+ * is ED_NEW then a dummy td is added and the state is changed to
+ * ED_UNLINK in all other cases the state is left unchanged the ed
+ * info fields are setted anyway even though most of them should not
+ * change
+ */
+static ohci_ed_t *ep_add_ed(endpoint_t *ep, int interval, int load)
+{
+	usbdev_t *dev = ep->dev;
+	ohci_td_t *td;
+	ohci_ed_t *ed;
+	ohci_t *ohci = OHCI_INST(dev->controller);
+	u32 hwINFO;
+	int ed_index = ((ep->endpoint & 0xf) << 1) | ((ep->direction == OUT) ? 1 : 0);
+
+	ed = ohci->ed[ed_index];
+
+	/* pending delete request? */
+	if ((ed->state & ED_DEL) || (ed->state & ED_URB_DEL)) {
+		err("%s: pending delete, ed_index %d\n", __func__, ed_index);
+		return NULL;
+	}
+
+	if (ed->state == ED_NEW) {
+		/* dummy td; end of td list for ed */
+		td = td_alloc(ohci, dev);
+		ed->hwTailP = virt_to_phys(td);
+		ed->hwHeadP = ed->hwTailP & ~0xf;
+		ed->state = ED_UNLINK;
+		ed->type = ep->type;
+		ohci->ed_cnt++;
+	}
+
+	hwINFO = dev->address & 0x7f;
+	hwINFO |= (ep->endpoint & 0xf) << 7;
+
+	if (ep->type == ISOCHRONOUS)
+		hwINFO |= 1 << 15;
+
+	if (ep->type != CONTROL)
+		hwINFO |= (ep->direction == OUT) ? (1 << 11) : (1 << 12);
+
+	hwINFO |= (dev->lowspeed) ? (1 << 13) : 0;
+	hwINFO |= (ep->maxpacketsize & 0x1fff) << 16;
+
+	ed->hwINFO = cpu_to_le32(hwINFO);
+
+	if (ep->type == INTERRUPT && ed->state == ED_UNLINK) {
+		ed->int_period = interval;
+		ed->int_load = load;
+	}
+
+	return (ohci_ed_t *) ed;
+}
+
+/*-------------------------------------------------------------------------*
+ * Interface functions (URB)
+ *-------------------------------------------------------------------------*/
+
+/* get a transfer request */
+
+int sohci_submit_job(ohci_t *ohci, urb_priv_t *urb, dev_req_t *setup)
+{
+	ohci_ed_t *ed;
+	int i, size = 0;
+	usbdev_t *dev = urb->ep->dev;
+
+	/* when controller's hung, permit only roothub cleanup attempts
+	 * such as powering down ports */
+	if (ohci->disabled)
+		return -1;
+
+	/* we're about to begin a new transaction here so mark the
+	 * URB unfinished */
+	urb->finished = 0;
+
+	/* every endpoint has a ed, locate and fill it */
+	ed = ep_add_ed(urb->ep, urb->interval, 1);
+	if (!ed) {
+		err("%s: ep_add_ed() failed\n", __func__);
+		return -1;
+	}
+
+	/* for the private part of the URB we need the number of TDs (size) */
+	switch (urb->ep->type) {
+	case BULK:
+		/* one TD for every 4096 Byte */
+		size = (urb->transfer_buffer_length - 1) / 4096 + 1;
+		break;
+
+	case CONTROL:
+		/* 1 TD for setup, 1 for ACK and 1 for every 4096 B */
+		size = (urb->transfer_buffer_length == 0) ? 2:
+			(urb->transfer_buffer_length - 1) / 4096 + 3;
+		break;
+
+	case INTERRUPT:
+		/* 1 TD */
+		size = 1;
+		break;
+
+	case ISOCHRONOUS:
+		err("%s: iso transfers not implemented\n", __func__);
+		return -1;
+	}
+
+	if (size >= (N_URB_TD - 1)) {
+		err("%s: need %d TDs, only have %d\n", __func__, size, N_URB_TD);
+		return -1;
+	}
+
+	/* fill the private part of the URB */
+	urb->length = size;
+	urb->ed = ed;
+	urb->actual_length = 0;
+	ed->purb = urb;
+
+	/* allocate the TDs - note that td[0] was allocated in ep_add_ed */
+	for (i = 0; i < size; i++) {
+		urb->td[i] = td_alloc(ohci, dev);
+		if (!urb->td[i]) {
+			urb->length = i;
+			urb_free_priv(urb);
+			err("%s: ENOMEM\n", __func__);
+			return -1;
+		}
+	}
+
+	if ((ed->state == ED_NEW) || (ed->state & ED_DEL)) {
+		urb_free_priv(urb);
+		err("%s: EINVAL", __func__);
+		return -1;
+	}
+
+	/* link the ed into a chain if is not already */
+	if (ed->state != ED_OPER)
+		ep_link(ohci, ed);
+
+	/* fill the TDs and link it to the ed */
+	td_submit_job(urb, setup);
+
+	return 0;
+}
+
+static inline int sohci_return_job(ohci_t *ohci, urb_priv_t *urb)
+{
+	endpoint_t *ep = urb->ep;
+
+	if (ep->type == ISOCHRONOUS)
+		/* not implemented */
+		return 0;
+
+	if (ep->type == INTERRUPT) {
+		/* implicitly requeued */
+		urb->actual_length = 0;
+		td_submit_job(urb, NULL);
+	}
+
+	return 1;
+}
+
+/*-------------------------------------------------------------------------*
+ * Done List handling functions
+ *-------------------------------------------------------------------------*/
+
+/* calculate the transfer length and update the urb */
+
+static void dl_transfer_length(ohci_td_t *td)
+{
+	u32 tdINFO;
+	u8 *tdBE, *tdCBP, *data;
+	urb_priv_t *lurb_priv = td->ed->purb;
+
+	tdINFO = le32_to_cpu(td->hwINFO);
+	tdBE   = (u8 *) phys_to_virt(td->hwBE);
+	tdCBP  = (u8 *) phys_to_virt(td->hwCBP);
+	data   = (u8 *) td->data;
+
+	if (!((lurb_priv->ep->type == CONTROL) &&
+	    ((td->index == 0) || (td->index == lurb_priv->length - 1)))) {
+		if (tdBE) {
+			if (td->hwCBP == 0)
+				lurb_priv->actual_length += tdBE - data + 1;
+			else
+				lurb_priv->actual_length += tdCBP - data;
+		}
+	}
+}
+
+/*-------------------------------------------------------------------------*/
+static void check_status(ohci_td_t *td_list)
+{
+	urb_priv_t *lurb_priv = td_list->ed->purb;
+	int urb_len = lurb_priv->length;
+	ohci_ed_t *ed = td_list->ed;
+	u32 *phwHeadP  = &td_list->ed->hwHeadP;
+	int cc;
+
+	cc = TD_CC_GET(le32_to_cpu(td_list->hwINFO));
+
+	if (!cc)
+		return;
+
+	err("USB error: %s (0x%x)\n", cc_to_string[cc], cc);
+
+	/* td halted? */
+	if (!(ed->hwHeadP & le32_to_cpu(0x1)))
+		return;
+
+	if (lurb_priv &&
+	    ((td_list->index + 1) < urb_len)) {
+		ed->hwHeadP = (lurb_priv->td[urb_len - 1]->hwNextTD & ~0xf) |
+				(ed->hwHeadP & le32_to_cpu(0x2));
+
+		lurb_priv->td_cnt += urb_len - td_list->index - 1;
+	} else
+		*phwHeadP &= ~0xd;
+}
+
+/* replies to the request have to be on a FIFO basis so
+ * we reverse the reversed done-list */
+static ohci_td_t *dl_reverse_done_list(ohci_t *ohci)
+{
+	ohci_td_t *td_rev = NULL;
+	ohci_td_t *td_list = NULL;
+	u32 hwNext = readl(&ohci->hcca->done_head) & ~0xf;
+
+	writel(0, &ohci->hcca->done_head);
+
+	while (hwNext) {
+		td_list = phys_to_virt(hwNext);
+		check_status(td_list);
+		td_list->next_dl_td = td_rev;
+		td_rev = td_list;
+		hwNext = td_list->hwNextTD & ~0xf;
+	}
+
+	return td_list;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void finish_urb(ohci_t *ohci, urb_priv_t *urb, int status)
+{
+	if ((status & (ED_OPER | ED_UNLINK)) && (urb->state != URB_DEL))
+		urb->finished = sohci_return_job(ohci, urb);
+	else
+		dbg("%s: strange: ED state %x, \n", __func__, status);
+}
+
+/*
+ * Used to take back a TD from the host controller. This would normally be
+ * called from within dl_done_list, however it may be called directly if the
+ * HC no longer sees the TD and it has not appeared on the donelist (after
+ * two frames).
+ */
+static int takeback_td(ohci_t *ohci, ohci_td_t *td_list)
+{
+	int cc;
+	int stat = 0;
+	u32 tdINFO;
+	ohci_ed_t *ed;
+	urb_priv_t *lurb_priv;
+
+	tdINFO = le32_to_cpu(td_list->hwINFO);
+
+	ed = td_list->ed;
+	lurb_priv = ed->purb;
+
+	dl_transfer_length(td_list);
+
+	lurb_priv->td_cnt++;
+
+	/* error code of transfer */
+	cc = TD_CC_GET(tdINFO);
+	if (cc) {
+		err("%s: USB error: %s (0x%x)\n", __func__,
+			cc_to_string[cc], cc);
+		stat = cc_to_error[cc];
+	}
+
+	/* see if this done list makes for all TD's of current URB,
+	* and mark the URB finished if so */
+	if (lurb_priv->td_cnt == lurb_priv->length)
+		finish_urb(ohci, lurb_priv, ed->state);
+
+	dbg("%s: processing TD %d @%p, len %x\n", __func__,
+		lurb_priv->td_cnt, td_list, lurb_priv->length);
+
+	if (ed->state != ED_NEW && (lurb_priv->ep->type != INTERRUPT))
+		/* unlink eds if they are not busy */
+		if (((ed->hwHeadP & ~0xf) == (ed->hwTailP & ~0xf)) && (ed->state == ED_OPER))
+			ep_unlink(ohci, ed);
+
+	return stat;
+}
+
+static int dl_done_list(ohci_t *ohci)
+{
+	int stat = 0;
+	ohci_td_t *td_list = dl_reverse_done_list(ohci);
+
+	while (td_list) {
+		ohci_td_t *td_next = td_list->next_dl_td;
+		stat = takeback_td(ohci, td_list);
+		td_list = td_next;
+	}
+
+	return stat;
+}
+
+static int
+ohci_common_msg (endpoint_t *ep, int transfer_len, unsigned char *buffer,
+		 void *setup, int setup_len, int interval)
+{
+	usbdev_t *dev = ep->dev;
+	ohci_t *ohci = OHCI_INST(dev->controller);
+	int stat = 0;
+	int timeout;
+	urb_priv_t *urb;
+
+	urb = malloc(sizeof(urb_priv_t));
+	memset(urb, 0, sizeof(urb_priv_t));
+
+	urb->ep = ep;
+	urb->transfer_buffer = buffer;
+	urb->transfer_buffer_length = transfer_len;
+	urb->interval = interval;
+
+	if (sohci_submit_job(ohci, urb, setup) < 0) {
+		err("sohci_submit_job failed\n");
+		urb_free_priv(urb);
+		return -1;
+	}
+
+	/* allow more time for a BULK device to react - some are slow */
+	/* all timeouts in milliseconds */
+
+	if (ep->type == BULK)
+		timeout = 5000;
+	else
+		timeout = 100;
+
+	/* wait for it to complete */
+	for (;;) {
+		/* check whether the controller is done */
+		stat = ohci_interrupt(ohci);
+		if (stat < 0) {
+			stat = USB_ST_CRC_ERR;
+			break;
+		}
+
+		/* NOTE: since we are not interrupt driven and always
+		 * handle only one URB at a time, we cannot assume the
+		 * transaction finished on the first successful return from
+		 * hc_interrupt().. unless the flag for current URB is set,
+		 * meaning that all TD's to/from device got actually
+		 * transferred and processed. If the current URB is not
+		 * finished we need to re-iterate this loop so as
+		 * hc_interrupt() gets called again as there needs to be some
+		 * more TD's to process still */
+
+		if ((stat >= 0) && (stat != 0xff) && (urb->finished))
+			/* 0xff is returned for an SF-interrupt */
+			break;
+
+		if (--timeout) {
+			mdelay(1);
+
+			if (!urb->finished)
+				dbg("*");
+
+		} else {
+			err("%s: timeout\n", __func__);
+			ohci_dump(ohci, 1);
+			urb->finished = 1;
+			stat = USB_ST_CRC_ERR;
+
+			pkt_print(urb, setup, "RET(ctlr)", 1);
+			mdelay(1);
+
+			break;
+		}
+	}
+
+	/* free TDs in urb_priv */
+	if (ep->type != INTERRUPT)
+		urb_free_priv(urb);
+
+	return stat ? -1 : 0;
+}
+
+static int
+ohci_bulk (endpoint_t *ep, int size, u8 *data, int finalize)
+{
+	/* FIXME: This shouldn't happen.
+	 * The MSC driver needs to be fixed to not send 0-length packets. */
+	if (size == 0)
+		return 0;
+
+	return ohci_common_msg(ep, size, data, NULL, 0, 0);
+}
+
+/**
+  * Send control messages to a given endpoint
+  */
+static int
+ohci_control (endpoint_t *ep, pid_t pid,
+	      int dr_length, void *devreq,
+	      int data_length, unsigned char *data)
+{
+	return ohci_common_msg(ep, data_length, data, devreq, dr_length, 0);
+}
+
+/* Start the OHCI controller, set the BUS operational
+ * enable interrupts
+ * connect the virtual root hub
+ */
+static int ohci_start(hci_t *hci)
+{
+	u32 mask;
+	unsigned int fminterval;
+	ohci_t *ohci = OHCI_INST(hci);
+
+	ohci->disabled = 1;
+
+	/* Flush the lists */
+	writel(0, &ohci->regs->ed_controlhead);
+	writel(0, &ohci->regs->ed_bulkhead);
+
+	writel(virt_to_phys(ohci->hcca), &ohci->regs->hcca);
+
+	/* TODO: put this frame interval stuff somewhere we can reuse it */
+	fminterval = 0x2edf;
+	writel((fminterval * 9) / 10, &ohci->regs->periodicstart);
+	fminterval |= ((((fminterval - 210) * 6) / 7) << 16);
+	writel(fminterval, &ohci->regs->fminterval);
+	writel(0x628, &ohci->regs->lsthresh);
+
+	/* start ohci operations */
+	ohci->hc_control = (OHCI_CTRL_CBSR & 0x3) | OHCI_CTRL_IE |
+				OHCI_CTRL_PLE | OHCI_USB_OPER;
+	ohci->disabled = 0;
+	writel(ohci->hc_control, &ohci->regs->control);
+
+	/* disable all interrupts */
+	mask = OHCI_INTR_SO | OHCI_INTR_WDH | OHCI_INTR_SF | OHCI_INTR_RD |
+			OHCI_INTR_UE | OHCI_INTR_FNO | OHCI_INTR_RHSC |
+			OHCI_INTR_OC | OHCI_INTR_MIE;
+	writel(mask, &ohci->regs->intrdisable);
+
+	/* clear all interrupts */
+	mask &= ~OHCI_INTR_MIE;
+	writel(mask, &ohci->regs->intrstatus);
+
+	/* Choose the interrupts we care about now - but w/o MIE */
+	mask = OHCI_INTR_RHSC | OHCI_INTR_UE | OHCI_INTR_WDH | OHCI_INTR_SO | OHCI_INTR_MIE;
+	writel(mask, &ohci->regs->intrenable);
+
+	/* required for AMD-756 and some Mac platforms */
+	writel((roothub_a(ohci) | RH_A_NPS) & ~RH_A_PSM,
+		&ohci->regs->roothub.a);
+	writel(RH_HS_LPSC, &ohci->regs->roothub.status);
+
+	/* POTPGT delay is bits 24-31, in 2 ms units. */
+	mdelay((roothub_a(ohci) >> 23) & 0x1fe);
+
+	dbg("%s: done\n", __func__);
+
+	return 0;
+}
+
+/**
+  * Resets the host controller
+  *
+  * @hci    the host controller to be reset
+  */
+static int ohci_reset(hci_t *hci)
+{
+	ohci_t *ohci = OHCI_INST(hci);
+
+	int timeout = 30;
+	int smm_timeout = 50; /* 0,5 sec */
+
+	if (readl(&ohci->regs->control) & OHCI_CTRL_IR) {
+		/* SMM owns the HC */
+		writel(OHCI_OCR, &ohci->regs->cmdstatus);/* request ownership */
+		printf("USB HC TakeOver from SMM");
+		while (readl(&ohci->regs->control) & OHCI_CTRL_IR) {
+			mdelay(10);
+			if (--smm_timeout == 0) {
+				printf("USB HC TakeOver failed!");
+				return -1;
+			}
+		}
+	}
+
+	/* Disable HC interrupts */
+	writel(OHCI_INTR_MIE, &ohci->regs->intrdisable);
+
+	dbg("%s: OHCI @%p: USB HC: ctrl = 0x%x\n", __func__,
+		ohci->regs, readl(&ohci->regs->control));
+
+	/* Reset USB (needed by some controllers) */
+	ohci->hc_control = 0;
+	writel(ohci->hc_control, &ohci->regs->control);
+
+	/* HC Reset requires max 10 us delay */
+	writel(OHCI_HCR,  &ohci->regs->cmdstatus);
+	while ((readl(&ohci->regs->cmdstatus) & OHCI_HCR) != 0) {
+		if (--timeout == 0) {
+			err("USB HC reset timed out!");
+			return -1;
+		}
+		udelay(1);
+	}
+
+	return 0;
+}
+
+/**
+  * Stops a host controller
+  *
+  * @param hci  the host controller to be stop
+  */
+static void ohci_stop(hci_t *hci)
+{
+	/* this gets called really early - before the controller has */
+	/* even been initialized */
+	if (hci->reg_base)
+		/* TODO release any interrupts, etc. */
+		ohci_reset(hci);
+}
+
+static void ohci_shutdown(hci_t *hci)
+{
+	int i;
+	usbdev_t *roothub;
+	ohci_t *ohci = OHCI_INST(hci);
+
+	if (!hci)
+		return;
+
+	roothub = hci->devices[0];
+
+	detach_controller(hci);
+
+	if (roothub)
+		roothub->destroy(roothub);
+
+	free(ohci->hcca);
+
+	for (i = 0; i < NUM_EDS; i++)
+		free(ohci->ed[i]);
+
+	for (i = 0; i < NUM_TDS; i++)
+		free(ohci->td[i]);
+
+	free(hci);
+}
+
+int ohci_interrupt(ohci_t *ohci)
+{
+	struct ohci_regs *regs = ohci->regs;
+	int ints, stat = -1;
+	u32 done_head = readl(&ohci->hcca->done_head);
+
+	ints = readl(&regs->intrstatus);
+
+	if (done_head && (!done_head & cpu_to_le32(0x1))) {
+		ints = OHCI_INTR_WDH;
+	} else {
+		if (ints == ~(u32) 0) {
+			ohci->disabled++;
+			err("OHCI @%p: device removed!\n", ohci->regs);
+			return -1;
+		} else {
+			ints &= readl(&regs->intrenable);
+			if (ints == 0)
+				return 0xff;
+		}
+	}
+
+	if (ints & OHCI_INTR_RHSC) {
+		/* ohci_rh_status_change() will start issuing messages to
+		 * the device, hence ACK the IRQ directly */
+		writel(OHCI_INTR_RHSC, &regs->intrstatus);
+		ohci_rh_status_change(ohci);
+		return 0xff;
+	}
+
+	if (ints & OHCI_INTR_UE) {
+		ohci->disabled++;
+		err("OHCI @%p: Unrecoverable Error, controller disabled\n",
+			ohci->regs);
+		/* e.g. due to PCI Master/Target Abort */
+
+		ohci_dump(ohci, 1);
+		ohci_reset(ohci->hci);
+		return -1;
+	}
+
+	if (ints & OHCI_INTR_WDH) {
+		writel(OHCI_INTR_WDH, &regs->intrdisable);
+		readl(&regs->intrdisable);
+
+		stat = dl_done_list(ohci);
+
+		writel(OHCI_INTR_WDH, &regs->intrenable);
+		readl(&regs->intrdisable);
+	}
+
+	if (ints & OHCI_INTR_SO) {
+		dbg("USB Schedule overrun\n");
+		writel(OHCI_INTR_SO, &regs->intrenable);
+		stat = -1;
+	}
+
+	/* FIXME:  this assumes SOF (1/ms) interrupts don't get lost... */
+	if (ints & OHCI_INTR_SF) {
+		u16 frame = le16_to_cpu(readl(&ohci->hcca->frame_no)) & 1;
+
+		mdelay(1);
+		writel(OHCI_INTR_SF, &regs->intrdisable);
+		if (ohci->ed_rm_list[frame] != NULL)
+			writel(OHCI_INTR_SF, &regs->intrenable);
+		stat = 0xff;
+	}
+
+	writel(ints, &regs->intrstatus);
+	return stat;
+}
+
+/**
+  * Perform the host controller initialization and configuration
+  *
+  * @param hci  the host controller to be set
+  */
+int _ohci_init_(hci_t *hci)
+{
+	ohci_t *ohci = OHCI_INST(hci);
+
+	ohci->disabled = 1;
+	ohci->sleeping = 0;
+	ohci->irq = -1;
+	ohci->flags = 0;
+
+	/* reset */
+	if (ohci_reset(hci) < 0) {
+		err("OHCI @%p: can't reset", ohci->regs);
+		return -1;
+	}
+
+	/* link root hub */
+	hci->devices[0]->controller = hci;
+	hci->devices[0]->init = ohci_rh_init;
+	hci->devices[0]->data = &ohci->rh_instance;
+	ohci_rh_init(hci->devices[0]);
+
+	/* bring it up */
+	if (ohci_start(hci) < 0) {
+		err("OHCI @%p: can't start", ohci->regs);
+		return -1;
+	}
+
+	return 0;
+}
+
+/**
+  * Sets up the bus address and the registers base address
+  *
+  * @param controller     the host controller interface
+  * @param  addr	  the pci device addr
+  */
+ohci_regs_t *ohci_set_reg_base(hci_t *hci, pcidev_t addr) {
+	u32 reg_base;
+	ohci_t *ohci = OHCI_INST(hci);
+
+	dbg("%s()\n", __func__);
+
+	hci->bus_address = addr;
+	reg_base = pci_read_config32(addr, 0x10);
+	hci->reg_base = (u32) phys_to_virt(reg_base);
+
+	ohci->regs = (struct ohci_regs *) hci->reg_base;
+
+	return ohci->regs;
+}
+
+/**
+  * Allocates the basic operations of an host controller(hci_t)
+  *
+  * @param hci_t    the host controller interface representation
+  * @return none
+  */
+int ohci_alloc(hci_t *hci)
+{
+	int i;
+	ohci_t *ohci = malloc (sizeof (ohci_t));
+
+	if (!ohci) {
+		err("unable to allocate ohci\n");
+		return -1;
+	}
+
+	memset(ohci, 0, sizeof(ohci_t));
+
+	ohci->hcca = memalign(256, sizeof(ohci_hcca_t));
+	if (!ohci->hcca) {
+		err("unable to allocate ohci->hcca\n");
+		return -1;
+	}
+
+	memset(ohci->hcca, 0, sizeof(ohci_hcca_t));
+
+	for (i = 0; i < NUM_EDS; i++) {
+		ohci->ed[i] = memalign(16, sizeof(ohci_ed_t));
+	
+		if (!ohci->ed[i]) {
+			err("unable to allocate eds\n");
+			return -1;
+		}
+
+		memset(ohci->ed[i], 0, sizeof(ohci_ed_t));
+	}
+
+	for (i = 0; i < NUM_TDS; i++) {
+		ohci->td[i] = memalign(16, sizeof(ohci_td_t));
+
+		if (!ohci->td[i]) {
+			err("unable to allocate tds\n");
+			return -1;
+		}
+
+		memset(ohci->td[i], 0, sizeof(ohci_td_t));
+	}
+
+	/* set up the basic api operations */
+	hci->start = ohci_start;
+	hci->stop = ohci_stop;
+	hci->reset = ohci_reset;
+	hci->shutdown = ohci_shutdown;
+	//hci->packet = ohci_packet;
+	hci->bulk = ohci_bulk;
+	hci->control = ohci_control;
+	//hci->create_intr_queue = ohci_create_intr_queue;
+	//hci->destroy_intr_queue = ohci_destroy_intr_queue;
+	//hci->poll_intr_queue = ohci_poll_intr_queue;
+
+	for (i = 1; i < 128; i++)
+		hci->devices[i] = 0;
+
+	init_device_entry (hci, 0);
+
+	hci->instance = ohci;
+	ohci->hci = hci;
+
+	return 0;
+}
+
+/**
+ * Initializes the host controller
+ *
+ * @param addr  the pci device address
+ */
+hci_t * ohci_init (pcidev_t addr) {
+	u32 rev;
+	ohci_regs_t *regs;
+
+	printf("Initializing OHCI driver\n");
+
+	hci_t *hci = new_controller();
+	if (!hci) {
+		err("new_controller() failed.\n");
+		return NULL;
+	}
+
+	if (ohci_alloc(hci) != 0) {
+		detach_controller(hci);
+		free(hci);
+		return NULL;
+	}
+
+	regs = ohci_set_reg_base(hci, addr);
+
+	rev = readl(&regs->revision);
+	printf("OHCI revision: %d.%d\n", (rev >> 4) & 0xf, rev & 0xf);
+
+	if (_ohci_init_(hci) != 0) {
+		detach_controller(hci);
+		free(hci);
+		return NULL;
+	}
+
+	printf("OHCI @%p: initialized\n", regs);
+
+	return hci;
+}
diff --git a/drivers/usb/ohci.h b/drivers/usb/ohci.h
new file mode 100644
index 0000000..7752250
--- /dev/null
+++ b/drivers/usb/ohci.h
@@ -0,0 +1,320 @@
+/*
+ * URB OHCI HCD (Host Controller Driver) for USB.
+ *
+ * (C) Copyright 1999 Roman Weissgaerber <weissg at vienna.at>
+ * (C) Copyright 2000-2001 David Brownell <dbrownell at users.sourceforge.net>
+ *
+ * usb-ohci.h
+ */
+
+#ifndef __OHCI_H
+#define __OHCI_H
+
+#include <usb/usb.h>
+
+
+//#define DEBUG
+//#define OHCI_FILL_TRACE
+
+#define err(x...) do { printf(x); mdelay(10); } while(0)
+
+typedef struct rh_inst rh_inst_t;
+typedef struct ohci ohci_t;
+typedef struct urb_priv urb_priv_t;
+typedef struct ohci_ed ohci_ed_t;
+typedef struct ohci_td ohci_td_t;
+typedef struct ohci_hcca ohci_hcca_t;
+typedef struct ohci_regs ohci_regs_t;
+
+#define MAX_DOWNSTREAM_PORTS 15
+
+struct rh_inst {
+	int devnum;
+	int port_connected[MAX_DOWNSTREAM_PORTS];
+};
+
+#define RH_INST(dev) ((rh_inst_t*)(dev)->data)
+
+/* USB-status codes */
+#define USB_ST_STALLED          0x02	/* TD is stalled */
+#define USB_ST_BUF_ERR          0x04	/* buffer error */
+#define USB_ST_CRC_ERR          0x20	/* CRC/timeout Error */
+#define USB_ST_BIT_ERR          0x40	/* Bitstuff error */
+
+/* ED States */
+#define ED_NEW		0x00
+#define ED_UNLINK	0x01
+#define ED_OPER		0x02
+#define ED_DEL		0x04
+#define ED_URB_DEL	0x08
+
+/* usb_ohci_ed */
+struct ohci_ed {
+	u32 hwINFO;
+	u32 hwTailP;
+	u32 hwHeadP;
+	u32 hwNextED;
+
+	ohci_ed_t *ed_prev;
+	u8 int_period;
+	u8 int_branch;
+	u8 int_load;
+	u8 int_interval;
+	u8 state;
+	u8 type;
+	u16 last_iso;
+	ohci_ed_t *ed_rm_list;
+
+	usbdev_t *usb_dev;
+	urb_priv_t *purb;
+	u32 unused[2];
+} __attribute__((aligned(16)));
+
+/* TD info field */
+#define TD_CC	    0xf0000000
+#define TD_CC_GET(td_p) ((td_p >> 28) & 0x0f)
+#define TD_EC	    0x0C000000
+#define TD_T	    0x03000000
+#define TD_T_DATA0  0x02000000
+#define TD_T_DATA1  0x03000000
+#define TD_T_TOGGLE 0x00000000
+#define TD_R	    0x00040000
+#define TD_DI	    0x00E00000
+#define TD_DP_SETUP 0x00000000
+#define TD_DP_IN    0x00100000
+#define TD_DP_OUT   0x00080000
+
+#define TD_ISO	    0x00010000
+#define TD_DEL	    0x00020000
+
+/* CC Codes */
+#define TD_CC_NOERROR	   0x00
+#define TD_CC_CRC	   0x01
+#define TD_CC_BITSTUFFING  0x02
+#define TD_CC_DATATOGGLEM  0x03
+#define TD_CC_STALL	   0x04
+#define TD_DEVNOTRESP	   0x05
+#define TD_PIDCHECKFAIL	   0x06
+#define TD_UNEXPECTEDPID   0x07
+#define TD_DATAOVERRUN	   0x08
+#define TD_DATAUNDERRUN	   0x09
+#define TD_BUFFEROVERRUN   0x0C
+#define TD_BUFFERUNDERRUN  0x0D
+#define TD_NOTACCESSED	   0x0F
+
+#define MAXPSW 1
+
+struct ohci_td {
+	u32 hwINFO;
+	u32 hwCBP;		/* Current Buffer Pointer */
+	u32 hwNextTD;		/* Next TD Pointer */
+	u32 hwBE;		/* Memory Buffer End Pointer */
+	u16 hwPSW[MAXPSW];
+	u8 unused;
+	u8 index;
+	ohci_ed_t *ed;
+	ohci_td_t *next_dl_td;
+	usbdev_t *usb_dev;
+	int transfer_len;
+	u8 *data;
+
+	u32 unused2[2];
+} __attribute__((aligned(32)));
+
+#define OHCI_ED_SKIP	(1 << 14)
+
+/*
+ * The HCCA (Host Controller Communications Area) is a 256 byte
+ * structure defined in the OHCI spec. that the host controller is
+ * told the base address of.  It must be 256-byte aligned.
+ */
+
+#define NUM_INTS 32	/* part of the OHCI standard */
+struct ohci_hcca {
+	u32	int_table[NUM_INTS];	/* Interrupt ED table */
+	u16	frame_no;		/* current frame number */
+	u16	pad1;			/* set to 0 on each frame_no change */
+	u32	done_head;		/* info returned for an interrupt */
+	u8	reserved_for_hc[116];
+} __attribute__((aligned(256)));
+
+/*
+ * This is the structure of the OHCI controller's memory mapped I/O
+ * region.  This is Memory Mapped I/O.	You must use the readl() and
+ * writel() macros defined in asm/io.h to access these!!
+ */
+struct ohci_regs {
+	/* control and status registers */
+	u32	revision;
+	u32	control;
+	u32	cmdstatus;
+	u32	intrstatus;
+	u32	intrenable;
+	u32	intrdisable;
+
+	/* memory pointers */
+	u32	hcca;
+	u32	ed_periodcurrent;
+	u32	ed_controlhead;
+	u32	ed_controlcurrent;
+	u32	ed_bulkhead;
+	u32	ed_bulkcurrent;
+	u32	donehead;
+
+	/* frame counters */
+	u32	fminterval;
+	u32	fmremaining;
+	u32	fmnumber;
+	u32	periodicstart;
+	u32	lsthresh;
+
+	/* Root hub ports */
+	struct	ohci_roothub_regs {
+		u32	a;
+		u32	b;
+		u32	status;
+		u32	portstatus[MAX_DOWNSTREAM_PORTS];
+	} roothub;
+} __attribute__((aligned(32)));
+
+/* Some EHCI controls */
+#define EHCI_USBCMD_OFF		0x20
+#define EHCI_USBCMD_HCRESET	(1 << 1)
+
+/* OHCI CONTROL AND STATUS REGISTER MASKS */
+
+/*
+ * HcControl (control) register masks
+ */
+#define OHCI_CTRL_CBSR	(3 << 0)	/* control/bulk service ratio */
+#define OHCI_CTRL_PLE	(1 << 2)	/* periodic list enable */
+#define OHCI_CTRL_IE	(1 << 3)	/* isochronous enable */
+#define OHCI_CTRL_CLE	(1 << 4)	/* control list enable */
+#define OHCI_CTRL_BLE	(1 << 5)	/* bulk list enable */
+#define OHCI_CTRL_HCFS	(3 << 6)	/* host controller functional state */
+#define OHCI_CTRL_IR	(1 << 8)	/* interrupt routing */
+#define OHCI_CTRL_RWC	(1 << 9)	/* remote wakeup connected */
+#define OHCI_CTRL_RWE	(1 << 10)	/* remote wakeup enable */
+
+/* pre-shifted values for HCFS */
+#define OHCI_USB_RESET		(0 << 6)
+#define OHCI_USB_RESUME		(1 << 6)
+#define OHCI_USB_OPER		(2 << 6)
+#define OHCI_USB_SUSPEND 	(3 << 6)
+
+/*
+ * HcCommandStatus (cmdstatus) register masks
+ */
+#define OHCI_HCR	(1 << 0)	/* host controller reset */
+#define OHCI_CLF	(1 << 1)	/* control list filled */
+#define OHCI_BLF	(1 << 2)	/* bulk list filled */
+#define OHCI_OCR	(1 << 3)	/* ownership change request */
+#define OHCI_SOC	(3 << 16)	/* scheduling overrun count */
+
+/*
+ * masks used with interrupt registers:
+ * HcInterruptStatus (intrstatus)
+ * HcInterruptEnable (intrenable)
+ * HcInterruptDisable (intrdisable)
+ */
+#define OHCI_INTR_SO	(1 << 0)	/* scheduling overrun */
+#define OHCI_INTR_WDH	(1 << 1)	/* writeback of done_head */
+#define OHCI_INTR_SF	(1 << 2)	/* start frame */
+#define OHCI_INTR_RD	(1 << 3)	/* resume detect */
+#define OHCI_INTR_UE	(1 << 4)	/* unrecoverable error */
+#define OHCI_INTR_FNO	(1 << 5)	/* frame number overflow */
+#define OHCI_INTR_RHSC	(1 << 6)	/* root hub status change */
+#define OHCI_INTR_OC	(1 << 30)	/* ownership change */
+#define OHCI_INTR_MIE	(1 << 31)	/* master interrupt enable */
+
+/* urb */
+#define N_URB_TD 48
+struct urb_priv {
+	endpoint_t *ep;
+	ohci_ed_t *ed;
+	ohci_td_t *td[N_URB_TD];	/* list pointer to all corresponding TDs associated with this request */
+	u16 length;			/* number of tds associated with this request */
+	u16 td_cnt;			/* number of tds already serviced */
+	void *transfer_buffer;
+	int transfer_buffer_length;
+	int state;
+	int interval;
+	int actual_length;
+	int finished;
+};
+#define URB_DEL 1
+
+/* num of preallocated endpoint descriptors */
+#define NUM_EDS 8
+/* we need more TDs than EDs */
+#define NUM_TDS 64
+
+/*
+ * This is the full ohci controller description
+ *
+ * Note how the "proper" USB information is just
+ * a subset of what the full implementation needs. (Linus)
+ */
+struct ohci {
+	hci_t *hci;
+	ohci_hcca_t *hcca;		/* hcca */
+
+	int irq;
+	int disabled;			/* e.g. got a UE, we're hung */
+	int sleeping;
+	unsigned long flags;		/* for HC bugs */
+
+	ohci_regs_t *regs;		/* OHCI controller's memory */
+
+	int ohci_int_load[32];		/* load of the 32 Interrupt Chains (for load balancing)*/
+
+	int intrstatus;
+	u32 hc_control;			/* FIXME copy of the hc control reg */
+
+	rh_inst_t rh_instance;
+
+	ohci_ed_t *ed_rm_list[2];	/* lists of all endpoints to be removed */
+	ohci_ed_t *ed_bulktail;		/* last endpoint of bulk list */
+	ohci_ed_t *ed_controltail;	/* last endpoint of control list */
+
+	/* the ED pool */
+	ohci_ed_t *ed[NUM_EDS];
+	int ed_cnt;
+
+	/* the TD pool */
+	ohci_td_t *td[NUM_TDS];
+};
+
+/* libpayload usb api */
+hci_t * ohci_init (pcidev_t addr);
+
+/* irq handler called from roothub's poll() */
+int ohci_interrupt(ohci_t *ohci);
+
+#define OHCI_INST(controller) ((ohci_t*)((controller)->instance))
+
+static inline u32 roothub_a(ohci_t *hc)
+        { return readl(&hc->regs->roothub.a); }
+static inline u32 roothub_b(ohci_t *hc)
+        { return readl(&hc->regs->roothub.b); }
+static inline u32 roothub_status(ohci_t *hc)
+        { return readl(&hc->regs->roothub.status); }
+static inline u32 roothub_portstatus(ohci_t *hc, int i)
+        { return readl(&hc->regs->roothub.portstatus[i]); }
+
+#ifdef DEBUG
+void ohci_dump(ohci_t *ohci, int verbose);
+void pkt_print(urb_priv_t *urb, void *setup, const char *str, int verbose);
+void ohci_dump_roothub(ohci_t *ohci, int verbose);
+void ep_print_int_eds(ohci_t *ohci, const char *str);
+#define dbg(x...) printf(x)
+#else
+#define dbg(x...) do {} while(0)
+static inline void pkt_print(urb_priv_t *urb, void *setup,
+				const char *str, int verbose) {};
+static inline void ohci_dump(ohci_t *ohci, int verbose) {};
+static inline void ohci_dump_roothub(ohci_t *ohci, int verbose) {};
+static inline void ep_print_int_eds(ohci_t *ohci, const char *str) {};
+#endif
+
+#endif //__OHCI_H
diff --git a/drivers/usb/ohci_rh.c b/drivers/usb/ohci_rh.c
new file mode 100644
index 0000000..219edba
--- /dev/null
+++ b/drivers/usb/ohci_rh.c
@@ -0,0 +1,150 @@
+/*
+ * This file is part of the libpayload project.
+ *
+ * Copyright (C) 2009 Daniel Mack <daniel at caiaq.de>
+ * 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 <libpayload.h>
+#include <arch/endian.h>
+#include <usb/usb.h>
+
+#include "ohci.h"
+#include "ohci_rh.h"
+
+static void ohci_roothub_destroy (usbdev_t *dev)
+{
+	dbg("%s()\n", __func__);
+}
+
+static void ohci_roothub_poll (usbdev_t *dev)
+{
+	/* let the OHCI controller do the job */
+	ohci_interrupt(OHCI_INST(dev->controller));
+}
+
+static int port_reset(ohci_t *ohci, int port)
+{
+	int timeout = 1000;
+
+	writel(RH_PS_PRS, &ohci->regs->roothub.portstatus[port]);
+
+	dbg("%s(%d)", __func__, port);
+
+	while (!(readl(&ohci->regs->roothub.portstatus[port]) & RH_PS_PRSC) &&
+		--timeout) {
+		dbg(".");
+		mdelay(1);
+	}
+
+	dbg("\n");
+
+	writel(RH_PS_PRSC, &ohci->regs->roothub.portstatus[port]);
+
+	if (timeout == 0)
+		err("timeout waiting for port #%d reset\n");
+
+	return timeout == 0;
+}
+
+void ohci_rh_status_change(ohci_t *ohci)
+{
+	hci_t *hci = ohci->hci;
+	rh_inst_t *rh = RH_INST(hci->devices[0]);
+	u32 i, ndp = (roothub_a(ohci) & RH_A_NDP) & 0xf;
+
+	dbg("%s\n", __func__);
+
+	for (i = 0; i < ndp; i++) {
+		u32 status = roothub_portstatus(ohci, i);
+
+		/* clear */
+		writel((status & RH_PS_CSC), &ohci->regs->roothub.portstatus[i]);
+
+		if (!(status & RH_PS_CSC))
+			continue;
+
+		if (!!(status & RH_PS_CCS) == rh->port_connected[i])
+			continue;
+
+		rh->port_connected[i] = !!(status & RH_PS_CCS);
+
+		if (status & RH_PS_CCS) {
+			dbg("device connected on rh port %d\n", i);
+
+			/* wait at least 100ms, see 9.1.2 */
+			mdelay(100);
+
+			if (port_reset(ohci, i) == 0)
+				usb_attach_device(ohci->hci,
+						  ohci->hci->devices[0]->address,
+						  i, status & RH_PS_LSDA);
+		} else {
+			int j;
+
+			dbg("device disconnected on rh port %d\n", i);
+
+			/* skip device #0 which is the roothub */
+			for (j = 1; j < 128; j++) {
+				usbdev_t *d = ohci->hci->devices[j];
+				if (d && d->hub == 0 && d->port == i)
+					dbg("device %p detached\n", d);
+					usb_detach_device(ohci->hci, d->address);
+			}
+		}
+	}
+}
+
+void ohci_rh_init(usbdev_t *dev)
+{
+	u32 temp, ndp, i;
+	rh_inst_t *rh = RH_INST(dev);
+	ohci_t *ohci = OHCI_INST(dev->controller);
+
+	dbg("%s()\n", __func__);
+
+	dev->poll = ohci_roothub_poll;
+	dev->destroy = ohci_roothub_destroy;
+
+	dev->address = 0;
+	dev->hub = -1;
+	dev->port = -1;
+
+	for (i = 0; i < MAX_DOWNSTREAM_PORTS; i++)
+		rh->port_connected[i] = 0;
+
+	temp = roothub_a(ohci);
+	ndp = (temp & RH_A_NDP) & 0xf;
+
+	temp = roothub_status(ohci);
+	temp |= 1 << 16;
+	writel(temp, &ohci->regs->roothub.status);
+
+	dbg("%s: %d downstream ports\n", __func__, ndp);
+
+	for (i = 0; i < ndp; i++)
+		writel(RH_PS_PES, &ohci->regs->roothub.portstatus[i]);
+}
diff --git a/drivers/usb/ohci_rh.h b/drivers/usb/ohci_rh.h
new file mode 100644
index 0000000..490dfef
--- /dev/null
+++ b/drivers/usb/ohci_rh.h
@@ -0,0 +1,44 @@
+#ifndef OHCI_ROOTHUB_H
+#define OHCI_ROOTHUB_H
+
+/* OHCI ROOT HUB REGISTER MASKS */
+
+/* roothub.portstatus[] bits */
+#define RH_PS_CCS            0x00000001         /* current connect status */
+#define RH_PS_PES            0x00000002         /* port enable status*/
+#define RH_PS_PSS            0x00000004         /* port suspend status */
+#define RH_PS_POCI           0x00000008         /* port over current indicator */
+#define RH_PS_PRS            0x00000010         /* port reset status */
+#define RH_PS_PPS            0x00000100         /* port power status */
+#define RH_PS_LSDA           0x00000200         /* low speed device attached */
+#define RH_PS_CSC            0x00010000         /* connect status change */
+#define RH_PS_PESC           0x00020000         /* port enable status change */
+#define RH_PS_PSSC           0x00040000         /* port suspend status change */
+#define RH_PS_OCIC           0x00080000         /* over current indicator change */
+#define RH_PS_PRSC           0x00100000         /* port reset status change */
+
+/* roothub.status bits */
+#define RH_HS_LPS            0x00000001         /* local power status */
+#define RH_HS_OCI            0x00000002         /* over current indicator */
+#define RH_HS_DRWE           0x00008000         /* device remote wakeup enable */
+#define RH_HS_LPSC           0x00010000         /* local power status change */
+#define RH_HS_OCIC           0x00020000         /* over current indicator change */
+#define RH_HS_CRWE           0x80000000         /* clear remote wakeup enable */
+
+/* roothub.b masks */
+#define RH_B_DR         0x0000ffff              /* device removable flags */
+#define RH_B_PPCM       0xffff0000              /* port power control mask */
+
+/* roothub.a masks */
+#define RH_A_NDP        (0xff << 0)             /* number of downstream ports */
+#define RH_A_PSM        (1 << 8)                /* power switching mode */
+#define RH_A_NPS        (1 << 9)                /* no power switching */
+#define RH_A_DT         (1 << 10)               /* device type (mbz) */
+#define RH_A_OCPM       (1 << 11)               /* over current protection mode */
+#define RH_A_NOCP       (1 << 12)               /* no over current protection */
+#define RH_A_POTPGT     (0xff << 24)            /* power on to power good time */
+
+void ohci_rh_init (usbdev_t *dev);
+void ohci_rh_status_change(ohci_t *ohci);
+
+#endif /* OHCI_ROOTHUB_H */
diff --git a/drivers/usb/usbinit.c b/drivers/usb/usbinit.c
index ead9846..0e58627 100644
--- a/drivers/usb/usbinit.c
+++ b/drivers/usb/usbinit.c
@@ -30,6 +30,7 @@
 #include <libpayload-config.h>
 #include <usb/usb.h>
 #include "uhci.h"
+#include "ohci.h"
 #include <usb/usbdisk.h>
 
 /**
@@ -79,7 +80,7 @@ usb_controller_initialize (int bus, int dev, int func)
 		if (prog_if == 0x10) {
 			printf ("OHCI controller\n");
 #ifdef CONFIG_USB_OHCI
-			// ohci_init(addr);
+			ohci_init(addr);
 #else
 			printf ("Not supported.\n");
 #endif
-- 
1.6.0.4





More information about the coreboot mailing list