aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/dwc3/dwc3-of-simple.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/dwc3/dwc3-of-simple.c')
-rw-r--r--drivers/usb/dwc3/dwc3-of-simple.c507
1 files changed, 506 insertions, 1 deletions
diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c
index d055e00f8180..462edbb7069d 100644
--- a/drivers/usb/dwc3/dwc3-of-simple.c
+++ b/drivers/usb/dwc3/dwc3-of-simple.c
@@ -21,16 +21,260 @@
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
+#include <linux/soc/xilinx/zynqmp/fw.h>
+#include <linux/firmware/xlnx-zynqmp.h>
+#include <linux/slab.h>
+
+#include <linux/phy/phy-zynqmp.h>
+#include <linux/of_address.h>
+
+#include "core.h"
+
+/* USB phy reset mask register */
+#define XLNX_USB_PHY_RST 0x001C
+#define XLNX_PHY_RST_MASK 0x1
+
+/* Xilinx USB 3.0 IP Register */
+#define XLNX_USB_COHERENCY 0x005C
+#define XLNX_USB_COHERENCY_ENABLE 0x1
+
+/* ULPI control registers */
+#define ULPI_OTG_CTRL_SET 0xB
+#define ULPI_OTG_CTRL_CLEAR 0XC
+#define OTG_CTRL_DRVVBUS_OFFSET 5
+
+#define XLNX_USB_CUR_PWR_STATE 0x0000
+#define XLNX_CUR_PWR_STATE_D0 0x00
+#define XLNX_CUR_PWR_STATE_D3 0x0F
+#define XLNX_CUR_PWR_STATE_BITMASK 0x0F
+
+#define XLNX_USB_PME_ENABLE 0x0034
+#define XLNX_PME_ENABLE_SIG_GEN 0x01
+
+#define XLNX_USB_REQ_PWR_STATE 0x003c
+#define XLNX_REQ_PWR_STATE_D0 0x00
+#define XLNX_REQ_PWR_STATE_D3 0x03
+
+/* Number of retries for USB operations */
+#define DWC3_PWR_STATE_RETRIES 1000
+#define DWC3_PWR_TIMEOUT 100
+
+/* Versal USB Node ID */
+#define VERSAL_USB_NODE_ID 0x18224018
+
+/* Versal USB Reset ID */
+#define VERSAL_USB_RESET_ID 0xC104036
+
+#define DWC3_OF_ADDRESS(ADDR) ((ADDR) - DWC3_GLOBALS_REGS_START)
+
+static const struct zynqmp_eemi_ops *eemi_ops;
struct dwc3_of_simple {
struct device *dev;
struct clk_bulk_data *clks;
int num_clocks;
+ void __iomem *regs;
+ struct dwc3 *dwc;
+ struct phy *phy;
+ bool wakeup_capable;
+ bool dis_u3_susphy_quirk;
+ bool enable_d3_suspend;
+ char soc_rev;
struct reset_control *resets;
bool pulse_resets;
bool need_reset;
};
+int dwc3_enable_hw_coherency(struct device *dev)
+{
+ struct device_node *node = of_get_parent(dev->of_node);
+
+ if (of_device_is_compatible(node, "xlnx,zynqmp-dwc3")) {
+ struct platform_device *pdev_parent;
+ struct dwc3_of_simple *simple;
+ void __iomem *regs;
+ u32 reg;
+
+ pdev_parent = of_find_device_by_node(node);
+ simple = platform_get_drvdata(pdev_parent);
+ regs = simple->regs;
+
+ reg = readl(regs + XLNX_USB_COHERENCY);
+ reg |= XLNX_USB_COHERENCY_ENABLE;
+ writel(reg, regs + XLNX_USB_COHERENCY);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(dwc3_enable_hw_coherency);
+
+void dwc3_mask_phy_reset(struct device *dev, bool mask)
+{
+ struct device_node *node = of_get_parent(dev->of_node);
+
+ /* This is only valid for versal platforms */
+ if (of_device_is_compatible(node, "xlnx,versal-dwc3")) {
+ struct platform_device *pdev_parent;
+ struct dwc3_of_simple *simple;
+ u32 reg;
+
+ pdev_parent = of_find_device_by_node(node);
+ simple = platform_get_drvdata(pdev_parent);
+
+ reg = readl(simple->regs + XLNX_USB_PHY_RST);
+
+ if (mask)
+ /*
+ * Mask the phy reset signal from comtroller
+ * reaching ULPI phy. This can be done by
+ * writing 0 into usb2_phy_reset register
+ */
+ reg &= ~XLNX_PHY_RST_MASK;
+ else
+ /*
+ * Allow phy reset signal from controller to
+ * reset ULPI phy. This can be done by writing
+ * 0x1 into usb2_phy_reset register
+ */
+ reg |= XLNX_PHY_RST_MASK;
+
+ writel(reg, simple->regs + XLNX_USB_PHY_RST);
+ }
+}
+EXPORT_SYMBOL(dwc3_mask_phy_reset);
+
+void dwc3_set_simple_data(struct dwc3 *dwc)
+{
+ struct device_node *node = of_get_parent(dwc->dev->of_node);
+
+ if (node && (of_device_is_compatible(node, "xlnx,zynqmp-dwc3") ||
+ of_device_is_compatible(node, "xlnx,versal-dwc3"))) {
+ struct platform_device *pdev_parent;
+ struct dwc3_of_simple *simple;
+
+ pdev_parent = of_find_device_by_node(node);
+ simple = platform_get_drvdata(pdev_parent);
+
+ /* Set (struct dwc3 *) to simple->dwc for future use */
+ simple->dwc = dwc;
+ }
+}
+EXPORT_SYMBOL(dwc3_set_simple_data);
+
+void dwc3_simple_check_quirks(struct dwc3 *dwc)
+{
+ struct device_node *node = of_get_parent(dwc->dev->of_node);
+
+ if (node && of_device_is_compatible(node, "xlnx,zynqmp-dwc3")) {
+ struct platform_device *pdev_parent;
+ struct dwc3_of_simple *simple;
+
+ pdev_parent = of_find_device_by_node(node);
+ simple = platform_get_drvdata(pdev_parent);
+
+ /* Add snps,dis_u3_susphy_quirk */
+ dwc->dis_u3_susphy_quirk = simple->dis_u3_susphy_quirk;
+ }
+}
+EXPORT_SYMBOL(dwc3_simple_check_quirks);
+
+void dwc3_simple_wakeup_capable(struct device *dev, bool wakeup)
+{
+ struct device_node *node = of_node_get(dev->parent->of_node);
+
+ /* check for valid parent node */
+ while (node) {
+ if (!of_device_is_compatible(node, "xlnx,zynqmp-dwc3") ||
+ !of_device_is_compatible(node, "xlnx,versal-dwc3"))
+ node = of_get_next_parent(node);
+ else
+ break;
+ }
+
+ if (node) {
+ struct platform_device *pdev_parent;
+ struct dwc3_of_simple *simple;
+
+ pdev_parent = of_find_device_by_node(node);
+ simple = platform_get_drvdata(pdev_parent);
+
+ /* Set wakeup capable as true or false */
+ simple->wakeup_capable = wakeup;
+
+ /* Allow D3 state if wakeup capable only */
+ simple->enable_d3_suspend = wakeup;
+ }
+}
+EXPORT_SYMBOL(dwc3_simple_wakeup_capable);
+
+static int dwc3_simple_set_phydata(struct dwc3_of_simple *simple)
+{
+ struct device *dev = simple->dev;
+ struct device_node *np = dev->of_node;
+ struct phy *phy;
+
+ np = of_get_next_child(np, NULL);
+
+ if (np) {
+ phy = of_phy_get(np, "usb3-phy");
+ if (IS_ERR(phy)) {
+ dev_err(dev, "%s: Can't find usb3-phy\n", __func__);
+ return PTR_ERR(phy);
+ }
+
+ /* Store phy for future usage */
+ simple->phy = phy;
+
+ /* assign USB vendor regs addr to phy platform_data */
+ phy->dev.platform_data = simple->regs;
+
+ phy_put(phy);
+ } else {
+ dev_err(dev, "%s: Can't find child node\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int dwc3_dis_u3phy_suspend(struct platform_device *pdev,
+ struct dwc3_of_simple *simple)
+{
+ char *soc_rev;
+
+ /* The below is only valid for ZynqMP SOC */
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "xlnx,zynqmp-dwc3")) {
+ /* read Silicon version using nvmem driver */
+ soc_rev = zynqmp_nvmem_get_silicon_version(&pdev->dev,
+ "soc_revision");
+
+ if (PTR_ERR(soc_rev) == -EPROBE_DEFER)
+ /* Do a deferred probe */
+ return -EPROBE_DEFER;
+ else if (!IS_ERR(soc_rev) && *soc_rev < ZYNQMP_SILICON_V4)
+ /* Add snps,dis_u3_susphy_quirk
+ * for SOC revison less than v4
+ */
+ simple->dis_u3_susphy_quirk = true;
+
+ if (!IS_ERR(soc_rev)) {
+ /* Update soc_rev to simple for future use */
+ simple->soc_rev = *soc_rev;
+
+ /* Clean soc_rev if got a valid pointer from nvmem
+ * driver else we may end up in kernel panic
+ */
+ kfree(soc_rev);
+ } else {
+ /* Return error */
+ return PTR_ERR(soc_rev);
+ }
+ }
+
+ return 0;
+}
+
static int dwc3_of_simple_probe(struct platform_device *pdev)
{
struct dwc3_of_simple *simple;
@@ -44,9 +288,43 @@ static int dwc3_of_simple_probe(struct platform_device *pdev)
if (!simple)
return -ENOMEM;
+ eemi_ops = zynqmp_pm_get_eemi_ops();
+ if (IS_ERR(eemi_ops)) {
+ dev_err(dev, "Failed to get eemi_ops\n");
+ return PTR_ERR(eemi_ops);
+ }
+
platform_set_drvdata(pdev, simple);
simple->dev = dev;
+ if (of_device_is_compatible(pdev->dev.of_node, "xlnx,zynqmp-dwc3") ||
+ of_device_is_compatible(pdev->dev.of_node, "xlnx,versal-dwc3")) {
+
+ struct resource *res;
+ void __iomem *regs;
+
+ res = platform_get_resource(pdev,
+ IORESOURCE_MEM, 0);
+
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ /* Store the usb control regs into simple for further usage */
+ simple->regs = regs;
+
+ /*
+ * ZynqMP silicon revision lesser than 4.0 needs to disable
+ * suspend of usb 3.0 phy.
+ */
+ ret = dwc3_dis_u3phy_suspend(pdev, simple);
+ if (ret)
+ return ret;
+ }
+
+ /* Set phy data for future use */
+ dwc3_simple_set_phydata(simple);
+
/*
* Some controllers need to toggle the usb3-otg reset before trying to
* initialize the PHY, otherwise the PHY times out.
@@ -132,6 +410,218 @@ static int dwc3_of_simple_remove(struct platform_device *pdev)
return 0;
}
+#ifdef CONFIG_PM
+static void dwc3_simple_vbus(struct dwc3 *dwc, bool vbus_off)
+{
+ u32 reg, addr;
+ u8 val;
+
+ if (vbus_off)
+ addr = ULPI_OTG_CTRL_CLEAR;
+ else
+ addr = ULPI_OTG_CTRL_SET;
+
+ val = (1 << OTG_CTRL_DRVVBUS_OFFSET);
+
+ reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_GUSB2PHYACC_ADDR(addr);
+ reg |= DWC3_GUSB2PHYACC_WRITE | val;
+
+ addr = DWC3_OF_ADDRESS(DWC3_GUSB2PHYACC(0));
+ writel(reg, dwc->regs + addr);
+}
+
+static void dwc3_usb2phycfg(struct dwc3 *dwc, bool suspend)
+{
+ u32 addr, reg;
+
+ addr = DWC3_OF_ADDRESS(DWC3_GUSB2PHYCFG(0));
+
+ if (suspend) {
+ reg = readl(dwc->regs + addr);
+ if (!(reg & DWC3_GUSB2PHYCFG_SUSPHY)) {
+ reg |= DWC3_GUSB2PHYCFG_SUSPHY;
+ writel(reg, (dwc->regs + addr));
+ }
+ } else {
+ reg = readl(dwc->regs + addr);
+ if ((reg & DWC3_GUSB2PHYCFG_SUSPHY)) {
+ reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
+ writel(reg, (dwc->regs + addr));
+ }
+ }
+}
+
+static int dwc3_zynqmp_power_req(struct dwc3 *dwc, bool on)
+{
+ u32 reg, retries;
+ void __iomem *reg_base;
+ struct platform_device *pdev_parent;
+ struct dwc3_of_simple *simple;
+ struct device_node *node = of_get_parent(dwc->dev->of_node);
+
+ pdev_parent = of_find_device_by_node(node);
+ simple = platform_get_drvdata(pdev_parent);
+ reg_base = simple->regs;
+
+ /* Check if entering into D3 state is allowed during suspend */
+ if ((simple->soc_rev < ZYNQMP_SILICON_V4) || !simple->enable_d3_suspend)
+ return 0;
+
+ if (!simple->phy)
+ return 0;
+
+ if (on) {
+ dev_dbg(dwc->dev, "trying to set power state to D0....\n");
+
+ /* Release USB core reset , which was assert during D3 entry */
+ xpsgtr_usb_crst_release(simple->phy);
+
+ /* change power state to D0 */
+ writel(XLNX_REQ_PWR_STATE_D0,
+ reg_base + XLNX_USB_REQ_PWR_STATE);
+
+ /* wait till current state is changed to D0 */
+ retries = DWC3_PWR_STATE_RETRIES;
+ do {
+ reg = readl(reg_base + XLNX_USB_CUR_PWR_STATE);
+ if ((reg & XLNX_CUR_PWR_STATE_BITMASK) ==
+ XLNX_CUR_PWR_STATE_D0)
+ break;
+
+ udelay(DWC3_PWR_TIMEOUT);
+ } while (--retries);
+
+ if (!retries) {
+ dev_err(dwc->dev, "Failed to set power state to D0\n");
+ return -EIO;
+ }
+
+ dwc->is_d3 = false;
+
+ /* Clear Suspend PHY bit if dis_u2_susphy_quirk is set */
+ if (dwc->dis_u2_susphy_quirk)
+ dwc3_usb2phycfg(dwc, false);
+ } else {
+ dev_dbg(dwc->dev, "Trying to set power state to D3...\n");
+
+ /*
+ * Set Suspend PHY bit before entering D3 if
+ * dis_u2_susphy_quirk is set
+ */
+ if (dwc->dis_u2_susphy_quirk)
+ dwc3_usb2phycfg(dwc, true);
+
+ /* enable PME to wakeup from hibernation */
+ writel(XLNX_PME_ENABLE_SIG_GEN, reg_base + XLNX_USB_PME_ENABLE);
+
+ /* change power state to D3 */
+ writel(XLNX_REQ_PWR_STATE_D3,
+ reg_base + XLNX_USB_REQ_PWR_STATE);
+
+ /* wait till current state is changed to D3 */
+ retries = DWC3_PWR_STATE_RETRIES;
+ do {
+ reg = readl(reg_base + XLNX_USB_CUR_PWR_STATE);
+ if ((reg & XLNX_CUR_PWR_STATE_BITMASK) ==
+ XLNX_CUR_PWR_STATE_D3)
+ break;
+
+ udelay(DWC3_PWR_TIMEOUT);
+ } while (--retries);
+
+ if (!retries) {
+ dev_err(dwc->dev, "Failed to set power state to D3\n");
+ return -EIO;
+ }
+
+ /* Assert USB core reset after entering D3 state */
+ xpsgtr_usb_crst_assert(simple->phy);
+
+ dwc->is_d3 = true;
+ }
+
+ return 0;
+}
+
+static int dwc3_versal_power_req(struct dwc3 *dwc, bool on)
+{
+ int ret;
+ struct platform_device *pdev_parent;
+ struct dwc3_of_simple *simple;
+ struct device_node *node = of_get_parent(dwc->dev->of_node);
+
+ pdev_parent = of_find_device_by_node(node);
+ simple = platform_get_drvdata(pdev_parent);
+
+ if (!eemi_ops->ioctl || !eemi_ops->reset_assert)
+ return -ENOMEM;
+
+ if (on) {
+ dev_dbg(dwc->dev, "Trying to set power state to D0....\n");
+ ret = eemi_ops->reset_assert(VERSAL_USB_RESET_ID,
+ PM_RESET_ACTION_RELEASE);
+ if (ret < 0)
+ dev_err(simple->dev, "failed to De-assert Reset\n");
+
+ ret = eemi_ops->ioctl(VERSAL_USB_NODE_ID, IOCTL_USB_SET_STATE,
+ XLNX_REQ_PWR_STATE_D0,
+ DWC3_PWR_STATE_RETRIES * DWC3_PWR_TIMEOUT,
+ NULL);
+ if (ret < 0)
+ dev_err(simple->dev, "failed to enter D0 state\n");
+
+ dwc->is_d3 = false;
+
+ /* Clear Suspend PHY bit if dis_u2_susphy_quirk is set */
+ if (dwc->dis_u2_susphy_quirk)
+ dwc3_usb2phycfg(dwc, false);
+ } else {
+ dev_dbg(dwc->dev, "Trying to set power state to D3...\n");
+
+ /*
+ * Set Suspend PHY bit before entering D3 if
+ * dis_u2_susphy_quirk is set
+ */
+ if (dwc->dis_u2_susphy_quirk)
+ dwc3_usb2phycfg(dwc, true);
+
+ ret = eemi_ops->ioctl(VERSAL_USB_NODE_ID, IOCTL_USB_SET_STATE,
+ XLNX_REQ_PWR_STATE_D3,
+ DWC3_PWR_STATE_RETRIES * DWC3_PWR_TIMEOUT,
+ NULL);
+ if (ret < 0)
+ dev_err(simple->dev, "failed to enter D3 state\n");
+
+ ret = eemi_ops->reset_assert(VERSAL_USB_RESET_ID,
+ PM_RESET_ACTION_ASSERT);
+ if (ret < 0)
+ dev_err(simple->dev, "failed to assert Reset\n");
+
+ dwc->is_d3 = true;
+ }
+
+ return ret;
+}
+
+int dwc3_set_usb_core_power(struct dwc3 *dwc, bool on)
+{
+ int ret;
+ struct device_node *node = of_get_parent(dwc->dev->of_node);
+
+ if (of_device_is_compatible(node, "xlnx,zynqmp-dwc3"))
+ /* Set D3/D0 state for ZynqMP */
+ ret = dwc3_zynqmp_power_req(dwc, on);
+ else if (of_device_is_compatible(node, "xlnx,versal-dwc3"))
+ /* Set D3/D0 state for Versal */
+ ret = dwc3_versal_power_req(dwc, on);
+ else
+ /* This is only for Xilinx devices */
+ return 0;
+
+ return ret;
+}
+EXPORT_SYMBOL(dwc3_set_usb_core_power);
+#endif
static int __maybe_unused dwc3_of_simple_runtime_suspend(struct device *dev)
{
@@ -153,6 +643,13 @@ static int __maybe_unused dwc3_of_simple_suspend(struct device *dev)
{
struct dwc3_of_simple *simple = dev_get_drvdata(dev);
+ if (!simple->wakeup_capable && !simple->dwc->is_d3) {
+ /* Ask ULPI to turn OFF Vbus */
+ dwc3_simple_vbus(simple->dwc, true);
+
+ clk_bulk_disable(simple->num_clocks, simple->clks);
+ }
+
if (simple->need_reset)
reset_control_assert(simple->resets);
@@ -162,11 +659,18 @@ static int __maybe_unused dwc3_of_simple_suspend(struct device *dev)
static int __maybe_unused dwc3_of_simple_resume(struct device *dev)
{
struct dwc3_of_simple *simple = dev_get_drvdata(dev);
+ int ret;
+
+ if (simple->wakeup_capable || simple->dwc->is_d3)
+ return 0;
+
+ ret = clk_bulk_enable(simple->num_clocks, simple->clks);
+ dwc3_simple_vbus(simple->dwc, false);
if (simple->need_reset)
reset_control_deassert(simple->resets);
- return 0;
+ return ret;
}
static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = {
@@ -178,6 +682,7 @@ static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = {
static const struct of_device_id of_dwc3_simple_match[] = {
{ .compatible = "rockchip,rk3399-dwc3" },
{ .compatible = "xlnx,zynqmp-dwc3" },
+ { .compatible = "xlnx,versal-dwc3" },
{ .compatible = "cavium,octeon-7130-usb-uctl" },
{ .compatible = "sprd,sc9860-dwc3" },
{ .compatible = "amlogic,meson-axg-dwc3" },