dwc3: Add QTI MSM platform specific feature and other changes
This change adds QTI MSM platform specific USB related features
and fixes.
Change-Id: I0f733d05f3f88a4a734d8489b5eb548391ab3af3
Signed-off-by: Mayank Rana <mrana@codeaurora.org>
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 9466431..74bc159 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -35,6 +35,7 @@
#include <linux/of.h>
#include <linux/acpi.h>
#include <linux/pinctrl/consumer.h>
+#include <linux/irq.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
@@ -49,6 +50,20 @@
#define DWC3_DEFAULT_AUTOSUSPEND_DELAY 5000 /* ms */
+void dwc3_usb3_phy_suspend(struct dwc3 *dwc, int suspend)
+{
+ u32 reg;
+
+ reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
+
+ if (suspend)
+ reg |= DWC3_GUSB3PIPECTL_SUSPHY;
+ else
+ reg &= ~DWC3_GUSB3PIPECTL_SUSPHY;
+
+ dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
+}
+
void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
{
u32 reg;
@@ -57,6 +72,37 @@
reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
reg |= DWC3_GCTL_PRTCAPDIR(mode);
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+
+ /*
+ * Set this bit so that device attempts three more times at SS, even
+ * if it failed previously to operate in SS mode.
+ */
+ reg |= DWC3_GCTL_U2RSTECN;
+ reg &= ~(DWC3_GCTL_SOFITPSYNC);
+ reg &= ~(DWC3_GCTL_PWRDNSCALEMASK);
+ reg |= DWC3_GCTL_PWRDNSCALE(2);
+ reg |= DWC3_GCTL_U2EXIT_LFPS;
+ dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+
+ if (mode == DWC3_GCTL_PRTCAP_OTG || mode == DWC3_GCTL_PRTCAP_HOST) {
+ /*
+ * Allow ITP generated off of ref clk based counter instead
+ * of UTMI/ULPI clk based counter, when superspeed only is
+ * active so that UTMI/ULPI PHY can be suspened.
+ *
+ * Starting with revision 2.50A, GFLADJ_REFCLK_LPM_SEL is used
+ * instead.
+ */
+ if (dwc->revision < DWC3_REVISION_250A) {
+ reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+ reg |= DWC3_GCTL_SOFITPSYNC;
+ dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+ } else {
+ reg = dwc3_readl(dwc->regs, DWC3_GFLADJ);
+ reg |= DWC3_GFLADJ_REFCLK_LPM_SEL;
+ dwc3_writel(dwc->regs, DWC3_GFLADJ, reg);
+ }
+ }
}
u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type)
@@ -74,17 +120,36 @@
}
/**
- * dwc3_core_soft_reset - Issues core soft reset and PHY reset
+ * Peforms initialization of HS and SS PHYs.
+ * If used as a part of POR or init sequence it is recommended
+ * that we should perform hard reset of the PHYs prior to invoking
+ * this function.
* @dwc: pointer to our context structure
- */
-static int dwc3_core_soft_reset(struct dwc3 *dwc)
+*/
+static int dwc3_init_usb_phys(struct dwc3 *dwc)
{
- u32 reg;
- int retries = 1000;
int ret;
- usb_phy_init(dwc->usb2_phy);
- usb_phy_init(dwc->usb3_phy);
+ /* Bring up PHYs */
+ ret = usb_phy_init(dwc->usb2_phy);
+ if (ret) {
+ pr_err("%s: usb_phy_init(dwc->usb2_phy) returned %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = usb_phy_init(dwc->usb3_phy);
+ if (ret == -EBUSY) {
+ /*
+ * Setting Max speed as high when USB3 PHY initialiation
+ * is failing and USB superspeed can't be supported.
+ */
+ dwc->maximum_speed = USB_SPEED_HIGH;
+ } else if (ret) {
+ pr_err("%s: usb_phy_init(dwc->usb3_phy) returned %d\n",
+ __func__, ret);
+ return ret;
+ }
ret = phy_init(dwc->usb2_generic_phy);
if (ret < 0)
return ret;
@@ -95,27 +160,34 @@
return ret;
}
- /*
- * We're resetting only the device side because, if we're in host mode,
- * XHCI driver will reset the host block. If dwc3 was configured for
- * host-only mode, then we can return early.
- */
- if (dwc->dr_mode == USB_DR_MODE_HOST)
- return 0;
+ return 0;
+}
- reg = dwc3_readl(dwc->regs, DWC3_DCTL);
- reg |= DWC3_DCTL_CSFTRST;
- dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+/**
+ * dwc3_core_reset - Issues core soft reset and PHY reset
+ * @dwc: pointer to our context structure
+ */
+static int dwc3_core_reset(struct dwc3 *dwc)
+{
+ int ret;
- do {
- reg = dwc3_readl(dwc->regs, DWC3_DCTL);
- if (!(reg & DWC3_DCTL_CSFTRST))
- return 0;
+ /* Reset PHYs */
+ usb_phy_reset(dwc->usb2_phy);
+ usb_phy_reset(dwc->usb3_phy);
- udelay(1);
- } while (--retries);
+ /* Initialize PHYs */
+ ret = dwc3_init_usb_phys(dwc);
+ if (ret) {
+ pr_err("%s: dwc3_init_phys returned %d\n",
+ __func__, ret);
+ return ret;
+ }
- return -ETIMEDOUT;
+ dwc3_notify_event(dwc, DWC3_CONTROLLER_RESET_EVENT);
+
+ dwc3_notify_event(dwc, DWC3_CONTROLLER_POST_RESET_EVENT);
+
+ return 0;
}
/**
@@ -249,7 +321,7 @@
*
* Returns 0 on success otherwise negative errno.
*/
-static int dwc3_event_buffers_setup(struct dwc3 *dwc)
+int dwc3_event_buffers_setup(struct dwc3 *dwc)
{
struct dwc3_event_buffer *evt;
@@ -526,7 +598,7 @@
*
* Returns 0 on success otherwise negative errno.
*/
-static int dwc3_core_init(struct dwc3 *dwc)
+int dwc3_core_init(struct dwc3 *dwc)
{
u32 hwparams4 = dwc->hwparams.hwparams4;
u32 reg;
@@ -561,11 +633,12 @@
}
/* issue device SoftReset too */
- ret = dwc3_soft_reset(dwc);
+ ret = dwc3_core_reset(dwc);
if (ret)
goto err0;
- ret = dwc3_core_soft_reset(dwc);
+ /* issue device SoftReset too */
+ ret = dwc3_soft_reset(dwc);
if (ret)
goto err0;
@@ -639,13 +712,26 @@
if (dwc->revision < DWC3_REVISION_190A)
reg |= DWC3_GCTL_U2RSTECN;
+ dwc3_core_num_eps(dwc);
+
+ /*
+ * Disable clock gating to work around a known HW bug that causes the
+ * internal RAM clock to get stuck when entering low power modes.
+ */
+ if (dwc->disable_clk_gating) {
+ dev_dbg(dwc->dev, "Disabling controller clock gating.\n");
+ reg |= DWC3_GCTL_DSBLCLKGTNG;
+ }
+
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
- dwc3_core_num_eps(dwc);
+ ret = dwc3_alloc_scratch_buffers(dwc);
+ if (ret)
+ goto err1;
ret = dwc3_setup_scratch_buffers(dwc);
if (ret)
- goto err1;
+ goto err2;
/* Adjust Frame Length */
dwc3_frame_length_adjustment(dwc);
@@ -656,28 +742,21 @@
if (ret < 0)
goto err2;
- ret = phy_power_on(dwc->usb3_generic_phy);
- if (ret < 0)
- goto err3;
-
- ret = dwc3_event_buffers_setup(dwc);
- if (ret) {
- dev_err(dwc->dev, "failed to setup event buffers\n");
- goto err4;
+ /*
+ * clear Elastic buffer mode in GUSBPIPE_CTRL(0) register, otherwise
+ * it results in high link errors and could cause SS mode transfer
+ * failure.
+ */
+ if (!dwc->nominal_elastic_buffer) {
+ reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
+ reg &= ~DWC3_GUSB3PIPECTL_ELASTIC_BUF_MODE;
+ dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
}
return 0;
-err4:
- phy_power_off(dwc->usb2_generic_phy);
-
-err3:
- phy_power_off(dwc->usb3_generic_phy);
-
err2:
- usb_phy_set_suspend(dwc->usb2_phy, 1);
- usb_phy_set_suspend(dwc->usb3_phy, 1);
- dwc3_core_exit(dwc);
+ dwc3_free_scratch_buffers(dwc);
err1:
usb_phy_shutdown(dwc->usb2_phy);
@@ -759,42 +838,16 @@
static int dwc3_core_init_mode(struct dwc3 *dwc)
{
struct device *dev = dwc->dev;
- int ret;
switch (dwc->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
- ret = dwc3_gadget_init(dwc);
- if (ret) {
- if (ret != -EPROBE_DEFER)
- dev_err(dev, "failed to initialize gadget\n");
- return ret;
- }
break;
case USB_DR_MODE_HOST:
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
- ret = dwc3_host_init(dwc);
- if (ret) {
- if (ret != -EPROBE_DEFER)
- dev_err(dev, "failed to initialize host\n");
- return ret;
- }
break;
case USB_DR_MODE_OTG:
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
- ret = dwc3_host_init(dwc);
- if (ret) {
- if (ret != -EPROBE_DEFER)
- dev_err(dev, "failed to initialize host\n");
- return ret;
- }
-
- ret = dwc3_gadget_init(dwc);
- if (ret) {
- if (ret != -EPROBE_DEFER)
- dev_err(dev, "failed to initialize gadget\n");
- return ret;
- }
break;
default:
dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
@@ -823,6 +876,96 @@
}
}
+/* XHCI reset, resets other CORE registers as well, re-init those */
+void dwc3_post_host_reset_core_init(struct dwc3 *dwc)
+{
+ dwc3_core_init(dwc);
+ dwc3_gadget_restart(dwc);
+}
+
+static void (*notify_event)(struct dwc3 *, unsigned int);
+void dwc3_set_notifier(void (*notify)(struct dwc3 *, unsigned int))
+{
+ notify_event = notify;
+}
+EXPORT_SYMBOL(dwc3_set_notifier);
+
+int dwc3_notify_event(struct dwc3 *dwc, unsigned int event)
+{
+ int ret = 0;
+
+ if (dwc->notify_event)
+ dwc->notify_event(dwc, event);
+ else
+ ret = -ENODEV;
+
+ return ret;
+}
+EXPORT_SYMBOL(dwc3_notify_event);
+
+int dwc3_core_pre_init(struct dwc3 *dwc)
+{
+ int ret;
+
+ dwc3_cache_hwparams(dwc);
+
+ ret = dwc3_phy_setup(dwc);
+ if (ret)
+ goto err0;
+
+ if (!dwc->ev_buf) {
+ ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
+ if (ret) {
+ dev_err(dwc->dev, "failed to allocate event buffers\n");
+ ret = -ENOMEM;
+ goto err1;
+ }
+ }
+
+ ret = dwc3_core_init(dwc);
+ if (ret) {
+ dev_err(dwc->dev, "failed to initialize core\n");
+ goto err2;
+ }
+
+ ret = phy_power_on(dwc->usb2_generic_phy);
+ if (ret < 0)
+ goto err3;
+
+ ret = phy_power_on(dwc->usb3_generic_phy);
+ if (ret < 0)
+ goto err4;
+
+ ret = dwc3_event_buffers_setup(dwc);
+ if (ret) {
+ dev_err(dwc->dev, "failed to setup event buffers\n");
+ goto err5;
+ }
+
+ ret = dwc3_core_init_mode(dwc);
+ if (ret) {
+ dev_err(dwc->dev, "failed to set mode with dwc3 core\n");
+ goto err6;
+ }
+
+ return ret;
+
+err6:
+ dwc3_event_buffers_cleanup(dwc);
+err5:
+ phy_power_off(dwc->usb3_generic_phy);
+err4:
+ phy_power_off(dwc->usb2_generic_phy);
+err3:
+ dwc3_core_exit(dwc);
+err2:
+ dwc3_free_event_buffers(dwc);
+err1:
+ dwc3_ulpi_exit(dwc);
+err0:
+ return ret;
+}
+
#define DWC3_ALIGN_MASK (16 - 1)
static int dwc3_probe(struct platform_device *pdev)
@@ -833,6 +976,7 @@
u8 lpm_nyet_threshold;
u8 tx_de_emphasis;
u8 hird_threshold;
+ int irq;
int ret;
@@ -847,12 +991,37 @@
dwc->mem = mem;
dwc->dev = dev;
+ dwc->notify_event = notify_event;
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(dev, "missing IRQ\n");
+ return -ENODEV;
+ }
+ dwc->xhci_resources[1].start = res->start;
+ dwc->xhci_resources[1].end = res->end;
+ dwc->xhci_resources[1].flags = res->flags;
+ dwc->xhci_resources[1].name = res->name;
+
+ irq = platform_get_irq(to_platform_device(dwc->dev), 0);
+
+ /* will be enabled in dwc3_msm_resume() */
+ irq_set_status_flags(irq, IRQ_NOAUTOEN);
+ ret = devm_request_threaded_irq(dev, irq, NULL, dwc3_interrupt,
+ IRQF_SHARED | IRQF_ONESHOT, "dwc3", dwc);
+ if (ret) {
+ dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
+ irq, ret);
+ return -ENODEV;
+ }
+
+ dwc->irq = irq;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "missing memory resource\n");
return -ENODEV;
}
+ dwc->reg_phys = res->start;
dwc->xhci_resources[0].start = res->start;
dwc->xhci_resources[0].end = dwc->xhci_resources[0].start +
DWC3_XHCI_REGS_END;
@@ -934,14 +1103,19 @@
device_property_read_u32(dev, "snps,quirk-frame-length-adjustment",
&dwc->fladj);
+ if (dwc->enable_bus_suspend) {
+ pm_runtime_set_autosuspend_delay(dev, 500);
+ pm_runtime_use_autosuspend(dev);
+ }
+
dwc->lpm_nyet_threshold = lpm_nyet_threshold;
dwc->tx_de_emphasis = tx_de_emphasis;
dwc->hird_threshold = hird_threshold
| (dwc->is_utmi_l1_suspend << 4);
+ init_waitqueue_head(&dwc->wait_linkstate);
platform_set_drvdata(pdev, dwc);
- dwc3_cache_hwparams(dwc);
ret = dwc3_core_get_phy(dwc);
if (ret)
@@ -955,43 +1129,21 @@
dma_set_coherent_mask(dev, dev->parent->coherent_dma_mask);
}
+ pm_runtime_no_callbacks(dev);
pm_runtime_set_active(dev);
- pm_runtime_use_autosuspend(dev);
- pm_runtime_set_autosuspend_delay(dev, DWC3_DEFAULT_AUTOSUSPEND_DELAY);
pm_runtime_enable(dev);
- ret = pm_runtime_get_sync(dev);
- if (ret < 0)
- goto err1;
-
pm_runtime_forbid(dev);
- ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
- if (ret) {
- dev_err(dwc->dev, "failed to allocate event buffers\n");
- ret = -ENOMEM;
- goto err2;
- }
-
- if (IS_ENABLED(CONFIG_USB_DWC3_HOST) &&
- (dwc->dr_mode == USB_DR_MODE_OTG ||
- dwc->dr_mode == USB_DR_MODE_UNKNOWN))
+ if (IS_ENABLED(CONFIG_USB_DWC3_HOST))
dwc->dr_mode = USB_DR_MODE_HOST;
else if (IS_ENABLED(CONFIG_USB_DWC3_GADGET) &&
(dwc->dr_mode == USB_DR_MODE_OTG ||
dwc->dr_mode == USB_DR_MODE_UNKNOWN))
dwc->dr_mode = USB_DR_MODE_PERIPHERAL;
- if (dwc->dr_mode == USB_DR_MODE_UNKNOWN)
+ if (dwc->dr_mode == USB_DR_MODE_UNKNOWN) {
dwc->dr_mode = USB_DR_MODE_OTG;
-
- ret = dwc3_alloc_scratch_buffers(dwc);
- if (ret)
- goto err3;
-
- ret = dwc3_core_init(dwc);
- if (ret) {
- dev_err(dev, "failed to initialize core\n");
- goto err4;
+ dwc->is_drd = true;
}
/* Check the maximum_speed parameter */
@@ -1021,32 +1173,48 @@
break;
}
- ret = dwc3_core_init_mode(dwc);
- if (ret)
- goto err5;
+ /* Adjust Frame Length */
+ dwc3_frame_length_adjustment(dwc);
- dwc3_debugfs_init(dwc);
- pm_runtime_put(dev);
+ /* Hardcode number of eps */
+ dwc->num_in_eps = 16;
+ dwc->num_out_eps = 16;
+ if (dwc->dr_mode == USB_DR_MODE_OTG ||
+ dwc->dr_mode == USB_DR_MODE_PERIPHERAL) {
+ ret = dwc3_gadget_init(dwc);
+ if (ret) {
+ dev_err(dev, "failed to initialize gadget\n");
+ goto err0;
+ }
+ }
+
+ if (dwc->dr_mode == USB_DR_MODE_OTG ||
+ dwc->dr_mode == USB_DR_MODE_HOST) {
+ ret = dwc3_host_init(dwc);
+ if (ret) {
+ dev_err(dev, "failed to initialize host\n");
+ goto err_gadget;
+ }
+ }
+
+ ret = dwc3_debugfs_init(dwc);
+ if (ret) {
+ dev_err(dev, "failed to initialize debugfs\n");
+ goto err_host;
+ }
+
+ pm_runtime_allow(dev);
return 0;
-err5:
- dwc3_event_buffers_cleanup(dwc);
-
-err4:
- dwc3_free_scratch_buffers(dwc);
-
-err3:
- dwc3_free_event_buffers(dwc);
- dwc3_ulpi_exit(dwc);
-
-err2:
- pm_runtime_allow(&pdev->dev);
-
-err1:
- pm_runtime_put_sync(&pdev->dev);
- pm_runtime_disable(&pdev->dev);
-
+err_host:
+ if (dwc->dr_mode == USB_DR_MODE_OTG ||
+ dwc->dr_mode == USB_DR_MODE_HOST)
+ dwc3_host_exit(dwc);
+err_gadget:
+ if (dwc->dr_mode == USB_DR_MODE_OTG ||
+ dwc->dr_mode == USB_DR_MODE_PERIPHERAL)
+ dwc3_gadget_exit(dwc);
err0:
/*
* restore res->start back to its original value so that, in case the
@@ -1157,8 +1325,9 @@
struct dwc3 *dwc = dev_get_drvdata(dev);
int ret;
- if (dwc3_runtime_checks(dwc))
- return -EBUSY;
+ /* Check if platform glue driver handling PM, if not then handle here */
+ if (!dwc3_notify_event(dwc, DWC3_CORE_PM_RESUME_EVENT))
+ return 0;
ret = dwc3_suspend_common(dwc);
if (ret)
@@ -1174,6 +1343,10 @@
struct dwc3 *dwc = dev_get_drvdata(dev);
int ret;
+ /* Check if platform glue driver handling PM, if not then handle here */
+ if (!dwc3_notify_event(dwc, DWC3_CORE_PM_RESUME_EVENT))
+ return 0;
+
device_init_wakeup(dev, false);
ret = dwc3_resume_common(dwc);