When combining dwc3 with an redriver for a USB Type-C device solution, it sometimes have problems with leaving U1/U2 for certain hosts, resulting in link training errors and reconnects. This create an interface via configfs for disabling U1/U2, enabling a workaround for devices based on dwc3. Signed-off-by: Claus H. Stovgaard <cst@xxxxxxxxxxxx> --- drivers/usb/dwc3/ep0.c | 9 ++++++- drivers/usb/gadget/configfs.c | 56 +++++++++++++++++++++++++++++++++++++++++++ include/linux/usb/gadget.h | 6 ++++- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 8efde17..5b2d26b 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -379,6 +379,8 @@ static int dwc3_ep0_handle_u1(struct dwc3 *dwc, enum usb_device_state state, if ((dwc->speed != DWC3_DSTS_SUPERSPEED) && (dwc->speed != DWC3_DSTS_SUPERSPEED_PLUS)) return -EINVAL; + if (dwc->gadget_driver->lpm_U1_disable) + return -EINVAL; reg = dwc3_readl(dwc->regs, DWC3_DCTL); if (set) @@ -401,6 +403,8 @@ static int dwc3_ep0_handle_u2(struct dwc3 *dwc, enum usb_device_state state, if ((dwc->speed != DWC3_DSTS_SUPERSPEED) && (dwc->speed != DWC3_DSTS_SUPERSPEED_PLUS)) return -EINVAL; + if (dwc->gadget_driver->lpm_U2_disable) + return -EINVAL; reg = dwc3_readl(dwc->regs, DWC3_DCTL); if (set) @@ -626,7 +630,10 @@ static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) * nothing is pending from application. */ reg = dwc3_readl(dwc->regs, DWC3_DCTL); - reg |= (DWC3_DCTL_ACCEPTU1ENA | DWC3_DCTL_ACCEPTU2ENA); + if (!dwc->gadget_driver->lpm_U1_disable) + reg |= DWC3_DCTL_ACCEPTU1ENA; + if (!dwc->gadget_driver->lpm_U2_disable) + reg |= DWC3_DCTL_ACCEPTU2ENA; dwc3_writel(dwc->regs, DWC3_DCTL, reg); } break; diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 0251299..2ee9d10 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -229,6 +229,56 @@ static ssize_t gadget_dev_desc_bcdUSB_store(struct config_item *item, return len; } +static ssize_t gadget_dev_desc_lpm_U1_disable_show(struct config_item *item, + char *page) +{ + struct gadget_info *gi = to_gadget_info(item); + + return sprintf(page, "%d\n", + gi->composite.gadget_driver.lpm_U1_disable); +} + +static ssize_t gadget_dev_desc_lpm_U1_disable_store(struct config_item *item, + const char *page, size_t len) +{ + struct gadget_info *gi = to_gadget_info(item); + bool disable; + int ret; + + ret = strtobool(page, &disable); + if (!ret) { + gi->composite.gadget_driver.lpm_U1_disable = disable; + ret = len; + } + + return ret; +} + +static ssize_t gadget_dev_desc_lpm_U2_disable_show(struct config_item *item, + char *page) +{ + struct gadget_info *gi = to_gadget_info(item); + + return sprintf(page, "%d\n", + gi->composite.gadget_driver.lpm_U2_disable); +} + +static ssize_t gadget_dev_desc_lpm_U2_disable_store(struct config_item *item, + const char *page, size_t len) +{ + struct gadget_info *gi = to_gadget_info(item); + bool disable; + int ret; + + ret = strtobool(page, &disable); + if (!ret) { + gi->composite.gadget_driver.lpm_U2_disable = disable; + ret = len; + } + + return ret; +} + static ssize_t gadget_dev_desc_UDC_show(struct config_item *item, char *page) { char *udc_name = to_gadget_info(item)->composite.gadget_driver.udc_name; @@ -299,6 +349,8 @@ CONFIGFS_ATTR(gadget_dev_desc_, idVendor); CONFIGFS_ATTR(gadget_dev_desc_, idProduct); CONFIGFS_ATTR(gadget_dev_desc_, bcdDevice); CONFIGFS_ATTR(gadget_dev_desc_, bcdUSB); +CONFIGFS_ATTR(gadget_dev_desc_, lpm_U1_disable); +CONFIGFS_ATTR(gadget_dev_desc_, lpm_U2_disable); CONFIGFS_ATTR(gadget_dev_desc_, UDC); static struct configfs_attribute *gadget_root_attrs[] = { @@ -310,6 +362,8 @@ static struct configfs_attribute *gadget_root_attrs[] = { &gadget_dev_desc_attr_idProduct, &gadget_dev_desc_attr_bcdDevice, &gadget_dev_desc_attr_bcdUSB, + &gadget_dev_desc_attr_lpm_U1_disable, + &gadget_dev_desc_attr_lpm_U2_disable, &gadget_dev_desc_attr_UDC, NULL, }; @@ -1408,6 +1462,8 @@ static const struct usb_gadget_driver configfs_driver_template = { .name = "configfs-gadget", }, .match_existing_only = 1, + .lpm_U1_disable = 0, + .lpm_U2_disable = 0, }; static struct config_group *gadgets_make( diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 7595056..25fe72b 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -619,7 +619,9 @@ static inline int usb_gadget_activate(struct usb_gadget *gadget) * this driver will be bound to any available UDC. * @pending: UDC core private data used for deferred probe of this driver. * @match_existing_only: If udc is not found, return an error and don't add this - * gadget driver to list of pending driver + * gadget driver to list of pending driver. + * @lpm_U1_disable: Instruct the UDC to disable U1 if possible. + * @lpm_U2_disable: Instruct the UDC to disable U2 if possible. * * Devices are disabled till a gadget driver successfully bind()s, which * means the driver will handle setup() requests needed to enumerate (and @@ -684,6 +686,8 @@ struct usb_gadget_driver { char *udc_name; struct list_head pending; unsigned match_existing_only:1; + unsigned lpm_U1_disable:1; + unsigned lpm_U2_disable:1; }; -- 2.7.4