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