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 */
