Add a new driver for supporting Xilinx platforms. This driver handles the USB 3.0 PHY initialization and PIPE control & reset operations for ZynqMP platforms. This also handles the USB 2.0 PHY initialization and reset operations for Versal platforms. Signed-off-by: Manish Narani <manish.narani@xxxxxxxxxx> --- drivers/usb/dwc3/Kconfig | 8 + drivers/usb/dwc3/Makefile | 1 + drivers/usb/dwc3/dwc3-of-simple.c | 1 - drivers/usb/dwc3/dwc3-xilinx.c | 416 ++++++++++++++++++++++++++++++ 4 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 drivers/usb/dwc3/dwc3-xilinx.c diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 7a2304565a73..416063ee9d05 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -139,4 +139,12 @@ config USB_DWC3_QCOM for peripheral mode support. Say 'Y' or 'M' if you have one such device. +config USB_DWC3_XILINX + tristate "Xilinx Platforms" + depends on (ARCH_ZYNQMP || ARCH_VERSAL) && OF + default USB_DWC3 + help + Support Xilinx SoCs with DesignWare Core USB3 IP. + Say 'Y' or 'M' if you have one such device. + endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index ae86da0dc5bd..add567578b1f 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -51,3 +51,4 @@ obj-$(CONFIG_USB_DWC3_MESON_G12A) += dwc3-meson-g12a.o obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o +obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c index 7df115012935..e3a485b76818 100644 --- a/drivers/usb/dwc3/dwc3-of-simple.c +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -172,7 +172,6 @@ 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 = "cavium,octeon-7130-usb-uctl" }, { .compatible = "sprd,sc9860-dwc3" }, { .compatible = "allwinner,sun50i-h6-dwc3" }, diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c new file mode 100644 index 000000000000..272906797a7a --- /dev/null +++ b/drivers/usb/dwc3/dwc3-xilinx.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * dwc3-xilinx.c - Xilinx DWC3 controller specific glue driver + * + * Authors: Anurag Kumar Vulisha <anurag.kumar.vulisha@xxxxxxxxxx> + * Manish Narani <manish.narani@xxxxxxxxxx> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> +#include <linux/of_address.h> +#include <linux/delay.h> +#include <linux/firmware/xlnx-zynqmp.h> +#include <linux/io.h> + +#include <linux/phy/phy.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 + +/* Versal USB Reset ID */ +#define VERSAL_USB_RESET_ID 0xC104036 + +#define PIPE_CLK_OFFSET 0x7c +#define PIPE_CLK_ON 1 +#define PIPE_CLK_OFF 0 +#define PIPE_POWER_OFFSET 0x80 +#define PIPE_POWER_ON 1 +#define PIPE_POWER_OFF 0 + +#define RST_TIMEOUT 1000 + +struct dwc3_xlnx { + int num_clocks; + struct clk_bulk_data *clks; + struct device *dev; + void __iomem *regs; + struct dwc3 *dwc; + struct phy *phy; + struct phy *usb3_phy; + struct reset_control *crst; + struct reset_control *hibrst; + struct reset_control *apbrst; +}; + +static void dwc3_xlnx_mask_phy_rst(struct dwc3_xlnx *priv_data, bool mask) +{ + u32 reg; + + reg = readl(priv_data->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, priv_data->regs + XLNX_USB_PHY_RST); +} + +static int dwc3_xlnx_rst_assert(struct reset_control *rstc) +{ + unsigned long loop_time = msecs_to_jiffies(RST_TIMEOUT); + unsigned long timeout; + + reset_control_assert(rstc); + + /* wait until reset is asserted or timeout */ + timeout = jiffies + loop_time; + + while (!time_after_eq(jiffies, timeout)) { + if (reset_control_status(rstc) > 0) + return 0; + + cpu_relax(); + } + + return -ETIMEDOUT; +} + +static int dwc3_xlnx_rst_deassert(struct reset_control *rstc) +{ + unsigned long loop_time = msecs_to_jiffies(RST_TIMEOUT); + unsigned long timeout; + + reset_control_deassert(rstc); + + /* wait until reset is de-asserted or timeout */ + timeout = jiffies + loop_time; + while (!time_after_eq(jiffies, timeout)) { + if (!reset_control_status(rstc)) + return 0; + + cpu_relax(); + } + + return -ETIMEDOUT; +} + +static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data) +{ + struct device *dev = priv_data->dev; + int ret; + + dwc3_xlnx_mask_phy_rst(priv_data, false); + + /* Assert and De-assert reset */ + ret = zynqmp_pm_reset_assert(VERSAL_USB_RESET_ID, + PM_RESET_ACTION_ASSERT); + if (ret < 0) { + dev_err(dev, "failed to assert Reset\n"); + return ret; + } + + ret = zynqmp_pm_reset_assert(VERSAL_USB_RESET_ID, + PM_RESET_ACTION_RELEASE); + if (ret < 0) { + dev_err(dev, "failed to De-assert Reset\n"); + return ret; + } + + dwc3_xlnx_mask_phy_rst(priv_data, true); + + return 0; +} + +static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data) +{ + struct device *dev = priv_data->dev; + int ret; + u32 reg; + + priv_data->crst = devm_reset_control_get(dev, "usb_crst"); + if (IS_ERR(priv_data->crst)) { + dev_err(dev, "failed to get %s reset signal\n", "usb_crst"); + ret = PTR_ERR(priv_data->crst); + goto err; + } + + priv_data->hibrst = devm_reset_control_get(dev, "usb_hibrst"); + if (IS_ERR(priv_data->hibrst)) { + dev_err(dev, "failed to get %s reset signal\n", "usb_hibrst"); + ret = PTR_ERR(priv_data->hibrst); + goto err; + } + + priv_data->apbrst = devm_reset_control_get(dev, "usb_apbrst"); + if (IS_ERR(priv_data->apbrst)) { + dev_err(dev, "failed to get %s reset signal\n", "usb_apbrst"); + ret = PTR_ERR(priv_data->apbrst); + goto err; + } + + priv_data->usb3_phy = devm_phy_get(dev, "usb3-phy"); + + ret = dwc3_xlnx_rst_assert(priv_data->crst); + if (ret < 0) { + dev_err(dev, "%s: %d: Failed to assert reset\n", + __func__, __LINE__); + goto err; + } + + ret = dwc3_xlnx_rst_assert(priv_data->hibrst); + if (ret < 0) { + dev_err(dev, "%s: %d: Failed to assert reset\n", + __func__, __LINE__); + goto err; + } + + ret = dwc3_xlnx_rst_assert(priv_data->apbrst); + if (ret < 0) { + dev_err(dev, "%s: %d: Failed to assert reset\n", + __func__, __LINE__); + goto err; + } + + ret = phy_init(priv_data->usb3_phy); + if (ret < 0) { + phy_exit(priv_data->usb3_phy); + goto err; + } + + ret = dwc3_xlnx_rst_deassert(priv_data->apbrst); + if (ret < 0) { + dev_err(dev, "%s: %d: Failed to release reset\n", + __func__, __LINE__); + goto err; + } + + /* Set PIPE power present signal */ + writel(PIPE_POWER_ON, priv_data->regs + PIPE_POWER_OFFSET); + + /* Clear PIPE CLK signal */ + writel(PIPE_CLK_OFF, priv_data->regs + PIPE_CLK_OFFSET); + + ret = dwc3_xlnx_rst_deassert(priv_data->crst); + if (ret < 0) { + dev_err(dev, "%s: %d: Failed to release reset\n", + __func__, __LINE__); + goto err; + } + + ret = dwc3_xlnx_rst_deassert(priv_data->hibrst); + if (ret < 0) { + dev_err(dev, "%s: %d: Failed to release reset\n", + __func__, __LINE__); + goto err; + } + + ret = phy_power_on(priv_data->usb3_phy); + if (ret < 0) { + phy_exit(priv_data->usb3_phy); + goto err; + } + + /* + * This routes the usb dma traffic to go through CCI path instead + * of reaching DDR directly. This traffic routing is needed to + * make SMMU and CCI work with USB dma. + */ + if (of_dma_is_coherent(dev->of_node) || dev->iommu_group) { + reg = readl(priv_data->regs + XLNX_USB_COHERENCY); + reg |= XLNX_USB_COHERENCY_ENABLE; + writel(reg, priv_data->regs + XLNX_USB_COHERENCY); + } + +err: + return ret; +} + +static int dwc3_xlnx_probe(struct platform_device *pdev) +{ + struct dwc3_xlnx *priv_data; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct resource *res; + void __iomem *regs; + int ret; + + priv_data = devm_kzalloc(dev, sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "missing memory resource\n"); + return -ENODEV; + } + + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + /* Store the usb control regs into priv_data for further usage */ + priv_data->regs = regs; + + priv_data->dev = dev; + + platform_set_drvdata(pdev, priv_data); + + ret = clk_bulk_get_all(priv_data->dev, &priv_data->clks); + if (ret < 0) + return ret; + + priv_data->num_clocks = ret; + + ret = clk_bulk_prepare_enable(priv_data->num_clocks, priv_data->clks); + if (ret) + return ret; + + if (of_device_is_compatible(pdev->dev.of_node, "xlnx,zynqmp-dwc3")) { + ret = dwc3_xlnx_init_zynqmp(priv_data); + if (ret) + goto err_clk_put; + } + + if (of_device_is_compatible(pdev->dev.of_node, "xlnx,versal-dwc3")) { + ret = dwc3_xlnx_init_versal(priv_data); + if (ret) + goto err_clk_put; + } + + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret) + goto err_clk_put; + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_suspend_ignore_children(dev, false); + pm_runtime_get_sync(dev); + + pm_runtime_forbid(dev); + + return 0; + +err_clk_put: + clk_bulk_disable_unprepare(priv_data->num_clocks, priv_data->clks); + clk_bulk_put_all(priv_data->num_clocks, priv_data->clks); + + return ret; +} + +static int dwc3_xlnx_remove(struct platform_device *pdev) +{ + struct dwc3_xlnx *priv_data = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + of_platform_depopulate(dev); + + clk_bulk_disable_unprepare(priv_data->num_clocks, priv_data->clks); + clk_bulk_put_all(priv_data->num_clocks, priv_data->clks); + priv_data->num_clocks = 0; + + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + pm_runtime_set_suspended(dev); + + return 0; +} + +#ifdef CONFIG_PM +static int dwc3_xlnx_runtime_suspend(struct device *dev) +{ + struct dwc3_xlnx *priv_data = dev_get_drvdata(dev); + + clk_bulk_disable(priv_data->num_clocks, priv_data->clks); + + return 0; +} + +static int dwc3_xlnx_runtime_idle(struct device *dev) +{ + pm_runtime_mark_last_busy(dev); + pm_runtime_autosuspend(dev); + + return 0; +} + +static int dwc3_xlnx_runtime_resume(struct device *dev) +{ + struct dwc3_xlnx *priv_data = dev_get_drvdata(dev); + + return clk_bulk_enable(priv_data->num_clocks, priv_data->clks); +} +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_SLEEP +static int dwc3_xlnx_suspend(struct device *dev) +{ + struct dwc3_xlnx *priv_data = dev_get_drvdata(dev); + + /* Disable the clocks */ + clk_bulk_disable(priv_data->num_clocks, priv_data->clks); + + return 0; +} + +static int dwc3_xlnx_resume(struct device *dev) +{ + struct dwc3_xlnx *priv_data = dev_get_drvdata(dev); + + return clk_bulk_enable(priv_data->num_clocks, priv_data->clks); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops dwc3_xlnx_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_xlnx_suspend, dwc3_xlnx_resume) + SET_RUNTIME_PM_OPS(dwc3_xlnx_runtime_suspend, dwc3_xlnx_runtime_resume, + dwc3_xlnx_runtime_idle) +}; + +static const struct of_device_id of_dwc3_xlnx_match[] = { + { .compatible = "xlnx,zynqmp-dwc3" }, + { .compatible = "xlnx,versal-dwc3" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, of_dwc3_xlnx_match); + +static struct platform_driver dwc3_xlnx_driver = { + .probe = dwc3_xlnx_probe, + .remove = dwc3_xlnx_remove, + .driver = { + .name = "dwc3-xilinx", + .of_match_table = of_dwc3_xlnx_match, + .pm = &dwc3_xlnx_dev_pm_ops, + }, +}; + +module_platform_driver(dwc3_xlnx_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Xilinx DWC3 controller specific glue driver"); +MODULE_AUTHOR("Anurag Kumar Vulisha <anurag.kumar.vulisha@xxxxxxxxxx>"); +MODULE_AUTHOR("Manish Narani <manish.narani@xxxxxxxxxx>"); -- 2.17.1