aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/chipidea
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/chipidea')
-rw-r--r--drivers/usb/chipidea/ci.h2
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.c3
-rw-r--r--drivers/usb/chipidea/ci_hdrc_msm.c2
-rw-r--r--drivers/usb/chipidea/ci_hdrc_npcm.c2
-rw-r--r--drivers/usb/chipidea/ci_hdrc_tegra.c2
-rw-r--r--drivers/usb/chipidea/ci_hdrc_usb2.c2
-rw-r--r--drivers/usb/chipidea/core.c6
-rw-r--r--drivers/usb/chipidea/udc.c178
-rw-r--r--drivers/usb/chipidea/udc.h2
-rw-r--r--drivers/usb/chipidea/usbmisc_imx.c4
10 files changed, 190 insertions, 13 deletions
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index 2a38e1eb6546..97437de52ef6 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -25,6 +25,7 @@
#define TD_PAGE_COUNT 5
#define CI_HDRC_PAGE_SIZE 4096ul /* page size for TD's */
#define ENDPT_MAX 32
+#define CI_MAX_REQ_SIZE (4 * CI_HDRC_PAGE_SIZE)
#define CI_MAX_BUF_SIZE (TD_PAGE_COUNT * CI_HDRC_PAGE_SIZE)
/******************************************************************************
@@ -260,6 +261,7 @@ struct ci_hdrc {
bool b_sess_valid_event;
bool imx28_write_fix;
bool has_portsc_pec_bug;
+ bool has_short_pkt_limit;
bool supports_runtime_pm;
bool in_lpm;
bool wakeup_int;
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c
index c64ab0e07ea0..f2801700be8e 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.c
+++ b/drivers/usb/chipidea/ci_hdrc_imx.c
@@ -342,6 +342,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
struct ci_hdrc_platform_data pdata = {
.name = dev_name(&pdev->dev),
.capoffset = DEF_CAPOFFSET,
+ .flags = CI_HDRC_HAS_SHORT_PKT_LIMIT,
.notify_event = ci_hdrc_imx_notify_event,
};
int ret;
@@ -675,7 +676,7 @@ static const struct dev_pm_ops ci_hdrc_imx_pm_ops = {
};
static struct platform_driver ci_hdrc_imx_driver = {
.probe = ci_hdrc_imx_probe,
- .remove_new = ci_hdrc_imx_remove,
+ .remove = ci_hdrc_imx_remove,
.shutdown = ci_hdrc_imx_shutdown,
.driver = {
.name = "imx_usb",
diff --git a/drivers/usb/chipidea/ci_hdrc_msm.c b/drivers/usb/chipidea/ci_hdrc_msm.c
index 1661639cd2eb..3ab3daa78e34 100644
--- a/drivers/usb/chipidea/ci_hdrc_msm.c
+++ b/drivers/usb/chipidea/ci_hdrc_msm.c
@@ -292,7 +292,7 @@ MODULE_DEVICE_TABLE(of, msm_ci_dt_match);
static struct platform_driver ci_hdrc_msm_driver = {
.probe = ci_hdrc_msm_probe,
- .remove_new = ci_hdrc_msm_remove,
+ .remove = ci_hdrc_msm_remove,
.driver = {
.name = "msm_hsusb",
.of_match_table = msm_ci_dt_match,
diff --git a/drivers/usb/chipidea/ci_hdrc_npcm.c b/drivers/usb/chipidea/ci_hdrc_npcm.c
index 3e5e05dbda89..e52a2b05cbe2 100644
--- a/drivers/usb/chipidea/ci_hdrc_npcm.c
+++ b/drivers/usb/chipidea/ci_hdrc_npcm.c
@@ -98,7 +98,7 @@ MODULE_DEVICE_TABLE(of, npcm_udc_dt_match);
static struct platform_driver npcm_udc_driver = {
.probe = npcm_udc_probe,
- .remove_new = npcm_udc_remove,
+ .remove = npcm_udc_remove,
.driver = {
.name = "npcm_udc",
.of_match_table = npcm_udc_dt_match,
diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c
index 9538d425f0a0..372788f0f970 100644
--- a/drivers/usb/chipidea/ci_hdrc_tegra.c
+++ b/drivers/usb/chipidea/ci_hdrc_tegra.c
@@ -406,7 +406,7 @@ static struct platform_driver tegra_usb_driver = {
.pm = pm_ptr(&tegra_usb_pm),
},
.probe = tegra_usb_probe,
- .remove_new = tegra_usb_remove,
+ .remove = tegra_usb_remove,
};
module_platform_driver(tegra_usb_driver);
diff --git a/drivers/usb/chipidea/ci_hdrc_usb2.c b/drivers/usb/chipidea/ci_hdrc_usb2.c
index 97379f653b06..8ffa1e95d8e8 100644
--- a/drivers/usb/chipidea/ci_hdrc_usb2.c
+++ b/drivers/usb/chipidea/ci_hdrc_usb2.c
@@ -116,7 +116,7 @@ static void ci_hdrc_usb2_remove(struct platform_device *pdev)
static struct platform_driver ci_hdrc_usb2_driver = {
.probe = ci_hdrc_usb2_probe,
- .remove_new = ci_hdrc_usb2_remove,
+ .remove = ci_hdrc_usb2_remove,
.driver = {
.name = "chipidea-usb2",
.of_match_table = ci_hdrc_usb2_of_match,
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 835bf2428dc6..694b4a8e4e1d 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -765,7 +765,7 @@ static int ci_get_platdata(struct device *dev,
ext_id = ERR_PTR(-ENODEV);
ext_vbus = ERR_PTR(-ENODEV);
- if (of_property_read_bool(dev->of_node, "extcon")) {
+ if (of_property_present(dev->of_node, "extcon")) {
/* Each one of them is not mandatory */
ext_vbus = extcon_get_edev_by_phandle(dev, 0);
if (IS_ERR(ext_vbus) && PTR_ERR(ext_vbus) != -ENODEV)
@@ -1076,6 +1076,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)
CI_HDRC_SUPPORTS_RUNTIME_PM);
ci->has_portsc_pec_bug = !!(ci->platdata->flags &
CI_HDRC_HAS_PORTSC_PEC_MISSED);
+ ci->has_short_pkt_limit = !!(ci->platdata->flags &
+ CI_HDRC_HAS_SHORT_PKT_LIMIT);
platform_set_drvdata(pdev, ci);
ret = hw_device_init(ci, base);
@@ -1495,7 +1497,7 @@ static const struct dev_pm_ops ci_pm_ops = {
static struct platform_driver ci_hdrc_driver = {
.probe = ci_hdrc_probe,
- .remove_new = ci_hdrc_remove,
+ .remove = ci_hdrc_remove,
.driver = {
.name = "ci_hdrc",
.pm = &ci_pm_ops,
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index 69ef3cd8d4f8..8a9b31fd5c89 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -10,6 +10,7 @@
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dmapool.h>
+#include <linux/dma-direct.h>
#include <linux/err.h>
#include <linux/irqreturn.h>
#include <linux/kernel.h>
@@ -540,6 +541,126 @@ static int prepare_td_for_sg(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
return ret;
}
+/*
+ * Verify if the scatterlist is valid by iterating each sg entry.
+ * Return invalid sg entry index which is less than num_sgs.
+ */
+static int sglist_get_invalid_entry(struct device *dma_dev, u8 dir,
+ struct usb_request *req)
+{
+ int i;
+ struct scatterlist *s = req->sg;
+
+ if (req->num_sgs == 1)
+ return 1;
+
+ dir = dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
+
+ for (i = 0; i < req->num_sgs; i++, s = sg_next(s)) {
+ /* Only small sg (generally last sg) may be bounced. If
+ * that happens. we can't ensure the addr is page-aligned
+ * after dma map.
+ */
+ if (dma_kmalloc_needs_bounce(dma_dev, s->length, dir))
+ break;
+
+ /* Make sure each sg start address (except first sg) is
+ * page-aligned and end address (except last sg) is also
+ * page-aligned.
+ */
+ if (i == 0) {
+ if (!IS_ALIGNED(s->offset + s->length,
+ CI_HDRC_PAGE_SIZE))
+ break;
+ } else {
+ if (s->offset)
+ break;
+ if (!sg_is_last(s) && !IS_ALIGNED(s->length,
+ CI_HDRC_PAGE_SIZE))
+ break;
+ }
+ }
+
+ return i;
+}
+
+static int sglist_do_bounce(struct ci_hw_req *hwreq, int index,
+ bool copy, unsigned int *bounced)
+{
+ void *buf;
+ int i, ret, nents, num_sgs;
+ unsigned int rest, rounded;
+ struct scatterlist *sg, *src, *dst;
+
+ nents = index + 1;
+ ret = sg_alloc_table(&hwreq->sgt, nents, GFP_KERNEL);
+ if (ret)
+ return ret;
+
+ sg = src = hwreq->req.sg;
+ num_sgs = hwreq->req.num_sgs;
+ rest = hwreq->req.length;
+ dst = hwreq->sgt.sgl;
+
+ for (i = 0; i < index; i++) {
+ memcpy(dst, src, sizeof(*src));
+ rest -= src->length;
+ src = sg_next(src);
+ dst = sg_next(dst);
+ }
+
+ /* create one bounce buffer */
+ rounded = round_up(rest, CI_HDRC_PAGE_SIZE);
+ buf = kmalloc(rounded, GFP_KERNEL);
+ if (!buf) {
+ sg_free_table(&hwreq->sgt);
+ return -ENOMEM;
+ }
+
+ sg_set_buf(dst, buf, rounded);
+
+ hwreq->req.sg = hwreq->sgt.sgl;
+ hwreq->req.num_sgs = nents;
+ hwreq->sgt.sgl = sg;
+ hwreq->sgt.nents = num_sgs;
+
+ if (copy)
+ sg_copy_to_buffer(src, num_sgs - index, buf, rest);
+
+ *bounced = rest;
+
+ return 0;
+}
+
+static void sglist_do_debounce(struct ci_hw_req *hwreq, bool copy)
+{
+ void *buf;
+ int i, nents, num_sgs;
+ struct scatterlist *sg, *src, *dst;
+
+ sg = hwreq->req.sg;
+ num_sgs = hwreq->req.num_sgs;
+ src = sg_last(sg, num_sgs);
+ buf = sg_virt(src);
+
+ if (copy) {
+ dst = hwreq->sgt.sgl;
+ for (i = 0; i < num_sgs - 1; i++)
+ dst = sg_next(dst);
+
+ nents = hwreq->sgt.nents - num_sgs + 1;
+ sg_copy_from_buffer(dst, nents, buf, sg_dma_len(src));
+ }
+
+ hwreq->req.sg = hwreq->sgt.sgl;
+ hwreq->req.num_sgs = hwreq->sgt.nents;
+ hwreq->sgt.sgl = sg;
+ hwreq->sgt.nents = num_sgs;
+
+ kfree(buf);
+ sg_free_table(&hwreq->sgt);
+}
+
/**
* _hardware_enqueue: configures a request at hardware level
* @hwep: endpoint
@@ -552,6 +673,8 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
struct ci_hdrc *ci = hwep->ci;
int ret = 0;
struct td_node *firstnode, *lastnode;
+ unsigned int bounced_size;
+ struct scatterlist *sg;
/* don't queue twice */
if (hwreq->req.status == -EALREADY)
@@ -559,11 +682,29 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
hwreq->req.status = -EALREADY;
+ if (hwreq->req.num_sgs && hwreq->req.length &&
+ ci->has_short_pkt_limit) {
+ ret = sglist_get_invalid_entry(ci->dev->parent, hwep->dir,
+ &hwreq->req);
+ if (ret < hwreq->req.num_sgs) {
+ ret = sglist_do_bounce(hwreq, ret, hwep->dir == TX,
+ &bounced_size);
+ if (ret)
+ return ret;
+ }
+ }
+
ret = usb_gadget_map_request_by_dev(ci->dev->parent,
&hwreq->req, hwep->dir);
if (ret)
return ret;
+ if (hwreq->sgt.sgl) {
+ /* We've mapped a bigger buffer, now recover the actual size */
+ sg = sg_last(hwreq->req.sg, hwreq->req.num_sgs);
+ sg_dma_len(sg) = min(sg_dma_len(sg), bounced_size);
+ }
+
if (hwreq->req.num_mapped_sgs)
ret = prepare_td_for_sg(hwep, hwreq);
else
@@ -612,10 +753,17 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
do {
hw_write(ci, OP_USBCMD, USBCMD_ATDTW, USBCMD_ATDTW);
tmp_stat = hw_read(ci, OP_ENDPTSTAT, BIT(n));
- } while (!hw_read(ci, OP_USBCMD, USBCMD_ATDTW));
+ } while (!hw_read(ci, OP_USBCMD, USBCMD_ATDTW) && tmp_stat);
hw_write(ci, OP_USBCMD, USBCMD_ATDTW, 0);
if (tmp_stat)
goto done;
+
+ /* OP_ENDPTSTAT will be clear by HW when the endpoint met
+ * err. This dTD don't push to dQH if current dTD point is
+ * not the last one in previous request.
+ */
+ if (hwep->qh.ptr->curr != cpu_to_le32(prevlastnode->dma))
+ goto done;
}
/* QH configuration */
@@ -676,6 +824,7 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
unsigned remaining_length;
unsigned actual = hwreq->req.length;
struct ci_hdrc *ci = hwep->ci;
+ bool is_isoc = hwep->type == USB_ENDPOINT_XFER_ISOC;
if (hwreq->req.status != -EALREADY)
return -EINVAL;
@@ -689,7 +838,7 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
int n = hw_ep_bit(hwep->num, hwep->dir);
if (ci->rev == CI_REVISION_24 ||
- ci->rev == CI_REVISION_22)
+ ci->rev == CI_REVISION_22 || is_isoc)
if (!hw_read(ci, OP_ENDPTSTAT, BIT(n)))
reprime_dtd(ci, hwep, node);
hwreq->req.status = -EALREADY;
@@ -708,11 +857,15 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
hwreq->req.status = -EPROTO;
break;
} else if ((TD_STATUS_TR_ERR & hwreq->req.status)) {
- hwreq->req.status = -EILSEQ;
- break;
+ if (is_isoc) {
+ hwreq->req.status = 0;
+ } else {
+ hwreq->req.status = -EILSEQ;
+ break;
+ }
}
- if (remaining_length) {
+ if (remaining_length && !is_isoc) {
if (hwep->dir == TX) {
hwreq->req.status = -EPROTO;
break;
@@ -733,6 +886,10 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
usb_gadget_unmap_request_by_dev(hwep->ci->dev->parent,
&hwreq->req, hwep->dir);
+ /* sglist bounced */
+ if (hwreq->sgt.sgl)
+ sglist_do_debounce(hwreq, hwep->dir == RX);
+
hwreq->req.actual += actual;
if (hwreq->req.status)
@@ -960,6 +1117,12 @@ static int _ep_queue(struct usb_ep *ep, struct usb_request *req,
return -EMSGSIZE;
}
+ if (ci->has_short_pkt_limit &&
+ hwreq->req.length > CI_MAX_REQ_SIZE) {
+ dev_err(hwep->ci->dev, "request length too big (max 16KB)\n");
+ return -EMSGSIZE;
+ }
+
/* first nuke then test link, e.g. previous status has not sent */
if (!list_empty(&hwreq->queue)) {
dev_err(hwep->ci->dev, "request already in queue\n");
@@ -1574,6 +1737,9 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)
usb_gadget_unmap_request(&hwep->ci->gadget, req, hwep->dir);
+ if (hwreq->sgt.sgl)
+ sglist_do_debounce(hwreq, false);
+
req->status = -ECONNRESET;
if (hwreq->req.complete != NULL) {
@@ -2063,7 +2229,7 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci)
}
}
- if (USBi_UI & intr)
+ if ((USBi_UI | USBi_UEI) & intr)
isr_tr_complete_handler(ci);
if ((USBi_SLI & intr) && !(ci->suspended)) {
diff --git a/drivers/usb/chipidea/udc.h b/drivers/usb/chipidea/udc.h
index 5193df1e18c7..c8a47389a46b 100644
--- a/drivers/usb/chipidea/udc.h
+++ b/drivers/usb/chipidea/udc.h
@@ -69,11 +69,13 @@ struct td_node {
* @req: request structure for gadget drivers
* @queue: link to QH list
* @tds: link to TD list
+ * @sgt: hold original sglist when bounce sglist
*/
struct ci_hw_req {
struct usb_request req;
struct list_head queue;
struct list_head tds;
+ struct sg_table sgt;
};
#ifdef CONFIG_USB_CHIPIDEA_UDC
diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c
index 173c78afd502..1394881fde5f 100644
--- a/drivers/usb/chipidea/usbmisc_imx.c
+++ b/drivers/usb/chipidea/usbmisc_imx.c
@@ -1285,6 +1285,10 @@ static const struct of_device_id usbmisc_imx_dt_ids[] = {
.compatible = "fsl,imx7ulp-usbmisc",
.data = &imx7ulp_usbmisc_ops,
},
+ {
+ .compatible = "fsl,imx8ulp-usbmisc",
+ .data = &imx7ulp_usbmisc_ops,
+ },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids);