diff options
author | Mian Yousaf Kaukab <yousaf.kaukab@intel.com> | 2015-01-09 12:38:58 (GMT) |
---|---|---|
committer | Felipe Balbi <balbi@ti.com> | 2015-01-12 21:34:03 (GMT) |
commit | fe0b94abcdf6d80bd575e91baf1e0f85a4978d49 (patch) | |
tree | e25d3f5ab252d451f1111063a5c59b0cc7e236b0 | |
parent | 1141ea01d5faa82f4910b4a8f896d10490860f24 (diff) | |
download | linux-fe0b94abcdf6d80bd575e91baf1e0f85a4978d49.tar.xz |
usb: dwc2: gadget: manage ep0 state in software
Manage ep0 state in software to add handling of status OUT stage.
Just toggling hsotg->setup in s3c_hsotg_handle_outdone leaves it in
wrong state in 2-stage control transfers.
Moreover, ensure that for setup-packet s3c_hsotg_handle_outdone is
called either from SetupDone or OutDone but not both. Dwc2 ip v3.00a
generates both SetupDone and OutDone on setup packets.
Tested-by: Robert Baldyga <r.baldyga@samsung.com>
Acked-by: Paul Zimmerman <paulz@synopsys.com>
Signed-off-by: Mian Yousaf Kaukab <yousaf.kaukab@intel.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
-rw-r--r-- | drivers/usb/dwc2/core.h | 13 | ||||
-rw-r--r-- | drivers/usb/dwc2/gadget.c | 156 |
2 files changed, 89 insertions, 80 deletions
diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index db5348c..e963aef 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -196,6 +196,15 @@ enum dwc2_lx_state { #define DWC2_G_P_LEGACY_TX_FIFO_SIZE {256, 256, 256, 256, 768, 768, 768, \ 768, 0, 0, 0, 0, 0, 0, 0} +/* Gadget ep0 states */ +enum dwc2_ep0_state { + DWC2_EP0_SETUP, + DWC2_EP0_DATA_IN, + DWC2_EP0_DATA_OUT, + DWC2_EP0_STATUS_IN, + DWC2_EP0_STATUS_OUT, +}; + /** * struct dwc2_core_params - Parameters for configuring the core * @@ -563,7 +572,7 @@ struct dwc2_hw_params { * @ep0_buff: Buffer for EP0 reply data, if needed. * @ctrl_buff: Buffer for EP0 control requests. * @ctrl_req: Request for EP0 control packets. - * @setup: NAK management for EP0 SETUP + * @ep0_state: EP0 control transfers state * @last_rst: Time of last reset * @eps: The endpoints being supplied to the gadget framework * @g_using_dma: Indicate if dma usage is enabled @@ -696,11 +705,11 @@ struct dwc2_hsotg { struct usb_request *ctrl_req; void *ep0_buff; void *ctrl_buff; + enum dwc2_ep0_state ep0_state; struct usb_gadget gadget; unsigned int enabled:1; unsigned int connected:1; - unsigned int setup:1; unsigned long last_rst; struct s3c_hsotg_ep *eps_in[MAX_EPS_CHANNELS]; struct s3c_hsotg_ep *eps_out[MAX_EPS_CHANNELS]; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 3dda17e..c3977a8 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -638,15 +638,12 @@ static void s3c_hsotg_start_req(struct dwc2_hsotg *hsotg, ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ ctrl |= DXEPCTL_USBACTEP; - dev_dbg(hsotg->dev, "setup req:%d\n", hsotg->setup); + dev_dbg(hsotg->dev, "ep0 state:%d\n", hsotg->ep0_state); /* For Setup request do not clear NAK */ - if (hsotg->setup && index == 0) - hsotg->setup = 0; - else + if (!(index == 0 && hsotg->ep0_state == DWC2_EP0_SETUP)) ctrl |= DXEPCTL_CNAK; /* clear NAK set by core */ - dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl); writel(ctrl, hsotg->regs + epctrl_reg); @@ -865,8 +862,6 @@ static int s3c_hsotg_send_reply(struct dwc2_hsotg *hsotg, if (length) memcpy(req->buf, buff, length); - else - ep->sent_zlp = 1; ret = s3c_hsotg_ep_queue(&ep->ep, req, GFP_ATOMIC); if (ret) { @@ -1080,26 +1075,20 @@ static void s3c_hsotg_process_control(struct dwc2_hsotg *hsotg, int ret = 0; u32 dcfg; - ep0->sent_zlp = 0; - dev_dbg(hsotg->dev, "ctrl Req=%02x, Type=%02x, V=%04x, L=%04x\n", ctrl->bRequest, ctrl->bRequestType, ctrl->wValue, ctrl->wLength); - /* - * record the direction of the request, for later use when enquing - * packets onto EP0. - */ - - ep0->dir_in = (ctrl->bRequestType & USB_DIR_IN) ? 1 : 0; - dev_dbg(hsotg->dev, "ctrl: dir_in=%d\n", ep0->dir_in); - - /* - * if we've no data with this request, then the last part of the - * transaction is going to implicitly be IN. - */ - if (ctrl->wLength == 0) + if (ctrl->wLength == 0) { + ep0->dir_in = 1; + hsotg->ep0_state = DWC2_EP0_STATUS_IN; + } else if (ctrl->bRequestType & USB_DIR_IN) { ep0->dir_in = 1; + hsotg->ep0_state = DWC2_EP0_DATA_IN; + } else { + ep0->dir_in = 0; + hsotg->ep0_state = DWC2_EP0_DATA_OUT; + } if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { switch (ctrl->bRequest) { @@ -1198,6 +1187,8 @@ static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg) } hsotg->eps_out[0]->dir_in = 0; + hsotg->eps_out[0]->sent_zlp = 0; + hsotg->ep0_state = DWC2_EP0_SETUP; ret = s3c_hsotg_ep_queue(&hsotg->eps_out[0]->ep, req, GFP_ATOMIC); if (ret < 0) { @@ -1209,6 +1200,27 @@ static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg) } } +static void s3c_hsotg_program_zlp(struct dwc2_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep) +{ + u32 ctrl; + u8 index = hs_ep->index; + u32 epctl_reg = hs_ep->dir_in ? DIEPCTL(index) : DOEPCTL(index); + u32 epsiz_reg = hs_ep->dir_in ? DIEPTSIZ(index) : DOEPTSIZ(index); + + dev_dbg(hsotg->dev, "Sending zero-length packet on ep%d\n", index); + + writel(DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | + DXEPTSIZ_XFERSIZE(0), hsotg->regs + + epsiz_reg); + + ctrl = readl(hsotg->regs + epctl_reg); + ctrl |= DXEPCTL_CNAK; /* clear NAK set by core */ + ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ + ctrl |= DXEPCTL_USBACTEP; + writel(ctrl, hsotg->regs + epctl_reg); +} + /** * s3c_hsotg_complete_request - complete a request given to us * @hsotg: The device state. @@ -1341,9 +1353,9 @@ static void s3c_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) } /** - * s3c_hsotg_send_zlp - send zero-length packet on control endpoint + * s3c_hsotg_ep0_zlp - send/receive zero-length packet on control endpoint * @hsotg: The device instance - * @req: The request currently on this endpoint + * @dir_in: If IN zlp * * Generate a zero-length IN packet request for terminating a SETUP * transaction. @@ -1352,51 +1364,25 @@ static void s3c_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) * currently believed that we do not need to wait for any space in * the TxFIFO. */ -static void s3c_hsotg_send_zlp(struct dwc2_hsotg *hsotg, - struct s3c_hsotg_req *req) +static void s3c_hsotg_ep0_zlp(struct dwc2_hsotg *hsotg, bool dir_in) { - u32 ctrl; - - if (!req) { - dev_warn(hsotg->dev, "%s: no request?\n", __func__); - return; - } - - if (req->req.length == 0) { - hsotg->eps_out[0]->sent_zlp = 1; - s3c_hsotg_enqueue_setup(hsotg); - return; - } - /* eps_out[0] is used in both directions */ - hsotg->eps_out[0]->dir_in = 1; - hsotg->eps_out[0]->sent_zlp = 1; - - dev_dbg(hsotg->dev, "sending zero-length packet\n"); - - /* issue a zero-sized packet to terminate this */ - writel(DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | - DXEPTSIZ_XFERSIZE(0), hsotg->regs + DIEPTSIZ(0)); + hsotg->eps_out[0]->dir_in = dir_in; + hsotg->ep0_state = dir_in ? DWC2_EP0_STATUS_IN : DWC2_EP0_STATUS_OUT; - ctrl = readl(hsotg->regs + DIEPCTL0); - ctrl |= DXEPCTL_CNAK; /* clear NAK set by core */ - ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ - ctrl |= DXEPCTL_USBACTEP; - writel(ctrl, hsotg->regs + DIEPCTL0); + s3c_hsotg_program_zlp(hsotg, hsotg->eps_out[0]); } /** * s3c_hsotg_handle_outdone - handle receiving OutDone/SetupDone from RXFIFO * @hsotg: The device instance * @epnum: The endpoint received from - * @was_setup: Set if processing a SetupDone event. * * The RXFIFO has delivered an OutDone event, which means that the data * transfer for an OUT endpoint has been completed, either by a short * packet or by the finish of a transfer. */ -static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, - int epnum, bool was_setup) +static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, int epnum) { u32 epsize = readl(hsotg->regs + DOEPTSIZ(epnum)); struct s3c_hsotg_ep *hs_ep = hsotg->eps_out[epnum]; @@ -1410,6 +1396,13 @@ static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, return; } + if (epnum == 0 && hsotg->ep0_state == DWC2_EP0_STATUS_OUT) { + dev_dbg(hsotg->dev, "zlp packet received\n"); + s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + s3c_hsotg_enqueue_setup(hsotg); + return; + } + if (using_dma(hsotg)) { unsigned size_done; @@ -1432,12 +1425,6 @@ static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, if (req->actual < req->length && size_left == 0) { s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); return; - } else if (epnum == 0) { - /* - * After was_setup = 1 => - * set CNAK for non Setup requests - */ - hsotg->setup = was_setup ? 0 : 1; } if (req->actual < req->length && req->short_not_ok) { @@ -1450,13 +1437,10 @@ static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, */ } - if (epnum == 0) { - /* - * Condition req->complete != s3c_hsotg_complete_setup says: - * send ZLP when we have an asynchronous request from gadget - */ - if (!was_setup && req->complete != s3c_hsotg_complete_setup) - s3c_hsotg_send_zlp(hsotg, hs_req); + if (epnum == 0 && hsotg->ep0_state == DWC2_EP0_DATA_OUT) { + /* Move to STATUS IN */ + s3c_hsotg_ep0_zlp(hsotg, true); + return; } s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, result); @@ -1522,7 +1506,7 @@ static void s3c_hsotg_handle_rx(struct dwc2_hsotg *hsotg) s3c_hsotg_read_frameno(hsotg)); if (!using_dma(hsotg)) - s3c_hsotg_handle_outdone(hsotg, epnum, false); + s3c_hsotg_handle_outdone(hsotg, epnum); break; case GRXSTS_PKTSTS_SETUPDONE: @@ -1530,8 +1514,13 @@ static void s3c_hsotg_handle_rx(struct dwc2_hsotg *hsotg) "SetupDone (Frame=0x%08x, DOPEPCTL=0x%08x)\n", s3c_hsotg_read_frameno(hsotg), readl(hsotg->regs + DOEPCTL(0))); - - s3c_hsotg_handle_outdone(hsotg, epnum, true); + /* + * Call s3c_hsotg_handle_outdone here if it was not called from + * GRXSTS_PKTSTS_OUTDONE. That is, if the core didn't + * generate GRXSTS_PKTSTS_OUTDONE for setup packet. + */ + if (hsotg->ep0_state == DWC2_EP0_SETUP) + s3c_hsotg_handle_outdone(hsotg, epnum); break; case GRXSTS_PKTSTS_OUTRX: @@ -1544,6 +1533,8 @@ static void s3c_hsotg_handle_rx(struct dwc2_hsotg *hsotg) s3c_hsotg_read_frameno(hsotg), readl(hsotg->regs + DOEPCTL(0))); + WARN_ON(hsotg->ep0_state != DWC2_EP0_SETUP); + s3c_hsotg_rx_data(hsotg, epnum, size); break; @@ -1723,9 +1714,10 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, } /* Finish ZLP handling for IN EP0 transactions */ - if (hsotg->eps_out[0]->sent_zlp) { - dev_dbg(hsotg->dev, "zlp packet received\n"); + if (hs_ep->index == 0 && hsotg->ep0_state == DWC2_EP0_STATUS_IN) { + dev_dbg(hsotg->dev, "zlp packet sent\n"); s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + s3c_hsotg_enqueue_setup(hsotg); return; } @@ -1767,7 +1759,7 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, !(hs_req->req.length % hs_ep->ep.maxpacket)) { dev_dbg(hsotg->dev, "ep0 zlp IN packet sent\n"); - s3c_hsotg_send_zlp(hsotg, hs_req); + s3c_hsotg_program_zlp(hsotg, hs_ep); return; } @@ -1775,8 +1767,16 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, if (!size_left && hs_req->req.actual < hs_req->req.length) { dev_dbg(hsotg->dev, "%s trying more for req...\n", __func__); s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); - } else - s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + return; + } + + if (hs_ep->index == 0 && hsotg->ep0_state == DWC2_EP0_DATA_IN) { + /* Move to STATUS OUT */ + s3c_hsotg_ep0_zlp(hsotg, false); + return; + } + + s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); } /** @@ -1845,7 +1845,7 @@ static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, * as we ignore the RXFIFO. */ - s3c_hsotg_handle_outdone(hsotg, idx, false); + s3c_hsotg_handle_outdone(hsotg, idx); } } @@ -1884,7 +1884,7 @@ static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, if (dir_in) WARN_ON_ONCE(1); else - s3c_hsotg_handle_outdone(hsotg, 0, true); + s3c_hsotg_handle_outdone(hsotg, 0); } } |