| From 61d471c8da972c7ebbaf63779bf8100ee1ec54eb Mon Sep 17 00:00:00 2001 |
| From: Ran Wang <ran.wang_1@nxp.com> |
| Date: Wed, 16 Jan 2019 13:23:17 +0800 |
| Subject: [PATCH] usb: dwc3: Add workaround for host mode VBUS glitch when boot |
| |
| When DWC3 is set to host mode by programming register DWC3_GCTL, VBUS |
| (or its control signal) will be turned on immediately on related Root Hub |
| ports. Then, the VBUS is turned off for a little while(15us) when do xhci |
| reset (conducted by xhci driver) and back to normal finally, we can |
| observe a negative glitch of related signal happen. |
| |
| This VBUS glitch might cause some USB devices enumeration fail if kernel |
| boot with them connected. Such as LS1012AFWRY/LS1043ARDB/LX2160AQDS |
| /LS1088ARDB with Kingston 16GB USB2.0/Kingston USB3.0/JetFlash Transcend |
| 4GB USB2.0 drives. The fail cases include enumerated as full-speed device |
| or report wrong device descriptor, etc. |
| |
| One SW workaround which can fix this is by programing all xhci PORTSC[PP] |
| to 0 to turn off VBUS immediately after setting host mode in DWC3 driver |
| (per signal measurement result, it will be too late to do it in |
| xhci-plat.c or xhci.c). Then, after xhci reset complete in xhci driver, |
| PORTSC[PP]s' value will back to 1 automatically and VBUS on at that time, |
| no glitch happen and normal enumeration process has no impact. |
| |
| Signed-off-by: Ran Wang <ran.wang_1@nxp.com> |
| Reviewed-by: Peter Chen <peter.chen@nxp.com> |
| --- |
| drivers/usb/dwc3/core.c | 3 +++ |
| drivers/usb/dwc3/core.h | 3 +++ |
| drivers/usb/dwc3/host.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ |
| 3 files changed, 54 insertions(+) |
| |
| --- a/drivers/usb/dwc3/core.c |
| +++ b/drivers/usb/dwc3/core.c |
| @@ -1370,6 +1370,9 @@ static void dwc3_get_properties(struct d |
| dwc->dis_split_quirk = device_property_read_bool(dev, |
| "snps,dis-split-quirk"); |
| |
| + dwc->host_vbus_glitches = device_property_read_bool(dev, |
| + "snps,host-vbus-glitches"); |
| + |
| dwc->lpm_nyet_threshold = lpm_nyet_threshold; |
| dwc->tx_de_emphasis = tx_de_emphasis; |
| |
| --- a/drivers/usb/dwc3/core.h |
| +++ b/drivers/usb/dwc3/core.h |
| @@ -1049,6 +1049,8 @@ struct dwc3_scratchpad_array { |
| * 3 - Reserved |
| * @dis_metastability_quirk: set to disable metastability quirk. |
| * @dis_split_quirk: set to disable split boundary. |
| + * @host_vbus_glitches: set to avoid vbus glitch during |
| + * xhci reset. |
| * @imod_interval: set the interrupt moderation interval in 250ns |
| * increments or 0 to disable. |
| */ |
| @@ -1245,6 +1247,8 @@ struct dwc3 { |
| |
| unsigned dis_split_quirk:1; |
| |
| + unsigned host_vbus_glitches:1; |
| + |
| u16 imod_interval; |
| }; |
| |
| --- a/drivers/usb/dwc3/host.c |
| +++ b/drivers/usb/dwc3/host.c |
| @@ -9,8 +9,48 @@ |
| |
| #include <linux/platform_device.h> |
| |
| +#include "../host/xhci.h" |
| + |
| #include "core.h" |
| |
| +#define XHCI_HCSPARAMS1 0x4 |
| +#define XHCI_PORTSC_BASE 0x400 |
| + |
| +/* |
| + * dwc3_power_off_all_roothub_ports - Power off all Root hub ports |
| + * @dwc3: Pointer to our controller context structure |
| + */ |
| +static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) |
| +{ |
| + int i, port_num; |
| + u32 reg, op_regs_base, offset; |
| + void __iomem *xhci_regs; |
| + |
| + /* xhci regs is not mapped yet, do it temperary here */ |
| + if (dwc->xhci_resources[0].start) { |
| + xhci_regs = ioremap(dwc->xhci_resources[0].start, |
| + DWC3_XHCI_REGS_END); |
| + if (IS_ERR(xhci_regs)) { |
| + dev_err(dwc->dev, "Failed to ioremap xhci_regs\n"); |
| + return; |
| + } |
| + |
| + op_regs_base = HC_LENGTH(readl(xhci_regs)); |
| + reg = readl(xhci_regs + XHCI_HCSPARAMS1); |
| + port_num = HCS_MAX_PORTS(reg); |
| + |
| + for (i = 1; i <= port_num; i++) { |
| + offset = op_regs_base + XHCI_PORTSC_BASE + 0x10*(i-1); |
| + reg = readl(xhci_regs + offset); |
| + reg &= ~PORT_POWER; |
| + writel(reg, xhci_regs + offset); |
| + } |
| + |
| + iounmap(xhci_regs); |
| + } else |
| + dev_err(dwc->dev, "xhci base reg invalid\n"); |
| +} |
| + |
| static int dwc3_host_get_irq(struct dwc3 *dwc) |
| { |
| struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); |
| @@ -50,6 +90,13 @@ int dwc3_host_init(struct dwc3 *dwc) |
| struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); |
| int prop_idx = 0; |
| |
| + /* |
| + * We have to power off all Root hub ports immediately after DWC3 set |
| + * to host mode to avoid VBUS glitch happen when xhci get reset later. |
| + */ |
| + if (dwc->host_vbus_glitches) |
| + dwc3_power_off_all_roothub_ports(dwc); |
| + |
| irq = dwc3_host_get_irq(dwc); |
| if (irq < 0) |
| return irq; |