|
@@ -21,12 +21,14 @@
|
|
* MA 02111-1307 USA
|
|
* MA 02111-1307 USA
|
|
*/
|
|
*/
|
|
#include <common.h>
|
|
#include <common.h>
|
|
|
|
+#include <errno.h>
|
|
#include <asm/byteorder.h>
|
|
#include <asm/byteorder.h>
|
|
#include <asm/unaligned.h>
|
|
#include <asm/unaligned.h>
|
|
#include <usb.h>
|
|
#include <usb.h>
|
|
#include <asm/io.h>
|
|
#include <asm/io.h>
|
|
#include <malloc.h>
|
|
#include <malloc.h>
|
|
#include <watchdog.h>
|
|
#include <watchdog.h>
|
|
|
|
+#include <linux/compiler.h>
|
|
|
|
|
|
#include "ehci.h"
|
|
#include "ehci.h"
|
|
|
|
|
|
@@ -39,7 +41,10 @@ static struct ehci_ctrl {
|
|
struct ehci_hcor *hcor;
|
|
struct ehci_hcor *hcor;
|
|
int rootdev;
|
|
int rootdev;
|
|
uint16_t portreset;
|
|
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];
|
|
} ehcic[CONFIG_USB_MAX_CONTROLLER_COUNT];
|
|
|
|
|
|
#define ALIGN_END_ADDR(type, ptr, size) \
|
|
#define ALIGN_END_ADDR(type, ptr, size) \
|
|
@@ -858,6 +863,8 @@ int usb_lowlevel_init(int index, void **controller)
|
|
uint32_t reg;
|
|
uint32_t reg;
|
|
uint32_t cmd;
|
|
uint32_t cmd;
|
|
struct QH *qh_list;
|
|
struct QH *qh_list;
|
|
|
|
+ struct QH *periodic;
|
|
|
|
+ int i;
|
|
|
|
|
|
if (ehci_hcd_init(index, &ehcic[index].hccr, &ehcic[index].hcor))
|
|
if (ehci_hcd_init(index, &ehcic[index].hccr, &ehcic[index].hcor))
|
|
return -1;
|
|
return -1;
|
|
@@ -887,6 +894,40 @@ int usb_lowlevel_init(int index, void **controller)
|
|
qh_list->qh_overlay.qt_token =
|
|
qh_list->qh_overlay.qt_token =
|
|
cpu_to_hc32(QT_TOKEN_STATUS(QT_TOKEN_STATUS_HALTED));
|
|
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);
|
|
reg = ehci_readl(&ehcic[index].hccr->cr_hcsparams);
|
|
descriptor.hub.bNbrPorts = HCS_N_PORTS(reg);
|
|
descriptor.hub.bNbrPorts = HCS_N_PORTS(reg);
|
|
debug("Register %x NbrPorts %d\n", reg, descriptor.hub.bNbrPorts);
|
|
debug("Register %x NbrPorts %d\n", reg, descriptor.hub.bNbrPorts);
|
|
@@ -956,10 +997,254 @@ submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
|
|
return ehci_submit_async(dev, pipe, buffer, length, setup);
|
|
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
|
|
int
|
|
submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
|
|
submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
|
|
int length, int interval)
|
|
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",
|
|
debug("dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d",
|
|
dev, pipe, buffer, length, interval);
|
|
dev, pipe, buffer, length, interval);
|
|
|
|
|
|
@@ -975,9 +1260,31 @@ submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
|
|
* not require more than a single qTD.
|
|
* not require more than a single qTD.
|
|
*/
|
|
*/
|
|
if (length > usb_maxpacket(dev, pipe)) {
|
|
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 -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;
|
|
}
|
|
}
|