From ebd3da7dba7d72b0d7a4fa50ffbad61259562447 Mon Sep 17 00:00:00 2001 From: Yunzhi Li Date: Thu, 2 Jul 2015 15:28:11 +0800 Subject: libpayload: usb: dwc2: support split transaction With split transaction, dwc2 host controller can handle full- and low-speed devices on hub in high-speed mode. This commit adds support for split control and interrupt transfers BUG=None TEST=Connect usb keyboard through hub, usb keyboard can work BRANCH=None Change-Id: If7a00db21c8ad4c635f39581382b877603075d1a Signed-off-by: Patrick Georgi Original-Commit-Id: 4fb514b7f7f7e414fa94bfce05420957b1c57019 Original-Change-Id: I07e64064c6182d33905ae4efb13712645de7cf93 Original-Signed-off-by: Yunzhi Li Original-Reviewed-on: https://chromium-review.googlesource.com/283282 Original-Tested-by: Lin Huang Original-Commit-Queue: Lin Huang Original-Reviewed-by: Julius Werner Reviewed-on: http://review.coreboot.org/10956 Tested-by: build bot (Jenkins) Reviewed-by: Stefan Reinauer --- payloads/libpayload/drivers/usb/dwc2.c | 134 +++++++++++++++++++++-- payloads/libpayload/drivers/usb/dwc2_private.h | 8 ++ payloads/libpayload/drivers/usb/ehci.c | 24 ---- payloads/libpayload/drivers/usb/usb.c | 26 +++++ payloads/libpayload/include/usb/dwc2_registers.h | 24 ++++ payloads/libpayload/include/usb/usb.h | 2 + 6 files changed, 182 insertions(+), 36 deletions(-) (limited to 'payloads/libpayload') diff --git a/payloads/libpayload/drivers/usb/dwc2.c b/payloads/libpayload/drivers/usb/dwc2.c index fa0fd34e3c..a3979e51ac 100644 --- a/payloads/libpayload/drivers/usb/dwc2.c +++ b/payloads/libpayload/drivers/usb/dwc2.c @@ -164,16 +164,20 @@ wait_for_complete(endpoint_t *ep, uint32_t ch_num) if (hcint.chhltd) { writel(hcint.d32, ®->host.hchn[ch_num].hcintn); - if (hcint.xfercomp) + if (hcint.xfercomp || hcint.ack) return hctsiz.xfersize; else if (hcint.nak || hcint.frmovrun) - return hctsiz.xfersize; + return -HCSTAT_NAK; else if (hcint.xacterr) return -HCSTAT_XFERERR; else if (hcint.bblerr) return -HCSTAT_BABBLE; else if (hcint.stall) return -HCSTAT_STALL; + else if (hcint.ack) + return -HCSTAT_ACK; + else if (hcint.nyet) + return -HCSTAT_NYET; else return -HCSTAT_UNKNOW; } @@ -204,7 +208,7 @@ wait_for_complete(endpoint_t *ep, uint32_t ch_num) } static int -dwc2_transfer(endpoint_t *ep, int size, int pid, ep_dir_t dir, +dwc2_do_xfer(endpoint_t *ep, int size, int pid, ep_dir_t dir, uint32_t ch_num, u8 *data_buf) { uint32_t do_copy; @@ -241,6 +245,8 @@ dwc2_transfer(endpoint_t *ep, int size, int pid, ep_dir_t dir, hcchar.devaddr = ep->dev->address; hcchar.chdis = 0; hcchar.chen = 1; + if (ep->dev->speed == LOW_SPEED) + hcchar.lspddev = 1; if (size > DMA_SIZE) { usb_debug("Transfer too large: %d\n", size); @@ -284,6 +290,110 @@ dwc2_transfer(endpoint_t *ep, int size, int pid, ep_dir_t dir, return transferred; } +static int +dwc2_split_transfer(endpoint_t *ep, int size, int pid, ep_dir_t dir, + uint32_t ch_num, u8 *data_buf, split_info_t *split) +{ + dwc2_reg_t *reg = DWC2_REG(ep->dev->controller); + hfnum_t hfnum; + hcsplit_t hcsplit = { .d32 = 0 }; + int ret, transferred = 0; + + hcsplit.hubaddr = split->hubaddr; + hcsplit.prtaddr = split->hubport; + hcsplit.spltena = 1; + writel(hcsplit.d32, ®->host.hchn[ch_num].hcspltn); + + /* Wait for next frame boundary */ + do { + hfnum.d32 = readl(®->host.hfnum); + } while (hfnum.frnum % 8 != 0); + + /* Handle Start-Split */ + ret = dwc2_do_xfer(ep, dir == EPDIR_IN ? 0 : size, pid, dir, ch_num, + data_buf); + if (ret < 0) + goto out; + + hcsplit.spltena = 1; + hcsplit.compsplt = 1; + writel(hcsplit.d32, ®->host.hchn[ch_num].hcspltn); + ep->toggle = pid; + + if (dir == EPDIR_OUT) + transferred += ret; + + /* Handle Complete-Split */ + do { + ret = dwc2_do_xfer(ep, dir == EPDIR_OUT ? 0 : size, ep->toggle, + dir, ch_num, data_buf); + } while (ret == -HCSTAT_NYET); + + if (dir == EPDIR_IN) + transferred += ret; + +out: + /* Clear hcsplit reg */ + hcsplit.spltena = 0; + hcsplit.compsplt = 0; + writel(hcsplit.d32, ®->host.hchn[ch_num].hcspltn); + + if (ret < 0) + return ret; + + return transferred; +} + +static int dwc2_need_split(usbdev_t *dev, split_info_t *split) +{ + if (dev->speed == HIGH_SPEED) + return 0; + + if (closest_usb2_hub(dev, &split->hubaddr, &split->hubport)) + return 0; + + return 1; +} + +static int +dwc2_transfer(endpoint_t *ep, int size, int pid, ep_dir_t dir, uint32_t ch_num, + u8 *src, uint8_t skip_nak) +{ + split_info_t split; + int ret, transferred = 0, timeout = 3000; + + ep->toggle = pid; + + do { + if (dwc2_need_split(ep->dev, &split)) { +nak_retry: + ret = dwc2_split_transfer(ep, size, ep->toggle, dir, 0, + src, &split); + + /* + * dwc2_split_transfer() waits for the next FullSpeed + * frame boundary, so we have one try per millisecond. + * It's 3s timeout for each split transfer. + */ + if (ret == -HCSTAT_NAK && !skip_nak && --timeout) { + udelay(500); + goto nak_retry; + } + } else { + ret = dwc2_do_xfer(ep, size, pid, dir, 0, src); + } + + if (ret < 0) + return ret; + + size -= ret; + src += ret; + transferred += ret; + } while (size > 0); + + return transferred; +} + static int dwc2_bulk(endpoint_t *ep, int size, u8 *src, int finalize) { @@ -296,7 +406,7 @@ dwc2_bulk(endpoint_t *ep, int size, u8 *src, int finalize) else return -1; - return dwc2_transfer(ep, size, ep->toggle, data_dir, 0, src); + return dwc2_transfer(ep, size, ep->toggle, data_dir, 0, src, 0); } static int @@ -304,8 +414,8 @@ dwc2_control(usbdev_t *dev, direction_t dir, int drlen, void *setup, int dalen, u8 *src) { int ret = 0; - ep_dir_t data_dir; + endpoint_t *ep = &dev->endpoints[0]; if (dir == IN) data_dir = EPDIR_IN; @@ -315,19 +425,19 @@ dwc2_control(usbdev_t *dev, direction_t dir, int drlen, void *setup, return -1; /* Setup Phase */ - if (dwc2_transfer(&dev->endpoints[0], drlen, PID_SETUP, EPDIR_OUT, 0, - setup) < 0) + if (dwc2_transfer(ep, drlen, PID_SETUP, EPDIR_OUT, 0, setup, 0) < 0) return -1; + /* Data Phase */ + ep->toggle = PID_DATA1; if (dalen > 0) { - ret = dwc2_transfer(&dev->endpoints[0], dalen, PID_DATA1, - data_dir, 0, src); + ret = dwc2_transfer(ep, dalen, ep->toggle, data_dir, 0, src, 0); if (ret < 0) return -1; } + /* Status Phase */ - if (dwc2_transfer(&dev->endpoints[0], 0, PID_DATA1, !data_dir, 0, - NULL) < 0) + if (dwc2_transfer(ep, 0, PID_DATA1, !data_dir, 0, NULL, 0) < 0) return -1; return ret; @@ -345,7 +455,7 @@ dwc2_intr(endpoint_t *ep, int size, u8 *src) else return -1; - return dwc2_transfer(ep, size, ep->toggle, data_dir, 0, src); + return dwc2_transfer(ep, size, ep->toggle, data_dir, 0, src, 1); } static u32 dwc2_intr_get_timestamp(intr_queue_t *q) diff --git a/payloads/libpayload/drivers/usb/dwc2_private.h b/payloads/libpayload/drivers/usb/dwc2_private.h index c1090420ec..5b1a547f8d 100644 --- a/payloads/libpayload/drivers/usb/dwc2_private.h +++ b/payloads/libpayload/drivers/usb/dwc2_private.h @@ -36,6 +36,11 @@ typedef struct { u32 timestamp; } intr_queue_t; +typedef struct { + int hubaddr; + int hubport; +} split_info_t; + #define DWC2_INST(controller) ((dwc_ctrl_t *)((controller)->instance)) #define DWC2_REG(controller) ((dwc2_reg_t *)((controller)->reg_base)) @@ -44,6 +49,9 @@ typedef enum { HCSTAT_XFERERR, HCSTAT_BABBLE, HCSTAT_STALL, + HCSTAT_ACK, + HCSTAT_NAK, + HCSTAT_NYET, HCSTAT_UNKNOW, HCSTAT_TIMEOUT, } hcstat_t; diff --git a/payloads/libpayload/drivers/usb/ehci.c b/payloads/libpayload/drivers/usb/ehci.c index 0aa799d09c..4636e7cc03 100644 --- a/payloads/libpayload/drivers/usb/ehci.c +++ b/payloads/libpayload/drivers/usb/ehci.c @@ -194,30 +194,6 @@ static void ehci_shutdown (hci_t *controller) enum { EHCI_OUT=0, EHCI_IN=1, EHCI_SETUP=2 }; -/* - * returns the address of the closest USB2.0 hub, which is responsible for - * split transactions, along with the number of the used downstream port - */ -static int closest_usb2_hub(const usbdev_t *dev, int *const addr, int *const port) -{ - const usbdev_t *usb1dev; - do { - usb1dev = dev; - if ((dev->hub >= 0) && (dev->hub < 128)) - dev = dev->controller->devices[dev->hub]; - else - dev = NULL; - } while (dev && (dev->speed < 2)); - if (dev) { - *addr = usb1dev->hub; - *port = usb1dev->port; - return 0; - } else { - usb_debug("ehci: Couldn't find closest USB2.0 hub.\n"); - return 1; - } -} - /* returns handled bytes. assumes that the fields it writes are empty on entry */ static int fill_td(qtd_t *td, void* data, int datalen) { diff --git a/payloads/libpayload/drivers/usb/usb.c b/payloads/libpayload/drivers/usb/usb.c index 6174d639d2..e00d92f00a 100644 --- a/payloads/libpayload/drivers/usb/usb.c +++ b/payloads/libpayload/drivers/usb/usb.c @@ -653,3 +653,29 @@ usb_generic_init (usbdev_t *dev) usb_detach_device(dev->controller, dev->address); } } + +/* + * returns the address of the closest USB2.0 hub, which is responsible for + * split transactions, along with the number of the used downstream port + */ +int closest_usb2_hub(const usbdev_t *dev, int *const addr, int *const port) +{ + const usbdev_t *usb1dev; + + do { + usb1dev = dev; + if ((dev->hub >= 0) && (dev->hub < 128)) + dev = dev->controller->devices[dev->hub]; + else + dev = NULL; + } while (dev && (dev->speed < 2)); + + if (dev) { + *addr = usb1dev->hub; + *port = usb1dev->port; + return 0; + } + + usb_debug("Couldn't find closest USB2.0 hub.\n"); + return 1; +} diff --git a/payloads/libpayload/include/usb/dwc2_registers.h b/payloads/libpayload/include/usb/dwc2_registers.h index 0e46985d4b..b44a5ac2f7 100644 --- a/payloads/libpayload/include/usb/dwc2_registers.h +++ b/payloads/libpayload/include/usb/dwc2_registers.h @@ -597,6 +597,30 @@ typedef union { }; } hcchar_t; +/** + * This union represents the bit fields in the Host Channel-n Split Control + * Register. + */ +typedef union { + /* raw register data */ + uint32_t d32; + + /* register bits */ + struct { + /** Port Address */ + unsigned prtaddr:7; + /** Hub Address */ + unsigned hubaddr:7; + /** Transaction Position */ + unsigned xactpos:2; + /** Do Complete Split */ + unsigned compsplt:1; + unsigned reserved:14; + /** Split Enable */ + unsigned spltena:1; + }; +} hcsplit_t; + typedef enum { EPDIR_OUT = 0, EPDIR_IN, diff --git a/payloads/libpayload/include/usb/usb.h b/payloads/libpayload/include/usb/usb.h index df507db6ea..cf52a4aece 100644 --- a/payloads/libpayload/include/usb/usb.h +++ b/payloads/libpayload/include/usb/usb.h @@ -277,6 +277,8 @@ void usb_hid_init (usbdev_t *dev); void usb_msc_init (usbdev_t *dev); void usb_generic_init (usbdev_t *dev); +int closest_usb2_hub(const usbdev_t *dev, int *const addr, int *const port); + static inline unsigned char gen_bmRequestType (dev_req_dir dir, dev_req_type type, dev_req_recp recp) { -- cgit v1.2.3