summaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2015-10-07 08:23:51 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2015-10-07 08:23:51 +0200
commit54bf38665024f797518726d0ecf612c3cf50e4d9 (patch)
treed9ddecce8162b28f61eab6242c54bbc74a4476fd /drivers/usb
parentdd4da6d328ebb6e871a5584b3864d8c5cc64af86 (diff)
parentd0a5ea479e6d3d89ea3ce0586de7eb51c9c39131 (diff)
downloadbarebox-54bf38665024f797518726d0ecf612c3cf50e4d9.tar.gz
barebox-54bf38665024f797518726d0ecf612c3cf50e4d9.tar.xz
Merge branch 'for-next/usb'
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/core/usb.c10
-rw-r--r--drivers/usb/gadget/fsl_udc.c13
-rw-r--r--drivers/usb/host/ehci-hcd.c474
-rw-r--r--drivers/usb/host/ehci.h15
-rw-r--r--drivers/usb/imx/chipidea-imx.c7
5 files changed, 500 insertions, 19 deletions
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index 9073fff4be..a3fb1e8808 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -191,7 +191,7 @@ static int usb_parse_config(struct usb_device *dev, unsigned char *buffer, int c
return -1;
}
memcpy(&dev->config, buffer, buffer[0]);
- le16_to_cpus(&(dev->config.wTotalLength));
+ le16_to_cpus(&(dev->config.desc.wTotalLength));
dev->config.no_of_if = 0;
index = dev->config.desc.bLength;
@@ -445,9 +445,9 @@ int usb_new_device(struct usb_device *dev)
dev_add_param_fixed(&dev->dev, "Product", dev->prod);
dev_add_param_fixed(&dev->dev, "SerialNumber", dev->serial);
dev_add_param_int_ro(&dev->dev, "idVendor",
- le16_to_cpu(dev->descriptor->idVendor), "%04x");
+ dev->descriptor->idVendor, "%04x");
dev_add_param_int_ro(&dev->dev, "idProduct",
- le16_to_cpu(dev->descriptor->idProduct), "%04x");
+ dev->descriptor->idProduct, "%04x");
list_add_tail(&dev->list, &usb_device_list);
dev_count++;
@@ -938,11 +938,11 @@ int usb_driver_register(struct usb_driver *drv)
static int usb_match_device(struct usb_device *dev, const struct usb_device_id *id)
{
if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
- id->idVendor != le16_to_cpu(dev->descriptor->idVendor))
+ id->idVendor != dev->descriptor->idVendor)
return 0;
if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
- id->idProduct != le16_to_cpu(dev->descriptor->idProduct))
+ id->idProduct != dev->descriptor->idProduct)
return 0;
return 1;
diff --git a/drivers/usb/gadget/fsl_udc.c b/drivers/usb/gadget/fsl_udc.c
index 19f3da65bb..9b59669773 100644
--- a/drivers/usb/gadget/fsl_udc.c
+++ b/drivers/usb/gadget/fsl_udc.c
@@ -2293,6 +2293,13 @@ err_out:
return ret;
}
+void ci_udc_unregister(void)
+{
+ if (udc_controller)
+ usb_del_gadget_udc(&udc_controller->gadget);
+
+}
+
static int fsl_udc_probe(struct device_d *dev)
{
void __iomem *regs = dev_request_mem_region(dev, 0);
@@ -2303,8 +2310,14 @@ static int fsl_udc_probe(struct device_d *dev)
return ci_udc_register(dev, regs);
}
+static void fsl_udc_remove(struct device_d *dev)
+{
+ ci_udc_unregister();
+}
+
static struct driver_d fsl_udc_driver = {
.name = "fsl-udc",
.probe = fsl_udc_probe,
+ .remove = fsl_udc_remove,
};
device_platform_driver(fsl_udc_driver);
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 25e7a3e520..b43b6c7f51 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -48,6 +48,21 @@ struct ehci_priv {
int (*init)(void *drvdata);
int (*post_init)(void *drvdata);
void *drvdata;
+ int periodic_schedules;
+ struct QH *periodic_queue;
+ uint32_t *periodic_list;
+ int sem;
+ struct device_d *usedby;
+};
+
+struct int_queue {
+ int elementsize;
+ int queuesize;
+ unsigned long pipe;
+ struct QH *first;
+ struct QH *current;
+ struct QH *last;
+ struct qTD *tds;
};
#define to_ehci(ptr) container_of(ptr, struct ehci_priv, host)
@@ -123,6 +138,20 @@ static struct descriptor {
#define ehci_is_TDI() (ehci->flags & EHCI_HAS_TT)
+static void inline ehci_reentrance_detect(struct ehci_priv *ehci,
+ struct usb_device *dev,
+ const char *fname)
+{
+ if (ehci->sem)
+ dev_err(&dev->dev, "%s: re-entrance %d (%s:%s)\n",
+ fname,
+ ehci->sem,
+ ehci->usedby->driver->name,
+ ehci->usedby->name);
+ ehci->sem++;
+ ehci->usedby = &dev->dev;
+}
+
static int handshake(uint32_t *ptr, uint32_t mask, uint32_t done, int usec)
{
uint32_t result;
@@ -137,7 +166,7 @@ static int handshake(uint32_t *ptr, uint32_t mask, uint32_t done, int usec)
result &= mask;
if (result == done)
return 0;
- if (is_timeout(start, usec * USECOND))
+ if (is_timeout_non_interruptible(start, usec * USECOND))
return -ETIMEDOUT;
}
}
@@ -356,7 +385,7 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
vtd = td;
do {
token = hc32_to_cpu(vtd->qt_token);
- if (is_timeout(start, timeout_val)) {
+ if (is_timeout_non_interruptible(start, timeout_val)) {
/* Disable async schedule. */
cmd = ehci_readl(&ehci->hcor->or_usbcmd);
cmd &= ~CMD_ASE;
@@ -659,7 +688,7 @@ ehci_submit_root(struct usb_device *dev, unsigned long pipe, void *buffer,
* root
*/
ehci_powerup_fixup(ehci);
- mdelay(50);
+ mdelay_non_interruptible(50);
ehci->portreset |= 1 << port;
/* terminate the reset */
ehci_writel(status_reg, reg & ~EHCI_PS_PR);
@@ -722,7 +751,7 @@ ehci_submit_root(struct usb_device *dev, unsigned long pipe, void *buffer,
goto unknown;
}
- mdelay(1);
+ mdelay_non_interruptible(1);
len = min3(srclen, (int)le16_to_cpu(req->length), length);
if (srcptr != NULL && len > 0)
memcpy(buffer, srcptr, len);
@@ -768,6 +797,8 @@ static int ehci_init(struct usb_host *host)
uint32_t reg;
uint32_t cmd;
int ret = 0;
+ struct QH *periodic;
+ int i;
ehci_halt(ehci);
@@ -793,6 +824,44 @@ static int ehci_init(struct usb_host *host)
/* Set async. queue head pointer. */
ehci_writel(&ehci->hcor->or_asynclistaddr, (uint32_t)ehci->qh_list);
+ /*
+ * Set up periodic list
+ * Step 1: Parent QH for all periodic transfers.
+ */
+ ehci->periodic_schedules = 0;
+ periodic = ehci->periodic_queue;
+ memset(periodic, 0, sizeof(*periodic));
+ periodic->qh_link = cpu_to_hc32(QH_LINK_TERMINATE);
+ periodic->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
+ periodic->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.
+ */
+ if (ehci->periodic_list == NULL)
+ /*
+ * FIXME: this memory chunk have to be 4k aligned AND
+ * reside in coherent memory. Current implementation of
+ * dma_alloc_coherent() allocates PAGE_SIZE aligned memory chunks.
+ * PAGE_SIZE less then 4k will break this code.
+ */
+ ehci->periodic_list = dma_alloc_coherent(1024 * 4,
+ DMA_ADDRESS_BROKEN);
+ for (i = 0; i < 1024; i++) {
+ ehci->periodic_list[i] = cpu_to_hc32((unsigned long)periodic
+ | QH_LINK_TYPE_QH);
+ }
+
+ /* Set periodic list base address */
+ ehci_writel(&ehci->hcor->or_periodiclistbase,
+ (unsigned long)ehci->periodic_list);
+
reg = ehci_readl(&ehci->hccr->cr_hcsparams);
descriptor.hub.bNbrPorts = HCS_N_PORTS(reg);
@@ -819,7 +888,7 @@ static int ehci_init(struct usb_host *host)
ehci_writel(&ehci->hcor->or_configflag, cmd);
/* unblock posted write */
cmd = ehci_readl(&ehci->hcor->or_usbcmd);
- mdelay(5);
+ mdelay_non_interruptible(5);
ehci->rootdev = 0;
@@ -835,12 +904,18 @@ submit_bulk_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
{
struct usb_host *host = dev->host;
struct ehci_priv *ehci = to_ehci(host);
+ int ret;
+
+ ehci_reentrance_detect(ehci, dev, __func__);
if (usb_pipetype(pipe) != PIPE_BULK) {
dev_dbg(ehci->dev, "non-bulk pipe (type=%lu)", usb_pipetype(pipe));
+ ehci->sem--;
return -1;
}
- return ehci_submit_async(dev, pipe, buffer, length, NULL);
+ ret = ehci_submit_async(dev, pipe, buffer, length, NULL);
+ ehci->sem--;
+ return ret;
}
static int
@@ -849,6 +924,9 @@ submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
{
struct usb_host *host = dev->host;
struct ehci_priv *ehci = to_ehci(host);
+ int ret;
+
+ ehci_reentrance_detect(ehci, dev, __func__);
if (usb_pipetype(pipe) != PIPE_CONTROL) {
dev_dbg(ehci->dev, "non-control pipe (type=%lu)", usb_pipetype(pipe));
@@ -858,9 +936,322 @@ submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
if (usb_pipedevice(pipe) == ehci->rootdev) {
if (ehci->rootdev == 0)
dev->speed = USB_SPEED_HIGH;
- return ehci_submit_root(dev, pipe, buffer, length, setup);
+ ret = ehci_submit_root(dev, pipe, buffer, length, setup);
+ ehci->sem--;
+ return ret;
}
- return ehci_submit_async(dev, pipe, buffer, length, setup);
+ ret = ehci_submit_async(dev, pipe, buffer, length, setup);
+ ehci->sem--;
+ return ret;
+}
+
+static int
+disable_periodic(struct ehci_priv *ehci)
+{
+ uint32_t cmd;
+ struct ehci_hcor *hcor = ehci->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;
+}
+
+#define NEXT_QH(qh) (struct QH *)((unsigned long)hc32_to_cpu((qh)->qh_link) & ~0x1f)
+
+static int
+enable_periodic(struct ehci_priv *ehci)
+{
+ uint32_t cmd;
+ struct ehci_hcor *hcor = ehci->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;
+ }
+ mdelay_non_interruptible(1);
+ return 0;
+}
+
+static inline u8 ehci_encode_speed(enum usb_device_speed speed)
+{
+ #define QH_HIGH_SPEED 2
+ #define QH_FULL_SPEED 0
+ #define QH_LOW_SPEED 1
+ if (speed == USB_SPEED_HIGH)
+ return QH_HIGH_SPEED;
+ if (speed == USB_SPEED_LOW)
+ return QH_LOW_SPEED;
+ return QH_FULL_SPEED;
+}
+
+static void ehci_update_endpt2_dev_n_port(struct usb_device *udev,
+ struct QH *qh)
+{
+ struct usb_device *ttdev;
+ int parent_devnum;
+
+ if (udev->speed != USB_SPEED_LOW && udev->speed != USB_SPEED_FULL)
+ return;
+
+ /*
+ * For full / low speed devices we need to get the devnum and portnr of
+ * the tt, so of the first upstream usb-2 hub, there may be usb-1 hubs
+ * in the tree before that one!
+ */
+
+ ttdev = udev;
+ while (ttdev->parent && ttdev->parent->speed != USB_SPEED_HIGH)
+ ttdev = ttdev->parent;
+ if (!ttdev->parent)
+ return;
+ parent_devnum = ttdev->parent->devnum;
+
+ qh->qh_endpt2 |= cpu_to_hc32(QH_ENDPT2_PORTNUM(ttdev->portnr) |
+ QH_ENDPT2_HUBADDR(parent_devnum));
+}
+
+static struct int_queue *ehci_create_int_queue(struct usb_device *dev,
+ unsigned long pipe, int queuesize, int elementsize,
+ void *buffer, int interval)
+{
+ struct usb_host *host = dev->host;
+ struct ehci_priv *ehci = to_ehci(host);
+ struct int_queue *result = NULL;
+ uint32_t i, toggle;
+ struct QH *list = ehci->periodic_queue;
+
+ /*
+ * Interrupt transfers requiring several transactions are not supported
+ * because bInterval is ignored.
+ *
+ * Also, ehci_submit_async() relies on wMaxPacketSize being a power of 2
+ * <= PKT_ALIGN if several qTDs are required, while the USB
+ * specification does not constrain this for interrupt transfers. That
+ * means that ehci_submit_async() would support interrupt transfers
+ * requiring several transactions only as long as the transfer size does
+ * not require more than a single qTD.
+ */
+ if (elementsize > usb_maxpacket(dev, pipe)) {
+ dev_err(&dev->dev,
+ "%s: xfers requiring several transactions are not supported.\n",
+ __func__);
+ return NULL;
+ }
+
+ debug("Enter create_int_queue\n");
+ if (usb_pipetype(pipe) != PIPE_INTERRUPT) {
+ dev_dbg(&dev->dev,
+ "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) {
+ dev_dbg(&dev->dev,
+ "too large elements for interrupt transfers\n");
+ return NULL;
+ }
+
+ result = xzalloc(sizeof(*result));
+ result->elementsize = elementsize;
+ result->queuesize = queuesize;
+ result->pipe = pipe;
+ result->first = dma_alloc_coherent(sizeof(struct QH) * queuesize,
+ DMA_ADDRESS_BROKEN);
+ result->current = result->first;
+ result->last = result->first + queuesize - 1;
+ result->tds = dma_alloc_coherent(sizeof(struct qTD) * queuesize,
+ DMA_ADDRESS_BROKEN);
+ memset(result->first, 0, sizeof(struct QH) * queuesize);
+ memset(result->tds, 0, sizeof(struct qTD) * queuesize);
+
+ toggle = usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe));
+
+ 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 = cpu_to_hc32((unsigned long)(qh+1) | QH_LINK_TYPE_QH);
+ if (i == queuesize - 1)
+ qh->qh_link = cpu_to_hc32(QH_LINK_TERMINATE);
+
+ qh->qt_next = cpu_to_hc32((unsigned long)td);
+ qh->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
+ qh->qh_endpt1 =
+ cpu_to_hc32((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 = cpu_to_hc32((1 << 30) | /* 1 Tx per mframe */
+ (1 << 0)); /* S-mask: microframe 0 */
+ if (dev->speed == USB_SPEED_LOW ||
+ dev->speed == USB_SPEED_FULL) {
+ /* C-mask: microframes 2-4 */
+ qh->qh_endpt2 |= cpu_to_hc32((0x1c << 8));
+ }
+ ehci_update_endpt2_dev_n_port(dev, qh);
+
+ td->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
+ td->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
+ dev_dbg(&dev->dev,
+ "communication direction is '%s'\n",
+ usb_pipein(pipe) ? "in" : "out");
+ td->qt_token = cpu_to_hc32(
+ QT_TOKEN_DT(toggle) |
+ (elementsize << 16) |
+ ((usb_pipein(pipe) ? 1 : 0) << 8) | /* IN/OUT token */
+ 0x80); /* active */
+ td->qt_buffer[0] =
+ cpu_to_hc32((unsigned long)buffer + i * elementsize);
+ td->qt_buffer[1] =
+ cpu_to_hc32((td->qt_buffer[0] + 0x1000) & ~0xfff);
+ td->qt_buffer[2] =
+ cpu_to_hc32((td->qt_buffer[0] + 0x2000) & ~0xfff);
+ td->qt_buffer[3] =
+ cpu_to_hc32((td->qt_buffer[0] + 0x3000) & ~0xfff);
+ td->qt_buffer[4] =
+ cpu_to_hc32((td->qt_buffer[0] + 0x4000) & ~0xfff);
+
+ *buf = buffer + i * elementsize;
+ toggle ^= 1;
+ }
+
+ if (ehci->periodic_schedules > 0) {
+ if (disable_periodic(ehci) < 0) {
+ dev_err(&dev->dev,
+ "FATAL: periodic should never fail, but did");
+ goto fail3;
+ }
+ }
+
+ /* hook up to periodic list */
+ result->last->qh_link = list->qh_link;
+ list->qh_link = cpu_to_hc32((unsigned long)result->first | QH_LINK_TYPE_QH);
+
+ if (enable_periodic(ehci) < 0) {
+ dev_err(&dev->dev,
+ "FATAL: periodic should never fail, but did");
+ goto fail3;
+ }
+ ehci->periodic_schedules++;
+
+ dev_dbg(&dev->dev, "Exit create_int_queue\n");
+ return result;
+fail3:
+ dma_free_coherent(result->tds, 0, sizeof(struct qTD) * queuesize);
+ dma_free_coherent(result->first, 0, sizeof(struct QH) * queuesize);
+ free(result);
+ return NULL;
+}
+
+static void *ehci_poll_int_queue(struct usb_device *dev,
+ struct int_queue *queue)
+{
+ struct QH *cur = queue->current;
+ struct qTD *cur_td;
+ uint32_t token, toggle;
+ unsigned long pipe = queue->pipe;
+
+ /* depleted queue */
+ if (cur == NULL) {
+ dev_dbg(&dev->dev, "Exit poll_int_queue with completed queue\n");
+ return NULL;
+ }
+ /* still active */
+ cur_td = &queue->tds[queue->current - queue->first];
+ token = hc32_to_cpu(cur_td->qt_token);
+ if (QT_TOKEN_GET_STATUS(token) & QT_TOKEN_STATUS_ACTIVE) {
+ dev_dbg(&dev->dev,
+ "Exit poll_int_queue with no completed intr transfer. token is %x\n",
+ token);
+ return NULL;
+ }
+
+ toggle = QT_TOKEN_GET_DT(token);
+ usb_settoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe), toggle);
+
+ if (!(cur->qh_link & QH_LINK_TERMINATE))
+ queue->current++;
+ else
+ queue->current = NULL;
+
+ dev_dbg(&dev->dev,
+ "Exit poll_int_queue with completed intr transfer. token is %x at %p (first at %p)\n",
+ token, cur, queue->first);
+ return cur->buffer;
+}
+
+static int ehci_destroy_int_queue(struct usb_device *dev,
+ struct int_queue *queue)
+{
+ int result = -EINVAL;
+ struct usb_host *host = dev->host;
+ struct ehci_priv *ehci = to_ehci(host);
+ struct QH *cur = ehci->periodic_queue;
+ uint64_t start;
+
+ if (disable_periodic(ehci) < 0) {
+ dev_err(&dev->dev,
+ "FATAL: periodic should never fail, but did\n");
+ goto out;
+ }
+ ehci->periodic_schedules--;
+
+ start = get_time_ns();
+ while (!(cur->qh_link & cpu_to_hc32(QH_LINK_TERMINATE))) {
+ dev_dbg(&dev->dev,
+ "considering %p, with qh_link %x\n",
+ cur, cur->qh_link);
+ if (NEXT_QH(cur) == queue->first) {
+ dev_dbg(&dev->dev,
+ "found candidate. removing from chain\n");
+ cur->qh_link = queue->last->qh_link;
+ result = 0;
+ break;
+ }
+ cur = NEXT_QH(cur);
+ if (is_timeout_non_interruptible(start, 500 * MSECOND)) {
+ dev_err(&dev->dev,
+ "Timeout destroying interrupt endpoint queue\n");
+ result = -ETIMEDOUT;
+ goto out;
+ }
+ }
+
+ if (ehci->periodic_schedules > 0) {
+ result = enable_periodic(ehci);
+ if (result < 0)
+ dev_err(&dev->dev,
+ "FATAL: periodic should never fail, but did");
+ }
+
+out:
+ dma_free_coherent(queue->tds, 0, sizeof(struct qTD) * queue->queuesize);
+ dma_free_coherent(queue->first, 0, sizeof(struct QH) * queue->queuesize);
+ free(queue);
+ return result;
}
static int
@@ -869,10 +1260,51 @@ submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
{
struct usb_host *host = dev->host;
struct ehci_priv *ehci = to_ehci(host);
+ struct int_queue *queue;
+ uint64_t start;
+ void *backbuffer;
+ int result = 0, ret;
+
+ ehci_reentrance_detect(ehci, dev, __func__);
dev_dbg(ehci->dev, "dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d",
dev, pipe, buffer, length, interval);
- return -1;
+
+ dma_sync_single_for_device((unsigned long)buffer, length,
+ DMA_BIDIRECTIONAL);
+
+ queue = ehci_create_int_queue(dev, pipe, 1, length, buffer, interval);
+ if (!queue) {
+ ehci->sem--;
+ return -EINVAL;
+ }
+
+ start = get_time_ns();
+ while ((backbuffer = ehci_poll_int_queue(dev, queue)) == NULL)
+ if (is_timeout_non_interruptible(start,
+ USB_CNTL_TIMEOUT * MSECOND)) {
+ dev_err(&dev->dev,
+ "Timeout poll on interrupt endpoint\n");
+ result = -ETIMEDOUT;
+ break;
+ }
+
+ if (backbuffer != buffer) {
+ dev_err(&dev->dev,
+ "got wrong buffer back (%p instead of %p)\n",
+ backbuffer, buffer);
+ if (!result)
+ result = -EINVAL;
+ }
+
+ dma_sync_single_for_cpu((unsigned long)buffer, length,
+ DMA_BIDIRECTIONAL);
+
+ ret = ehci_destroy_int_queue(dev, queue);
+ if (!result)
+ result = ret;
+ ehci->sem--;
+ return result;
}
static int ehci_detect(struct device_d *dev)
@@ -907,6 +1339,8 @@ int ehci_register(struct device_d *dev, struct ehci_data *data)
ehci->qh_list = dma_alloc_coherent(sizeof(struct QH) * NUM_TD,
DMA_ADDRESS_BROKEN);
+ ehci->periodic_queue = dma_alloc_coherent(sizeof(struct QH),
+ DMA_ADDRESS_BROKEN);
ehci->td = dma_alloc_coherent(sizeof(struct qTD) * NUM_TD,
DMA_ADDRESS_BROKEN);
@@ -934,13 +1368,18 @@ static int ehci_probe(struct device_d *dev)
{
struct ehci_data data = {};
struct ehci_platform_data *pdata = dev->platform_data;
+ struct device_node *dn = dev->device_node;
- /* default to EHCI_HAS_TT to not change behaviour of boards
- * without platform_data
- */
if (pdata)
data.flags = pdata->flags;
- else
+ else if (dn) {
+ data.flags = 0;
+ if (of_property_read_bool(dn, "has-transaction-translator"))
+ data.flags |= EHCI_HAS_TT;
+ } else
+ /* default to EHCI_HAS_TT to not change behaviour of boards
+ * without platform_data
+ */
data.flags = EHCI_HAS_TT;
data.hccr = dev_request_mem_region(dev, 0);
@@ -961,9 +1400,18 @@ static void ehci_remove(struct device_d *dev)
ehci_halt(ehci);
}
+static __maybe_unused struct of_device_id ehci_platform_dt_ids[] = {
+ {
+ .compatible = "generic-ehci",
+ }, {
+ /* sentinel */
+ }
+};
+
static struct driver_d ehci_driver = {
.name = "ehci",
.probe = ehci_probe,
.remove = ehci_remove,
+ .of_compatible = DRV_OF_COMPAT(ehci_platform_dt_ids),
};
device_platform_driver(ehci_driver);
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index d71d0565e8..39de763749 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -20,6 +20,15 @@
#include <io.h>
+#define QH_ENDPT1_EPS(x) (((x) & 0x3) << 12) /* Endpoint Speed */
+#define QH_ENDPT2_PORTNUM(x) (((x) & 0x7f) << 23) /* Port Number */
+#define QH_ENDPT2_HUBADDR(x) (((x) & 0x7f) << 16) /* Hub Address */
+
+#define QT_TOKEN_DT(x) (((x) & 0x1) << 31) /* Data Toggle */
+#define QT_TOKEN_GET_STATUS(x) (((x) >> 0) & 0xff)
+#define QT_TOKEN_STATUS_ACTIVE 0x80
+#define QT_TOKEN_GET_DT(x) (((x) >> 31) & 0x1)
+
#if !defined(CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS)
#define CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS 16
#endif
@@ -51,6 +60,7 @@ struct ehci_hcor {
#define CMD_RUN (1 << 0) /* start/stop HC */
uint32_t or_usbsts;
#define STD_ASS (1 << 15)
+#define STS_PSS (1 << 14)
#define STS_HALT (1 << 12)
uint32_t or_usbintr;
uint32_t or_frindex;
@@ -148,7 +158,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;
+ };
};
#endif /* USB_EHCI_H */
diff --git a/drivers/usb/imx/chipidea-imx.c b/drivers/usb/imx/chipidea-imx.c
index 28a0fff328..ae307183bc 100644
--- a/drivers/usb/imx/chipidea-imx.c
+++ b/drivers/usb/imx/chipidea-imx.c
@@ -276,6 +276,12 @@ static int imx_chipidea_probe(struct device_d *dev)
return ret;
};
+static void imx_chipidea_remove(struct device_d *dev)
+{
+ if (IS_ENABLED(CONFIG_USB_GADGET_DRIVER_ARC))
+ ci_udc_unregister();
+}
+
static __maybe_unused struct of_device_id imx_chipidea_dt_ids[] = {
{
.compatible = "fsl,imx27-usb",
@@ -288,5 +294,6 @@ static struct driver_d imx_chipidea_driver = {
.name = "imx-usb",
.probe = imx_chipidea_probe,
.of_compatible = DRV_OF_COMPAT(imx_chipidea_dt_ids),
+ .remove = imx_chipidea_remove,
};
device_platform_driver(imx_chipidea_driver);