Here comes a set of six patches to support OHCI controllers in libpayload.
This work was originally started by Leandro Dorilex and we worked together on this. However, his first approach to develop everything from scratch was a little too ambitious and so we decided to take an existing stack from U-Boot (which was again derived from Linux 2.4).
I finished the work on my own now as we urgently need it and Leandro didn't have a proper development environment.
The stack was heavily cleaned up from many legacy code pathes and adopted to libpayload's USB API. Also, U-Boot's stack wasn't aware of any virtual/physical address mappings, so I had to add that.
What's missing at the moment are the implementation bits for interrupt queues and isocronous transfers. The first one should be easy to do as I left in all the functions for it. I just couldn't test it though.
Also unclear is the situation on big endian machines. I only have an Geode based board here which matches the endianess of the USB controller, so I couldn't test that either. Volounteers welcome :)
Best regards, Daniel
From: Leandro Dorilex ldorileo@gmail.com
To fit our current ohci implementation we have to make some hc functions to return int instead of void returning we currenctly verify handle errors using those returns. --- drivers/usb/uhci.c | 32 +++++++++++++++++--------------- drivers/usb/usb.c | 14 +++++++------- drivers/usb/usbmsc.c | 4 ++-- include/usb/usb.h | 9 ++++++--- 4 files changed, 32 insertions(+), 27 deletions(-)
diff --git a/drivers/usb/uhci.c b/drivers/usb/uhci.c index def6b45..7eb947b 100644 --- a/drivers/usb/uhci.c +++ b/drivers/usb/uhci.c @@ -31,19 +31,27 @@ #include "uhci.h" #include <arch/virtual.h>
-static void uhci_start (hci_t *controller); +static int uhci_start (hci_t *controller); static void uhci_stop (hci_t *controller); -static void uhci_reset (hci_t *controller); +static int 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, +static int uhci_control (endpoint_t *ep, pid_t dir, int drlen, void *devreq, int dalen, u8 *data); static void* uhci_create_intr_queue (endpoint_t *ep, int reqsize, int reqcount, int reqtiming); static void uhci_destroy_intr_queue (endpoint_t *ep, void *queue); static u8* uhci_poll_intr_queue (void *queue);
+int min (int a, int b) +{ + if (a < b) + return a; + else + return b; +} + #if 0 /* dump uhci */ static void @@ -96,7 +104,7 @@ td_dump (td_t *td) printf (" still active - timeout?\n"); }
-static void +static int uhci_reset (hci_t *controller) { /* reset */ @@ -122,6 +130,7 @@ uhci_reset (hci_t *controller) uhci_reg_mask16 (controller, USBCMD, ~0, 0xc0); // max packets, configure flag
uhci_start (controller); + return 1; }
hci_t * @@ -246,10 +255,11 @@ uhci_shutdown (hci_t *controller) free (controller); }
-static void +static int uhci_start (hci_t *controller) { uhci_reg_mask16 (controller, USBCMD, ~0, 1); // start work on schedule + return 1; }
static void @@ -296,18 +306,10 @@ maxlen (int size) }
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, +uhci_control (endpoint_t *ep, pid_t dir, int drlen, void *devreq, int dalen, unsigned char *data) { + usbdev_t *dev = ep->dev; int endp = 0; /* this is control: always 0 */ int mlen = dev->endpoints[0].maxpacketsize; int count = (2 + (dalen + mlen - 1) / mlen); diff --git a/drivers/usb/usb.c b/drivers/usb/usb.c index 25e8006..4a0dbad 100644 --- a/drivers/usb/usb.c +++ b/drivers/usb/usb.c @@ -111,7 +111,7 @@ set_feature (usbdev_t *dev, int endp, int feature, int rtype) dr.wValue = feature; dr.wIndex = endp; dr.wLength = 0; - dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0); + dev->controller->control (&dev->endpoints[0], OUT, sizeof (dr), &dr, 0, 0); }
void @@ -125,7 +125,7 @@ get_status (usbdev_t *dev, int intf, int rtype, int len, void *data) dr.wValue = 0; dr.wIndex = intf; dr.wLength = len; - dev->controller->control (dev, IN, sizeof (dr), &dr, len, data); + dev->controller->control (&dev->endpoints[0], IN, sizeof (dr), &dr, len, data); }
u8 * @@ -143,7 +143,7 @@ get_descriptor (usbdev_t *dev, unsigned char bmRequestType, int descType, dr.wValue = (descType << 8) | descIdx; dr.wIndex = langID; dr.wLength = 8; - if (dev->controller->control (dev, IN, sizeof (dr), &dr, 8, buf)) { + if (dev->controller->control (&dev->endpoints[0], IN, sizeof (dr), &dr, 8, buf)) { printf ("getting descriptor size (type %x) failed\n", descType); } @@ -167,7 +167,7 @@ get_descriptor (usbdev_t *dev, unsigned char bmRequestType, int descType, memset (result, 0, size); dr.wLength = size; if (dev->controller-> - control (dev, IN, sizeof (dr), &dr, size, result)) { + control (&dev->endpoints[0], IN, sizeof (dr), &dr, size, result)) { printf ("getting descriptor (type %x, size %x) failed\n", descType, size); } @@ -185,7 +185,7 @@ set_configuration (usbdev_t *dev) dr.wValue = dev->configuration[5]; dr.wIndex = 0; dr.wLength = 0; - dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0); + dev->controller->control (&dev->endpoints[0], OUT, sizeof (dr), &dr, 0, 0); }
int @@ -203,7 +203,7 @@ clear_stall (endpoint_t *ep) dr.wValue = ENDPOINT_HALT; dr.wIndex = endp; dr.wLength = 0; - dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0); + dev->controller->control (&dev->endpoints[0], OUT, sizeof (dr), &dr, 0, 0); return 0; }
@@ -248,7 +248,7 @@ set_address (hci_t *controller, int lowspeed) dev->endpoints[0].toggle = 0; dev->endpoints[0].direction = SETUP; mdelay (50); - if (dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0)) { + if (dev->controller->control (&dev->endpoints[0], OUT, sizeof (dr), &dr, 0, 0)) { printf ("set_address failed\n"); return -1; } diff --git a/drivers/usb/usbmsc.c b/drivers/usb/usbmsc.c index f24bd6d..fb5d9b6 100644 --- a/drivers/usb/usbmsc.c +++ b/drivers/usb/usbmsc.c @@ -122,7 +122,7 @@ typedef struct { dr.wValue = 0; dr.wIndex = 0; dr.wLength = 0; - dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0); + dev->controller->control (&dev->endpoints[0], OUT, sizeof (dr), &dr, 0, 0); clear_stall (MSC_INST (dev)->bulk_in); clear_stall (MSC_INST (dev)->bulk_out); } @@ -143,7 +143,7 @@ get_max_luns (usbdev_t *dev) dr.wValue = 0; dr.wIndex = 0; dr.wLength = 1; - if (dev->controller->control (dev, IN, sizeof (dr), &dr, 1, &luns)) { + if (dev->controller->control (&dev->endpoints[0], IN, sizeof (dr), &dr, 1, &luns)) { luns = 0; // assume only 1 lun if req fails } return luns; diff --git a/include/usb/usb.h b/include/usb/usb.h index 9f38b84..c3c5f19 100644 --- a/include/usb/usb.h +++ b/include/usb/usb.h @@ -115,14 +115,14 @@ struct usbdev_hc { pcidev_t bus_address; u32 reg_base; usbdev_t *devices[128]; // dev 0 is root hub, 127 is last addressable - void (*start) (hci_t *controller); + int (*start) (hci_t *controller); void (*stop) (hci_t *controller); - void (*reset) (hci_t *controller); + int (*reset) (hci_t *controller); void (*shutdown) (hci_t *controller); int (*packet) (usbdev_t *dev, int endp, int pid, int toggle, int length, u8 *data); int (*bulk) (endpoint_t *ep, int size, u8 *data, int finalize); - int (*control) (usbdev_t *dev, pid_t pid, int dr_length, + int (*control) (endpoint_t *ep, pid_t pid, int dr_length, void *devreq, int data_length, u8 *data); void* (*create_intr_queue) (endpoint_t *ep, int reqsize, int reqcount, int reqtiming); void (*destroy_intr_queue) (endpoint_t *ep, void *queue); @@ -228,4 +228,7 @@ void usb_detach_device(hci_t *controller, int devno); int usb_attach_device(hci_t *controller, int hubaddress, int port, int lowspeed);
void usb_fatal(const char *message) __attribute__ ((noreturn)); + +#define ED_TO_HCI_T(endpoint)((hci_t *) endpoint->dev->controller) + #endif
Signed-off-by: Daniel Mack daniel@caiaq.de --- include/usb/usb.h | 7 +++++++ 1 files changed, 7 insertions(+), 0 deletions(-)
diff --git a/include/usb/usb.h b/include/usb/usb.h index c3c5f19..95cbdae 100644 --- a/include/usb/usb.h +++ b/include/usb/usb.h @@ -29,6 +29,7 @@
#ifndef __USB_H #define __USB_H + #include <libpayload.h> #include <pci.h>
@@ -54,6 +55,12 @@ typedef enum { } bRequest_Codes;
typedef enum { + DEVICE = 1, + CONFIG = 2, + STRING = 3 +} bDescriptor_Types; + +typedef enum { ENDPOINT_HALT = 0, DEVICE_REMOTE_WAKEUP = 1, TEST_MODE = 2
This is needed by the OHCI stack.
Signed-off-by: Daniel Mack daniel@caiaq.de --- drivers/usb/usb.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/drivers/usb/usb.c b/drivers/usb/usb.c index 4a0dbad..c08de54 100644 --- a/drivers/usb/usb.c +++ b/drivers/usb/usb.c @@ -247,6 +247,7 @@ set_address (hci_t *controller, int lowspeed) dev->endpoints[0].maxpacketsize = 8; dev->endpoints[0].toggle = 0; dev->endpoints[0].direction = SETUP; + dev->endpoints[0].type = CONTROL; mdelay (50); if (dev->controller->control (&dev->endpoints[0], OUT, sizeof (dr), &dr, 0, 0)) { printf ("set_address failed\n");
Signed-off-by: Daniel Mack daniel@caiaq.de --- include/i386/arch/endian.h | 11 +++++++++++ include/powerpc/arch/endian.h | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/include/i386/arch/endian.h b/include/i386/arch/endian.h index a50ac1f..743787f 100644 --- a/include/i386/arch/endian.h +++ b/include/i386/arch/endian.h @@ -38,4 +38,15 @@ (( (in) & 0xFF0000) >> 8) | (( (in) & 0xFF000000) >> 24))
#define ntohll(in) (((u64) ntohl( (in) & 0xFFFFFFFF) << 32) | ((u64) ntohl( (in) >> 32))) + +#define cpu_to_le16(in) (in) +#define cpu_to_le32(in) (in) +#define cpu_to_be16(in) ntohw(in) +#define cpu_to_be32(in) ntohl(in) + +#define le16_to_cpu(in) (in) +#define le32_to_cpu(in) (in) +#define be16_to_cpu(in) ntohw(in) +#define be32_to_cpu(in) ntohl(in) + #endif diff --git a/include/powerpc/arch/endian.h b/include/powerpc/arch/endian.h index 8ffad70..1cc7c57 100644 --- a/include/powerpc/arch/endian.h +++ b/include/powerpc/arch/endian.h @@ -33,9 +33,19 @@ #include <arch/types.h>
#define ntohw(in) (in) - #define ntohl(in) (in) - #define ntohll(in) (in)
+#define cpu_to_le16(in) ((( (in) & 0xFF) << 8) | (( (in) & 0xFF00) >> 8)) +#define cpu_to_le32(in) ((( (in) & 0xFF) << 24) | (( (in) & 0xFF00) << 8) | \ + (( (in) & 0xFF0000) >> 8) | (( (in) & 0xFF000000) >> 24)) + +#define cpu_to_be16(in) (in) +#define cpu_to_be32(in) (in) + +#define le16_to_cpu(in) cpu_to_le16(in) +#define le32_to_cpu(in) cpu_to_le32(in) +#define be16_to_cpu(in) (in) +#define be32_to_cpu(in) (in) + #endif
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@caiaq.de Thanks-to: Leandro Dorilex ldorileo@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@freescale.com + * Gary Jennejohn, DENX Software Engineering garyj@denx.de + * Roman Weissgaerber weissg@vienna.at + * David Brownell + * + * Port to libpayload by Daniel Mack daniel@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(®s->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(®s->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, ®s->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, ®s->intrdisable); + readl(®s->intrdisable); + + stat = dl_done_list(ohci); + + writel(OHCI_INTR_WDH, ®s->intrenable); + readl(®s->intrdisable); + } + + if (ints & OHCI_INTR_SO) { + dbg("USB Schedule overrun\n"); + writel(OHCI_INTR_SO, ®s->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, ®s->intrdisable); + if (ohci->ed_rm_list[frame] != NULL) + writel(OHCI_INTR_SF, ®s->intrenable); + stat = 0xff; + } + + writel(ints, ®s->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(®s->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@vienna.at + * (C) Copyright 2000-2001 David Brownell dbrownell@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@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
Signed-off-by: Daniel Mack daniel@caiaq.de --- drivers/usb/ohci_dbg.c | 297 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 297 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/ohci_dbg.c
diff --git a/drivers/usb/ohci_dbg.c b/drivers/usb/ohci_dbg.c new file mode 100644 index 0000000..8df83cb --- /dev/null +++ b/drivers/usb/ohci_dbg.c @@ -0,0 +1,297 @@ +/* + * 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@freescale.com + * Gary Jennejohn, DENX Software Engineering garyj@denx.de + * Roman Weissgaerber weissg@vienna.at + * David Brownell + * + * Port to libpayload by Daniel Mack daniel@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" + +#ifdef DEBUG + +static int sohci_get_current_frame_number(ohci_t *ohci) +{ + return readl(&ohci->hcca->frame_no); +} + +static const char *ep_type_to_str(endpoint_t *ep) +{ + switch (ep->type) { + case CONTROL: return "CONTROL"; break; + case BULK: return "BULK"; break; + case INTERRUPT: return "INTERRUPT"; break; + case ISOCHRONOUS: return "ISOCHRONOUS"; break; + } + + return "BOGUS"; +} + +void pkt_print(urb_priv_t *urb, void *setup, const char *str, int verbose) +{ + endpoint_t *ep = urb->ep; + usbdev_t *dev = ep->dev; + ohci_t *ohci = OHCI_INST(dev->controller); + + dbg("%s URB:[%4x] dev:%2lu,ep:%2lu-%c,type:%s,len:%d/%d\n", + str, + sohci_get_current_frame_number(ohci), + dev->address, + ep->endpoint, + (ep->direction == OUT) ? 'O': + (ep->direction == SETUP) ? 'S' : 'I', + ep_type_to_str(ep), + (urb ? urb->actual_length : 0), + urb->transfer_buffer_length); + + if (!verbose) + return; + + if (ep->direction != IN) { + int i, len; + + if (ep->type == CONTROL) { + printf( "%s: cmd(8):\n", __func__); + for (i = 0; i < 8 ; i++) + printf(" %02x", ((u8 *) setup) [i]); + printf("\n"); + } + + if (urb->transfer_buffer_length > 0 && urb->transfer_buffer) { + printf("%s: data(%d/%d):", + __func__, + urb->actual_length, + urb->transfer_buffer_length); + len = (ep->direction == OUT) ? urb->actual_length : 0; + + for (i = 0; i < 16 && i < len; i++) + printf(" %02x", ((u8 *) urb->transfer_buffer)[i]); + + printf("%s\n", i < len ? "...": ""); + } + } +} + +/* prints non-empty branches of the int ed tree inclusive iso eds */ +void ep_print_int_eds(ohci_t *ohci, const char *str) +{ + int i, j; + volatile u32 *ed_p; + + for (i = 0; i < 32; i++) { + j = 5; + ed_p = &(ohci->hcca->int_table[i]); + if (!*ed_p) + continue; + + dbg("%s: %s branch int %d (%x):", __func__, str, i, i); + while (*ed_p && j--) { + ohci_ed_t *ed = (ohci_ed_t *) le32_to_cpu(ed_p); + dbg(" ed: %4x;", ed->hwINFO); + ed_p = &ed->hwNextED; + } + dbg("\n"); + } +} + +static void ohci_dump_intr_mask(char *label, u32 mask) +{ + dbg("%s: 0x%08x%s%s%s%s%s%s%s%s%s\n", + label, + mask, + (mask & OHCI_INTR_MIE) ? " MIE" : "", + (mask & OHCI_INTR_OC) ? " OC" : "", + (mask & OHCI_INTR_RHSC) ? " RHSC" : "", + (mask & OHCI_INTR_FNO) ? " FNO" : "", + (mask & OHCI_INTR_UE) ? " UE" : "", + (mask & OHCI_INTR_RD) ? " RD" : "", + (mask & OHCI_INTR_SF) ? " SF" : "", + (mask & OHCI_INTR_WDH) ? " WDH" : "", + (mask & OHCI_INTR_SO) ? " SO" : "" + ); +} + +static void print_eds(const char *label, u32 value) +{ + ohci_ed_t *ed = phys_to_virt(value); + u32 hwNext; + + if (!value) + return; + + dbg("%s @%p: hwINFO %08x hwTailP %08x hwHeadP %08x hwNextED %08x\n", + label, ed, ed->hwINFO, ed->hwTailP, ed->hwHeadP, ed->hwNextED); + + hwNext = ed->hwHeadP & ~0xf; + + while (hwNext) { + ohci_td_t *td = phys_to_virt(hwNext); + dbg(" --- TD @%p: hwINFO %08x hwCBP %08x hwBE %08x hwNextTD %08x ", + td, td->hwINFO, td->hwCBP, td->hwBE, td->hwNextTD); + + if (td->hwCBP) { + u8 *buf = phys_to_virt(td->hwCBP); + dbg("(%02x %02x %02x %02x %02x %02x %02x %02x)", + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7]); + } + + dbg("\n"); + + hwNext = td->hwNextTD; + } +} + +static char *hcfs2string(int state) +{ + switch (state) { + case OHCI_USB_RESET: return "reset"; + case OHCI_USB_RESUME: return "resume"; + case OHCI_USB_OPER: return "operational"; + case OHCI_USB_SUSPEND: return "suspend"; + } + + return "?"; +} + +/* dump control and status registers */ +static void ohci_dump_status(ohci_t *ohci) +{ + u32 temp; + struct ohci_regs *regs = ohci->regs; + + temp = readl(®s->control); + dbg("control: 0x%08x%s%s%s HCFS=%s%s%s%s%s CBSR=%d\n", temp, + (temp & OHCI_CTRL_RWE) ? " RWE" : "", + (temp & OHCI_CTRL_RWC) ? " RWC" : "", + (temp & OHCI_CTRL_IR) ? " IR" : "", + hcfs2string(temp & OHCI_CTRL_HCFS), + (temp & OHCI_CTRL_BLE) ? " BLE" : "", + (temp & OHCI_CTRL_CLE) ? " CLE" : "", + (temp & OHCI_CTRL_IE) ? " IE" : "", + (temp & OHCI_CTRL_PLE) ? " PLE" : "", + temp & OHCI_CTRL_CBSR + ); + + temp = readl(®s->cmdstatus); + dbg("cmdstatus: 0x%08x SOC=%d%s%s%s%s\n", temp, + (temp & OHCI_SOC) >> 16, + (temp & OHCI_OCR) ? " OCR" : "", + (temp & OHCI_BLF) ? " BLF" : "", + (temp & OHCI_CLF) ? " CLF" : "", + (temp & OHCI_HCR) ? " HCR" : "" + ); + + ohci_dump_intr_mask("intrstatus", readl(®s->intrstatus)); + ohci_dump_intr_mask("intrenable", readl(®s->intrenable)); + + print_eds("ed_periodcurrent", readl(®s->ed_periodcurrent)); + print_eds("ed_controlhead", readl(®s->ed_controlhead)); + print_eds("ed_controlcurrent", readl(®s->ed_controlcurrent)); + print_eds("ed_bulkhead", readl(®s->ed_bulkhead)); + print_eds("ed_bulkcurrent", readl(®s->ed_bulkcurrent)); + print_eds("donehead", readl(®s->donehead)); +} + +void ohci_dump_roothub(ohci_t *ohci, int verbose) +{ + u32 temp, ndp, i; + + temp = roothub_a(ohci); + ndp = (temp & RH_A_NDP) & 0xf; + + if (verbose) { + dbg("roothub.a: %08x POTPGT=%d%s%s%s%s%s NDP=%d\n", temp, + ((temp & RH_A_POTPGT) >> 24) & 0xff, + (temp & RH_A_NOCP) ? " NOCP" : "", + (temp & RH_A_OCPM) ? " OCPM" : "", + (temp & RH_A_DT) ? " DT" : "", + (temp & RH_A_NPS) ? " NPS" : "", + (temp & RH_A_PSM) ? " PSM" : "", + ndp + ); + temp = roothub_b(ohci); + dbg("roothub.b: %08x PPCM=%04x DR=%04x\n", + temp, + (temp & RH_B_PPCM) >> 16, + (temp & RH_B_DR) + ); + temp = roothub_status(ohci); + dbg("roothub.status: %08x%s%s%s%s%s%s\n", + temp, + (temp & RH_HS_CRWE) ? " CRWE" : "", + (temp & RH_HS_OCIC) ? " OCIC" : "", + (temp & RH_HS_LPSC) ? " LPSC" : "", + (temp & RH_HS_DRWE) ? " DRWE" : "", + (temp & RH_HS_OCI) ? " OCI" : "", + (temp & RH_HS_LPS) ? " LPS" : "" + ); + } + + for (i = 0; i < ndp; i++) { + temp = roothub_portstatus(ohci, i); + dbg("roothub.portstatus[%d] = 0x%08x%s%s%s%s%s%s%s%s%s%s%s%s\n", + i, + temp, + (temp & RH_PS_PRSC) ? " PRSC" : "", + (temp & RH_PS_OCIC) ? " OCIC" : "", + (temp & RH_PS_PSSC) ? " PSSC" : "", + (temp & RH_PS_PESC) ? " PESC" : "", + (temp & RH_PS_CSC) ? " CSC" : "", + + (temp & RH_PS_LSDA) ? " LSDA" : "", + (temp & RH_PS_PPS) ? " PPS" : "", + (temp & RH_PS_PRS) ? " PRS" : "", + (temp & RH_PS_POCI) ? " POCI" : "", + (temp & RH_PS_PSS) ? " PSS" : "", + + (temp & RH_PS_PES) ? " PES" : "", + (temp & RH_PS_CCS) ? " CCS" : "" + ); + } +} + +void ohci_dump(ohci_t *ohci, int verbose) +{ + dbg("----------------------\n"); + dbg("OHCI @%p ohci state:", ohci->regs); + + ohci_dump_status(ohci); + + if (verbose) + ep_print_int_eds(ohci, "hcca"); + + dbg("hcca frame #%04x\n", ohci->hcca->frame_no); + ohci_dump_roothub(ohci, 1); + dbg("----------------------\n"); +} + +#endif /* DEBUG */
On Mon, Oct 05, 2009 at 10:44:42AM +0800, Daniel Mack wrote:
Here comes a set of six patches to support OHCI controllers in libpayload.
Forgot to mention that the last upstream commit was r4499.
Daniel
Am Montag, den 05.10.2009, 10:44 +0800 schrieb Daniel Mack:
This work was originally started by Leandro Dorilex and we worked together on this. However, his first approach to develop everything from scratch was a little too ambitious and so we decided to take an existing stack from U-Boot (which was again derived from Linux 2.4).
Thank you for your effort. Given its ancestry, I suppose this code is GPL? If so, we can't merge it upstream easily, as libpayload is BSD.
What's missing at the moment are the implementation bits for interrupt queues and isocronous transfers. The first one should be easy to do as I left in all the functions for it. I just couldn't test it though.
Interrupt transfers are important for keyboard support. Isochronous transfers aren't supported by the stack so far (except if you added it), but I fail to see a need for it in firmware anyway.
Regards, Patrick
On Mon, Oct 05, 2009 at 09:51:22AM +0200, Patrick Georgi wrote:
Am Montag, den 05.10.2009, 10:44 +0800 schrieb Daniel Mack:
This work was originally started by Leandro Dorilex and we worked together on this. However, his first approach to develop everything from scratch was a little too ambitious and so we decided to take an existing stack from U-Boot (which was again derived from Linux 2.4).
Thank you for your effort. Given its ancestry, I suppose this code is GPL? If so, we can't merge it upstream easily, as libpayload is BSD.
The code is derived from GPL'ed sources, so it's GPL, yes. But quoting from the LICENSES file:
The copyright on libpayload is owned by various individual developers and/or companies. Please check the individual source files for details.
So where so you see the problem?
Daniel
Am Montag, den 05.10.2009, 09:55 +0200 schrieb Daniel Mack:
The code is derived from GPL'ed sources, so it's GPL, yes. But quoting from the LICENSES file:
The copyright on libpayload is owned by various individual developers and/or companies. Please check the individual source files for details.
So where so you see the problem?
This statement talks about copyright. The issue is licensing.
Right now, libpayload is BSD licensed, so users of the library are free to do whatever they please with the code (except removing copyright notices)
A libpayload with your patches becomes (as a combined work) GPL licensed (the BSD portions are sublicensed as GPL, which is possible), which restricts the uses of it, and imposes conditions on the user beyond leaving the copyright notices alone.
It would be possible to add a "GPL" configuration flag, and let the OHCI driver depend on it, so people have to choose "libpayload, GPL edition" to get this driver.
But then, what happens if someone comes along with a zfs driver (derived from OpenSolaris, hence CDDL)? People have to decide to use either BSD +CDDL or BSD+GPL code - ie. either OHCI or ZFS, as the GPL doesn't allow code under different licenses, and both GPL and CDDL don't allow sublicensing.
Such a model isn't really sustainable with many licenses (and why should the GPL get any special treatment?), and that's the (good) reason why very few projects do that.
Patrick
On Mon, Oct 05, 2009 at 10:14:19AM +0200, Patrick Georgi wrote:
Am Montag, den 05.10.2009, 09:55 +0200 schrieb Daniel Mack:
The code is derived from GPL'ed sources, so it's GPL, yes. But quoting from the LICENSES file:
The copyright on libpayload is owned by various individual developers and/or companies. Please check the individual source files for details.
So where so you see the problem?
This statement talks about copyright. The issue is licensing.
Right now, libpayload is BSD licensed, so users of the library are free to do whatever they please with the code (except removing copyright notices)
A libpayload with your patches becomes (as a combined work) GPL licensed (the BSD portions are sublicensed as GPL, which is possible), which restricts the uses of it, and imposes conditions on the user beyond leaving the copyright notices alone.
It would be possible to add a "GPL" configuration flag, and let the OHCI driver depend on it, so people have to choose "libpayload, GPL edition" to get this driver.
But then, what happens if someone comes along with a zfs driver (derived from OpenSolaris, hence CDDL)? People have to decide to use either BSD +CDDL or BSD+GPL code - ie. either OHCI or ZFS, as the GPL doesn't allow code under different licenses, and both GPL and CDDL don't allow sublicensing.
Such a model isn't really sustainable with many licenses (and why should the GPL get any special treatment?), and that's the (good) reason why very few projects do that.
Ok, I can't judge that. And I can't change the license as I'm not the author of the original sources. Up to you to decide then :)
Daniel
Am Montag, den 05.10.2009, 10:23 +0200 schrieb Daniel Mack:
Ok, I can't judge that. And I can't change the license as I'm not the author of the original sources. Up to you to decide then :)
Just curious, what are you using libpayload for, FILO?
If so, I'd assume that's the primary user of USB functionality in libpayload, and we could stuff the OHCI code there - but using the libpayload interfaces, so the code changes were really small (basically, changes to the build system)
That way, libpayload stays "pure", while this can still be used where it doesn't trouble anyone.
One other option would be to have a libpayload-gpl library, but that would really take it too far, I think, as library users would have to collect the bits from various places.
Patrick
On Mon, Oct 05, 2009 at 10:30:39AM +0200, Patrick Georgi wrote:
Am Montag, den 05.10.2009, 10:23 +0200 schrieb Daniel Mack:
Ok, I can't judge that. And I can't change the license as I'm not the author of the original sources. Up to you to decide then :)
Just curious, what are you using libpayload for, FILO?
Yes.
If so, I'd assume that's the primary user of USB functionality in libpayload, and we could stuff the OHCI code there - but using the libpayload interfaces, so the code changes were really small (basically, changes to the build system)
That way, libpayload stays "pure", while this can still be used where it doesn't trouble anyone.
Following the recent discussion about USB keyboard support in SeaBIOS I thought putting everything which all payloads share is the right thing to do, no? Wouldn't we end up having clones of that stack appearing in other payloads if we don't put it in a common place?
One other option would be to have a libpayload-gpl library, but that would really take it too far, I think, as library users would have to collect the bits from various places.
buildrom could do that automagically, though. But I'm unsure, too. I'd really like to leave that up to you to pack the stack to a place which seems appropriate. I just don't to get it lost, it was too much work for that ;)
Daniel
On 05.10.2009 10:45, Daniel Mack wrote:
On Mon, Oct 05, 2009 at 10:30:39AM +0200, Patrick Georgi wrote:
If so, I'd assume that's the primary user of USB functionality in libpayload, and we could stuff the OHCI code there - but using the libpayload interfaces, so the code changes were really small (basically, changes to the build system)
That way, libpayload stays "pure", while this can still be used where it doesn't trouble anyone.
Following the recent discussion about USB keyboard support in SeaBIOS I thought putting everything which all payloads share is the right thing to do, no? Wouldn't we end up having clones of that stack appearing in other payloads if we don't put it in a common place?
Yes. The reason we had Leandro Dorileo write that thing from scratch was to get a BSD licensed USB stack. Otherwise we'd just have merged/adapted the USB stack from U-Boot or Linux. It's unfortunate that this piece of information never reached you.
One other option would be to have a libpayload-gpl library, but that would really take it too far, I think, as library users would have to collect the bits from various places.
buildrom could do that automagically, though. But I'm unsure, too. I'd really like to leave that up to you to pack the stack to a place which seems appropriate. I just don't to get it lost, it was too much work for that ;)
Definitely. We don't want to throw away useful code. Now we just have to figure out a way to use it in a way which still keeps libpayload BSD licensed.
Regards, Carl-Daniel
On Mon, Oct 05, 2009 at 10:56:07AM +0200, Carl-Daniel Hailfinger wrote:
On 05.10.2009 10:45, Daniel Mack wrote:
On Mon, Oct 05, 2009 at 10:30:39AM +0200, Patrick Georgi wrote:
If so, I'd assume that's the primary user of USB functionality in libpayload, and we could stuff the OHCI code there - but using the libpayload interfaces, so the code changes were really small (basically, changes to the build system)
That way, libpayload stays "pure", while this can still be used where it doesn't trouble anyone.
Following the recent discussion about USB keyboard support in SeaBIOS I thought putting everything which all payloads share is the right thing to do, no? Wouldn't we end up having clones of that stack appearing in other payloads if we don't put it in a common place?
Yes. The reason we had Leandro Dorileo write that thing from scratch was to get a BSD licensed USB stack. Otherwise we'd just have merged/adapted the USB stack from U-Boot or Linux. It's unfortunate that this piece of information never reached you.
In fact, we did talk about that. However, I read the LICENSES file and thought that dual-licenced code for different parts of the lib is ok. (You can't GPL'ize pure BSD code either).
And taking into account how much work it was to port and debug the other stack it would probably have been the same efford to write it from scratch.
And - just to clarify that - I didn't want to take away any workpackage from anyone. I just couldn't wait any longer as we urgently needed the code for a device which goes to production these days.
Daniel
Daniel Mack wrote:
I can't change the license as I'm not the author of the original sources.
You could try contacting the author, assuming you yourself would be comfortable using BSD for your work, if they are.
Up to you to decide then :)
libpayload must stay BSD.. My guess is that your work will not be included in libpayload proper. It seems the best you can do now is to maintain the patch for compatibility whenever libpayload changes.
What a waste. :\
//Peter
On Mon, Oct 05, 2009 at 02:15:16PM +0200, Peter Stuge wrote:
Daniel Mack wrote:
I can't change the license as I'm not the author of the original sources.
You could try contacting the author, assuming you yourself would be comfortable using BSD for your work, if they are.
There are some dozen authors as the stack was orginally written for Linux 2.4 and then ported to U-Boot. Probably some bugfixes were also backported. I don't know. So changing the license is not an option.
libpayload must stay BSD.. My guess is that your work will not be included in libpayload proper.
Unless there is a separate module with complementary code which is then released under the GPL. That way, it could also take some more code from the Linux kernel without running into licensing issues again.
It seems the best you can do now is to maintain the patch for compatibility whenever libpayload changes.
I certainly won't do that, simply because I don't have the time for that, sorry.
Daniel
On Tue, Oct 06, 2009 at 02:49:08AM +0200, Daniel Mack wrote:
On Mon, Oct 05, 2009 at 02:15:16PM +0200, Peter Stuge wrote:
Daniel Mack wrote:
I can't change the license as I'm not the author of the original sources.
You could try contacting the author, assuming you yourself would be comfortable using BSD for your work, if they are.
There are some dozen authors as the stack was orginally written for Linux 2.4 and then ported to U-Boot. Probably some bugfixes were also backported. I don't know. So changing the license is not an option.
libpayload must stay BSD.. My guess is that your work will not be included in libpayload proper.
Unless there is a separate module with complementary code which is then released under the GPL. That way, it could also take some more code from the Linux kernel without running into licensing issues again.
Btw - Patrick - IIRC - came up with that idea in #coreboot IRC channel. Has there been any movement in this direction? I just ask because I really want to avoid the code getting lost.
Thanks, Daniel