summaryrefslogtreecommitdiffstats
path: root/payloads/libpayload/drivers/usb
diff options
context:
space:
mode:
authorYunzhi Li <lyz@rock-chips.com>2015-06-19 17:09:04 +0800
committerPatrick Georgi <pgeorgi@google.com>2015-07-06 09:40:02 +0200
commitaa33609d289c4ee07ec10e4825bc055492fa107c (patch)
tree1ef5470236076ecd703ec52dd1a33a00c1fc88f7 /payloads/libpayload/drivers/usb
parent394933640bc0515617117ed0cb8de7702bc9c1bf (diff)
downloadcoreboot-aa33609d289c4ee07ec10e4825bc055492fa107c.tar.gz
coreboot-aa33609d289c4ee07ec10e4825bc055492fa107c.tar.bz2
coreboot-aa33609d289c4ee07ec10e4825bc055492fa107c.zip
libpayload: usb: dwc2: support interrupt transfer
dwc2 host core do not have a periodic schedule list, so try to send an interrupt packet in poll_intr_queue() function and use frame number read from usb core register to calculate time and schedule transfers. BUG=None TEST=Tested on RK3288 with two USB keyboards(connect to SoC without USB hub), both work correctly. BRANCH=None Change-Id: I16f7977c45a84b37c32b7c495ca78ad76be9f0ce Signed-off-by: Patrick Georgi <pgeorgi@chromium.org> Original-Commit-Id: 3d0206b86634bcfdbe03da3e2c8adf186470e157 Original-Change-Id: Ie54699162ef799f4d3d2a0abf850dbeb62417777 Original-Signed-off-by: Yunzhi Li <lyz@rock-chips.com> Original-Reviewed-on: https://chromium-review.googlesource.com/280750 Original-Reviewed-by: Julius Werner <jwerner@chromium.org> Original-Commit-Queue: Lin Huang <hl@rock-chips.com> Original-Tested-by: Lin Huang <hl@rock-chips.com> Reviewed-on: http://review.coreboot.org/10774 Tested-by: build bot (Jenkins) Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
Diffstat (limited to 'payloads/libpayload/drivers/usb')
-rw-r--r--payloads/libpayload/drivers/usb/dwc2.c117
-rw-r--r--payloads/libpayload/drivers/usb/dwc2_private.h11
2 files changed, 119 insertions, 9 deletions
diff --git a/payloads/libpayload/drivers/usb/dwc2.c b/payloads/libpayload/drivers/usb/dwc2.c
index faf1f4628292..fa0fd34e3c09 100644
--- a/payloads/libpayload/drivers/usb/dwc2.c
+++ b/payloads/libpayload/drivers/usb/dwc2.c
@@ -37,7 +37,8 @@ static void dwc2_reinit(hci_t *controller)
grxfsiz_t grxfsiz = { .d32 = 0 };
ghwcfg3_t hwcfg3 = { .d32 = 0 };
hcintmsk_t hcintmsk = { .d32 = 0 };
- gnptxfsiz_t gnptxfsiz = { .d32 = 0 };
+ gtxfsiz_t gnptxfsiz = { .d32 = 0 };
+ gtxfsiz_t hptxfsiz = { .d32 = 0 };
const int timeout = 10000;
int i, fifo_blocks, tx_blocks;
@@ -97,15 +98,19 @@ static void dwc2_reinit(hci_t *controller)
* Reserve 2 spaces for the status entries of received packets
* and 2 spaces for bulk and control OUT endpoints. Calculate how
* many blocks can be alloted, assume largest packet size is 512.
+ * 16 locations reserved for periodic TX .
*/
- fifo_blocks = (hwcfg3.dfifodepth - 4) / (512 / 4);
+ fifo_blocks = (hwcfg3.dfifodepth - 4 - 16) / (512 / 4);
tx_blocks = fifo_blocks / 2;
grxfsiz.rxfdep = (fifo_blocks - tx_blocks) * (512 / 4) + 4;
writel(grxfsiz.d32, &reg->core.grxfsiz);
- gnptxfsiz.nptxfstaddr = grxfsiz.rxfdep;
- gnptxfsiz.nptxfdep = tx_blocks * (512 / 4);
+ gnptxfsiz.txfstaddr = grxfsiz.rxfdep;
+ gnptxfsiz.txfdep = tx_blocks * (512 / 4);
writel(gnptxfsiz.d32, &reg->core.gnptxfsiz);
+ hptxfsiz.txfstaddr = gnptxfsiz.txfstaddr + gnptxfsiz.txfdep;
+ hptxfsiz.txfdep = 16;
+ writel(hptxfsiz.d32, &reg->core.hptxfsiz);
/* Init host channels */
hcintmsk.xfercomp = 1;
@@ -159,9 +164,10 @@ 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)
return hctsiz.xfersize;
+ else if (hcint.nak || hcint.frmovrun)
+ return hctsiz.xfersize;
else if (hcint.xacterr)
return -HCSTAT_XFERERR;
else if (hcint.bblerr)
@@ -327,6 +333,101 @@ dwc2_control(usbdev_t *dev, direction_t dir, int drlen, void *setup,
return ret;
}
+static int
+dwc2_intr(endpoint_t *ep, int size, u8 *src)
+{
+ ep_dir_t data_dir;
+
+ if (ep->direction == IN)
+ data_dir = EPDIR_IN;
+ else if (ep->direction == OUT)
+ data_dir = EPDIR_OUT;
+ else
+ return -1;
+
+ return dwc2_transfer(ep, size, ep->toggle, data_dir, 0, src);
+}
+
+static u32 dwc2_intr_get_timestamp(intr_queue_t *q)
+{
+ hprt_t hprt;
+ hfnum_t hfnum;
+ hci_t *controller = q->endp->dev->controller;
+ dwc_ctrl_t *dwc2 = DWC2_INST(controller);
+ dwc2_reg_t *reg = DWC2_REG(controller);
+
+ hfnum.d32 = readl(&reg->host.hfnum);
+ hprt.d32 = readl(dwc2->hprt0);
+
+ /*
+ * hfnum.frnum increments when a new SOF is transmitted on
+ * the USB, and is reset to 0 when it reaches 16'h3FFF
+ */
+ switch (hprt.prtspd) {
+ case PRTSPD_HIGH:
+ /* 8 micro-frame per ms for high-speed */
+ return hfnum.frnum / 8;
+ case PRTSPD_FULL:
+ case PRTSPD_LOW:
+ default:
+ /* 1 micro-frame per ms for high-speed */
+ return hfnum.frnum / 1;
+ }
+}
+
+/* create and hook-up an intr queue into device schedule */
+static void *
+dwc2_create_intr_queue(endpoint_t *ep, const int reqsize,
+ const int reqcount, const int reqtiming)
+{
+ intr_queue_t *q = (intr_queue_t *)xzalloc(sizeof(intr_queue_t));
+
+ q->data = dma_memalign(4, reqsize);
+ q->endp = ep;
+ q->reqsize = reqsize;
+ q->reqtiming = reqtiming;
+
+ return q;
+}
+
+static void
+dwc2_destroy_intr_queue(endpoint_t *ep, void *_q)
+{
+ intr_queue_t *q = (intr_queue_t *)_q;
+
+ free(q->data);
+ free(q);
+}
+
+/*
+ * read one intr-packet from queue, if available. extend the queue for
+ * new input. Return NULL if nothing new available.
+ * Recommended use: while (data=poll_intr_queue(q)) process(data);
+ */
+static u8 *
+dwc2_poll_intr_queue(void *_q)
+{
+ intr_queue_t *q = (intr_queue_t *)_q;
+ int ret = 0;
+ u32 timestamp = dwc2_intr_get_timestamp(q);
+
+ /*
+ * If hfnum.frnum run overflow it will schedule
+ * an interrupt transfer immediately
+ */
+ if (timestamp - q->timestamp < q->reqtiming)
+ return NULL;
+
+ q->timestamp = timestamp;
+
+ ret = dwc2_intr(q->endp, q->reqsize, q->data);
+
+ if (ret > 0)
+ return q->data;
+ else
+ return NULL;
+}
+
hci_t *dwc2_init(void *bar)
{
hci_t *controller = new_controller();
@@ -349,9 +450,9 @@ hci_t *dwc2_init(void *bar)
controller->set_address = generic_set_address;
controller->finish_device_config = NULL;
controller->destroy_device = NULL;
- controller->create_intr_queue = NULL;
- controller->destroy_intr_queue = NULL;
- controller->poll_intr_queue = NULL;
+ controller->create_intr_queue = dwc2_create_intr_queue;
+ controller->destroy_intr_queue = dwc2_destroy_intr_queue;
+ controller->poll_intr_queue = dwc2_poll_intr_queue;
controller->reg_base = (uintptr_t)bar;
init_device_entry(controller, 0);
diff --git a/payloads/libpayload/drivers/usb/dwc2_private.h b/payloads/libpayload/drivers/usb/dwc2_private.h
index 7e6621dce814..c1090420ec5e 100644
--- a/payloads/libpayload/drivers/usb/dwc2_private.h
+++ b/payloads/libpayload/drivers/usb/dwc2_private.h
@@ -24,9 +24,18 @@
typedef struct dwc_ctrl {
#define DMA_SIZE (64 * 1024)
void *dma_buffer;
- uint32_t *hprt0;
+ u32 *hprt0;
+ u32 frame;
} dwc_ctrl_t;
+typedef struct {
+ u8 *data;
+ endpoint_t *endp;
+ int reqsize;
+ u32 reqtiming;
+ u32 timestamp;
+} intr_queue_t;
+
#define DWC2_INST(controller) ((dwc_ctrl_t *)((controller)->instance))
#define DWC2_REG(controller) ((dwc2_reg_t *)((controller)->reg_base))