summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/usb.c6
-rw-r--r--common/usb_storage.c4
-rw-r--r--drivers/usb/host/ehci-hcd.c91
-rw-r--r--drivers/usb/host/ehci.h1
-rw-r--r--drivers/usb/host/usb-uclass.c11
-rw-r--r--include/usb.h11
6 files changed, 110 insertions, 14 deletions
diff --git a/common/usb.c b/common/usb.c
index 349e838f1d..686f09a77d 100644
--- a/common/usb.c
+++ b/common/usb.c
@@ -172,6 +172,12 @@ int usb_detect_change(void)
return change;
}
+/* Lock or unlock async schedule on the controller */
+__weak int usb_lock_async(struct usb_device *dev, int lock)
+{
+ return 0;
+}
+
/*
* disables the asynch behaviour of the control message. This is used for data
* transfers that uses the exclusiv access to the control and bulk messages.
diff --git a/common/usb_storage.c b/common/usb_storage.c
index 097b6729c1..b291ac55d1 100644
--- a/common/usb_storage.c
+++ b/common/usb_storage.c
@@ -1157,6 +1157,7 @@ static unsigned long usb_stor_read(struct blk_desc *block_dev, lbaint_t blknr,
ss = (struct us_data *)udev->privptr;
usb_disable_asynch(1); /* asynch transfer not allowed */
+ usb_lock_async(udev, 1);
srb->lun = block_dev->lun;
buf_addr = (uintptr_t)buffer;
start = blknr;
@@ -1195,6 +1196,7 @@ retry_it:
debug("usb_read: end startblk " LBAF ", blccnt %x buffer %lx\n",
start, smallblks, buf_addr);
+ usb_lock_async(udev, 0);
usb_disable_asynch(0); /* asynch transfer allowed */
if (blkcnt >= ss->max_xfer_blk)
debug("\n");
@@ -1239,6 +1241,7 @@ static unsigned long usb_stor_write(struct blk_desc *block_dev, lbaint_t blknr,
ss = (struct us_data *)udev->privptr;
usb_disable_asynch(1); /* asynch transfer not allowed */
+ usb_lock_async(udev, 1);
srb->lun = block_dev->lun;
buf_addr = (uintptr_t)buffer;
@@ -1280,6 +1283,7 @@ retry_it:
debug("usb_write: end startblk " LBAF ", blccnt %x buffer %lx\n",
start, smallblks, buf_addr);
+ usb_lock_async(udev, 0);
usb_disable_asynch(0); /* asynch transfer allowed */
if (blkcnt >= ss->max_xfer_blk)
debug("\n");
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 1cc02052f5..1edb344d0f 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -298,6 +298,51 @@ static void ehci_update_endpt2_dev_n_port(struct usb_device *udev,
QH_ENDPT2_HUBADDR(hubaddr));
}
+static int ehci_enable_async(struct ehci_ctrl *ctrl)
+{
+ u32 cmd;
+ int ret;
+
+ /* Enable async. schedule. */
+ cmd = ehci_readl(&ctrl->hcor->or_usbcmd);
+ if (cmd & CMD_ASE)
+ return 0;
+
+ cmd |= CMD_ASE;
+ ehci_writel(&ctrl->hcor->or_usbcmd, cmd);
+
+ ret = handshake((uint32_t *)&ctrl->hcor->or_usbsts, STS_ASS, STS_ASS,
+ 100 * 1000);
+ if (ret < 0)
+ printf("EHCI fail timeout STS_ASS set\n");
+
+ return ret;
+}
+
+static int ehci_disable_async(struct ehci_ctrl *ctrl)
+{
+ u32 cmd;
+ int ret;
+
+ if (ctrl->async_locked)
+ return 0;
+
+ /* Disable async schedule. */
+ cmd = ehci_readl(&ctrl->hcor->or_usbcmd);
+ if (!(cmd & CMD_ASE))
+ return 0;
+
+ cmd &= ~CMD_ASE;
+ ehci_writel(&ctrl->hcor->or_usbcmd, cmd);
+
+ ret = handshake((uint32_t *)&ctrl->hcor->or_usbsts, STS_ASS, 0,
+ 100 * 1000);
+ if (ret < 0)
+ printf("EHCI fail timeout STS_ASS reset\n");
+
+ return ret;
+}
+
static int
ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
int length, struct devrequest *req)
@@ -311,7 +356,6 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
uint32_t *tdp;
uint32_t endpt, maxpacket, token, usbsts, qhtoken;
uint32_t c, toggle;
- uint32_t cmd;
int timeout;
int ret = 0;
struct ehci_ctrl *ctrl = ehci_get_ctrl(dev);
@@ -556,19 +600,9 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
usbsts = ehci_readl(&ctrl->hcor->or_usbsts);
ehci_writel(&ctrl->hcor->or_usbsts, (usbsts & 0x3f));
- /* Enable async. schedule. */
- cmd = ehci_readl(&ctrl->hcor->or_usbcmd);
- if (!(cmd & CMD_ASE)) {
- cmd |= CMD_ASE;
- ehci_writel(&ctrl->hcor->or_usbcmd, cmd);
-
- ret = handshake((uint32_t *)&ctrl->hcor->or_usbsts, STS_ASS, STS_ASS,
- 100 * 1000);
- if (ret < 0) {
- printf("EHCI fail timeout STS_ASS set\n");
- goto fail;
- }
- }
+ ret = ehci_enable_async(ctrl);
+ if (ret)
+ goto fail;
/* Wait for TDs to be processed. */
ts = get_timer(0);
@@ -611,6 +645,10 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
if (QT_TOKEN_GET_STATUS(token) & QT_TOKEN_STATUS_ACTIVE)
printf("EHCI timed out on TD - token=%#x\n", token);
+ ret = ehci_disable_async(ctrl);
+ if (ret)
+ goto fail;
+
if (!(QT_TOKEN_GET_STATUS(qhtoken) & QT_TOKEN_STATUS_ACTIVE)) {
debug("TOKEN=%#x\n", qhtoken);
switch (QT_TOKEN_GET_STATUS(qhtoken) &
@@ -1512,6 +1550,16 @@ static int _ehci_submit_int_msg(struct usb_device *dev, unsigned long pipe,
return result;
}
+static int _ehci_lock_async(struct ehci_ctrl *ctrl, int lock)
+{
+ ctrl->async_locked = lock;
+
+ if (lock)
+ return 0;
+
+ return ehci_disable_async(ctrl);
+}
+
#if !CONFIG_IS_ENABLED(DM_USB)
int submit_bulk_msg(struct usb_device *dev, unsigned long pipe,
void *buffer, int length)
@@ -1549,6 +1597,13 @@ int destroy_int_queue(struct usb_device *dev, struct int_queue *queue)
{
return _ehci_destroy_int_queue(dev, queue);
}
+
+int usb_lock_async(struct usb_device *dev, int lock)
+{
+ struct ehci_ctrl *ctrl = ehci_get_ctrl(dev);
+
+ return _ehci_lock_async(ctrl, lock);
+}
#endif
#if CONFIG_IS_ENABLED(DM_USB)
@@ -1612,6 +1667,13 @@ static int ehci_get_max_xfer_size(struct udevice *dev, size_t *size)
return 0;
}
+static int ehci_lock_async(struct udevice *dev, int lock)
+{
+ struct ehci_ctrl *ctrl = dev_get_priv(dev);
+
+ return _ehci_lock_async(ctrl, lock);
+}
+
int ehci_register(struct udevice *dev, struct ehci_hccr *hccr,
struct ehci_hcor *hcor, const struct ehci_ops *ops,
uint tweaks, enum usb_init_type init)
@@ -1678,6 +1740,7 @@ struct dm_usb_ops ehci_usb_ops = {
.poll_int_queue = ehci_poll_int_queue,
.destroy_int_queue = ehci_destroy_int_queue,
.get_max_xfer_size = ehci_get_max_xfer_size,
+ .lock_async = ehci_lock_async,
};
#endif
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 6c359af90c..66c1d61dbf 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -255,6 +255,7 @@ struct ehci_ctrl {
int periodic_schedules;
int ntds;
bool has_fsl_erratum_a005275; /* Freescale HS silicon quirk */
+ bool async_locked;
struct ehci_ops ops;
void *priv; /* client's private data */
};
diff --git a/drivers/usb/host/usb-uclass.c b/drivers/usb/host/usb-uclass.c
index 8521651588..5e423012df 100644
--- a/drivers/usb/host/usb-uclass.c
+++ b/drivers/usb/host/usb-uclass.c
@@ -22,6 +22,17 @@ struct usb_uclass_priv {
int companion_device_count;
};
+int usb_lock_async(struct usb_device *udev, int lock)
+{
+ struct udevice *bus = udev->controller_dev;
+ struct dm_usb_ops *ops = usb_get_ops(bus);
+
+ if (!ops->lock_async)
+ return -ENOSYS;
+
+ return ops->lock_async(bus, lock);
+}
+
int usb_disable_asynch(int disable)
{
int old_value = asynch_allowed;
diff --git a/include/usb.h b/include/usb.h
index efb67ea33f..22f6088fe6 100644
--- a/include/usb.h
+++ b/include/usb.h
@@ -269,6 +269,7 @@ int usb_bulk_msg(struct usb_device *dev, unsigned int pipe,
void *data, int len, int *actual_length, int timeout);
int usb_int_msg(struct usb_device *dev, unsigned long pipe,
void *buffer, int transfer_len, int interval, bool nonblock);
+int usb_lock_async(struct usb_device *dev, int lock);
int usb_disable_asynch(int disable);
int usb_maxpacket(struct usb_device *dev, unsigned long pipe);
int usb_get_configuration_no(struct usb_device *dev, int cfgno,
@@ -791,6 +792,16 @@ struct dm_usb_ops {
* in a USB transfer. USB class driver needs to be aware of this.
*/
int (*get_max_xfer_size)(struct udevice *bus, size_t *size);
+
+ /**
+ * lock_async() - Keep async schedule after a transfer
+ *
+ * It may be desired to keep the asynchronous schedule running even
+ * after a transfer finishes, usually when doing multiple transfers
+ * back-to-back. This callback allows signalling the USB controller
+ * driver to do just that.
+ */
+ int (*lock_async)(struct udevice *udev, int lock);
};
#define usb_get_ops(dev) ((struct dm_usb_ops *)(dev)->driver->ops)