diff options
Diffstat (limited to 'drivers/usb/dwc3/gadget.c')
| -rw-r--r-- | drivers/usb/dwc3/gadget.c | 1037 |
1 files changed, 696 insertions, 341 deletions
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 2fdd767f8fe..dab7927d100 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -6,34 +6,14 @@ * Authors: Felipe Balbi <balbi@ti.com>, * Sebastian Andrzej Siewior <bigeasy@linutronix.de> * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer, - * without modification. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The names of the above-listed copyright holders may not be used - * to endorse or promote products derived from this software without - * specific prior written permission. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. * - * ALTERNATIVELY, this software may be distributed under the terms of the - * GNU General Public License ("GPL") version 2, as published by the Free - * Software Foundation. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. */ #include <linux/kernel.h> @@ -88,6 +68,22 @@ int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode) } /** + * dwc3_gadget_get_link_state - Gets current state of USB Link + * @dwc: pointer to our context structure + * + * Caller should take care of locking. This function will + * return the link state on success (>= 0) or -ETIMEDOUT. + */ +int dwc3_gadget_get_link_state(struct dwc3 *dwc) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + + return DWC3_DSTS_USBLNKST(reg); +} + +/** * dwc3_gadget_set_link_state - Sets USB Link to a particular State * @dwc: pointer to our context structure * @state: the state to put link into @@ -191,15 +187,12 @@ int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc) * improve this algorithm so that we better use the internal * FIFO space */ - for (num = 0; num < DWC3_ENDPOINTS_NUM; num++) { - struct dwc3_ep *dep = dwc->eps[num]; - int fifo_number = dep->number >> 1; + for (num = 0; num < dwc->num_in_eps; num++) { + /* bit0 indicates direction; 1 means IN ep */ + struct dwc3_ep *dep = dwc->eps[(num << 1) | 1]; int mult = 1; int tmp; - if (!(dep->number & 1)) - continue; - if (!(dep->flags & DWC3_EP_ENABLED)) continue; @@ -228,8 +221,7 @@ int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc) dev_vdbg(dwc->dev, "%s: Fifo Addr %04x Size %d\n", dep->name, last_fifo_depth, fifo_size & 0xffff); - dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(fifo_number), - fifo_size); + dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(num), fifo_size); last_fifo_depth += (fifo_size & 0xffff); } @@ -241,21 +233,23 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, int status) { struct dwc3 *dwc = dep->dwc; + int i; if (req->queued) { - if (req->request.num_mapped_sgs) - dep->busy_slot += req->request.num_mapped_sgs; - else + i = 0; + do { dep->busy_slot++; - - /* - * Skip LINK TRB. We can't use req->trb and check for - * DWC3_TRBCTL_LINK_TRB because it points the TRB we just - * completed (not the LINK TRB). - */ - if (((dep->busy_slot & DWC3_TRB_MASK) == DWC3_TRB_NUM - 1) && + /* + * Skip LINK TRB. We can't use req->trb and check for + * DWC3_TRBCTL_LINK_TRB because it points the TRB we + * just completed (not the LINK TRB). + */ + if (((dep->busy_slot & DWC3_TRB_MASK) == + DWC3_TRB_NUM- 1) && usb_endpoint_xfer_isoc(dep->endpoint.desc)) - dep->busy_slot++; + dep->busy_slot++; + } while(++i < req->request.num_mapped_sgs); + req->queued = false; } list_del(&req->list); req->trb = NULL; @@ -304,11 +298,76 @@ static const char *dwc3_gadget_ep_cmd_string(u8 cmd) } } +static const char *dwc3_gadget_generic_cmd_string(u8 cmd) +{ + switch (cmd) { + case DWC3_DGCMD_SET_LMP: + return "Set LMP"; + case DWC3_DGCMD_SET_PERIODIC_PAR: + return "Set Periodic Parameters"; + case DWC3_DGCMD_XMIT_FUNCTION: + return "Transmit Function Wake Device Notification"; + case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO: + return "Set Scratchpad Buffer Array Address Lo"; + case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI: + return "Set Scratchpad Buffer Array Address Hi"; + case DWC3_DGCMD_SELECTED_FIFO_FLUSH: + return "Selected FIFO Flush"; + case DWC3_DGCMD_ALL_FIFO_FLUSH: + return "All FIFO Flush"; + case DWC3_DGCMD_SET_ENDPOINT_NRDY: + return "Set Endpoint NRDY"; + case DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK: + return "Run SoC Bus Loopback Test"; + default: + return "UNKNOWN"; + } +} + +static const char *dwc3_gadget_link_string(enum dwc3_link_state link_state) +{ + switch (link_state) { + case DWC3_LINK_STATE_U0: + return "U0"; + case DWC3_LINK_STATE_U1: + return "U1"; + case DWC3_LINK_STATE_U2: + return "U2"; + case DWC3_LINK_STATE_U3: + return "U3"; + case DWC3_LINK_STATE_SS_DIS: + return "SS.Disabled"; + case DWC3_LINK_STATE_RX_DET: + return "RX.Detect"; + case DWC3_LINK_STATE_SS_INACT: + return "SS.Inactive"; + case DWC3_LINK_STATE_POLL: + return "Polling"; + case DWC3_LINK_STATE_RECOV: + return "Recovery"; + case DWC3_LINK_STATE_HRESET: + return "Hot Reset"; + case DWC3_LINK_STATE_CMPLY: + return "Compliance"; + case DWC3_LINK_STATE_LPBK: + return "Loopback"; + case DWC3_LINK_STATE_RESET: + return "Reset"; + case DWC3_LINK_STATE_RESUME: + return "Resume"; + default: + return "UNKNOWN link state\n"; + } +} + int dwc3_send_gadget_generic_command(struct dwc3 *dwc, int cmd, u32 param) { u32 timeout = 500; u32 reg; + dev_vdbg(dwc->dev, "generic cmd '%s' [%d] param %08x\n", + dwc3_gadget_generic_cmd_string(cmd), cmd, param); + dwc3_writel(dwc->regs, DWC3_DGCMDPAR, param); dwc3_writel(dwc->regs, DWC3_DGCMD, cmd | DWC3_DGCMD_CMDACT); @@ -338,9 +397,9 @@ int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, u32 timeout = 500; u32 reg; - dev_vdbg(dwc->dev, "%s: cmd '%s' params %08x %08x %08x\n", + dev_vdbg(dwc->dev, "%s: cmd '%s' [%d] params %08x %08x %08x\n", dep->name, - dwc3_gadget_ep_cmd_string(cmd), params->param0, + dwc3_gadget_ep_cmd_string(cmd), cmd, params->param0, params->param1, params->param2); dwc3_writel(dwc->regs, DWC3_DEPCMDPAR0(ep), params->param0); @@ -435,7 +494,7 @@ static int dwc3_gadget_start_config(struct dwc3 *dwc, struct dwc3_ep *dep) static int dwc3_gadget_set_ep_config(struct dwc3 *dwc, struct dwc3_ep *dep, const struct usb_endpoint_descriptor *desc, const struct usb_ss_ep_comp_descriptor *comp_desc, - bool ignore) + bool ignore, bool restore) { struct dwc3_gadget_ep_cmd_params params; @@ -454,6 +513,11 @@ static int dwc3_gadget_set_ep_config(struct dwc3 *dwc, struct dwc3_ep *dep, if (ignore) params.param0 |= DWC3_DEPCFG_IGN_SEQ_NUM; + if (restore) { + params.param0 |= DWC3_DEPCFG_ACTION_RESTORE; + params.param2 |= dep->saved_state; + } + params.param1 = DWC3_DEPCFG_XFER_COMPLETE_EN | DWC3_DEPCFG_XFER_NOT_READY_EN; @@ -512,11 +576,13 @@ static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep) static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, const struct usb_endpoint_descriptor *desc, const struct usb_ss_ep_comp_descriptor *comp_desc, - bool ignore) + bool ignore, bool restore) { struct dwc3 *dwc = dep->dwc; u32 reg; - int ret = -ENOMEM; + int ret; + + dev_vdbg(dwc->dev, "Enabling %s\n", dep->name); if (!(dep->flags & DWC3_EP_ENABLED)) { ret = dwc3_gadget_start_config(dwc, dep); @@ -524,7 +590,8 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, return ret; } - ret = dwc3_gadget_set_ep_config(dwc, dep, desc, comp_desc, ignore); + ret = dwc3_gadget_set_ep_config(dwc, dep, desc, comp_desc, ignore, + restore); if (ret) return ret; @@ -564,13 +631,13 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, return 0; } -static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum); +static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force); static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep) { struct dwc3_request *req; if (!list_empty(&dep->req_queued)) { - dwc3_stop_active_transfer(dwc, dep->number); + dwc3_stop_active_transfer(dwc, dep->number, true); /* - giveback all requests to gadget driver */ while (!list_empty(&dep->req_queued)) { @@ -602,6 +669,10 @@ static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep) dwc3_remove_requests(dwc, dep); + /* make sure HW endpoint isn't stalled */ + if (dep->flags & DWC3_EP_STALL) + __dwc3_gadget_ep_set_halt(dep, 0); + reg = dwc3_readl(dwc->regs, DWC3_DALEPENA); reg &= ~DWC3_DALEPENA_EP(dep->number); dwc3_writel(dwc->regs, DWC3_DALEPENA, reg); @@ -674,10 +745,8 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, dev_err(dwc->dev, "invalid endpoint transfer type\n"); } - dev_vdbg(dwc->dev, "Enabling %s\n", dep->name); - spin_lock_irqsave(&dwc->lock, flags); - ret = __dwc3_gadget_ep_enable(dep, desc, ep->comp_desc, false); + ret = __dwc3_gadget_ep_enable(dep, desc, ep->comp_desc, false, false); spin_unlock_irqrestore(&dwc->lock, flags); return ret; @@ -749,33 +818,32 @@ static void dwc3_gadget_ep_free_request(struct usb_ep *ep, */ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_request *req, dma_addr_t dma, - unsigned length, unsigned last, unsigned chain) + unsigned length, unsigned last, unsigned chain, unsigned node) { struct dwc3 *dwc = dep->dwc; struct dwc3_trb *trb; - unsigned int cur_slot; - dev_vdbg(dwc->dev, "%s: req %p dma %08llx length %d%s%s\n", dep->name, req, (unsigned long long) dma, length, last ? " last" : "", chain ? " chain" : ""); - trb = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK]; - cur_slot = dep->free_slot; - dep->free_slot++; - /* Skip the LINK-TRB on ISOC */ - if (((cur_slot & DWC3_TRB_MASK) == DWC3_TRB_NUM - 1) && - usb_endpoint_xfer_isoc(dep->endpoint.desc)) - return; + trb = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK]; if (!req->trb) { dwc3_gadget_move_request_queued(req); req->trb = trb; req->trb_dma = dwc3_trb_dma_offset(dep, trb); + req->start_slot = dep->free_slot & DWC3_TRB_MASK; } + dep->free_slot++; + /* Skip the LINK-TRB on ISOC */ + if (((dep->free_slot & DWC3_TRB_MASK) == DWC3_TRB_NUM - 1) && + usb_endpoint_xfer_isoc(dep->endpoint.desc)) + dep->free_slot++; + trb->size = DWC3_TRB_SIZE_LENGTH(length); trb->bpl = lower_32_bits(dma); trb->bph = upper_32_bits(dma); @@ -786,10 +854,10 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, break; case USB_ENDPOINT_XFER_ISOC: - trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS_FIRST; - - if (!req->request.no_interrupt) - trb->ctrl |= DWC3_TRB_CTRL_IOC; + if (!node) + trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS_FIRST; + else + trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS; break; case USB_ENDPOINT_XFER_BULK: @@ -804,17 +872,19 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, BUG(); } + if (!req->request.no_interrupt && !chain) + trb->ctrl |= DWC3_TRB_CTRL_IOC; + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { trb->ctrl |= DWC3_TRB_CTRL_ISP_IMI; trb->ctrl |= DWC3_TRB_CTRL_CSP; - } else { - if (chain) - trb->ctrl |= DWC3_TRB_CTRL_CHN; - - if (last) - trb->ctrl |= DWC3_TRB_CTRL_LST; + } else if (last) { + trb->ctrl |= DWC3_TRB_CTRL_LST; } + if (chain) + trb->ctrl |= DWC3_TRB_CTRL_CHN; + if (usb_endpoint_xfer_bulk(dep->endpoint.desc) && dep->stream_capable) trb->ctrl |= DWC3_TRB_CTRL_SID_SOFN(req->request.stream_id); @@ -885,6 +955,7 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep, bool starting) list_for_each_entry_safe(req, n, &dep->request_list, list) { unsigned length; dma_addr_t dma; + last_one = false; if (req->request.num_mapped_sgs > 0) { struct usb_request *request = &req->request; @@ -900,7 +971,9 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep, bool starting) if (i == (request->num_mapped_sgs - 1) || sg_is_last(s)) { - last_one = true; + if (list_is_last(&req->list, + &dep->request_list)) + last_one = true; chain = false; } @@ -912,7 +985,7 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep, bool starting) chain = false; dwc3_prepare_one_trb(dep, req, dma, length, - last_one, chain); + last_one, chain, i); if (last_one) break; @@ -930,7 +1003,7 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep, bool starting) last_one = 1; dwc3_prepare_one_trb(dep, req, dma, length, - last_one, false); + last_one, false, 0); if (last_one) break; @@ -977,13 +1050,14 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param, } memset(¶ms, 0, sizeof(params)); - params.param0 = upper_32_bits(req->trb_dma); - params.param1 = lower_32_bits(req->trb_dma); - if (start_new) + if (start_new) { + params.param0 = upper_32_bits(req->trb_dma); + params.param1 = lower_32_bits(req->trb_dma); cmd = DWC3_DEPCMD_STARTTRANSFER; - else + } else { cmd = DWC3_DEPCMD_UPDATETRANSFER; + } cmd |= DWC3_DEPCMD_PARAM(cmd_param); ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); @@ -1082,8 +1156,6 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) * */ if (dep->flags & DWC3_EP_PENDING_REQUEST) { - int ret; - /* * If xfernotready is already elapsed and it is a case * of isoc transfer, then issue END TRANSFER, so that @@ -1091,7 +1163,10 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) * notion of current microframe. */ if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { - dwc3_stop_active_transfer(dwc, dep->number); + if (list_empty(&dep->req_queued)) { + dwc3_stop_active_transfer(dwc, dep->number, true); + dep->flags = DWC3_EP_ENABLED; + } return 0; } @@ -1099,6 +1174,7 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) if (ret && ret != -EBUSY) dev_dbg(dwc->dev, "%s: failed to kick transfers\n", dep->name); + return ret; } /* @@ -1115,16 +1191,24 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) if (ret && ret != -EBUSY) dev_dbg(dwc->dev, "%s: failed to kick transfers\n", dep->name); + return ret; } /* - * 3. Missed ISOC Handling. We need to start isoc transfer on the saved - * uframe number. + * 4. Stream Capable Bulk Endpoints. We need to start the transfer + * right away, otherwise host will not know we have streams to be + * handled. */ - if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && - (dep->flags & DWC3_EP_MISSED_ISOC)) { - __dwc3_gadget_start_isoc(dwc, dep, dep->current_uf); - dep->flags &= ~DWC3_EP_MISSED_ISOC; + if (dep->stream_capable) { + int ret; + + ret = __dwc3_gadget_kick_transfer(dep, 0, true); + if (ret && ret != -EBUSY) { + struct dwc3 *dwc = dep->dwc; + + dev_dbg(dwc->dev, "%s: failed to kick transfers\n", + dep->name); + } } return 0; @@ -1183,7 +1267,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, } if (r == req) { /* wait until it is processed */ - dwc3_stop_active_transfer(dwc, dep->number); + dwc3_stop_active_transfer(dwc, dep->number, true); goto out1; } dev_err(dwc->dev, "request %p was not queued to %s\n", @@ -1214,23 +1298,18 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value) ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, DWC3_DEPCMD_SETSTALL, ¶ms); if (ret) - dev_err(dwc->dev, "failed to %s STALL on %s\n", - value ? "set" : "clear", + dev_err(dwc->dev, "failed to set STALL on %s\n", dep->name); else dep->flags |= DWC3_EP_STALL; } else { - if (dep->flags & DWC3_EP_WEDGE) - return 0; - ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, DWC3_DEPCMD_CLEARSTALL, ¶ms); if (ret) - dev_err(dwc->dev, "failed to %s STALL on %s\n", - value ? "set" : "clear", + dev_err(dwc->dev, "failed to clear STALL on %s\n", dep->name); else - dep->flags &= ~DWC3_EP_STALL; + dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE); } return ret; @@ -1410,7 +1489,7 @@ static int dwc3_gadget_set_selfpowered(struct usb_gadget *g, return 0; } -static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on) +static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) { u32 reg; u32 timeout = 500; @@ -1425,8 +1504,18 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on) if (dwc->revision >= DWC3_REVISION_194A) reg &= ~DWC3_DCTL_KEEP_CONNECT; reg |= DWC3_DCTL_RUN_STOP; + + if (dwc->has_hibernation) + reg |= DWC3_DCTL_KEEP_CONNECT; + + dwc->pullups_connected = true; } else { reg &= ~DWC3_DCTL_RUN_STOP; + + if (dwc->has_hibernation && !suspend) + reg &= ~DWC3_DCTL_KEEP_CONNECT; + + dwc->pullups_connected = false; } dwc3_writel(dwc->regs, DWC3_DCTL, reg); @@ -1463,12 +1552,39 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) is_on = !!is_on; spin_lock_irqsave(&dwc->lock, flags); - ret = dwc3_gadget_run_stop(dwc, is_on); + ret = dwc3_gadget_run_stop(dwc, is_on, false); spin_unlock_irqrestore(&dwc->lock, flags); return ret; } +static void dwc3_gadget_enable_irq(struct dwc3 *dwc) +{ + u32 reg; + + /* Enable all but Start and End of Frame IRQs */ + reg = (DWC3_DEVTEN_VNDRDEVTSTRCVEDEN | + DWC3_DEVTEN_EVNTOVERFLOWEN | + DWC3_DEVTEN_CMDCMPLTEN | + DWC3_DEVTEN_ERRTICERREN | + DWC3_DEVTEN_WKUPEVTEN | + DWC3_DEVTEN_ULSTCNGEN | + DWC3_DEVTEN_CONNECTDONEEN | + DWC3_DEVTEN_USBRSTEN | + DWC3_DEVTEN_DISCONNEVTEN); + + dwc3_writel(dwc->regs, DWC3_DEVTEN, reg); +} + +static void dwc3_gadget_disable_irq(struct dwc3 *dwc) +{ + /* mask all interrupts */ + dwc3_writel(dwc->regs, DWC3_DEVTEN, 0x00); +} + +static irqreturn_t dwc3_interrupt(int irq, void *_dwc); +static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc); + static int dwc3_gadget_start(struct usb_gadget *g, struct usb_gadget_driver *driver) { @@ -1476,8 +1592,18 @@ static int dwc3_gadget_start(struct usb_gadget *g, struct dwc3_ep *dep; unsigned long flags; int ret = 0; + int irq; u32 reg; + irq = platform_get_irq(to_platform_device(dwc->dev), 0); + ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt, + IRQF_SHARED, "dwc3", dwc); + if (ret) { + dev_err(dwc->dev, "failed to request irq #%d --> %d\n", + irq, ret); + goto err0; + } + spin_lock_irqsave(&dwc->lock, flags); if (dwc->gadget_driver) { @@ -1485,11 +1611,10 @@ static int dwc3_gadget_start(struct usb_gadget *g, dwc->gadget.name, dwc->gadget_driver->driver.name); ret = -EBUSY; - goto err0; + goto err1; } dwc->gadget_driver = driver; - dwc->gadget.dev.driver = &driver->driver; reg = dwc3_readl(dwc->regs, DWC3_DCFG); reg &= ~(DWC3_DCFG_SPEED_MASK); @@ -1507,10 +1632,25 @@ static int dwc3_gadget_start(struct usb_gadget *g, * STAR#9000525659: Clock Domain Crossing on DCTL in * USB 2.0 Mode */ - if (dwc->revision < DWC3_REVISION_220A) + if (dwc->revision < DWC3_REVISION_220A) { reg |= DWC3_DCFG_SUPERSPEED; - else - reg |= dwc->maximum_speed; + } else { + switch (dwc->maximum_speed) { + case USB_SPEED_LOW: + reg |= DWC3_DSTS_LOWSPEED; + break; + case USB_SPEED_FULL: + reg |= DWC3_DSTS_FULLSPEED1; + break; + case USB_SPEED_HIGH: + reg |= DWC3_DSTS_HIGHSPEED; + break; + case USB_SPEED_SUPER: /* FALLTHROUGH */ + case USB_SPEED_UNKNOWN: /* FALTHROUGH */ + default: + reg |= DWC3_DSTS_SUPERSPEED; + } + } dwc3_writel(dwc->regs, DWC3_DCFG, reg); dwc->start_config_issued = false; @@ -1519,33 +1659,43 @@ static int dwc3_gadget_start(struct usb_gadget *g, dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); dep = dwc->eps[0]; - ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, + false); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); - goto err0; + goto err2; } dep = dwc->eps[1]; - ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false); + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, + false); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); - goto err1; + goto err3; } /* begin to receive SETUP packets */ dwc->ep0state = EP0_SETUP_PHASE; dwc3_ep0_out_start(dwc); + dwc3_gadget_enable_irq(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); return 0; -err1: +err3: __dwc3_gadget_ep_disable(dwc->eps[0]); -err0: +err2: + dwc->gadget_driver = NULL; + +err1: spin_unlock_irqrestore(&dwc->lock, flags); + free_irq(irq, dwc); + +err0: return ret; } @@ -1554,17 +1704,21 @@ static int dwc3_gadget_stop(struct usb_gadget *g, { struct dwc3 *dwc = gadget_to_dwc(g); unsigned long flags; + int irq; spin_lock_irqsave(&dwc->lock, flags); + dwc3_gadget_disable_irq(dwc); __dwc3_gadget_ep_disable(dwc->eps[0]); __dwc3_gadget_ep_disable(dwc->eps[1]); dwc->gadget_driver = NULL; - dwc->gadget.dev.driver = NULL; spin_unlock_irqrestore(&dwc->lock, flags); + irq = platform_get_irq(to_platform_device(dwc->dev), 0); + free_irq(irq, dwc); + return 0; } @@ -1579,14 +1733,15 @@ static const struct usb_gadget_ops dwc3_gadget_ops = { /* -------------------------------------------------------------------------- */ -static int dwc3_gadget_init_endpoints(struct dwc3 *dwc) +static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc, + u8 num, u32 direction) { struct dwc3_ep *dep; - u8 epnum; + u8 i; - INIT_LIST_HEAD(&dwc->gadget.ep_list); + for (i = 0; i < num; i++) { + u8 epnum = (i << 1) | (!!direction); - for (epnum = 0; epnum < DWC3_ENDPOINTS_NUM; epnum++) { dep = kzalloc(sizeof(*dep), GFP_KERNEL); if (!dep) { dev_err(dwc->dev, "can't allocate endpoint %d\n", @@ -1596,15 +1751,18 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc) dep->dwc = dwc; dep->number = epnum; + dep->direction = !!direction; dwc->eps[epnum] = dep; snprintf(dep->name, sizeof(dep->name), "ep%d%s", epnum >> 1, (epnum & 1) ? "in" : "out"); + dep->endpoint.name = dep->name; - dep->direction = (epnum & 1); + + dev_vdbg(dwc->dev, "initializing %s\n", dep->name); if (epnum == 0 || epnum == 1) { - dep->endpoint.maxpacket = 512; + usb_ep_set_maxpacket_limit(&dep->endpoint, 512); dep->endpoint.maxburst = 1; dep->endpoint.ops = &dwc3_gadget_ep0_ops; if (!epnum) @@ -1612,7 +1770,7 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc) } else { int ret; - dep->endpoint.maxpacket = 1024; + usb_ep_set_maxpacket_limit(&dep->endpoint, 1024); dep->endpoint.max_streams = 15; dep->endpoint.ops = &dwc3_gadget_ep_ops; list_add_tail(&dep->endpoint.ep_list, @@ -1630,6 +1788,27 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc) return 0; } +static int dwc3_gadget_init_endpoints(struct dwc3 *dwc) +{ + int ret; + + INIT_LIST_HEAD(&dwc->gadget.ep_list); + + ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_out_eps, 0); + if (ret < 0) { + dev_vdbg(dwc->dev, "failed to allocate OUT endpoints\n"); + return ret; + } + + ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_in_eps, 1); + if (ret < 0) { + dev_vdbg(dwc->dev, "failed to allocate IN endpoints\n"); + return ret; + } + + return 0; +} + static void dwc3_gadget_free_endpoints(struct dwc3 *dwc) { struct dwc3_ep *dep; @@ -1637,29 +1816,112 @@ static void dwc3_gadget_free_endpoints(struct dwc3 *dwc) for (epnum = 0; epnum < DWC3_ENDPOINTS_NUM; epnum++) { dep = dwc->eps[epnum]; - dwc3_free_trb_pool(dep); - - if (epnum != 0 && epnum != 1) + if (!dep) + continue; + /* + * Physical endpoints 0 and 1 are special; they form the + * bi-directional USB endpoint 0. + * + * For those two physical endpoints, we don't allocate a TRB + * pool nor do we add them the endpoints list. Due to that, we + * shouldn't do these two operations otherwise we would end up + * with all sorts of bugs when removing dwc3.ko. + */ + if (epnum != 0 && epnum != 1) { + dwc3_free_trb_pool(dep); list_del(&dep->endpoint.ep_list); + } kfree(dep); } } -static void dwc3_gadget_release(struct device *dev) +/* -------------------------------------------------------------------------- */ + +static int __dwc3_cleanup_done_trbs(struct dwc3 *dwc, struct dwc3_ep *dep, + struct dwc3_request *req, struct dwc3_trb *trb, + const struct dwc3_event_depevt *event, int status) { - dev_dbg(dev, "%s\n", __func__); + unsigned int count; + unsigned int s_pkt = 0; + unsigned int trb_status; + + if ((trb->ctrl & DWC3_TRB_CTRL_HWO) && status != -ESHUTDOWN) + /* + * We continue despite the error. There is not much we + * can do. If we don't clean it up we loop forever. If + * we skip the TRB then it gets overwritten after a + * while since we use them in a ring buffer. A BUG() + * would help. Lets hope that if this occurs, someone + * fixes the root cause instead of looking away :) + */ + dev_err(dwc->dev, "%s's TRB (%p) still owned by HW\n", + dep->name, trb); + count = trb->size & DWC3_TRB_SIZE_MASK; + + if (dep->direction) { + if (count) { + trb_status = DWC3_TRB_SIZE_TRBSTS(trb->size); + if (trb_status == DWC3_TRBSTS_MISSED_ISOC) { + dev_dbg(dwc->dev, "incomplete IN transfer %s\n", + dep->name); + /* + * If missed isoc occurred and there is + * no request queued then issue END + * TRANSFER, so that core generates + * next xfernotready and we will issue + * a fresh START TRANSFER. + * If there are still queued request + * then wait, do not issue either END + * or UPDATE TRANSFER, just attach next + * request in request_list during + * giveback.If any future queued request + * is successfully transferred then we + * will issue UPDATE TRANSFER for all + * request in the request_list. + */ + dep->flags |= DWC3_EP_MISSED_ISOC; + } else { + dev_err(dwc->dev, "incomplete IN transfer %s\n", + dep->name); + status = -ECONNRESET; + } + } else { + dep->flags &= ~DWC3_EP_MISSED_ISOC; + } + } else { + if (count && (event->status & DEPEVT_STATUS_SHORT)) + s_pkt = 1; + } + + /* + * We assume here we will always receive the entire data block + * which we should receive. Meaning, if we program RX to + * receive 4K but we receive only 2K, we assume that's all we + * should receive and we simply bounce the request back to the + * gadget driver for further processing. + */ + req->request.actual += req->request.length - count; + if (s_pkt) + return 1; + if ((event->status & DEPEVT_STATUS_LST) && + (trb->ctrl & (DWC3_TRB_CTRL_LST | + DWC3_TRB_CTRL_HWO))) + return 1; + if ((event->status & DEPEVT_STATUS_IOC) && + (trb->ctrl & DWC3_TRB_CTRL_IOC)) + return 1; + return 0; } -/* -------------------------------------------------------------------------- */ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, const struct dwc3_event_depevt *event, int status) { struct dwc3_request *req; struct dwc3_trb *trb; - unsigned int count; - unsigned int s_pkt = 0; - unsigned int trb_status; + unsigned int slot; + unsigned int i; + int ret; do { req = next_request(&dep->req_queued); @@ -1667,65 +1929,44 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, WARN_ON_ONCE(1); return 1; } + i = 0; + do { + slot = req->start_slot + i; + if ((slot == DWC3_TRB_NUM - 1) && + usb_endpoint_xfer_isoc(dep->endpoint.desc)) + slot++; + slot %= DWC3_TRB_NUM; + trb = &dep->trb_pool[slot]; + + ret = __dwc3_cleanup_done_trbs(dwc, dep, req, trb, + event, status); + if (ret) + break; + }while (++i < req->request.num_mapped_sgs); + + dwc3_gadget_giveback(dep, req, status); - trb = req->trb; + if (ret) + break; + } while (1); - if ((trb->ctrl & DWC3_TRB_CTRL_HWO) && status != -ESHUTDOWN) + if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && + list_empty(&dep->req_queued)) { + if (list_empty(&dep->request_list)) { /* - * We continue despite the error. There is not much we - * can do. If we don't clean it up we loop forever. If - * we skip the TRB then it gets overwritten after a - * while since we use them in a ring buffer. A BUG() - * would help. Lets hope that if this occurs, someone - * fixes the root cause instead of looking away :) + * If there is no entry in request list then do + * not issue END TRANSFER now. Just set PENDING + * flag, so that END TRANSFER is issued when an + * entry is added into request list. */ - dev_err(dwc->dev, "%s's TRB (%p) still owned by HW\n", - dep->name, req->trb); - count = trb->size & DWC3_TRB_SIZE_MASK; - - if (dep->direction) { - if (count) { - trb_status = DWC3_TRB_SIZE_TRBSTS(trb->size); - if (trb_status == DWC3_TRBSTS_MISSED_ISOC) { - dev_dbg(dwc->dev, "incomplete IN transfer %s\n", - dep->name); - dep->current_uf = event->parameters & - ~(dep->interval - 1); - dep->flags |= DWC3_EP_MISSED_ISOC; - } else { - dev_err(dwc->dev, "incomplete IN transfer %s\n", - dep->name); - status = -ECONNRESET; - } - } + dep->flags = DWC3_EP_PENDING_REQUEST; } else { - if (count && (event->status & DEPEVT_STATUS_SHORT)) - s_pkt = 1; + dwc3_stop_active_transfer(dwc, dep->number, true); + dep->flags = DWC3_EP_ENABLED; } + return 1; + } - /* - * We assume here we will always receive the entire data block - * which we should receive. Meaning, if we program RX to - * receive 4K but we receive only 2K, we assume that's all we - * should receive and we simply bounce the request back to the - * gadget driver for further processing. - */ - req->request.actual += req->request.length - count; - dwc3_gadget_giveback(dep, req, status); - if (s_pkt) - break; - if ((event->status & DEPEVT_STATUS_LST) && - (trb->ctrl & (DWC3_TRB_CTRL_LST | - DWC3_TRB_CTRL_HWO))) - break; - if ((event->status & DEPEVT_STATUS_IOC) && - (trb->ctrl & DWC3_TRB_CTRL_IOC)) - break; - } while (1); - - if ((event->status & DEPEVT_STATUS_IOC) && - (trb->ctrl & DWC3_TRB_CTRL_IOC)) - return 0; return 1; } @@ -1867,7 +2108,25 @@ static void dwc3_disconnect_gadget(struct dwc3 *dwc) } } -static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum) +static void dwc3_suspend_gadget(struct dwc3 *dwc) +{ + if (dwc->gadget_driver && dwc->gadget_driver->suspend) { + spin_unlock(&dwc->lock); + dwc->gadget_driver->suspend(&dwc->gadget); + spin_lock(&dwc->lock); + } +} + +static void dwc3_resume_gadget(struct dwc3 *dwc) +{ + if (dwc->gadget_driver && dwc->gadget_driver->resume) { + spin_unlock(&dwc->lock); + dwc->gadget_driver->resume(&dwc->gadget); + spin_lock(&dwc->lock); + } +} + +static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force) { struct dwc3_ep *dep; struct dwc3_gadget_ep_cmd_params params; @@ -1899,7 +2158,8 @@ static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum) */ cmd = DWC3_DEPCMD_ENDTRANSFER; - cmd |= DWC3_DEPCMD_HIPRI_FORCERM | DWC3_DEPCMD_CMDIOC; + cmd |= force ? DWC3_DEPCMD_HIPRI_FORCERM : 0; + cmd |= DWC3_DEPCMD_CMDIOC; cmd |= DWC3_DEPCMD_PARAM(dep->resource_index); memset(¶ms, 0, sizeof(params)); ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); @@ -1917,6 +2177,9 @@ static void dwc3_stop_active_transfers(struct dwc3 *dwc) struct dwc3_ep *dep; dep = dwc->eps[epnum]; + if (!dep) + continue; + if (!(dep->flags & DWC3_EP_ENABLED)) continue; @@ -1934,6 +2197,8 @@ static void dwc3_clear_stall_all_ep(struct dwc3 *dwc) int ret; dep = dwc->eps[epnum]; + if (!dep) + continue; if (!(dep->flags & DWC3_EP_STALL)) continue; @@ -1967,34 +2232,6 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc) dwc->setup_packet_pending = false; } -static void dwc3_gadget_usb3_phy_suspend(struct dwc3 *dwc, int suspend) -{ - u32 reg; - - reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); - - if (suspend) - reg |= DWC3_GUSB3PIPECTL_SUSPHY; - else - reg &= ~DWC3_GUSB3PIPECTL_SUSPHY; - - dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); -} - -static void dwc3_gadget_usb2_phy_suspend(struct dwc3 *dwc, int suspend) -{ - u32 reg; - - reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); - - if (suspend) - reg |= DWC3_GUSB2PHYCFG_SUSPHY; - else - reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; - - dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); -} - static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) { u32 reg; @@ -2033,14 +2270,7 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) } /* after reset -> Default State */ - dwc->dev_state = DWC3_DEFAULT_STATE; - - /* Recent versions support automatic phy suspend and don't need this */ - if (dwc->revision < DWC3_REVISION_194A) { - /* Resume PHYs */ - dwc3_gadget_usb2_phy_suspend(dwc, false); - dwc3_gadget_usb3_phy_suspend(dwc, false); - } + usb_gadget_set_state(&dwc->gadget, USB_STATE_DEFAULT); if (dwc->gadget.speed != USB_SPEED_UNKNOWN) dwc3_disconnect_gadget(dwc); @@ -2085,23 +2315,8 @@ static void dwc3_update_ram_clk_sel(struct dwc3 *dwc, u32 speed) dwc3_writel(dwc->regs, DWC3_GCTL, reg); } -static void dwc3_gadget_phy_suspend(struct dwc3 *dwc, u8 speed) -{ - switch (speed) { - case USB_SPEED_SUPER: - dwc3_gadget_usb2_phy_suspend(dwc, true); - break; - case USB_SPEED_HIGH: - case USB_SPEED_FULL: - case USB_SPEED_LOW: - dwc3_gadget_usb3_phy_suspend(dwc, true); - break; - } -} - static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) { - struct dwc3_gadget_ep_cmd_params params; struct dwc3_ep *dep; int ret; u32 reg; @@ -2109,8 +2324,6 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) dev_vdbg(dwc->dev, "%s\n", __func__); - memset(¶ms, 0x00, sizeof(params)); - reg = dwc3_readl(dwc->regs, DWC3_DSTS); speed = reg & DWC3_DSTS_CONNECTSPD; dwc->speed = speed; @@ -2157,21 +2370,41 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) break; } - /* Recent versions support automatic phy suspend and don't need this */ - if (dwc->revision < DWC3_REVISION_194A) { - /* Suspend unneeded PHY */ - dwc3_gadget_phy_suspend(dwc, dwc->gadget.speed); + /* Enable USB2 LPM Capability */ + + if ((dwc->revision > DWC3_REVISION_194A) + && (speed != DWC3_DCFG_SUPERSPEED)) { + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg |= DWC3_DCFG_LPM_CAP; + dwc3_writel(dwc->regs, DWC3_DCFG, reg); + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~(DWC3_DCTL_HIRD_THRES_MASK | DWC3_DCTL_L1_HIBER_EN); + + /* + * TODO: This should be configurable. For now using + * maximum allowed HIRD threshold value of 0b1100 + */ + reg |= DWC3_DCTL_HIRD_THRES(12); + + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + } else { + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_HIRD_THRES_MASK; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); } dep = dwc->eps[0]; - ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true); + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true, + false); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); return; } dep = dwc->eps[1]; - ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true); + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true, + false); if (ret) { dev_err(dwc->dev, "failed to enable %s\n", dep->name); return; @@ -2202,6 +2435,34 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc, unsigned int evtinfo) { enum dwc3_link_state next = evtinfo & DWC3_LINK_STATE_MASK; + unsigned int pwropt; + + /* + * WORKAROUND: DWC3 < 2.50a have an issue when configured without + * Hibernation mode enabled which would show up when device detects + * host-initiated U3 exit. + * + * In that case, device will generate a Link State Change Interrupt + * from U3 to RESUME which is only necessary if Hibernation is + * configured in. + * + * There are no functional changes due to such spurious event and we + * just need to ignore it. + * + * Refers to: + * + * STAR#9000570034 RTL: SS Resume event generated in non-Hibernation + * operational mode + */ + pwropt = DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1); + if ((dwc->revision < DWC3_REVISION_250A) && + (pwropt != DWC3_GHWPARAMS1_EN_PWROPT_HIB)) { + if ((dwc->link_state == DWC3_LINK_STATE_U3) && + (next == DWC3_LINK_STATE_RESUME)) { + dev_vdbg(dwc->dev, "ignoring transition U3 -> Resume\n"); + return; + } + } /* * WORKAROUND: DWC3 Revisions <1.83a have an issue which, depending @@ -2249,9 +2510,52 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc, } } + switch (next) { + case DWC3_LINK_STATE_U1: + if (dwc->speed == USB_SPEED_SUPER) + dwc3_suspend_gadget(dwc); + break; + case DWC3_LINK_STATE_U2: + case DWC3_LINK_STATE_U3: + dwc3_suspend_gadget(dwc); + break; + case DWC3_LINK_STATE_RESUME: + dwc3_resume_gadget(dwc); + break; + default: + /* do nothing */ + break; + } + + dev_vdbg(dwc->dev, "link change: %s [%d] -> %s [%d]\n", + dwc3_gadget_link_string(dwc->link_state), + dwc->link_state, dwc3_gadget_link_string(next), next); + dwc->link_state = next; +} - dev_vdbg(dwc->dev, "%s link %d\n", __func__, dwc->link_state); +static void dwc3_gadget_hibernation_interrupt(struct dwc3 *dwc, + unsigned int evtinfo) +{ + unsigned int is_ss = evtinfo & BIT(4); + + /** + * WORKAROUND: DWC3 revison 2.20a with hibernation support + * have a known issue which can cause USB CV TD.9.23 to fail + * randomly. + * + * Because of this issue, core could generate bogus hibernation + * events which SW needs to ignore. + * + * Refers to: + * + * STAR#9000546576: Device Mode Hibernation: Issue in USB 2.0 + * Device Fallback from SuperSpeed + */ + if (is_ss ^ (dwc->speed == USB_SPEED_SUPER)) + return; + + /* enter hibernation here */ } static void dwc3_gadget_interrupt(struct dwc3 *dwc, @@ -2270,6 +2574,13 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc, case DWC3_DEVICE_EVENT_WAKEUP: dwc3_gadget_wakeup_interrupt(dwc); break; + case DWC3_DEVICE_EVENT_HIBER_REQ: + if (dev_WARN_ONCE(dwc->dev, !dwc->has_hibernation, + "unexpected hibernation event\n")) + break; + + dwc3_gadget_hibernation_interrupt(dwc, event->event_info); + break; case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: dwc3_gadget_linksts_change_interrupt(dwc, event->event_info); break; @@ -2315,16 +2626,15 @@ static void dwc3_process_event_entry(struct dwc3 *dwc, static irqreturn_t dwc3_process_event_buf(struct dwc3 *dwc, u32 buf) { struct dwc3_event_buffer *evt; + irqreturn_t ret = IRQ_NONE; int left; - u32 count; - - count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(buf)); - count &= DWC3_GEVNTCOUNT_MASK; - if (!count) - return IRQ_NONE; + u32 reg; evt = dwc->ev_buffs[buf]; - left = count; + left = evt->count; + + if (!(evt->flags & DWC3_EVENT_PENDING)) + return IRQ_NONE; while (left > 0) { union dwc3_event event; @@ -2332,12 +2642,15 @@ static irqreturn_t dwc3_process_event_buf(struct dwc3 *dwc, u32 buf) event.raw = *(u32 *) (evt->buf + evt->lpos); dwc3_process_event_entry(dwc, &event); + /* - * XXX we wrap around correctly to the next entry as almost all - * entries are 4 bytes in size. There is one entry which has 12 - * bytes which is a regular entry followed by 8 bytes data. ATM - * I don't know how things are organized if were get next to the - * a boundary so I worry about that once we try to handle that. + * FIXME we wrap around correctly to the next entry as + * almost all entries are 4 bytes in size. There is one + * entry which has 12 bytes which is a regular entry + * followed by 8 bytes data. ATM I don't know how + * things are organized if we get next to the a + * boundary so I worry about that once we try to handle + * that. */ evt->lpos = (evt->lpos + 4) % DWC3_EVENT_BUFFERS_SIZE; left -= 4; @@ -2345,7 +2658,57 @@ static irqreturn_t dwc3_process_event_buf(struct dwc3 *dwc, u32 buf) dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(buf), 4); } - return IRQ_HANDLED; + evt->count = 0; + evt->flags &= ~DWC3_EVENT_PENDING; + ret = IRQ_HANDLED; + + /* Unmask interrupt */ + reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(buf)); + reg &= ~DWC3_GEVNTSIZ_INTMASK; + dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(buf), reg); + + return ret; +} + +static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc) +{ + struct dwc3 *dwc = _dwc; + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + int i; + + spin_lock_irqsave(&dwc->lock, flags); + + for (i = 0; i < dwc->num_event_buffers; i++) + ret |= dwc3_process_event_buf(dwc, i); + + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static irqreturn_t dwc3_check_event_buf(struct dwc3 *dwc, u32 buf) +{ + struct dwc3_event_buffer *evt; + u32 count; + u32 reg; + + evt = dwc->ev_buffs[buf]; + + count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(buf)); + count &= DWC3_GEVNTCOUNT_MASK; + if (!count) + return IRQ_NONE; + + evt->count = count; + evt->flags |= DWC3_EVENT_PENDING; + + /* Mask interrupt */ + reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(buf)); + reg |= DWC3_GEVNTSIZ_INTMASK; + dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(buf), reg); + + return IRQ_WAKE_THREAD; } static irqreturn_t dwc3_interrupt(int irq, void *_dwc) @@ -2359,8 +2722,8 @@ static irqreturn_t dwc3_interrupt(int irq, void *_dwc) for (i = 0; i < dwc->num_event_buffers; i++) { irqreturn_t status; - status = dwc3_process_event_buf(dwc, i); - if (status == IRQ_HANDLED) + status = dwc3_check_event_buf(dwc, i); + if (status == IRQ_WAKE_THREAD) ret = status; } @@ -2377,9 +2740,7 @@ static irqreturn_t dwc3_interrupt(int irq, void *_dwc) */ int dwc3_gadget_init(struct dwc3 *dwc) { - u32 reg; int ret; - int irq; dwc->ctrl_req = dma_alloc_coherent(dwc->dev, sizeof(*dwc->ctrl_req), &dwc->ctrl_req_addr, GFP_KERNEL); @@ -2413,22 +2774,19 @@ int dwc3_gadget_init(struct dwc3 *dwc) goto err3; } - dev_set_name(&dwc->gadget.dev, "gadget"); - dwc->gadget.ops = &dwc3_gadget_ops; dwc->gadget.max_speed = USB_SPEED_SUPER; dwc->gadget.speed = USB_SPEED_UNKNOWN; - dwc->gadget.dev.parent = dwc->dev; dwc->gadget.sg_supported = true; - - dma_set_coherent_mask(&dwc->gadget.dev, dwc->dev->coherent_dma_mask); - - dwc->gadget.dev.dma_parms = dwc->dev->dma_parms; - dwc->gadget.dev.dma_mask = dwc->dev->dma_mask; - dwc->gadget.dev.release = dwc3_gadget_release; dwc->gadget.name = "dwc3-gadget"; /* + * Per databook, DWC3 needs buffer size to be aligned to MaxPacketSize + * on ep out. + */ + dwc->gadget.quirk_ep_out_aligned_size = true; + + /* * REVISIT: Here we should clear all pending IRQs to be * sure we're starting from a well known location. */ @@ -2437,76 +2795,16 @@ int dwc3_gadget_init(struct dwc3 *dwc) if (ret) goto err4; - irq = platform_get_irq(to_platform_device(dwc->dev), 0); - - ret = request_irq(irq, dwc3_interrupt, IRQF_SHARED, - "dwc3", dwc); - if (ret) { - dev_err(dwc->dev, "failed to request irq #%d --> %d\n", - irq, ret); - goto err5; - } - - reg = dwc3_readl(dwc->regs, DWC3_DCFG); - reg |= DWC3_DCFG_LPM_CAP; - dwc3_writel(dwc->regs, DWC3_DCFG, reg); - - /* Enable all but Start and End of Frame IRQs */ - reg = (DWC3_DEVTEN_VNDRDEVTSTRCVEDEN | - DWC3_DEVTEN_EVNTOVERFLOWEN | - DWC3_DEVTEN_CMDCMPLTEN | - DWC3_DEVTEN_ERRTICERREN | - DWC3_DEVTEN_WKUPEVTEN | - DWC3_DEVTEN_ULSTCNGEN | - DWC3_DEVTEN_CONNECTDONEEN | - DWC3_DEVTEN_USBRSTEN | - DWC3_DEVTEN_DISCONNEVTEN); - dwc3_writel(dwc->regs, DWC3_DEVTEN, reg); - - /* Enable USB2 LPM and automatic phy suspend only on recent versions */ - if (dwc->revision >= DWC3_REVISION_194A) { - reg = dwc3_readl(dwc->regs, DWC3_DCFG); - reg |= DWC3_DCFG_LPM_CAP; - dwc3_writel(dwc->regs, DWC3_DCFG, reg); - - reg = dwc3_readl(dwc->regs, DWC3_DCTL); - reg &= ~(DWC3_DCTL_HIRD_THRES_MASK | DWC3_DCTL_L1_HIBER_EN); - - /* TODO: This should be configurable */ - reg |= DWC3_DCTL_HIRD_THRES(28); - - dwc3_writel(dwc->regs, DWC3_DCTL, reg); - - dwc3_gadget_usb2_phy_suspend(dwc, false); - dwc3_gadget_usb3_phy_suspend(dwc, false); - } - - ret = device_register(&dwc->gadget.dev); - if (ret) { - dev_err(dwc->dev, "failed to register gadget device\n"); - put_device(&dwc->gadget.dev); - goto err6; - } - ret = usb_add_gadget_udc(dwc->dev, &dwc->gadget); if (ret) { dev_err(dwc->dev, "failed to register udc\n"); - goto err7; + goto err4; } return 0; -err7: - device_unregister(&dwc->gadget.dev); - -err6: - dwc3_writel(dwc->regs, DWC3_DEVTEN, 0x00); - free_irq(irq, dwc); - -err5: - dwc3_gadget_free_endpoints(dwc); - err4: + dwc3_gadget_free_endpoints(dwc); dma_free_coherent(dwc->dev, DWC3_EP0_BOUNCE_SIZE, dwc->ep0_bounce, dwc->ep0_bounce_addr); @@ -2525,15 +2823,11 @@ err0: return ret; } +/* -------------------------------------------------------------------------- */ + void dwc3_gadget_exit(struct dwc3 *dwc) { - int irq; - usb_del_gadget_udc(&dwc->gadget); - irq = platform_get_irq(to_platform_device(dwc->dev), 0); - - dwc3_writel(dwc->regs, DWC3_DEVTEN, 0x00); - free_irq(irq, dwc); dwc3_gadget_free_endpoints(dwc); @@ -2547,6 +2841,67 @@ void dwc3_gadget_exit(struct dwc3 *dwc) dma_free_coherent(dwc->dev, sizeof(*dwc->ctrl_req), dwc->ctrl_req, dwc->ctrl_req_addr); +} + +int dwc3_gadget_prepare(struct dwc3 *dwc) +{ + if (dwc->pullups_connected) { + dwc3_gadget_disable_irq(dwc); + dwc3_gadget_run_stop(dwc, true, true); + } - device_unregister(&dwc->gadget.dev); + return 0; +} + +void dwc3_gadget_complete(struct dwc3 *dwc) +{ + if (dwc->pullups_connected) { + dwc3_gadget_enable_irq(dwc); + dwc3_gadget_run_stop(dwc, true, false); + } +} + +int dwc3_gadget_suspend(struct dwc3 *dwc) +{ + __dwc3_gadget_ep_disable(dwc->eps[0]); + __dwc3_gadget_ep_disable(dwc->eps[1]); + + dwc->dcfg = dwc3_readl(dwc->regs, DWC3_DCFG); + + return 0; +} + +int dwc3_gadget_resume(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + int ret; + + /* Start with SuperSpeed Default */ + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + + dep = dwc->eps[0]; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, + false); + if (ret) + goto err0; + + dep = dwc->eps[1]; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, + false); + if (ret) + goto err1; + + /* begin to receive SETUP packets */ + dwc->ep0state = EP0_SETUP_PHASE; + dwc3_ep0_out_start(dwc); + + dwc3_writel(dwc->regs, DWC3_DCFG, dwc->dcfg); + + return 0; + +err1: + __dwc3_gadget_ep_disable(dwc->eps[0]); + +err0: + return ret; } |
