Commit fe0b94ab authored by Mian Yousaf Kaukab's avatar Mian Yousaf Kaukab Committed by Felipe Balbi

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: default avatarRobert Baldyga <r.baldyga@samsung.com>
Acked-by: default avatarPaul Zimmerman <paulz@synopsys.com>
Signed-off-by: default avatarMian Yousaf Kaukab <yousaf.kaukab@intel.com>
Signed-off-by: default avatarFelipe Balbi <balbi@ti.com>
parent 1141ea01
......@@ -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];
......
......@@ -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;
hsotg->eps_out[0]->dir_in = dir_in;
hsotg->ep0_state = dir_in ? DWC2_EP0_STATUS_IN : DWC2_EP0_STATUS_OUT;
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));
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,7 +1767,15 @@ 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
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);
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment