[coreboot-gerrit] New patch to review for coreboot: libpayload: usb: dwc2: support split transaction

Patrick Georgi (pgeorgi@google.com) gerrit at coreboot.org
Thu Jul 16 18:13:26 CEST 2015


Patrick Georgi (pgeorgi at google.com) just uploaded a new patch set to gerrit, which you can find at http://review.coreboot.org/10956

-gerrit

commit 7fec532945610895aadfc7d3e624a515dab6e5cd
Author: Yunzhi Li <lyz at rock-chips.com>
Date:   Thu Jul 2 15:28:11 2015 +0800

    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 at chromium.org>
    Original-Commit-Id: 4fb514b7f7f7e414fa94bfce05420957b1c57019
    Original-Change-Id: I07e64064c6182d33905ae4efb13712645de7cf93
    Original-Signed-off-by: Yunzhi Li <lyz at rock-chips.com>
    Original-Reviewed-on: https://chromium-review.googlesource.com/283282
    Original-Tested-by: Lin Huang <hl at rock-chips.com>
    Original-Commit-Queue: Lin Huang <hl at rock-chips.com>
    Original-Reviewed-by: Julius Werner <jwerner at chromium.org>
---
 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(-)

diff --git a/payloads/libpayload/drivers/usb/dwc2.c b/payloads/libpayload/drivers/usb/dwc2.c
index fa0fd34..a3979e5 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 c109042..5b1a547 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 0aa799d..4636e7c 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 6174d63..e00d92f 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 0e46985..b44a5ac 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 df507db..cf52a4a 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)
 {



More information about the coreboot-gerrit mailing list