diff options
Diffstat (limited to 'drivers/usb/chipidea/udc.c')
| -rw-r--r-- | drivers/usb/chipidea/udc.c | 1434 |
1 files changed, 991 insertions, 443 deletions
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index e475fcda1d68..64a421ae0f05 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -1,32 +1,33 @@ +// 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.h> +#include <linux/usb/otg-fsm.h> #include <linux/usb/chipidea.h> #include "ci.h" #include "udc.h" #include "bits.h" -#include "debug.h" +#include "otg.h" +#include "otg_fsm.h" +#include "trace.h" /* control endpoint description */ static const struct usb_endpoint_descriptor @@ -49,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 @@ -58,7 +61,7 @@ ctrl_endpt_in_desc = { */ static inline int hw_ep_bit(int num, int dir) { - return num + (dir ? 16 : 0); + return num + ((dir == TX) ? 16 : 0); } static inline int ep_to_bit(struct ci_hdrc *ci, int n) @@ -73,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 @@ -83,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); } @@ -92,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 * @@ -103,7 +108,7 @@ static int hw_ep_flush(struct ci_hdrc *ci, int num, int dir) do { /* flush any pending transfer */ - hw_write(ci, OP_ENDPTFLUSH, BIT(n), BIT(n)); + hw_write(ci, OP_ENDPTFLUSH, ~0, BIT(n)); while (hw_read(ci, OP_ENDPTFLUSH, BIT(n))) cpu_relax(); } while (hw_read(ci, OP_ENDPTSTAT, BIT(n))); @@ -113,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 * @@ -120,14 +126,14 @@ static int hw_ep_flush(struct ci_hdrc *ci, int num, int dir) */ static int hw_ep_disable(struct ci_hdrc *ci, int num, int dir) { - hw_ep_flush(ci, num, dir); hw_write(ci, OP_ENDPTCTRL + num, - dir ? ENDPTCTRL_TXE : ENDPTCTRL_RXE, 0); + (dir == TX) ? ENDPTCTRL_TXE : ENDPTCTRL_RXE, 0); return 0; } /** * hw_ep_enable: enables endpoint (execute without interruption) + * @ci: the controller * @num: endpoint number * @dir: endpoint direction * @type: endpoint type @@ -138,7 +144,7 @@ static int hw_ep_enable(struct ci_hdrc *ci, int num, int dir, int type) { u32 mask, data; - if (dir) { + if (dir == TX) { mask = ENDPTCTRL_TXT; /* type */ data = type << __ffs(mask); @@ -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 * @@ -170,26 +177,14 @@ static int hw_ep_enable(struct ci_hdrc *ci, int num, int dir, int type) */ static int hw_ep_get_halt(struct ci_hdrc *ci, int num, int dir) { - u32 mask = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + u32 mask = (dir == TX) ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; return hw_read(ci, OP_ENDPTCTRL + num, mask) ? 1 : 0; } /** - * hw_test_and_clear_setup_status: test & clear setup status (execute without - * interruption) - * @n: endpoint number - * - * This function returns setup status - */ -static int hw_test_and_clear_setup_status(struct ci_hdrc *ci, int n) -{ - n = ep_to_bit(ci, n); - return hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(n)); -} - -/** * hw_ep_prime: primes endpoint (execute without interruption) + * @ci: the controller * @num: endpoint number * @dir: endpoint direction * @is_ctrl: true if control endpoint @@ -200,10 +195,13 @@ static int hw_ep_prime(struct ci_hdrc *ci, int num, int dir, int is_ctrl) { int n = hw_ep_bit(num, dir); + /* Synchronize before ep prime */ + wmb(); + if (is_ctrl && dir == RX && hw_read(ci, OP_ENDPTSETUPSTAT, BIT(num))) return -EAGAIN; - hw_write(ci, OP_ENDPTPRIME, BIT(n), BIT(n)); + hw_write(ci, OP_ENDPTPRIME, ~0, BIT(n)); while (hw_read(ci, OP_ENDPTPRIME, BIT(n))) cpu_relax(); @@ -217,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 @@ -230,8 +229,8 @@ static int hw_ep_set_halt(struct ci_hdrc *ci, int num, int dir, int value) do { enum ci_hw_regs reg = OP_ENDPTCTRL + num; - u32 mask_xs = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; - u32 mask_xr = dir ? ENDPTCTRL_TXR : ENDPTCTRL_RXR; + u32 mask_xs = (dir == TX) ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + u32 mask_xr = (dir == TX) ? ENDPTCTRL_TXR : ENDPTCTRL_RXR; /* data toggle - reserved for EP0 but it's in ESS */ hw_write(ci, reg, mask_xs|mask_xr, @@ -242,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 */ @@ -253,28 +253,9 @@ static int hw_port_is_high_speed(struct ci_hdrc *ci) } /** - * hw_read_intr_enable: returns interrupt enable register - * - * This function returns register data - */ -static u32 hw_read_intr_enable(struct ci_hdrc *ci) -{ - return hw_read(ci, OP_USBINTR, ~0); -} - -/** - * hw_read_intr_status: returns interrupt status register - * - * This function returns register data - */ -static u32 hw_read_intr_status(struct ci_hdrc *ci) -{ - return hw_read(ci, OP_USBSTS, ~0); -} - -/** * hw_test_and_clear_complete: test & clear complete status (execute without * interruption) + * @ci: the controller * @n: endpoint number * * This function returns complete status @@ -288,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 */ @@ -302,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 */ @@ -313,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 */ @@ -323,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) @@ -337,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 */ @@ -370,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; @@ -380,19 +366,31 @@ static int add_td_to_list(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, if (node == NULL) return -ENOMEM; - node->ptr = dma_pool_alloc(hwep->td_pool, GFP_ATOMIC, - &node->dma); + node->ptr = dma_pool_zalloc(hwep->td_pool, GFP_ATOMIC, &node->dma); if (node->ptr == NULL) { kfree(node); return -ENOMEM; } - memset(node->ptr, 0, sizeof(struct ci_hw_td)); node->ptr->token = cpu_to_le32(length << __ffs(TD_TOTAL_BYTES)); node->ptr->token &= cpu_to_le32(TD_TOTAL_BYTES); node->ptr->token |= cpu_to_le32(TD_STATUS_ACTIVE); + if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == TX) { + u32 mul = hwreq->req.length / hwep->ep.maxpacket; + + if (hwreq->req.length == 0 + || hwreq->req.length % hwep->ep.maxpacket) + mul++; + node->ptr->token |= cpu_to_le32(mul << __ffs(TD_MULTO)); + } + + 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); + } - 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++) { @@ -426,10 +424,247 @@ 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_queue: configures a request at hardware level - * @gadget: gadget + * _hardware_enqueue: configures a request at hardware level * @hwep: endpoint + * @hwreq: request * * This function returns an error code */ @@ -437,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) @@ -447,32 +682,36 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) hwreq->req.status = -EALREADY; - ret = usb_gadget_map_request(&ci->gadget, &hwreq->req, hwep->dir); + 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) - add_td_to_list(hwep, hwreq, 0); - - while (rest > 0) { - unsigned count = min(hwreq->req.length - hwreq->req.actual, - (unsigned)(pages * CI_HDRC_PAGE_SIZE)); - add_td_to_list(hwep, hwreq, count); - 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 - && (hwreq->req.length % hwep->ep.maxpacket == 0)) - add_td_to_list(hwep, hwreq, 0); + 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); @@ -480,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; @@ -497,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 */ @@ -513,23 +771,22 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) hwep->qh.ptr->td.token &= cpu_to_le32(~(TD_STATUS_HALTED|TD_STATUS_ACTIVE)); - if (hwep->type == USB_ENDPOINT_XFER_ISOC) { + if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == RX) { u32 mul = hwreq->req.length / hwep->ep.maxpacket; - if (hwreq->req.length % hwep->ep.maxpacket) + if (hwreq->req.length == 0 + || hwreq->req.length % hwep->ep.maxpacket) mul++; - hwep->qh.ptr->cap |= mul << __ffs(QH_MULT); + hwep->qh.ptr->cap |= cpu_to_le32(mul << __ffs(QH_MULT)); } - wmb(); /* synchronize before ep prime */ - ret = hw_ep_prime(ci, hwep->num, hwep->dir, hwep->type == USB_ENDPOINT_XFER_CONTROL); done: return ret; } -/* +/** * free_pending_td: remove a pending request for the endpoint * @hwep: endpoint */ @@ -542,10 +799,21 @@ static void free_pending_td(struct ci_hw_ep *hwep) kfree(pending); } +static int reprime_dtd(struct ci_hdrc *ci, struct ci_hw_ep *hwep, + struct td_node *node) +{ + hwep->qh.ptr->td.next = cpu_to_le32(node->dma); + hwep->qh.ptr->td.token &= + cpu_to_le32(~(TD_STATUS_HALTED | TD_STATUS_ACTIVE)); + + return hw_ep_prime(ci, hwep->num, hwep->dir, + hwep->type == USB_ENDPOINT_XFER_CONTROL); +} + /** * _hardware_dequeue: handles a request at hardware level - * @gadget: gadget - * @hwep: endpoint + * @hwep: endpoint + * @hwreq: request * * This function returns an error code */ @@ -555,6 +823,8 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) struct td_node *node, *tmpnode; 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; @@ -563,7 +833,14 @@ 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 || + ci->rev == CI_REVISION_22 || is_isoc) + if (!hw_read(ci, OP_ENDPTSTAT, BIT(n))) + reprime_dtd(ci, hwep, node); hwreq->req.status = -EALREADY; return -EBUSY; } @@ -580,12 +857,16 @@ 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 (hwep->dir) { + if (remaining_length && !is_isoc) { + if (hwep->dir == TX) { hwreq->req.status = -EPROTO; break; } @@ -602,7 +883,12 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) list_del_init(&node->td); } - usb_gadget_unmap_request(&hwep->ci->gadget, &hwreq->req, hwep->dir); + 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; @@ -647,7 +933,7 @@ __acquires(hwep->lock) if (hwreq->req.complete != NULL) { spin_unlock(hwep->lock); - hwreq->req.complete(&hwep->ep, &hwreq->req); + usb_gadget_giveback_request(&hwep->ep, &hwreq->req); spin_lock(hwep->lock); } } @@ -658,6 +944,44 @@ __acquires(hwep->lock) return 0; } +static int _ep_set_halt(struct usb_ep *ep, int value, bool check_transfer) +{ + struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + int direction, retval = 0; + unsigned long flags; + + if (ep == NULL || hwep->ep.desc == NULL) + return -EINVAL; + + if (usb_endpoint_xfer_isoc(hwep->ep.desc)) + return -EOPNOTSUPP; + + spin_lock_irqsave(hwep->lock, flags); + + if (value && hwep->dir == TX && check_transfer && + !list_empty(&hwep->qh.queue) && + !usb_endpoint_xfer_control(hwep->ep.desc)) { + spin_unlock_irqrestore(hwep->lock, flags); + return -EAGAIN; + } + + direction = hwep->dir; + do { + retval |= hw_ep_set_halt(hwep->ci, hwep->num, hwep->dir, value); + + if (!value) + hwep->wedge = 0; + + if (hwep->type == USB_ENDPOINT_XFER_CONTROL) + hwep->dir = (hwep->dir == TX) ? RX : TX; + + } while (hwep->dir != direction); + + spin_unlock_irqrestore(hwep->lock, flags); + return retval; +} + + /** * _gadget_stop_activity: stops all USB activity, flushes & disables all endpts * @gadget: gadget @@ -670,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); @@ -683,9 +1001,6 @@ static int _gadget_stop_activity(struct usb_gadget *gadget) usb_ep_fifo_flush(&ci->ep0out->ep); usb_ep_fifo_flush(&ci->ep0in->ep); - if (ci->driver) - ci->driver->disconnect(gadget); - /* make sure to disable all endpoints */ gadget_for_each_ep(ep, gadget) { usb_ep_disable(ep); @@ -696,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; } @@ -713,8 +1034,12 @@ __releases(ci->lock) __acquires(ci->lock) { int retval; + u32 intr; spin_unlock(&ci->lock); + if (ci->gadget.speed != USB_SPEED_UNKNOWN) + usb_gadget_udc_reset(&ci->gadget, ci->driver); + retval = _gadget_stop_activity(&ci->gadget); if (retval) goto done; @@ -723,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; @@ -752,8 +1082,12 @@ static void isr_get_status_complete(struct usb_ep *ep, struct usb_request *req) /** * _ep_queue: queues (submits) an I/O request to an endpoint + * @ep: endpoint + * @req: request + * @gfp_flags: GFP flags (not used) * * Caller must hold lock + * This function returns an error code */ static int _ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t __maybe_unused gfp_flags) @@ -772,18 +1106,23 @@ static int _ep_queue(struct usb_ep *ep, struct usb_request *req, ci->ep0out : ci->ep0in; if (!list_empty(&hwep->qh.queue)) { _ep_nuke(hwep); - retval = -EOVERFLOW; dev_warn(hwep->ci->dev, "endpoint ctrl %X nuked\n", _usb_addr(hwep)); } } if (usb_endpoint_xfer_isoc(hwep->ep.desc) && - hwreq->req.length > (1 + hwep->ep.mult) * hwep->ep.maxpacket) { + hwreq->req.length > hwep->ep.mult * hwep->ep.maxpacket) { dev_err(hwep->ci->dev, "request length too big for isochronous\n"); 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"); @@ -839,9 +1178,8 @@ __acquires(hwep->lock) } if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { - /* Assume that device is bus powered for now. */ - *(u16 *)req->buf = ci->remote_wakeup << 1; - retval = 0; + *(u16 *)req->buf = (ci->remote_wakeup << 1) | + ci->gadget.is_selfpowered; } else if ((setup->bRequestType & USB_RECIP_MASK) \ == USB_RECIP_ENDPOINT) { dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? @@ -880,9 +1218,14 @@ 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; + if (ci->address) + usb_gadget_set_state(&ci->gadget, USB_STATE_ADDRESS); } spin_lock_irqsave(&ci->lock, flags); @@ -899,16 +1242,22 @@ 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; + /* + * Unexpected USB controller behavior, caused by bad signal integrity + * or ground reference problems, can lead to isr_setup_status_phase + * being called with ci->status equal to NULL. + * If this situation occurs, you should review your USB hardware design. + */ + if (WARN_ON_ONCE(!ci->status)) + return -EPIPE; + hwep = (ci->ep0_dir == TX) ? ci->ep0out : ci->ep0in; 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); } /** @@ -937,7 +1286,7 @@ __acquires(hwep->lock) if ((hwep->type == USB_ENDPOINT_XFER_CONTROL) && hwreq->req.length) hweptemp = hwep->ci->ep0in; - hwreq->req.complete(&hweptemp->ep, &hwreq->req); + usb_gadget_giveback_request(&hweptemp->ep, &hwreq->req); spin_lock(hwep->lock); } } @@ -948,6 +1297,183 @@ __acquires(hwep->lock) return retval; } +static int otg_a_alt_hnp_support(struct ci_hdrc *ci) +{ + dev_warn(&ci->gadget.dev, + "connect the device to an alternate port if you want HNP\n"); + return isr_setup_status_phase(ci); +} + +/** + * isr_setup_packet_handler: setup packet handler + * @ci: UDC descriptor + * + * This function handles setup packet + */ +static void isr_setup_packet_handler(struct ci_hdrc *ci) +__releases(ci->lock) +__acquires(ci->lock) +{ + struct ci_hw_ep *hwep = &ci->ci_hw_ep[0]; + struct usb_ctrlrequest req; + int type, num, dir, err = -EINVAL; + u8 tmode = 0; + + /* + * Flush data and handshake transactions of previous + * setup packet. + */ + _ep_nuke(ci->ep0out); + _ep_nuke(ci->ep0in); + + /* read_setup_packet */ + do { + hw_test_and_set_setup_guard(ci); + memcpy(&req, &hwep->qh.ptr->setup, sizeof(req)); + } while (!hw_test_and_clear_setup_guard(ci)); + + type = req.bRequestType; + + ci->ep0_dir = (type & USB_DIR_IN) ? TX : RX; + + switch (req.bRequest) { + case USB_REQ_CLEAR_FEATURE: + if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) == + USB_ENDPOINT_HALT) { + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + dir = (num & USB_ENDPOINT_DIR_MASK) ? TX : RX; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir == TX) + num += ci->hw_ep_max / 2; + if (!ci->ci_hw_ep[num].wedge) { + spin_unlock(&ci->lock); + err = usb_ep_clear_halt( + &ci->ci_hw_ep[num].ep); + spin_lock(&ci->lock); + if (err) + break; + } + err = isr_setup_status_phase(ci); + } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) && + le16_to_cpu(req.wValue) == + USB_DEVICE_REMOTE_WAKEUP) { + if (req.wLength != 0) + break; + ci->remote_wakeup = 0; + err = isr_setup_status_phase(ci); + } else { + goto delegate; + } + break; + case USB_REQ_GET_STATUS: + if ((type != (USB_DIR_IN|USB_RECIP_DEVICE) || + le16_to_cpu(req.wIndex) == OTG_STS_SELECTOR) && + type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && + type != (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 2 || + le16_to_cpu(req.wValue) != 0) + break; + err = isr_get_status_response(ci, &req); + break; + case USB_REQ_SET_ADDRESS: + if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 0 || + le16_to_cpu(req.wIndex) != 0) + break; + ci->address = (u8)le16_to_cpu(req.wValue); + ci->setaddr = true; + err = isr_setup_status_phase(ci); + break; + case USB_REQ_SET_FEATURE: + if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) == + USB_ENDPOINT_HALT) { + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + dir = (num & USB_ENDPOINT_DIR_MASK) ? TX : RX; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir == TX) + num += ci->hw_ep_max / 2; + + spin_unlock(&ci->lock); + err = _ep_set_halt(&ci->ci_hw_ep[num].ep, 1, false); + spin_lock(&ci->lock); + if (!err) + isr_setup_status_phase(ci); + } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) { + if (req.wLength != 0) + break; + switch (le16_to_cpu(req.wValue)) { + case USB_DEVICE_REMOTE_WAKEUP: + ci->remote_wakeup = 1; + err = isr_setup_status_phase(ci); + break; + case USB_DEVICE_TEST_MODE: + tmode = le16_to_cpu(req.wIndex) >> 8; + switch (tmode) { + 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); + break; + default: + break; + } + break; + case USB_DEVICE_B_HNP_ENABLE: + if (ci_otg_is_fsm_mode(ci)) { + ci->gadget.b_hnp_enable = 1; + err = isr_setup_status_phase( + ci); + } + break; + case USB_DEVICE_A_ALT_HNP_SUPPORT: + if (ci_otg_is_fsm_mode(ci)) + err = otg_a_alt_hnp_support(ci); + break; + case USB_DEVICE_A_HNP_SUPPORT: + if (ci_otg_is_fsm_mode(ci)) { + ci->gadget.a_hnp_support = 1; + err = isr_setup_status_phase( + ci); + } + break; + default: + goto delegate; + } + } else { + goto delegate; + } + break; + default: +delegate: + if (req.wLength == 0) /* no data phase */ + ci->ep0_dir = TX; + + spin_unlock(&ci->lock); + err = ci->driver->setup(&ci->gadget, &req); + spin_lock(&ci->lock); + break; + } + + if (err < 0) { + spin_unlock(&ci->lock); + if (_ep_set_halt(&hwep->ep, 1, false)) + dev_err(ci->dev, "error: _ep_set_halt\n"); + spin_lock(&ci->lock); + } +} + /** * isr_tr_complete_handler: transaction complete interrupt handler * @ci: UDC descriptor @@ -959,12 +1485,10 @@ __releases(ci->lock) __acquires(ci->lock) { unsigned i; - u8 tmode = 0; + int err; for (i = 0; i < ci->hw_ep_max; i++) { struct ci_hw_ep *hwep = &ci->ci_hw_ep[i]; - int type, num, dir, err = -EINVAL; - struct usb_ctrlrequest req; if (hwep->ep.desc == NULL) continue; /* not configured */ @@ -976,163 +1500,25 @@ __acquires(ci->lock) err = isr_setup_status_phase(ci); if (err < 0) { spin_unlock(&ci->lock); - if (usb_ep_set_halt(&hwep->ep)) + if (_ep_set_halt(&hwep->ep, 1, false)) dev_err(ci->dev, - "error: ep_set_halt\n"); + "error: _ep_set_halt\n"); spin_lock(&ci->lock); } } } - if (hwep->type != USB_ENDPOINT_XFER_CONTROL || - !hw_test_and_clear_setup_status(ci, i)) - continue; - - if (i != 0) { - dev_warn(ci->dev, "ctrl traffic at endpoint %d\n", i); - continue; - } - - /* - * Flush data and handshake transactions of previous - * setup packet. - */ - _ep_nuke(ci->ep0out); - _ep_nuke(ci->ep0in); - - /* read_setup_packet */ - do { - hw_test_and_set_setup_guard(ci); - memcpy(&req, &hwep->qh.ptr->setup, sizeof(req)); - } while (!hw_test_and_clear_setup_guard(ci)); - - type = req.bRequestType; - - ci->ep0_dir = (type & USB_DIR_IN) ? TX : RX; - - switch (req.bRequest) { - case USB_REQ_CLEAR_FEATURE: - if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && - le16_to_cpu(req.wValue) == - USB_ENDPOINT_HALT) { - if (req.wLength != 0) - break; - num = le16_to_cpu(req.wIndex); - dir = num & USB_ENDPOINT_DIR_MASK; - num &= USB_ENDPOINT_NUMBER_MASK; - if (dir) /* TX */ - num += ci->hw_ep_max/2; - if (!ci->ci_hw_ep[num].wedge) { - spin_unlock(&ci->lock); - err = usb_ep_clear_halt( - &ci->ci_hw_ep[num].ep); - spin_lock(&ci->lock); - if (err) - break; - } - err = isr_setup_status_phase(ci); - } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) && - le16_to_cpu(req.wValue) == - USB_DEVICE_REMOTE_WAKEUP) { - if (req.wLength != 0) - break; - ci->remote_wakeup = 0; - err = isr_setup_status_phase(ci); - } else { - goto delegate; - } - break; - case USB_REQ_GET_STATUS: - if (type != (USB_DIR_IN|USB_RECIP_DEVICE) && - type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && - type != (USB_DIR_IN|USB_RECIP_INTERFACE)) - goto delegate; - if (le16_to_cpu(req.wLength) != 2 || - le16_to_cpu(req.wValue) != 0) - break; - err = isr_get_status_response(ci, &req); - break; - case USB_REQ_SET_ADDRESS: - if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) - goto delegate; - if (le16_to_cpu(req.wLength) != 0 || - le16_to_cpu(req.wIndex) != 0) - break; - ci->address = (u8)le16_to_cpu(req.wValue); - ci->setaddr = true; - err = isr_setup_status_phase(ci); - break; - case USB_REQ_SET_FEATURE: - if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && - le16_to_cpu(req.wValue) == - USB_ENDPOINT_HALT) { - if (req.wLength != 0) - break; - num = le16_to_cpu(req.wIndex); - dir = num & USB_ENDPOINT_DIR_MASK; - num &= USB_ENDPOINT_NUMBER_MASK; - if (dir) /* TX */ - num += ci->hw_ep_max/2; - - spin_unlock(&ci->lock); - err = usb_ep_set_halt(&ci->ci_hw_ep[num].ep); - spin_lock(&ci->lock); - if (!err) - isr_setup_status_phase(ci); - } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) { - if (req.wLength != 0) - break; - switch (le16_to_cpu(req.wValue)) { - case USB_DEVICE_REMOTE_WAKEUP: - ci->remote_wakeup = 1; - err = isr_setup_status_phase(ci); - break; - 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: - ci->test_mode = tmode; - err = isr_setup_status_phase( - ci); - break; - default: - break; - } - default: - goto delegate; - } - } else { - goto delegate; - } - break; - default: -delegate: - if (req.wLength == 0) /* no data phase */ - ci->ep0_dir = TX; - - spin_unlock(&ci->lock); - err = ci->driver->setup(&ci->gadget, &req); - spin_lock(&ci->lock); - break; - } - - if (err < 0) { - spin_unlock(&ci->lock); - if (usb_ep_set_halt(&hwep->ep)) - dev_err(ci->dev, "error: ep_set_halt\n"); - spin_lock(&ci->lock); - } + /* Only handle setup packet below */ + if (i == 0 && + hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(0))) + isr_setup_packet_handler(ci); } } /****************************************************************************** * ENDPT block *****************************************************************************/ -/** +/* * ep_enable: configure endpoint, making it usable * * Check usb_ep_enable() at "usb_gadget.h" for details @@ -1152,28 +1538,42 @@ static int ep_enable(struct usb_ep *ep, /* only internal SW should enable ctrl endpts */ - hwep->ep.desc = desc; - - if (!list_empty(&hwep->qh.queue)) + if (!list_empty(&hwep->qh.queue)) { dev_warn(hwep->ci->dev, "enabling a non-empty endpoint!\n"); + spin_unlock_irqrestore(hwep->lock, flags); + return -EBUSY; + } + + hwep->ep.desc = desc; hwep->dir = usb_endpoint_dir_in(desc) ? TX : RX; hwep->num = usb_endpoint_num(desc); hwep->type = usb_endpoint_type(desc); - hwep->ep.maxpacket = usb_endpoint_maxp(desc) & 0x07ff; - hwep->ep.mult = QH_ISO_MULT(usb_endpoint_maxp(desc)); + hwep->ep.maxpacket = usb_endpoint_maxp(desc); + hwep->ep.mult = usb_endpoint_maxp_mult(desc); if (hwep->type == USB_ENDPOINT_XFER_CONTROL) cap |= QH_IOS; - if (hwep->num) - cap |= QH_ZLT; + + cap |= QH_ZLT; cap |= (hwep->ep.maxpacket << __ffs(QH_MAX_PKT)) & QH_MAX_PKT; + /* + * For ISO-TX, we set mult at QH as the largest value, and use + * MultO at TD as real mult value. + */ + if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == TX) + cap |= 3 << __ffs(QH_MULT); hwep->qh.ptr->cap = cpu_to_le32(cap); hwep->qh.ptr->td.next |= cpu_to_le32(TD_TERMINATE); /* needed? */ + if (hwep->num != 0 && hwep->type == USB_ENDPOINT_XFER_CONTROL) { + dev_err(hwep->ci->dev, "Set control xfer at non-ep0\n"); + retval = -EINVAL; + } + /* * Enable endpoints in the HW other than ep0 as ep0 * is always enabled @@ -1186,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 @@ -1203,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 */ @@ -1222,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; @@ -1243,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 @@ -1276,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 @@ -1292,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 @@ -1307,6 +1715,7 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); struct ci_hw_req *hwreq = container_of(req, struct ci_hw_req, req); unsigned long flags; + struct td_node *node, *tmpnode; if (ep == NULL || req == NULL || hwreq->req.status != -EALREADY || hwep->ep.desc == NULL || list_empty(&hwreq->queue) || @@ -1314,19 +1723,28 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) return -EINVAL; spin_lock_irqsave(hwep->lock, flags); + if (hwep->ci->gadget.speed != USB_SPEED_UNKNOWN) + hw_ep_flush(hwep->ci, hwep->num, hwep->dir); - 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); + list_del(&node->td); + kfree(node); + } /* pop request */ list_del_init(&hwreq->queue); 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) { spin_unlock(hwep->lock); - hwreq->req.complete(&hwep->ep, &hwreq->req); + usb_gadget_giveback_request(&hwep->ep, &hwreq->req); spin_lock(hwep->lock); } @@ -1334,51 +1752,17 @@ 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 */ static int ep_set_halt(struct usb_ep *ep, int value) { - struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); - int direction, retval = 0; - unsigned long flags; - - if (ep == NULL || hwep->ep.desc == NULL) - return -EINVAL; - - if (usb_endpoint_xfer_isoc(hwep->ep.desc)) - return -EOPNOTSUPP; - - spin_lock_irqsave(hwep->lock, flags); - -#ifndef STALL_IN - /* g_file_storage MS compliant but g_zero fails chapter 9 compliance */ - if (value && hwep->type == USB_ENDPOINT_XFER_BULK && hwep->dir == TX && - !list_empty(&hwep->qh.queue)) { - spin_unlock_irqrestore(hwep->lock, flags); - return -EAGAIN; - } -#endif - - direction = hwep->dir; - do { - retval |= hw_ep_set_halt(hwep->ci, hwep->num, hwep->dir, value); - - if (!value) - hwep->wedge = 0; - - if (hwep->type == USB_ENDPOINT_XFER_CONTROL) - hwep->dir = (hwep->dir == TX) ? RX : TX; - - } while (hwep->dir != direction); - - spin_unlock_irqrestore(hwep->lock, flags); - return retval; + 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 @@ -1398,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 @@ -1414,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 */ @@ -1439,37 +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; - - if (!(ci->platdata->flags & CI_HDRC_PULLUP_ON_VBUS)) - return -EOPNOTSUPP; + 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; +} + +/* + * 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 (gadget_ready) { - if (is_active) { - pm_runtime_get_sync(&_gadget->dev); - hw_device_reset(ci, USBMODE_CM_DC); + 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 { - 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); + 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) @@ -1479,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; @@ -1497,42 +1933,90 @@ static int ci_udc_vbus_draw(struct usb_gadget *_gadget, unsigned ma) { struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); - if (ci->transceiver) - return usb_phy_set_power(ci->transceiver, ma); + if (ci->usb_phy) + return usb_phy_set_power(ci->usb_phy, ma); return -ENOTSUPP; } +static int ci_udc_selfpowered(struct usb_gadget *_gadget, int is_on) +{ + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + struct ci_hw_ep *hwep = ci->ep0in; + unsigned long flags; + + spin_lock_irqsave(hwep->lock, flags); + _gadget->is_selfpowered = (is_on != 0); + spin_unlock_irqrestore(hwep->lock, flags); + + return 0; +} + /* 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) { struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + /* + * Data+ pullup controlled by OTG state machine in OTG fsm mode; + * and don't touch Data+ in host mode for dual role config. + */ + if (ci_otg_is_fsm_mode(ci) || ci->role == CI_ROLE_HOST) + return 0; + + 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); + 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; } static int ci_udc_start(struct usb_gadget *gadget, struct usb_gadget_driver *driver); -static int ci_udc_stop(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, .pullup = ci_udc_pullup, .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) @@ -1553,20 +2037,32 @@ static int init_eps(struct ci_hdrc *ci) hwep->ep.name = hwep->name; hwep->ep.ops = &usb_ep_ops; + + if (i == 0) { + hwep->ep.caps.type_control = true; + } else { + hwep->ep.caps.type_iso = true; + hwep->ep.caps.type_bulk = true; + hwep->ep.caps.type_int = true; + } + + if (j == TX) + hwep->ep.caps.dir_in = true; + else + hwep->ep.caps.dir_out = true; + /* * for ep0: maxP defined in desc, for other * eps, maxP is set by epautoconfig() called * by gadget layer */ - hwep->ep.maxpacket = (unsigned short)~0; + usb_ep_set_maxpacket_limit(&hwep->ep, (unsigned short)~0); INIT_LIST_HEAD(&hwep->qh.queue); - hwep->qh.ptr = dma_pool_alloc(ci->qh_pool, GFP_KERNEL, - &hwep->qh.dma); + hwep->qh.ptr = dma_pool_zalloc(ci->qh_pool, GFP_KERNEL, + &hwep->qh.dma); if (hwep->qh.ptr == NULL) retval = -ENOMEM; - else - memset(hwep->qh.ptr, 0, sizeof(*hwep->qh.ptr)); /* * set up shorthands for ep0 out and in endpoints, @@ -1578,7 +2074,7 @@ static int init_eps(struct ci_hdrc *ci) else ci->ep0in = hwep; - hwep->ep.maxpacket = CTRL_PAYLOAD_MAX; + usb_ep_set_maxpacket_limit(&hwep->ep, CTRL_PAYLOAD_MAX); continue; } @@ -1595,6 +2091,8 @@ static void destroy_eps(struct ci_hdrc *ci) for (i = 0; i < ci->hw_ep_max; i++) { struct ci_hw_ep *hwep = &ci->ci_hw_ep[i]; + if (hwep->pending_td) + free_pending_td(hwep); dma_pool_free(ci->qh_pool, hwep->qh.ptr, hwep->qh.dma); } } @@ -1610,13 +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); - unsigned long flags; - 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) @@ -1626,62 +2122,71 @@ static int ci_udc_start(struct usb_gadget *gadget, retval = usb_ep_enable(&ci->ep0in->ep); if (retval) return retval; - spin_lock_irqsave(&ci->lock, flags); ci->driver = driver; - pm_runtime_get_sync(&ci->gadget.dev); - if (ci->platdata->flags & CI_HDRC_PULLUP_ON_VBUS) { - if (ci->vbus_active) { - if (ci->platdata->flags & CI_HDRC_REGS_SHARED) - hw_device_reset(ci, USBMODE_CM_DC); - } else { - pm_runtime_put_sync(&ci->gadget.dev); - goto done; - } + + /* Start otg fsm for B-device */ + if (ci_otg_is_fsm_mode(ci) && ci->fsm.id) { + ci_hdrc_otg_fsm_start(ci); + return retval; } - retval = hw_device_state(ci, ci->ep0out->qh.dma); - if (retval) - pm_runtime_put_sync(&ci->gadget.dev); + if (ci->vbus_active) + ci_hdrc_gadget_connect(gadget, 1); + else + usb_udc_vbus_handler(&ci->gadget, false); - done: - spin_unlock_irqrestore(&ci->lock, flags); return retval; } -/** +static void ci_udc_stop_for_otg_fsm(struct ci_hdrc *ci) +{ + if (!ci_otg_is_fsm_mode(ci)) + return; + + mutex_lock(&ci->fsm.lock); + if (ci->fsm.otg->state == OTG_STATE_A_PERIPHERAL) { + ci->fsm.a_bidl_adis_tmout = 1; + ci_hdrc_otg_fsm_start(ci); + } else if (ci->fsm.otg->state == OTG_STATE_B_PERIPHERAL) { + ci->fsm.protocol = PROTO_UNDEF; + ci->fsm.otg->state = OTG_STATE_UNDEFINED; + } + mutex_unlock(&ci->fsm.lock); +} + +/* * ci_udc_stop: unregister a gadget driver */ -static int ci_udc_stop(struct usb_gadget *gadget, - struct usb_gadget_driver *driver) +static int ci_udc_stop(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->driver = NULL; - if (!(ci->platdata->flags & CI_HDRC_PULLUP_ON_VBUS) || - ci->vbus_active) { + if (ci->vbus_active) { hw_device_state(ci, 0); + spin_unlock_irqrestore(&ci->lock, flags); if (ci->platdata->notify_event) ci->platdata->notify_event(ci, CI_HDRC_CONTROLLER_STOPPED_EVENT); - ci->driver = NULL; - spin_unlock_irqrestore(&ci->lock, flags); _gadget_stop_activity(&ci->gadget); spin_lock_irqsave(&ci->lock, flags); - pm_runtime_put(&ci->gadget.dev); + pm_runtime_put(ci->dev); } spin_unlock_irqrestore(&ci->lock, flags); + ci_udc_stop_for_otg_fsm(ci); return 0; } /****************************************************************************** * BUS block *****************************************************************************/ -/** +/* * udc_irq: ci interrupt handler * * This function returns IRQ_HANDLED if the IRQ has been handled @@ -1714,25 +2219,35 @@ 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->suspended && ci->driver->resume) { - spin_unlock(&ci->lock); - ci->driver->resume(&ci->gadget); - spin_lock(&ci->lock); + 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); + ci->driver->resume(&ci->gadget); + spin_lock(&ci->lock); + } ci->suspended = 0; + usb_gadget_set_state(&ci->gadget, + ci->resume_state); } } - if (USBi_UI & intr) + if ((USBi_UI | USBi_UEI) & intr) isr_tr_complete_handler(ci); - if (USBi_SLI & intr) { + if ((USBi_SLI & intr) && !(ci->suspended)) { + ci->suspended = 1; + ci->resume_state = ci->gadget.state; if (ci->gadget.speed != USB_SPEED_UNKNOWN && ci->driver->suspend) { - ci->suspended = 1; spin_unlock(&ci->lock); ci->driver->suspend(&ci->gadget); spin_lock(&ci->lock); } + usb_gadget_set_state(&ci->gadget, + USB_STATE_SUSPENDED); } retval = IRQ_HANDLED; } else { @@ -1750,26 +2265,34 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci) static int udc_start(struct ci_hdrc *ci) { struct device *dev = ci->dev; + struct usb_otg_caps *otg_caps = &ci->platdata->ci_otg_caps; int retval = 0; - spin_lock_init(&ci->lock); - ci->gadget.ops = &usb_gadget_ops; ci->gadget.speed = USB_SPEED_UNKNOWN; ci->gadget.max_speed = USB_SPEED_HIGH; - ci->gadget.is_otg = 0; 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)) + ci->gadget.is_otg = 1; INIT_LIST_HEAD(&ci->gadget.ep_list); /* alloc resources */ - ci->qh_pool = dma_pool_create("ci_hw_qh", dev, + ci->qh_pool = dma_pool_create("ci_hw_qh", dev->parent, sizeof(struct ci_hw_qh), 64, CI_HDRC_PAGE_SIZE); if (ci->qh_pool == NULL) return -ENOMEM; - ci->td_pool = dma_pool_create("ci_hw_td", dev, + ci->td_pool = dma_pool_create("ci_hw_td", dev->parent, sizeof(struct ci_hw_td), 64, CI_HDRC_PAGE_SIZE); if (ci->td_pool == NULL) { @@ -1783,52 +2306,12 @@ static int udc_start(struct ci_hdrc *ci) ci->gadget.ep0 = &ci->ep0in->ep; - if (ci->global_phy) { - ci->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); - if (IS_ERR(ci->transceiver)) - ci->transceiver = NULL; - } - - if (ci->platdata->flags & CI_HDRC_REQUIRE_TRANSCEIVER) { - if (ci->transceiver == NULL) { - retval = -ENODEV; - goto destroy_eps; - } - } - - if (!(ci->platdata->flags & CI_HDRC_REGS_SHARED)) { - retval = hw_device_reset(ci, USBMODE_CM_DC); - if (retval) - goto put_transceiver; - } - - if (ci->transceiver) { - retval = otg_set_peripheral(ci->transceiver->otg, - &ci->gadget); - if (retval) - goto put_transceiver; - } - retval = usb_add_gadget_udc(dev, &ci->gadget); if (retval) - goto remove_trans; - - pm_runtime_no_callbacks(&ci->gadget.dev); - pm_runtime_enable(&ci->gadget.dev); + goto destroy_eps; return retval; -remove_trans: - if (ci->transceiver) { - otg_set_peripheral(ci->transceiver->otg, NULL); - if (ci->global_phy) - usb_put_phy(ci->transceiver); - } - - dev_err(dev, "error = %i\n", retval); -put_transceiver: - if (ci->transceiver && ci->global_phy) - usb_put_phy(ci->transceiver); destroy_eps: destroy_eps(ci); free_pools: @@ -1838,14 +2321,14 @@ free_qh_pool: return retval; } -/** - * udc_remove: parent remove must call this to remove UDC +/* + * ci_hdrc_gadget_destroy: parent remove must call this to remove UDC * * No interrupts active, the IRQ has been released */ -static void udc_stop(struct ci_hdrc *ci) +void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) { - if (ci == NULL) + if (!ci->roles[CI_ROLE_GADGET]) return; usb_del_gadget_udc(&ci->gadget); @@ -1854,38 +2337,103 @@ static void udc_stop(struct ci_hdrc *ci) dma_pool_destroy(ci->td_pool); dma_pool_destroy(ci->qh_pool); +} + +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, + OTGSC_BSVIS | OTGSC_BSVIE); + + return 0; +} + +static void udc_id_switch_for_host(struct ci_hdrc *ci) +{ + /* + * host doesn't care B_SESSION_VALID event + * 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->transceiver) { - otg_set_peripheral(ci->transceiver->otg, NULL); - if (ci->global_phy) - usb_put_phy(ci->transceiver); + 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); } - /* my kobject is dynamic, I swear! */ - memset(&ci->gadget, 0, sizeof(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 enables the gadget role, if the device is "device capable". + * This function initializes the gadget, if the device is "device capable". */ int ci_hdrc_gadget_init(struct ci_hdrc *ci) { struct ci_role_driver *rdrv; + int ret; if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DC)) return -ENXIO; - rdrv = devm_kzalloc(ci->dev, sizeof(struct ci_role_driver), GFP_KERNEL); + rdrv = devm_kzalloc(ci->dev, sizeof(*rdrv), GFP_KERNEL); if (!rdrv) return -ENOMEM; - rdrv->start = udc_start; - rdrv->stop = udc_stop; + 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"; - ci->roles[CI_ROLE_GADGET] = rdrv; - return 0; + ret = udc_start(ci); + if (!ret) + ci->roles[CI_ROLE_GADGET] = rdrv; + + return ret; } |
