diff options
Diffstat (limited to 'drivers/usb/dwc2/gadget.c')
-rw-r--r-- | drivers/usb/dwc2/gadget.c | 121 |
1 files changed, 114 insertions, 7 deletions
diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 220c0f9b89b0..2d6d2c8244de 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -123,6 +123,24 @@ static inline void dwc2_gadget_incr_frame_num(struct dwc2_hsotg_ep *hs_ep) } /** + * dwc2_gadget_dec_frame_num_by_one - Decrements the targeted frame number + * by one. + * @hs_ep: The endpoint. + * + * This function used in service interval based scheduling flow to calculate + * descriptor frame number filed value. For service interval mode frame + * number in descriptor should point to last (u)frame in the interval. + * + */ +static inline void dwc2_gadget_dec_frame_num_by_one(struct dwc2_hsotg_ep *hs_ep) +{ + if (hs_ep->target_frame) + hs_ep->target_frame -= 1; + else + hs_ep->target_frame = DSTS_SOFFN_LIMIT; +} + +/** * dwc2_hsotg_en_gsint - enable one or more of the general interrupt * @hsotg: The device state * @ints: A bitmask of the interrupts to enable @@ -228,6 +246,27 @@ int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg) } /** + * dwc2_gadget_wkup_alert_handler - Handler for WKUP_ALERT interrupt + * + * @hsotg: Programming view of the DWC_otg controller + * + */ +static void dwc2_gadget_wkup_alert_handler(struct dwc2_hsotg *hsotg) +{ + u32 gintsts2; + u32 gintmsk2; + + gintsts2 = dwc2_readl(hsotg, GINTSTS2); + gintmsk2 = dwc2_readl(hsotg, GINTMSK2); + + if (gintsts2 & GINTSTS2_WKUP_ALERT_INT) { + dev_dbg(hsotg->dev, "%s: Wkup_Alert_Int\n", __func__); + dwc2_clear_bit(hsotg, GINTSTS2, GINTSTS2_WKUP_ALERT_INT); + dwc2_set_bit(hsotg, DCFG, DCTL_RMTWKUPSIG); + } +} + +/** * dwc2_hsotg_tx_fifo_average_depth - returns average depth of device mode * TX FIFOs * @@ -2812,6 +2851,23 @@ static void dwc2_gadget_handle_nak(struct dwc2_hsotg_ep *hs_ep) if (using_desc_dma(hsotg)) { hs_ep->target_frame = hsotg->frame_number; dwc2_gadget_incr_frame_num(hs_ep); + + /* In service interval mode target_frame must + * be set to last (u)frame of the service interval. + */ + if (hsotg->params.service_interval) { + /* Set target_frame to the first (u)frame of + * the service interval + */ + hs_ep->target_frame &= ~hs_ep->interval + 1; + + /* Set target_frame to the last (u)frame of + * the service interval + */ + dwc2_gadget_incr_frame_num(hs_ep); + dwc2_gadget_dec_frame_num_by_one(hs_ep); + } + dwc2_gadget_start_isoc_ddma(hs_ep); return; } @@ -3109,6 +3165,8 @@ static void kill_all_requests(struct dwc2_hsotg *hsotg, dwc2_hsotg_txfifo_flush(hsotg, ep->fifo_index); } +static int dwc2_hsotg_ep_disable(struct usb_ep *ep); + /** * dwc2_hsotg_disconnect - disconnect service * @hsotg: The device state. @@ -3127,13 +3185,12 @@ void dwc2_hsotg_disconnect(struct dwc2_hsotg *hsotg) hsotg->connected = 0; hsotg->test_mode = 0; + /* all endpoints should be shutdown */ for (ep = 0; ep < hsotg->num_of_eps; ep++) { if (hsotg->eps_in[ep]) - kill_all_requests(hsotg, hsotg->eps_in[ep], - -ESHUTDOWN); + dwc2_hsotg_ep_disable(&hsotg->eps_in[ep]->ep); if (hsotg->eps_out[ep]) - kill_all_requests(hsotg, hsotg->eps_out[ep], - -ESHUTDOWN); + dwc2_hsotg_ep_disable(&hsotg->eps_out[ep]->ep); } call_gadget(hsotg, disconnect); @@ -3191,13 +3248,23 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, u32 val; u32 usbcfg; u32 dcfg = 0; + int ep; /* Kill any ep0 requests as controller will be reinitialized */ kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); - if (!is_usb_reset) + if (!is_usb_reset) { if (dwc2_core_reset(hsotg, true)) return; + } else { + /* all endpoints should be shutdown */ + for (ep = 1; ep < hsotg->num_of_eps; ep++) { + if (hsotg->eps_in[ep]) + dwc2_hsotg_ep_disable(&hsotg->eps_in[ep]->ep); + if (hsotg->eps_out[ep]) + dwc2_hsotg_ep_disable(&hsotg->eps_out[ep]->ep); + } + } /* * we must now enable ep0 ready for host detection and then @@ -3312,6 +3379,10 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, dwc2_set_bit(hsotg, DIEPMSK, DIEPMSK_BNAININTRMSK); } + /* Enable Service Interval mode if supported */ + if (using_desc_dma(hsotg) && hsotg->params.service_interval) + dwc2_set_bit(hsotg, DCTL, DCTL_SERVICE_INTERVAL_SUPPORTED); + dwc2_writel(hsotg, 0, DAINTMSK); dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", @@ -3368,6 +3439,10 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, /* configure the core to support LPM */ dwc2_gadget_init_lpm(hsotg); + /* program GREFCLK register if needed */ + if (using_desc_dma(hsotg) && hsotg->params.service_interval) + dwc2_gadget_program_ref_clk(hsotg); + /* must be at-least 3ms to allow bus to see disconnect */ mdelay(3); @@ -3676,6 +3751,10 @@ irq_retry: if (gintsts & IRQ_RETRY_MASK && --retry_count > 0) goto irq_retry; + /* Check WKUP_ALERT interrupt*/ + if (hsotg->params.service_interval) + dwc2_gadget_wkup_alert_handler(hsotg); + spin_unlock(&hsotg->lock); return IRQ_HANDLED; @@ -3993,6 +4072,7 @@ static int dwc2_hsotg_ep_disable(struct usb_ep *ep) unsigned long flags; u32 epctrl_reg; u32 ctrl; + int locked; dev_dbg(hsotg->dev, "%s(ep %p)\n", __func__, ep); @@ -4008,7 +4088,9 @@ static int dwc2_hsotg_ep_disable(struct usb_ep *ep) epctrl_reg = dir_in ? DIEPCTL(index) : DOEPCTL(index); - spin_lock_irqsave(&hsotg->lock, flags); + locked = spin_is_locked(&hsotg->lock); + if (!locked) + spin_lock_irqsave(&hsotg->lock, flags); ctrl = dwc2_readl(hsotg, epctrl_reg); @@ -4032,7 +4114,9 @@ static int dwc2_hsotg_ep_disable(struct usb_ep *ep) hs_ep->fifo_index = 0; hs_ep->fifo_size = 0; - spin_unlock_irqrestore(&hsotg->lock, flags); + if (!locked) + spin_unlock_irqrestore(&hsotg->lock, flags); + return 0; } @@ -4944,6 +5028,29 @@ void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) val |= hsotg->params.besl ? GLPMCFG_ENBESL : 0; dwc2_writel(hsotg, val, GLPMCFG); dev_dbg(hsotg->dev, "GLPMCFG=0x%08x\n", dwc2_readl(hsotg, GLPMCFG)); + + /* Unmask WKUP_ALERT Interrupt */ + if (hsotg->params.service_interval) + dwc2_set_bit(hsotg, GINTMSK2, GINTMSK2_WKUP_ALERT_INT_MSK); +} + +/** + * dwc2_gadget_program_ref_clk - Program GREFCLK register in device mode + * + * @hsotg: Programming view of DWC_otg controller + * + */ +void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg) +{ + u32 val = 0; + + val |= GREFCLK_REF_CLK_MODE; + val |= hsotg->params.ref_clk_per << GREFCLK_REFCLKPER_SHIFT; + val |= hsotg->params.sof_cnt_wkup_alert << + GREFCLK_SOF_CNT_WKUP_ALERT_SHIFT; + + dwc2_writel(hsotg, val, GREFCLK); + dev_dbg(hsotg->dev, "GREFCLK=0x%08x\n", dwc2_readl(hsotg, GREFCLK)); } /** |