summaryrefslogtreecommitdiff
path: root/drivers/usb/chipidea/udc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/chipidea/udc.c')
-rw-r--r--drivers/usb/chipidea/udc.c668
1 files changed, 546 insertions, 122 deletions
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index d68b125796f9..64a421ae0f05 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -1,23 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* udc.c - ChipIdea UDC driver
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
*
* Author: David Lopo
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
#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>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
+#include <linux/pinctrl/consumer.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/otg-fsm.h>
@@ -28,6 +27,7 @@
#include "bits.h"
#include "otg.h"
#include "otg_fsm.h"
+#include "trace.h"
/* control endpoint description */
static const struct usb_endpoint_descriptor
@@ -50,6 +50,8 @@ ctrl_endpt_in_desc = {
.wMaxPacketSize = cpu_to_le16(CTRL_PAYLOAD_MAX),
};
+static int reprime_dtd(struct ci_hdrc *ci, struct ci_hw_ep *hwep,
+ struct td_node *node);
/**
* hw_ep_bit: calculates the bit number
* @num: endpoint number
@@ -74,6 +76,7 @@ static inline int ep_to_bit(struct ci_hdrc *ci, int n)
/**
* hw_device_state: enables/disables interrupts (execute without interruption)
+ * @ci: the controller
* @dma: 0 => disable, !0 => enable and set dma engine
*
* This function returns an error code
@@ -84,7 +87,7 @@ static int hw_device_state(struct ci_hdrc *ci, u32 dma)
hw_write(ci, OP_ENDPTLISTADDR, ~0, dma);
/* interrupt, error, port change, reset, sleep/suspend */
hw_write(ci, OP_USBINTR, ~0,
- USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI);
+ USBi_UI|USBi_UEI|USBi_PCI|USBi_URI);
} else {
hw_write(ci, OP_USBINTR, ~0, 0);
}
@@ -93,6 +96,7 @@ static int hw_device_state(struct ci_hdrc *ci, u32 dma)
/**
* hw_ep_flush: flush endpoint fifo (execute without interruption)
+ * @ci: the controller
* @num: endpoint number
* @dir: endpoint direction
*
@@ -114,6 +118,7 @@ static int hw_ep_flush(struct ci_hdrc *ci, int num, int dir)
/**
* hw_ep_disable: disables endpoint (execute without interruption)
+ * @ci: the controller
* @num: endpoint number
* @dir: endpoint direction
*
@@ -128,6 +133,7 @@ static int hw_ep_disable(struct ci_hdrc *ci, int num, int dir)
/**
* hw_ep_enable: enables endpoint (execute without interruption)
+ * @ci: the controller
* @num: endpoint number
* @dir: endpoint direction
* @type: endpoint type
@@ -163,6 +169,7 @@ static int hw_ep_enable(struct ci_hdrc *ci, int num, int dir, int type)
/**
* hw_ep_get_halt: return endpoint halt status
+ * @ci: the controller
* @num: endpoint number
* @dir: endpoint direction
*
@@ -177,6 +184,7 @@ static int hw_ep_get_halt(struct ci_hdrc *ci, int num, int dir)
/**
* hw_ep_prime: primes endpoint (execute without interruption)
+ * @ci: the controller
* @num: endpoint number
* @dir: endpoint direction
* @is_ctrl: true if control endpoint
@@ -207,6 +215,7 @@ static int hw_ep_prime(struct ci_hdrc *ci, int num, int dir, int is_ctrl)
/**
* hw_ep_set_halt: configures ep halt & resets data toggle after clear (execute
* without interruption)
+ * @ci: the controller
* @num: endpoint number
* @dir: endpoint direction
* @value: true => stall, false => unstall
@@ -232,7 +241,8 @@ static int hw_ep_set_halt(struct ci_hdrc *ci, int num, int dir, int value)
}
/**
- * hw_is_port_high_speed: test if port is high speed
+ * hw_port_is_high_speed: test if port is high speed
+ * @ci: the controller
*
* This function returns true if high speed port
*/
@@ -245,6 +255,7 @@ static int hw_port_is_high_speed(struct ci_hdrc *ci)
/**
* hw_test_and_clear_complete: test & clear complete status (execute without
* interruption)
+ * @ci: the controller
* @n: endpoint number
*
* This function returns complete status
@@ -258,6 +269,7 @@ static int hw_test_and_clear_complete(struct ci_hdrc *ci, int n)
/**
* hw_test_and_clear_intr_active: test & clear active interrupts (execute
* without interruption)
+ * @ci: the controller
*
* This function returns active interrutps
*/
@@ -272,6 +284,7 @@ static u32 hw_test_and_clear_intr_active(struct ci_hdrc *ci)
/**
* hw_test_and_clear_setup_guard: test & clear setup guard (execute without
* interruption)
+ * @ci: the controller
*
* This function returns guard value
*/
@@ -283,6 +296,7 @@ static int hw_test_and_clear_setup_guard(struct ci_hdrc *ci)
/**
* hw_test_and_set_setup_guard: test & set setup guard (execute without
* interruption)
+ * @ci: the controller
*
* This function returns guard value
*/
@@ -293,6 +307,7 @@ static int hw_test_and_set_setup_guard(struct ci_hdrc *ci)
/**
* hw_usb_set_address: configures USB address (execute without interruption)
+ * @ci: the controller
* @value: new USB address
*
* This function explicitly sets the address, without the "USBADRA" (advance)
@@ -307,6 +322,7 @@ static void hw_usb_set_address(struct ci_hdrc *ci, u8 value)
/**
* hw_usb_reset: restart device after a bus reset (execute without
* interruption)
+ * @ci: the controller
*
* This function returns an error code
*/
@@ -340,7 +356,7 @@ static int hw_usb_reset(struct ci_hdrc *ci)
*****************************************************************************/
static int add_td_to_list(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq,
- unsigned length)
+ unsigned int length, struct scatterlist *s)
{
int i;
u32 temp;
@@ -368,7 +384,13 @@ static int add_td_to_list(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq,
node->ptr->token |= cpu_to_le32(mul << __ffs(TD_MULTO));
}
- temp = (u32) (hwreq->req.dma + hwreq->req.actual);
+ if (s) {
+ temp = (u32) (sg_dma_address(s) + hwreq->req.actual);
+ node->td_remaining_size = CI_MAX_BUF_SIZE - length;
+ } else {
+ temp = (u32) (hwreq->req.dma + hwreq->req.actual);
+ }
+
if (length) {
node->ptr->page[0] = cpu_to_le32(temp);
for (i = 1; i < TD_PAGE_COUNT; i++) {
@@ -402,6 +424,243 @@ static inline u8 _usb_addr(struct ci_hw_ep *ep)
return ((ep->dir == TX) ? USB_ENDPOINT_DIR_MASK : 0) | ep->num;
}
+static int prepare_td_for_non_sg(struct ci_hw_ep *hwep,
+ struct ci_hw_req *hwreq)
+{
+ unsigned int rest = hwreq->req.length;
+ int pages = TD_PAGE_COUNT;
+ int ret = 0;
+
+ if (rest == 0) {
+ ret = add_td_to_list(hwep, hwreq, 0, NULL);
+ if (ret < 0)
+ return ret;
+ }
+
+ /*
+ * The first buffer could be not page aligned.
+ * In that case we have to span into one extra td.
+ */
+ if (hwreq->req.dma % PAGE_SIZE)
+ pages--;
+
+ while (rest > 0) {
+ unsigned int count = min(hwreq->req.length - hwreq->req.actual,
+ (unsigned int)(pages * CI_HDRC_PAGE_SIZE));
+
+ ret = add_td_to_list(hwep, hwreq, count, NULL);
+ if (ret < 0)
+ return ret;
+
+ rest -= count;
+ }
+
+ if (hwreq->req.zero && hwreq->req.length && hwep->dir == TX
+ && (hwreq->req.length % hwep->ep.maxpacket == 0)) {
+ ret = add_td_to_list(hwep, hwreq, 0, NULL);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int prepare_td_per_sg(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq,
+ struct scatterlist *s)
+{
+ unsigned int rest = sg_dma_len(s);
+ int ret = 0;
+
+ hwreq->req.actual = 0;
+ while (rest > 0) {
+ unsigned int count = min_t(unsigned int, rest,
+ CI_MAX_BUF_SIZE);
+
+ ret = add_td_to_list(hwep, hwreq, count, s);
+ if (ret < 0)
+ return ret;
+
+ rest -= count;
+ }
+
+ return ret;
+}
+
+static void ci_add_buffer_entry(struct td_node *node, struct scatterlist *s)
+{
+ int empty_td_slot_index = (CI_MAX_BUF_SIZE - node->td_remaining_size)
+ / CI_HDRC_PAGE_SIZE;
+ int i;
+ u32 token;
+
+ token = le32_to_cpu(node->ptr->token) + (sg_dma_len(s) << __ffs(TD_TOTAL_BYTES));
+ node->ptr->token = cpu_to_le32(token);
+
+ for (i = empty_td_slot_index; i < TD_PAGE_COUNT; i++) {
+ u32 page = (u32) sg_dma_address(s) +
+ (i - empty_td_slot_index) * CI_HDRC_PAGE_SIZE;
+
+ page &= ~TD_RESERVED_MASK;
+ node->ptr->page[i] = cpu_to_le32(page);
+ }
+}
+
+static int prepare_td_for_sg(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
+{
+ struct usb_request *req = &hwreq->req;
+ struct scatterlist *s = req->sg;
+ int ret = 0, i = 0;
+ struct td_node *node = NULL;
+
+ if (!s || req->zero || req->length == 0) {
+ dev_err(hwep->ci->dev, "not supported operation for sg\n");
+ return -EINVAL;
+ }
+
+ while (i++ < req->num_mapped_sgs) {
+ if (sg_dma_address(s) % PAGE_SIZE) {
+ dev_err(hwep->ci->dev, "not page aligned sg buffer\n");
+ return -EINVAL;
+ }
+
+ if (node && (node->td_remaining_size >= sg_dma_len(s))) {
+ ci_add_buffer_entry(node, s);
+ node->td_remaining_size -= sg_dma_len(s);
+ } else {
+ ret = prepare_td_per_sg(hwep, hwreq, s);
+ if (ret)
+ return ret;
+
+ node = list_entry(hwreq->tds.prev,
+ struct td_node, td);
+ }
+
+ s = sg_next(s);
+ }
+
+ 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
@@ -413,9 +672,9 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
{
struct ci_hdrc *ci = hwep->ci;
int ret = 0;
- unsigned rest = hwreq->req.length;
- int pages = TD_PAGE_COUNT;
struct td_node *firstnode, *lastnode;
+ unsigned int bounced_size;
+ struct scatterlist *sg;
/* don't queue twice */
if (hwreq->req.status == -EALREADY)
@@ -423,42 +682,36 @@ 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;
- /*
- * The first buffer could be not page aligned.
- * In that case we have to span into one extra td.
- */
- if (hwreq->req.dma % PAGE_SIZE)
- pages--;
-
- if (rest == 0) {
- ret = add_td_to_list(hwep, hwreq, 0);
- if (ret < 0)
- goto done;
- }
-
- while (rest > 0) {
- unsigned count = min(hwreq->req.length - hwreq->req.actual,
- (unsigned)(pages * CI_HDRC_PAGE_SIZE));
- ret = add_td_to_list(hwep, hwreq, count);
- if (ret < 0)
- goto done;
-
- rest -= count;
+ 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.zero && hwreq->req.length && hwep->dir == TX
- && (hwreq->req.length % hwep->ep.maxpacket == 0)) {
- ret = add_td_to_list(hwep, hwreq, 0);
- if (ret < 0)
- goto done;
- }
+ if (hwreq->req.num_mapped_sgs)
+ ret = prepare_td_for_sg(hwep, hwreq);
+ else
+ ret = prepare_td_for_non_sg(hwep, hwreq);
- firstnode = list_first_entry(&hwreq->tds, struct td_node, td);
+ if (ret)
+ return ret;
lastnode = list_entry(hwreq->tds.prev,
struct td_node, td);
@@ -466,6 +719,12 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
lastnode->ptr->next = cpu_to_le32(TD_TERMINATE);
if (!hwreq->req.no_interrupt)
lastnode->ptr->token |= cpu_to_le32(TD_IOC);
+
+ list_for_each_entry_safe(firstnode, lastnode, &hwreq->tds, td)
+ trace_ci_prepare_td(hwep, hwreq, firstnode);
+
+ firstnode = list_first_entry(&hwreq->tds, struct td_node, td);
+
wmb();
hwreq->req.actual = 0;
@@ -483,15 +742,28 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
prevlastnode->ptr->next = cpu_to_le32(next);
wmb();
+
+ if (ci->rev == CI_REVISION_22) {
+ if (!hw_read(ci, OP_ENDPTSTAT, BIT(n)))
+ reprime_dtd(ci, hwep, prevlastnode);
+ }
+
if (hw_read(ci, OP_ENDPTPRIME, BIT(n)))
goto done;
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 */
@@ -514,7 +786,7 @@ done:
return ret;
}
-/*
+/**
* free_pending_td: remove a pending request for the endpoint
* @hwep: endpoint
*/
@@ -540,8 +812,8 @@ static int reprime_dtd(struct ci_hdrc *ci, struct ci_hw_ep *hwep,
/**
* _hardware_dequeue: handles a request at hardware level
- * @gadget: gadget
- * @hwep: endpoint
+ * @hwep: endpoint
+ * @hwreq: request
*
* This function returns an error code
*/
@@ -552,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;
@@ -560,10 +833,12 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) {
tmptoken = le32_to_cpu(node->ptr->token);
+ trace_ci_complete_td(hwep, hwreq, node);
if ((TD_STATUS_ACTIVE & tmptoken) != 0) {
int n = hw_ep_bit(hwep->num, hwep->dir);
- if (ci->rev == CI_REVISION_24)
+ if (ci->rev == CI_REVISION_24 ||
+ ci->rev == CI_REVISION_22 || is_isoc)
if (!hw_read(ci, OP_ENDPTSTAT, BIT(n)))
reprime_dtd(ci, hwep, node);
hwreq->req.status = -EALREADY;
@@ -582,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;
@@ -607,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)
@@ -711,12 +994,6 @@ static int _gadget_stop_activity(struct usb_gadget *gadget)
struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget);
unsigned long flags;
- spin_lock_irqsave(&ci->lock, flags);
- ci->gadget.speed = USB_SPEED_UNKNOWN;
- ci->remote_wakeup = 0;
- ci->suspended = 0;
- spin_unlock_irqrestore(&ci->lock, flags);
-
/* flush all endpoints */
gadget_for_each_ep(ep, gadget) {
usb_ep_fifo_flush(ep);
@@ -734,6 +1011,12 @@ static int _gadget_stop_activity(struct usb_gadget *gadget)
ci->status = NULL;
}
+ spin_lock_irqsave(&ci->lock, flags);
+ ci->gadget.speed = USB_SPEED_UNKNOWN;
+ ci->remote_wakeup = 0;
+ ci->suspended = 0;
+ spin_unlock_irqrestore(&ci->lock, flags);
+
return 0;
}
@@ -751,6 +1034,7 @@ __releases(ci->lock)
__acquires(ci->lock)
{
int retval;
+ u32 intr;
spin_unlock(&ci->lock);
if (ci->gadget.speed != USB_SPEED_UNKNOWN)
@@ -764,6 +1048,11 @@ __acquires(ci->lock)
if (retval)
goto done;
+ /* clear SLI */
+ hw_write(ci, OP_USBSTS, USBi_SLI, USBi_SLI);
+ intr = hw_read(ci, OP_USBINTR, ~0);
+ hw_write(ci, OP_USBINTR, ~0, intr | USBi_SLI);
+
ci->status = usb_ep_alloc_request(&ci->ep0in->ep, GFP_ATOMIC);
if (ci->status == NULL)
retval = -ENOMEM;
@@ -828,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");
@@ -923,6 +1218,9 @@ isr_setup_status_complete(struct usb_ep *ep, struct usb_request *req)
struct ci_hdrc *ci = req->context;
unsigned long flags;
+ if (req->status < 0)
+ return;
+
if (ci->setaddr) {
hw_usb_set_address(ci, ci->address);
ci->setaddr = false;
@@ -944,7 +1242,6 @@ isr_setup_status_complete(struct usb_ep *ep, struct usb_request *req)
*/
static int isr_setup_status_phase(struct ci_hdrc *ci)
{
- int retval;
struct ci_hw_ep *hwep;
/*
@@ -960,9 +1257,7 @@ static int isr_setup_status_phase(struct ci_hdrc *ci)
ci->status->context = ci;
ci->status->complete = isr_setup_status_complete;
- retval = _ep_queue(&hwep->ep, ci->status, GFP_ATOMIC);
-
- return retval;
+ return _ep_queue(&hwep->ep, ci->status, GFP_ATOMIC);
}
/**
@@ -1122,11 +1417,11 @@ __acquires(ci->lock)
case USB_DEVICE_TEST_MODE:
tmode = le16_to_cpu(req.wIndex) >> 8;
switch (tmode) {
- case TEST_J:
- case TEST_K:
- case TEST_SE0_NAK:
- case TEST_PACKET:
- case TEST_FORCE_EN:
+ case USB_TEST_J:
+ case USB_TEST_K:
+ case USB_TEST_SE0_NAK:
+ case USB_TEST_PACKET:
+ case USB_TEST_FORCE_ENABLE:
ci->test_mode = tmode;
err = isr_setup_status_phase(
ci);
@@ -1223,7 +1518,7 @@ __acquires(ci->lock)
/******************************************************************************
* ENDPT block
*****************************************************************************/
-/**
+/*
* ep_enable: configure endpoint, making it usable
*
* Check usb_ep_enable() at "usb_gadget.h" for details
@@ -1291,7 +1586,7 @@ static int ep_enable(struct usb_ep *ep,
return retval;
}
-/**
+/*
* ep_disable: endpoint is no longer usable
*
* Check usb_ep_disable() at "usb_gadget.h" for details
@@ -1308,6 +1603,10 @@ static int ep_disable(struct usb_ep *ep)
return -EBUSY;
spin_lock_irqsave(hwep->lock, flags);
+ if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
+ spin_unlock_irqrestore(hwep->lock, flags);
+ return 0;
+ }
/* only internal SW should disable ctrl endpts */
@@ -1327,14 +1626,14 @@ static int ep_disable(struct usb_ep *ep)
return retval;
}
-/**
+/*
* ep_alloc_request: allocate a request object to use with this endpoint
*
* Check usb_ep_alloc_request() at "usb_gadget.h" for details
*/
static struct usb_request *ep_alloc_request(struct usb_ep *ep, gfp_t gfp_flags)
{
- struct ci_hw_req *hwreq = NULL;
+ struct ci_hw_req *hwreq;
if (ep == NULL)
return NULL;
@@ -1348,7 +1647,7 @@ static struct usb_request *ep_alloc_request(struct usb_ep *ep, gfp_t gfp_flags)
return (hwreq == NULL) ? NULL : &hwreq->req;
}
-/**
+/*
* ep_free_request: frees a request object
*
* Check usb_ep_free_request() at "usb_gadget.h" for details
@@ -1381,7 +1680,7 @@ static void ep_free_request(struct usb_ep *ep, struct usb_request *req)
spin_unlock_irqrestore(hwep->lock, flags);
}
-/**
+/*
* ep_queue: queues (submits) an I/O request to an endpoint
*
* Check usb_ep_queue()* at usb_gadget.h" for details
@@ -1397,12 +1696,16 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req,
return -EINVAL;
spin_lock_irqsave(hwep->lock, flags);
+ if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
+ spin_unlock_irqrestore(hwep->lock, flags);
+ return 0;
+ }
retval = _ep_queue(ep, req, gfp_flags);
spin_unlock_irqrestore(hwep->lock, flags);
return retval;
}
-/**
+/*
* ep_dequeue: dequeues (cancels, unlinks) an I/O request from an endpoint
*
* Check usb_ep_dequeue() at "usb_gadget.h" for details
@@ -1420,8 +1723,8 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)
return -EINVAL;
spin_lock_irqsave(hwep->lock, flags);
-
- hw_ep_flush(hwep->ci, hwep->num, hwep->dir);
+ if (hwep->ci->gadget.speed != USB_SPEED_UNKNOWN)
+ hw_ep_flush(hwep->ci, hwep->num, hwep->dir);
list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) {
dma_pool_free(hwep->td_pool, node->ptr, node->dma);
@@ -1434,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) {
@@ -1446,7 +1752,7 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)
return 0;
}
-/**
+/*
* ep_set_halt: sets the endpoint halt feature
*
* Check usb_ep_set_halt() at "usb_gadget.h" for details
@@ -1456,7 +1762,7 @@ static int ep_set_halt(struct usb_ep *ep, int value)
return _ep_set_halt(ep, value, true);
}
-/**
+/*
* ep_set_wedge: sets the halt feature and ignores clear requests
*
* Check usb_ep_set_wedge() at "usb_gadget.h" for details
@@ -1476,7 +1782,7 @@ static int ep_set_wedge(struct usb_ep *ep)
return usb_ep_set_halt(ep);
}
-/**
+/*
* ep_fifo_flush: flushes contents of a fifo
*
* Check usb_ep_fifo_flush() at "usb_gadget.h" for details
@@ -1492,13 +1798,17 @@ static void ep_fifo_flush(struct usb_ep *ep)
}
spin_lock_irqsave(hwep->lock, flags);
+ if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
+ spin_unlock_irqrestore(hwep->lock, flags);
+ return;
+ }
hw_ep_flush(hwep->ci, hwep->num, hwep->dir);
spin_unlock_irqrestore(hwep->lock, flags);
}
-/**
+/*
* Endpoint-specific part of the API to the USB controller hardware
* Check "usb_gadget.h" for details
*/
@@ -1517,40 +1827,81 @@ static const struct usb_ep_ops usb_ep_ops = {
/******************************************************************************
* GADGET block
*****************************************************************************/
-static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active)
+
+static int ci_udc_get_frame(struct usb_gadget *_gadget)
{
struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget);
unsigned long flags;
- int gadget_ready = 0;
+ int ret;
spin_lock_irqsave(&ci->lock, flags);
- ci->vbus_active = is_active;
- if (ci->driver)
- gadget_ready = 1;
+ ret = hw_read(ci, OP_FRINDEX, 0x3fff);
spin_unlock_irqrestore(&ci->lock, flags);
+ return ret >> 3;
+}
- if (gadget_ready) {
- if (is_active) {
- pm_runtime_get_sync(&_gadget->dev);
- hw_device_reset(ci);
+/*
+ * ci_hdrc_gadget_connect: caller makes sure gadget driver is binded
+ */
+static void ci_hdrc_gadget_connect(struct usb_gadget *_gadget, int is_active)
+{
+ struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget);
+
+ if (is_active) {
+ pm_runtime_get_sync(ci->dev);
+ hw_device_reset(ci);
+ spin_lock_irq(&ci->lock);
+ if (ci->driver) {
hw_device_state(ci, ci->ep0out->qh.dma);
usb_gadget_set_state(_gadget, USB_STATE_POWERED);
+ spin_unlock_irq(&ci->lock);
usb_udc_vbus_handler(_gadget, true);
} else {
- usb_udc_vbus_handler(_gadget, false);
- if (ci->driver)
- ci->driver->disconnect(&ci->gadget);
- hw_device_state(ci, 0);
- if (ci->platdata->notify_event)
- ci->platdata->notify_event(ci,
- CI_HDRC_CONTROLLER_STOPPED_EVENT);
- _gadget_stop_activity(&ci->gadget);
- pm_runtime_put_sync(&_gadget->dev);
- usb_gadget_set_state(_gadget, USB_STATE_NOTATTACHED);
+ spin_unlock_irq(&ci->lock);
}
+ } else {
+ usb_udc_vbus_handler(_gadget, false);
+ if (ci->driver)
+ ci->driver->disconnect(&ci->gadget);
+ hw_device_state(ci, 0);
+ if (ci->platdata->notify_event)
+ ci->platdata->notify_event(ci,
+ CI_HDRC_CONTROLLER_STOPPED_EVENT);
+ _gadget_stop_activity(&ci->gadget);
+ pm_runtime_put_sync(ci->dev);
+ usb_gadget_set_state(_gadget, USB_STATE_NOTATTACHED);
}
+}
- return 0;
+static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active)
+{
+ struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget);
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&ci->lock, flags);
+ ci->vbus_active = is_active;
+ spin_unlock_irqrestore(&ci->lock, flags);
+
+ if (ci->usb_phy)
+ usb_phy_set_charger_state(ci->usb_phy, is_active ?
+ USB_CHARGER_PRESENT : USB_CHARGER_ABSENT);
+
+ if (ci->platdata->notify_event)
+ ret = ci->platdata->notify_event(ci,
+ CI_HDRC_CONTROLLER_VBUS_EVENT);
+
+ if (ci->usb_phy) {
+ if (is_active)
+ usb_phy_set_event(ci->usb_phy, USB_EVENT_VBUS);
+ else
+ usb_phy_set_event(ci->usb_phy, USB_EVENT_NONE);
+ }
+
+ if (ci->driver)
+ ci_hdrc_gadget_connect(_gadget, is_active);
+
+ return ret;
}
static int ci_udc_wakeup(struct usb_gadget *_gadget)
@@ -1560,6 +1911,10 @@ static int ci_udc_wakeup(struct usb_gadget *_gadget)
int ret = 0;
spin_lock_irqsave(&ci->lock, flags);
+ if (ci->gadget.speed == USB_SPEED_UNKNOWN) {
+ spin_unlock_irqrestore(&ci->lock, flags);
+ return 0;
+ }
if (!ci->remote_wakeup) {
ret = -EOPNOTSUPP;
goto out;
@@ -1597,7 +1952,7 @@ static int ci_udc_selfpowered(struct usb_gadget *_gadget, int is_on)
}
/* Change Data+ pullup status
- * this func is used by usb_gadget_connect/disconnet
+ * this func is used by usb_gadget_connect/disconnect
*/
static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on)
{
@@ -1610,12 +1965,17 @@ static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on)
if (ci_otg_is_fsm_mode(ci) || ci->role == CI_ROLE_HOST)
return 0;
- pm_runtime_get_sync(&ci->gadget.dev);
+ pm_runtime_get_sync(ci->dev);
if (is_on)
hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS);
else
hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
- pm_runtime_put_sync(&ci->gadget.dev);
+
+ if (ci->platdata->notify_event) {
+ _gadget->connected = is_on;
+ ci->platdata->notify_event(ci, CI_HDRC_CONTROLLER_PULLUP_EVENT);
+ }
+ pm_runtime_put_sync(ci->dev);
return 0;
}
@@ -1623,12 +1983,32 @@ static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on)
static int ci_udc_start(struct usb_gadget *gadget,
struct usb_gadget_driver *driver);
static int ci_udc_stop(struct usb_gadget *gadget);
-/**
+
+/* Match ISOC IN from the highest endpoint */
+static struct usb_ep *ci_udc_match_ep(struct usb_gadget *gadget,
+ struct usb_endpoint_descriptor *desc,
+ struct usb_ss_ep_comp_descriptor *comp_desc)
+{
+ struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget);
+ struct usb_ep *ep;
+
+ if (usb_endpoint_xfer_isoc(desc) && usb_endpoint_dir_in(desc)) {
+ list_for_each_entry_reverse(ep, &ci->gadget.ep_list, ep_list) {
+ if (ep->caps.dir_in && !ep->claimed)
+ return ep;
+ }
+ }
+
+ return NULL;
+}
+
+/*
* Device operations part of the API to the USB controller hardware,
* which don't involve endpoints (or i/o)
* Check "usb_gadget.h" for details
*/
static const struct usb_gadget_ops usb_gadget_ops = {
+ .get_frame = ci_udc_get_frame,
.vbus_session = ci_udc_vbus_session,
.wakeup = ci_udc_wakeup,
.set_selfpowered = ci_udc_selfpowered,
@@ -1636,6 +2016,7 @@ static const struct usb_gadget_ops usb_gadget_ops = {
.vbus_draw = ci_udc_vbus_draw,
.udc_start = ci_udc_start,
.udc_stop = ci_udc_stop,
+ .match_ep = ci_udc_match_ep,
};
static int init_eps(struct ci_hdrc *ci)
@@ -1727,12 +2108,11 @@ static int ci_udc_start(struct usb_gadget *gadget,
struct usb_gadget_driver *driver)
{
struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget);
- int retval = -ENOMEM;
+ int retval;
if (driver->disconnect == NULL)
return -EINVAL;
-
ci->ep0out->ep.desc = &ctrl_endpt_out_desc;
retval = usb_ep_enable(&ci->ep0out->ep);
if (retval)
@@ -1751,18 +2131,10 @@ static int ci_udc_start(struct usb_gadget *gadget,
return retval;
}
- pm_runtime_get_sync(&ci->gadget.dev);
- if (ci->vbus_active) {
- hw_device_reset(ci);
- } else {
+ if (ci->vbus_active)
+ ci_hdrc_gadget_connect(gadget, 1);
+ else
usb_udc_vbus_handler(&ci->gadget, false);
- pm_runtime_put_sync(&ci->gadget.dev);
- return retval;
- }
-
- retval = hw_device_state(ci, ci->ep0out->qh.dma);
- if (retval)
- pm_runtime_put_sync(&ci->gadget.dev);
return retval;
}
@@ -1783,7 +2155,7 @@ static void ci_udc_stop_for_otg_fsm(struct ci_hdrc *ci)
mutex_unlock(&ci->fsm.lock);
}
-/**
+/*
* ci_udc_stop: unregister a gadget driver
*/
static int ci_udc_stop(struct usb_gadget *gadget)
@@ -1792,6 +2164,7 @@ static int ci_udc_stop(struct usb_gadget *gadget)
unsigned long flags;
spin_lock_irqsave(&ci->lock, flags);
+ ci->driver = NULL;
if (ci->vbus_active) {
hw_device_state(ci, 0);
@@ -1801,10 +2174,9 @@ static int ci_udc_stop(struct usb_gadget *gadget)
CI_HDRC_CONTROLLER_STOPPED_EVENT);
_gadget_stop_activity(&ci->gadget);
spin_lock_irqsave(&ci->lock, flags);
- pm_runtime_put(&ci->gadget.dev);
+ pm_runtime_put(ci->dev);
}
- ci->driver = NULL;
spin_unlock_irqrestore(&ci->lock, flags);
ci_udc_stop_for_otg_fsm(ci);
@@ -1814,7 +2186,7 @@ static int ci_udc_stop(struct usb_gadget *gadget)
/******************************************************************************
* BUS block
*****************************************************************************/
-/**
+/*
* udc_irq: ci interrupt handler
*
* This function returns IRQ_HANDLED if the IRQ has been handled
@@ -1847,6 +2219,9 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci)
if (USBi_PCI & intr) {
ci->gadget.speed = hw_port_is_high_speed(ci) ?
USB_SPEED_HIGH : USB_SPEED_FULL;
+ if (ci->usb_phy)
+ usb_phy_set_event(ci->usb_phy,
+ USB_EVENT_ENUMERATED);
if (ci->suspended) {
if (ci->driver->resume) {
spin_unlock(&ci->lock);
@@ -1859,7 +2234,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)) {
@@ -1898,6 +2273,11 @@ static int udc_start(struct ci_hdrc *ci)
ci->gadget.max_speed = USB_SPEED_HIGH;
ci->gadget.name = ci->platdata->name;
ci->gadget.otg_caps = otg_caps;
+ ci->gadget.sg_supported = 1;
+ ci->gadget.irq = ci->irq;
+
+ if (ci->platdata->flags & CI_HDRC_REQUIRES_ALIGNED_DMA)
+ ci->gadget.quirk_avoids_skb_reserve = 1;
if (ci->is_otg && (otg_caps->hnp_support || otg_caps->srp_support ||
otg_caps->adp_support))
@@ -1930,9 +2310,6 @@ static int udc_start(struct ci_hdrc *ci)
if (retval)
goto destroy_eps;
- pm_runtime_no_callbacks(&ci->gadget.dev);
- pm_runtime_enable(&ci->gadget.dev);
-
return retval;
destroy_eps:
@@ -1944,7 +2321,7 @@ free_qh_pool:
return retval;
}
-/**
+/*
* ci_hdrc_gadget_destroy: parent remove must call this to remove UDC
*
* No interrupts active, the IRQ has been released
@@ -1964,6 +2341,10 @@ void ci_hdrc_gadget_destroy(struct ci_hdrc *ci)
static int udc_id_switch_for_device(struct ci_hdrc *ci)
{
+ if (ci->platdata->pins_device)
+ pinctrl_select_state(ci->platdata->pctl,
+ ci->platdata->pins_device);
+
if (ci->is_otg)
/* Clear and enable BSV irq */
hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
@@ -1976,17 +2357,56 @@ static void udc_id_switch_for_host(struct ci_hdrc *ci)
{
/*
* host doesn't care B_SESSION_VALID event
- * so clear and disbale BSV irq
+ * so clear and disable BSV irq
*/
if (ci->is_otg)
hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS);
ci->vbus_active = 0;
+
+ if (ci->platdata->pins_device && ci->platdata->pins_default)
+ pinctrl_select_state(ci->platdata->pctl,
+ ci->platdata->pins_default);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void udc_suspend(struct ci_hdrc *ci)
+{
+ /*
+ * Set OP_ENDPTLISTADDR to be non-zero for
+ * checking if controller resume from power lost
+ * in non-host mode.
+ */
+ if (hw_read(ci, OP_ENDPTLISTADDR, ~0) == 0)
+ hw_write(ci, OP_ENDPTLISTADDR, ~0, ~0);
+
+ if (ci->gadget.connected &&
+ (!ci->suspended || !device_may_wakeup(ci->dev)))
+ usb_gadget_disconnect(&ci->gadget);
+}
+
+static void udc_resume(struct ci_hdrc *ci, bool power_lost)
+{
+ if (power_lost) {
+ if (ci->is_otg)
+ hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
+ OTGSC_BSVIS | OTGSC_BSVIE);
+ if (ci->vbus_active)
+ usb_gadget_vbus_disconnect(&ci->gadget);
+ } else if (ci->vbus_active && ci->driver &&
+ !ci->gadget.connected) {
+ usb_gadget_connect(&ci->gadget);
+ }
+
+ /* Restore value 0 if it was set for power lost check */
+ if (hw_read(ci, OP_ENDPTLISTADDR, ~0) == 0xFFFFFFFF)
+ hw_write(ci, OP_ENDPTLISTADDR, ~0, 0);
}
+#endif
/**
* ci_hdrc_gadget_init - initialize device related bits
- * ci: the controller
+ * @ci: the controller
*
* This function initializes the gadget, if the device is "device capable".
*/
@@ -2004,6 +2424,10 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci)
rdrv->start = udc_id_switch_for_device;
rdrv->stop = udc_id_switch_for_host;
+#ifdef CONFIG_PM_SLEEP
+ rdrv->suspend = udc_suspend;
+ rdrv->resume = udc_resume;
+#endif
rdrv->irq = udc_irq;
rdrv->name = "gadget";