[U-Boot] [PATCH v3 0/5] Various USB improvements

This series adds a few USB improvements for Marek:
- Support for interupt transfers - Basic 64-bit controller support (used by x86) - Multiple controller support for EHCI PCI
Changes in v3: - Set last pointer to correct position in create_int_queue() - Set S-mask bit always, not just on low-speed devices - Correct logic flow in destroy_int_queue()
Changes in v2: - Update to use errno - Remove TODOs - Tidy code style a little - Squash timeouts patch into interrupt patch - Add blank line before function return
Michael Spang (1): usb: usbeth: smsc95xx: remove EEPROM loaded check
Patrick Georgi (1): usb: ehci: Support interrupt transfers via periodic list
Simon Glass (1): x86: Enable USB features for coreboot
Vincent Palatin (2): usb: ehci: Fix aliasing issue in EHCI interrupt code usb: Add multiple controllers support for EHCI PCI
drivers/usb/eth/smsc95xx.c | 4 - drivers/usb/host/ehci-hcd.c | 315 +++++++++++++++++++++++++++++++++++++++++++- drivers/usb/host/ehci-pci.c | 52 +++++++- drivers/usb/host/ehci.h | 6 +- include/configs/coreboot.h | 17 +++ 5 files changed, 379 insertions(+), 15 deletions(-)

From: Patrick Georgi patrick@georgi-clan.de
Interrupt transfers aren't meant to be used from the async list (the EHCI spec indicates trouble with low/full-speed intr on async).
Build a periodic list instead, and provide an API to make use of it. Then, use that API from the existing interrupt transfer API.
This provides support for USB keyboards using EHCI.
Use timeouts to ensure we cannot get stuck in the keyboard scanning if something wrong happens (USB device unplugged or fatal I/O error)
Signed-off-by: Vincent Palatin vpalatin@chromium.org Signed-off-by: Julius Werner jwerner@chromium.org
Signed-off-by: Simon Glass sjg@chromium.org --- Changes in v3: - Set last pointer to correct position in create_int_queue() - Set S-mask bit always, not just on low-speed devices - Correct logic flow in destroy_int_queue()
Changes in v2: - Update to use errno - Remove TODOs - Tidy code style a little - Squash timeouts patch into interrupt patch
drivers/usb/host/ehci-hcd.c | 315 +++++++++++++++++++++++++++++++++++++++++++- drivers/usb/host/ehci.h | 6 +- 2 files changed, 316 insertions(+), 5 deletions(-)
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 7f98a63..25c2216 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -21,12 +21,14 @@ * MA 02111-1307 USA */ #include <common.h> +#include <errno.h> #include <asm/byteorder.h> #include <asm/unaligned.h> #include <usb.h> #include <asm/io.h> #include <malloc.h> #include <watchdog.h> +#include <linux/compiler.h>
#include "ehci.h"
@@ -39,7 +41,10 @@ static struct ehci_ctrl { struct ehci_hcor *hcor; int rootdev; uint16_t portreset; - struct QH qh_list __attribute__((aligned(USB_DMA_MINALIGN))); + struct QH qh_list __aligned(USB_DMA_MINALIGN); + struct QH periodic_queue __aligned(USB_DMA_MINALIGN); + uint32_t *periodic_list; + int ntds; } ehcic[CONFIG_USB_MAX_CONTROLLER_COUNT];
#define ALIGN_END_ADDR(type, ptr, size) \ @@ -858,6 +863,8 @@ int usb_lowlevel_init(int index, void **controller) uint32_t reg; uint32_t cmd; struct QH *qh_list; + struct QH *periodic; + int i;
if (ehci_hcd_init(index, &ehcic[index].hccr, &ehcic[index].hcor)) return -1; @@ -884,6 +891,40 @@ int usb_lowlevel_init(int index, void **controller) qh_list->qh_overlay.qt_token = cpu_to_hc32(QT_TOKEN_STATUS(QT_TOKEN_STATUS_HALTED));
+ /* Set async. queue head pointer. */ + ehci_writel(&ehcic[index].hcor->or_asynclistaddr, (uint32_t)qh_list); + + /* + * Set up periodic list + * Step 1: Parent QH for all periodic transfers. + */ + periodic = &ehcic[index].periodic_queue; + memset(periodic, 0, sizeof(*periodic)); + periodic->qh_link = cpu_to_hc32(QH_LINK_TERMINATE); + periodic->qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); + periodic->qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); + + /* + * Step 2: Setup frame-list: Every microframe, USB tries the same list. + * In particular, device specifications on polling frequency + * are disregarded. Keyboards seem to send NAK/NYet reliably + * when polled with an empty buffer. + * + * Split Transactions will be spread across microframes using + * S-mask and C-mask. + */ + ehcic[index].periodic_list = memalign(4096, 1024*4); + if (!ehcic[index].periodic_list) + return -ENOMEM; + for (i = 0; i < 1024; i++) { + ehcic[index].periodic_list[i] = (uint32_t)periodic + | QH_LINK_TYPE_QH; + } + + /* Set periodic list base address */ + ehci_writel(&ehcic[index].hcor->or_periodiclistbase, + (uint32_t)ehcic[index].periodic_list); + reg = ehci_readl(&ehcic[index].hccr->cr_hcsparams); descriptor.hub.bNbrPorts = HCS_N_PORTS(reg); debug("Register %x NbrPorts %d\n", reg, descriptor.hub.bNbrPorts); @@ -953,10 +994,254 @@ submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer, return ehci_submit_async(dev, pipe, buffer, length, setup); }
+struct int_queue { + struct QH *first; + struct QH *current; + struct QH *last; + struct qTD *tds; +}; + +#define NEXT_QH(qh) (struct QH *)((qh)->qh_link & ~0x1f) + +static int +enable_periodic(struct ehci_ctrl *ctrl) +{ + uint32_t cmd; + struct ehci_hcor *hcor = ctrl->hcor; + int ret; + + cmd = ehci_readl(&hcor->or_usbcmd); + cmd |= CMD_PSE; + ehci_writel(&hcor->or_usbcmd, cmd); + + ret = handshake((uint32_t *)&hcor->or_usbsts, + STS_PSS, STS_PSS, 100 * 1000); + if (ret < 0) { + printf("EHCI failed: timeout when enabling periodic list\n"); + return -ETIMEDOUT; + } + udelay(1000); + return 0; +} + +static int +disable_periodic(struct ehci_ctrl *ctrl) +{ + uint32_t cmd; + struct ehci_hcor *hcor = ctrl->hcor; + int ret; + + cmd = ehci_readl(&hcor->or_usbcmd); + cmd &= ~CMD_PSE; + ehci_writel(&hcor->or_usbcmd, cmd); + + ret = handshake((uint32_t *)&hcor->or_usbsts, + STS_PSS, 0, 100 * 1000); + if (ret < 0) { + printf("EHCI failed: timeout when disabling periodic list\n"); + return -ETIMEDOUT; + } + return 0; +} + +static int periodic_schedules; + +struct int_queue * +create_int_queue(struct usb_device *dev, unsigned long pipe, int queuesize, + int elementsize, void *buffer) +{ + struct ehci_ctrl *ctrl = dev->controller; + struct int_queue *result = NULL; + int i; + + debug("Enter create_int_queue\n"); + if (usb_pipetype(pipe) != PIPE_INTERRUPT) { + debug("non-interrupt pipe (type=%lu)", usb_pipetype(pipe)); + return NULL; + } + + /* limit to 4 full pages worth of data - + * we can safely fit them in a single TD, + * no matter the alignment + */ + if (elementsize >= 16384) { + debug("too large elements for interrupt transfers\n"); + return NULL; + } + + result = malloc(sizeof(*result)); + if (!result) { + debug("ehci intr queue: out of memory\n"); + goto fail1; + } + result->first = memalign(32, sizeof(struct QH) * queuesize); + if (!result->first) { + debug("ehci intr queue: out of memory\n"); + goto fail2; + } + result->current = result->first; + result->last = result->first + queuesize - 1; + result->tds = memalign(32, sizeof(struct qTD) * queuesize); + if (!result->tds) { + debug("ehci intr queue: out of memory\n"); + goto fail3; + } + memset(result->first, 0, sizeof(struct QH) * queuesize); + memset(result->tds, 0, sizeof(struct qTD) * queuesize); + + for (i = 0; i < queuesize; i++) { + struct QH *qh = result->first + i; + struct qTD *td = result->tds + i; + void **buf = &qh->buffer; + + qh->qh_link = (uint32_t)(qh+1) | QH_LINK_TYPE_QH; + if (i == queuesize - 1) + qh->qh_link = QH_LINK_TERMINATE; + + qh->qh_overlay.qt_next = (uint32_t)td; + qh->qh_endpt1 = (0 << 28) | /* No NAK reload (ehci 4.9) */ + (usb_maxpacket(dev, pipe) << 16) | /* MPS */ + (1 << 14) | + QH_ENDPT1_EPS(ehci_encode_speed(dev->speed)) | + (usb_pipeendpoint(pipe) << 8) | /* Endpoint Number */ + (usb_pipedevice(pipe) << 0); + qh->qh_endpt2 = (1 << 30) | /* 1 Tx per mframe */ + (1 << 0); /* S-mask: microframe 0 */ + if (dev->speed == USB_SPEED_LOW || + dev->speed == USB_SPEED_FULL) { + debug("TT: port: %d, hub address: %d\n", + dev->portnr, dev->parent->devnum); + qh->qh_endpt2 |= (dev->portnr << 23) | + (dev->parent->devnum << 16) | + (0x1c << 8); /* C-mask: microframes 2-4 */ + } + + td->qt_next = QT_NEXT_TERMINATE; + td->qt_altnext = QT_NEXT_TERMINATE; + debug("communication direction is '%s'\n", + usb_pipein(pipe) ? "in" : "out"); + td->qt_token = (elementsize << 16) | + ((usb_pipein(pipe) ? 1 : 0) << 8) | /* IN/OUT token */ + 0x80; /* active */ + td->qt_buffer[0] = (uint32_t)buffer + i * elementsize; + td->qt_buffer[1] = (td->qt_buffer[0] + 0x1000) & ~0xfff; + td->qt_buffer[2] = (td->qt_buffer[0] + 0x2000) & ~0xfff; + td->qt_buffer[3] = (td->qt_buffer[0] + 0x3000) & ~0xfff; + td->qt_buffer[4] = (td->qt_buffer[0] + 0x4000) & ~0xfff; + + *buf = buffer + i * elementsize; + } + + if (disable_periodic(ctrl) < 0) { + debug("FATAL: periodic should never fail, but did"); + goto fail3; + } + + /* hook up to periodic list */ + struct QH *list = &ctrl->periodic_queue; + result->last->qh_link = list->qh_link; + list->qh_link = (uint32_t)result->first | QH_LINK_TYPE_QH; + + if (enable_periodic(ctrl) < 0) { + debug("FATAL: periodic should never fail, but did"); + goto fail3; + } + periodic_schedules++; + + debug("Exit create_int_queue\n"); + return result; +fail3: + if (result->tds) + free(result->tds); +fail2: + if (result->first) + free(result->first); + if (result) + free(result); +fail1: + return NULL; +} + +void *poll_int_queue(struct usb_device *dev, struct int_queue *queue) +{ + struct QH *cur = queue->current; + + /* depleted queue */ + if (cur == NULL) { + debug("Exit poll_int_queue with completed queue\n"); + return NULL; + } + /* still active */ + if (cur->qh_overlay.qt_token & 0x80) { + debug("Exit poll_int_queue with no completed intr transfer. " + "token is %x\n", cur->qh_overlay.qt_token); + return NULL; + } + if (!(cur->qh_link & QH_LINK_TERMINATE)) + queue->current++; + else + queue->current = NULL; + debug("Exit poll_int_queue with completed intr transfer. " + "token is %x at %p (first at %p)\n", cur->qh_overlay.qt_token, + &cur->qh_overlay.qt_token, queue->first); + return cur->buffer; +} + +/* Do not free buffers associated with QHs, they're owned by someone else */ +int +destroy_int_queue(struct usb_device *dev, struct int_queue *queue) +{ + struct ehci_ctrl *ctrl = dev->controller; + int result = -1; + unsigned long timeout; + + if (disable_periodic(ctrl) < 0) { + debug("FATAL: periodic should never fail, but did"); + goto out; + } + periodic_schedules--; + + struct QH *cur = &ctrl->periodic_queue; + timeout = get_timer(0) + 500; /* abort after 500ms */ + while (!(cur->qh_link & QH_LINK_TERMINATE)) { + debug("considering %p, with qh_link %x\n", cur, cur->qh_link); + if (NEXT_QH(cur) == queue->first) { + debug("found candidate. removing from chain\n"); + cur->qh_link = queue->last->qh_link; + result = 0; + break; + } + cur = NEXT_QH(cur); + if (get_timer(0) > timeout) { + printf("Timeout destroying interrupt endpoint queue\n"); + result = -1; + goto out; + } + } + + if (periodic_schedules > 0) { + result = enable_periodic(ctrl); + if (result < 0) + debug("FATAL: periodic should never fail, but did"); + } + +out: + free(queue->tds); + free(queue->first); + free(queue); + + return result; +} + int submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, int length, int interval) { + void *backbuffer; + struct int_queue *queue; + unsigned long timeout; + int result = 0, ret; + debug("dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d", dev, pipe, buffer, length, interval);
@@ -972,9 +1257,31 @@ submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, * not require more than a single qTD. */ if (length > usb_maxpacket(dev, pipe)) { - printf("%s: Interrupt transfers requiring several transactions " - "are not supported.\n", __func__); + printf("%s: Interrupt transfers requiring several " + "transactions are not supported.\n", __func__); return -1; } - return ehci_submit_async(dev, pipe, buffer, length, NULL); + + queue = create_int_queue(dev, pipe, 1, length, buffer); + + timeout = get_timer(0) + USB_TIMEOUT_MS(pipe); + while ((backbuffer = poll_int_queue(dev, queue)) == NULL) + if (get_timer(0) > timeout) { + printf("Timeout poll on interrupt endpoint\n"); + result = -ETIMEDOUT; + break; + } + + if (backbuffer != buffer) { + debug("got wrong buffer back (%x instead of %x)\n", + (uint32_t)backbuffer, (uint32_t)buffer); + return -EINVAL; + } + + ret = destroy_int_queue(dev, queue); + if (ret < 0) + return ret; + + /* everything worked out fine */ + return result; } diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 1e3cd79..46b535f 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -69,6 +69,7 @@ struct ehci_hcor { #define CMD_RUN (1 << 0) /* start/stop HC */ uint32_t or_usbsts; #define STS_ASS (1 << 15) +#define STS_PSS (1 << 14) #define STS_HALT (1 << 12) uint32_t or_usbintr; #define INTR_UE (1 << 0) /* USB interrupt enable */ @@ -245,7 +246,10 @@ struct QH { * Add dummy fill value to make the size of this struct * aligned to 32 bytes */ - uint8_t fill[16]; + union { + uint8_t fill[16]; + void *buffer; + }; };
/* Low level init functions */

From: Vincent Palatin vpalatin@chromium.org
The interrupt endpoint handling code stores the buffer pointer in the QH padding field. We need to make it the size of a pointer to avoid strict aliasing issue with the compiler.
Signed-off-by: Vincent Palatin vpalatin@chromium.org
Signed-off-by: Simon Glass sjg@chromium.org --- Changes in v3: None Changes in v2: None
drivers/usb/host/ehci.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 46b535f..d090f0a 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -247,7 +247,7 @@ struct QH { * aligned to 32 bytes */ union { - uint8_t fill[16]; + uint32_t fill[4]; void *buffer; }; };

From: Michael Spang spang@chromium.org
[port of Linux kernel commit bcd218be5aeb by Steve Glendinning]
The eeprom read & write commands currently check the E2P_CMD_LOADED_ bit is set before allowing any operations. This prevents any reading or writing unless a correctly programmed EEPROM is installed.
Signed-off-by: Michael Spang spang@chromium.org Signed-off-by: Simon Glass sjg@chromium.org Acked-by: Marek Vasut marex@denx.de --- Changes in v3: None Changes in v2: None
drivers/usb/eth/smsc95xx.c | 4 ---- 1 file changed, 4 deletions(-)
diff --git a/drivers/usb/eth/smsc95xx.c b/drivers/usb/eth/smsc95xx.c index dc5ca65..fd8f8a7 100644 --- a/drivers/usb/eth/smsc95xx.c +++ b/drivers/usb/eth/smsc95xx.c @@ -265,10 +265,6 @@ static int smsc95xx_eeprom_confirm_not_busy(struct ueth_data *dev)
do { smsc95xx_read_reg(dev, E2P_CMD, &val); - if (!(val & E2P_CMD_LOADED_)) { - debug("No EEPROM present\n"); - return -1; - } if (!(val & E2P_CMD_BUSY_)) return 0; udelay(40);

From: Vincent Palatin vpalatin@chromium.org
Use the ability to have several active EHCI controller on a system in the PCI EHCI controller implementation.
Signed-off-by: Simon Glass sjg@chromium.org --- Changes in v3: None Changes in v2: - Add blank line before function return
drivers/usb/host/ehci-pci.c | 52 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 6 deletions(-)
diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 29af02d..61a9909 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -32,30 +32,70 @@ static struct pci_device_id ehci_pci_ids[] = { {0x12D8, 0x400F}, /* Pericom */ {0, 0} }; +#else +static pci_dev_t ehci_find_class(int index) +{ + int bus; + int devnum; + pci_dev_t bdf; + uint32_t class; + + for (bus = 0; bus <= pci_last_busno(); bus++) { + for (devnum = 0; devnum < PCI_MAX_PCI_DEVICES-1; devnum++) { + pci_read_config_dword(PCI_BDF(bus, devnum, 0), + PCI_CLASS_REVISION, &class); + if (class >> 16 == 0xffff) + continue; + + for (bdf = PCI_BDF(bus, devnum, 0); + bdf <= PCI_BDF(bus, devnum, + PCI_MAX_PCI_FUNCTIONS - 1); + bdf += PCI_BDF(0, 0, 1)) { + pci_read_config_dword(bdf, PCI_CLASS_REVISION, + &class); + if ((class >> 8 == PCI_CLASS_SERIAL_USB_EHCI) + && !index--) + return bdf; + } + } + } + + return -ENODEV; +} #endif
/* * Create the appropriate control structures to manage * a new EHCI host controller. */ -int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor) +int ehci_hcd_init(int index, struct ehci_hccr **ret_hccr, + struct ehci_hcor **ret_hcor) { pci_dev_t pdev; + struct ehci_hccr *hccr; + struct ehci_hcor *hcor;
+#ifdef CONFIG_PCI_EHCI_DEVICE pdev = pci_find_devices(ehci_pci_ids, CONFIG_PCI_EHCI_DEVICE); +#else + pdev = ehci_find_class(index); +#endif if (pdev == -1) { printf("EHCI host controller not found\n"); return -1; }
- *hccr = (struct ehci_hccr *)pci_map_bar(pdev, + hccr = (struct ehci_hccr *)pci_map_bar(pdev, PCI_BASE_ADDRESS_0, PCI_REGION_MEM); - *hcor = (struct ehci_hcor *)((uint32_t) *hccr + - HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase))); + hcor = (struct ehci_hcor *)((uint32_t) hccr + + HC_LENGTH(ehci_readl(&hccr->cr_capbase)));
debug("EHCI-PCI init hccr 0x%x and hcor 0x%x hc_length %d\n", - (uint32_t)*hccr, (uint32_t)*hcor, - (uint32_t)HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase))); + (uint32_t)hccr, (uint32_t)hcor, + (uint32_t)HC_LENGTH(ehci_readl(&hccr->cr_capbase))); + + *ret_hccr = hccr; + *ret_hcor = hcor;
return 0; }

Enable PCI EHCI, storage, keyboard and Ethernet for USB.
Signed-off-by: Simon Glass sjg@chromium.org --- Changes in v3: None Changes in v2: None
include/configs/coreboot.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
diff --git a/include/configs/coreboot.h b/include/configs/coreboot.h index c7f36ff..ebc165f 100644 --- a/include/configs/coreboot.h +++ b/include/configs/coreboot.h @@ -267,6 +267,23 @@ */ #define CONFIG_PCI
+/*----------------------------------------------------------------------- + * USB configuration + */ +#define CONFIG_USB_EHCI +#define CONFIG_USB_EHCI_PCI +#define CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS 12 +#define CONFIG_USB_MAX_CONTROLLER_COUNT 2 +#define CONFIG_USB_STORAGE +#define CONFIG_USB_KEYBOARD +#define CONFIG_SYS_USB_EVENT_POLL + +#define CONFIG_USB_HOST_ETHER +#define CONFIG_USB_ETHER_ASIX +#define CONFIG_USB_ETHER_SMSC95XX + +#define CONFIG_CMD_USB + #define CONFIG_EXTRA_ENV_SETTINGS \ CONFIG_STD_DEVICES_SETTINGS
participants (1)
-
Simon Glass