From 3aff0ad1bb43e46002bf3f7d270c1213ab29b680 Mon Sep 17 00:00:00 2001 From: Ye Li Date: Tue, 18 Feb 2020 21:27:15 -0800 Subject: [PATCH] MLK-23361 usb: cdns3: gadget: reset endpoint before set DEVDS On CDNS3 usb, the IOC interrupt for IN is triggered when data move from memory to FIFO, but not for the transfer completion. When running fastboot command "continue" to disconnect USB device. The last message "OKAY" sent from device setups a callback to IOC interrupt handler and disconnect with host in this callback. However, the real transfer hasn't finished due to host doesn't send IN at that time, it causes the error at host side. This patch will reset all enabled endpoints in pull up before disable the device mode. The reset checks the EP_STS.BUFFEREMPTY bit to ensure data has sent to host. Signed-off-by: Ye Li Signed-off-by: Peter Chen Reviewed-by: Peter Chen (cherry picked from commit 9be87bc89c1e75755b524703dc536c93bd3fbe5c) (cherry picked from commit 076f49edefab2ef832b09ada855f968d44c15928) --- drivers/usb/cdns3/dev-regs-macro.h | 1 + drivers/usb/cdns3/gadget.c | 77 ++++++++++++++++++++++++++---- drivers/usb/cdns3/gadget.h | 1 + 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/drivers/usb/cdns3/dev-regs-macro.h b/drivers/usb/cdns3/dev-regs-macro.h index 7c8ea81d1e..e09d5c28ad 100644 --- a/drivers/usb/cdns3/dev-regs-macro.h +++ b/drivers/usb/cdns3/dev-regs-macro.h @@ -91,6 +91,7 @@ #define EP_STS__TRBERR__MASK 0x00000080U #define EP_STS__NRDY__MASK 0x00000100U #define EP_STS__DBUSY__MASK 0x00000200U +#define EP_STS__BUFFEMPTY__MASK 0x00000400U #define EP_STS__OUTSMM__MASK 0x00004000U #define EP_STS__ISOERR__MASK 0x00008000U diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c index 0648957cb7..5bd80cf986 100644 --- a/drivers/usb/cdns3/gadget.c +++ b/drivers/usb/cdns3/gadget.c @@ -22,6 +22,7 @@ #include "gadget-export.h" #include "gadget.h" #include "io.h" +#include static void __cdns3_gadget_start(struct usb_ss_dev *usb_ss); static void cdns_prepare_setup_packet(struct usb_ss_dev *usb_ss); @@ -1379,6 +1380,61 @@ static int usb_ss_gadget_ep_enable(struct usb_ep *ep, return 0; } +static int cdns3_disable_reset_ep(struct usb_ss_dev *usb_ss, + struct usb_ss_endpoint *usb_ss_ep) +{ + u32 val; + int ret; + unsigned long flags; + + spin_lock_irqsave(&usb_ss->lock, flags); + + if (usb_ss_ep->hw_reset_flag) { + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; + } + + select_ep(usb_ss_ep->usb_ss, + usb_ss_ep->endpoint.desc->bEndpointAddress); + + /** + * Driver needs some time before resetting endpoint. + * It need waits for clearing DBUSY bit or for timeout expired. + * 10us is enough time for controller to stop transfer. + */ + ret = readl_poll_timeout(&usb_ss->regs->ep_sts, val, + !(val & EP_STS__DBUSY__MASK), 10); + if (unlikely(ret)) + dev_err(&usb_ss->dev, "Timeout: %s wait dbusy\n", + usb_ss->gadget.name); + + ret = readl_poll_timeout(&usb_ss->regs->ep_sts, val, + (val & EP_STS__BUFFEMPTY__MASK), 1000); + if (unlikely(ret)) + dev_err(&usb_ss->dev, "Timeout: %s: %s wait buffer empty\n", + usb_ss_ep->name, usb_ss->gadget.name); + + writel(EP_CMD__EPRST__MASK, &usb_ss->regs->ep_cmd); + + ret = readl_poll_timeout(&usb_ss->regs->ep_cmd, val, + !(val & (EP_CMD__CSTALL__MASK | EP_CMD__EPRST__MASK)), + 1000); + + if (unlikely(ret)) + dev_err(&usb_ss->dev, "Timeout: %s resetting failed.\n", + usb_ss->gadget.name); + + + val = readl(&usb_ss->regs->ep_cfg); + val &= ~EP_CFG__ENABLE__MASK; + writel(val, &usb_ss->regs->ep_cfg); + + usb_ss_ep->hw_reset_flag = 1; + spin_unlock_irqrestore(&usb_ss->lock, flags); + + return ret; +} + static int usb_ss_gadget_ep_conf(struct usb_gadget *gadget, struct usb_ep *ep, struct usb_endpoint_descriptor *desc) @@ -1431,7 +1487,6 @@ static int usb_ss_gadget_ep_disable(struct usb_ep *ep) unsigned long flags; int ret = 0; struct usb_request *request; - u32 ep_cfg; if (!ep) { pr_debug("usb-ss: invalid parameters\n"); @@ -1452,6 +1507,8 @@ static int usb_ss_gadget_ep_disable(struct usb_ep *ep) dev_dbg(&usb_ss->dev, "Disabling endpoint: %s\n", ep->name); + ret = cdns3_disable_reset_ep(usb_ss, usb_ss_ep); + while (!list_empty(&usb_ss_ep->request_list)) { request = next_request(&usb_ss_ep->request_list); usb_gadget_unmap_request(&usb_ss->gadget, request, @@ -1464,10 +1521,6 @@ static int usb_ss_gadget_ep_disable(struct usb_ep *ep) spin_lock(&usb_ss->lock); } - select_ep(usb_ss, ep->desc->bEndpointAddress); - ep_cfg = cdns_readl(&usb_ss->regs->ep_cfg); - ep_cfg &= ~EP_CFG__ENABLE__MASK; - cdns_writel(&usb_ss->regs->ep_cfg, ep_cfg); ep->desc = NULL; usb_ss_ep->enabled = 0; @@ -1733,17 +1786,25 @@ static int usb_ss_gadget_set_selfpowered(struct usb_gadget *gadget, static int usb_ss_gadget_pullup(struct usb_gadget *gadget, int is_on) { struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + struct usb_ep *ep; if (!usb_ss->start_gadget) return 0; dev_dbg(&usb_ss->dev, "%s: %d\n", __func__, is_on); - if (is_on) + if (is_on) { cdns_writel(&usb_ss->regs->usb_conf, USB_CONF__DEVEN__MASK); - else - cdns_writel(&usb_ss->regs->usb_conf, USB_CONF__DEVDS__MASK); + } else { + list_for_each_entry(ep, + &usb_ss->gadget.ep_list, + ep_list) { + if (to_usb_ss_ep(ep)->enabled) + cdns3_disable_reset_ep(usb_ss, to_usb_ss_ep(ep)); + } + cdns_writel(&usb_ss->regs->usb_conf, USB_CONF__DEVDS__MASK); + } return 0; } diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h index 91f59e8da7..e92f283b84 100644 --- a/drivers/usb/cdns3/gadget.h +++ b/drivers/usb/cdns3/gadget.h @@ -160,6 +160,7 @@ struct usb_ss_endpoint { int hw_pending_flag; int stalled_flag; int wedge_flag; + int hw_reset_flag; void *cpu_addr; dma_addr_t dma_addr; u8 dir; -- 2.17.1