summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYunzhi Li <lyz@rock-chips.com>2015-07-02 15:28:11 +0800
committerPatrick Georgi <pgeorgi@google.com>2015-07-16 22:39:59 +0200
commitebd3da7dba7d72b0d7a4fa50ffbad61259562447 (patch)
tree715a8a4873f22173fad0c73cbf311d3ca1081a96
parentae3d71a4d10b28bb5da1b3399358988acdcf1ace (diff)
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 <pgeorgi@chromium.org> Original-Commit-Id: 4fb514b7f7f7e414fa94bfce05420957b1c57019 Original-Change-Id: I07e64064c6182d33905ae4efb13712645de7cf93 Original-Signed-off-by: Yunzhi Li <lyz@rock-chips.com> Original-Reviewed-on: https://chromium-review.googlesource.com/283282 Original-Tested-by: Lin Huang <hl@rock-chips.com> Original-Commit-Queue: Lin Huang <hl@rock-chips.com> Original-Reviewed-by: Julius Werner <jwerner@chromium.org> Reviewed-on: http://review.coreboot.org/10956 Tested-by: build bot (Jenkins) Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
-rw-r--r--payloads/libpayload/drivers/usb/dwc2.c134
-rw-r--r--payloads/libpayload/drivers/usb/dwc2_private.h8
-rw-r--r--payloads/libpayload/drivers/usb/ehci.c24
-rw-r--r--payloads/libpayload/drivers/usb/usb.c26
-rw-r--r--payloads/libpayload/include/usb/dwc2_registers.h24
-rw-r--r--payloads/libpayload/include/usb/usb.h2
6 files changed, 182 insertions, 36 deletions
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, &reg->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);
@@ -285,6 +291,110 @@ dwc2_transfer(endpoint_t *ep, int size, int pid, ep_dir_t dir,
}
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, &reg->host.hchn[ch_num].hcspltn);
+
+ /* Wait for next frame boundary */
+ do {
+ hfnum.d32 = readl(&reg->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, &reg->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, &reg->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)
{
ep_dir_t data_dir;
@@ -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)
{