usb: port SpacemiT DWC3/USB3 and related PHY vendor drivers 67/317067/1
authorMarek Szyprowski <m.szyprowski@samsung.com>
Tue, 3 Sep 2024 09:14:31 +0000 (11:14 +0200)
committerMarek Szyprowski <m.szyprowski@samsung.com>
Tue, 3 Sep 2024 10:51:18 +0000 (12:51 +0200)
These drivers are required for the host USB ports to work correctly. Port
them from the vendor kernel [1].

[1] - https://github.com/BPI-SINOVOIP/pi-linux.git

Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
Change-Id: I8a4f1bccf0eb7eced7ea87d1ac9c881947ed34ab

17 files changed:
drivers/phy/Kconfig
drivers/phy/Makefile
drivers/phy/spacemit/Kconfig [new file with mode: 0644]
drivers/phy/spacemit/Makefile [new file with mode: 0644]
drivers/phy/spacemit/phy-spacemit-k1x-combphy.c [new file with mode: 0644]
drivers/usb/dwc3/Kconfig
drivers/usb/dwc3/Makefile
drivers/usb/dwc3/dwc3-spacemit.c [new file with mode: 0644]
drivers/usb/misc/Kconfig
drivers/usb/misc/Makefile
drivers/usb/misc/spacemit_onboard_hub.c [new file with mode: 0644]
drivers/usb/misc/spacemit_onboard_hub.h [new file with mode: 0644]
drivers/usb/phy/Kconfig
drivers/usb/phy/Makefile
drivers/usb/phy/phy-k1x-ci-usb2.c [new file with mode: 0644]
drivers/usb/phy/phy-k1x-ci-usb2.h [new file with mode: 0644]
include/linux/platform_data/k1x_ci_usb.h [new file with mode: 0755]

index e4502958fd62d6da620593d5156dd8d92e46413a..ff2b0c91fd7724b3868fd8ec73b2d27ebd253cde 100644 (file)
@@ -91,6 +91,7 @@ source "drivers/phy/renesas/Kconfig"
 source "drivers/phy/rockchip/Kconfig"
 source "drivers/phy/samsung/Kconfig"
 source "drivers/phy/socionext/Kconfig"
+source "drivers/phy/spacemit/Kconfig"
 source "drivers/phy/st/Kconfig"
 source "drivers/phy/starfive/Kconfig"
 source "drivers/phy/sunplus/Kconfig"
index fb3dc9de611154abf78ebcf51c055eba03d263b5..d0b1f105ce72fe79da6ce526524bda20524fe41f 100644 (file)
@@ -30,6 +30,7 @@ obj-y                                 += allwinner/   \
                                           rockchip/    \
                                           samsung/     \
                                           socionext/   \
+                                          spacemit/    \
                                           st/          \
                                           starfive/    \
                                           sunplus/     \
diff --git a/drivers/phy/spacemit/Kconfig b/drivers/phy/spacemit/Kconfig
new file mode 100644 (file)
index 0000000..8dfa10d
--- /dev/null
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Phy drivers for Spacemit platforms
+#
+config PHY_SPACEMIT_K1X_COMBPHY
+       tristate "Spacemit K1-x USB3&PCIE combo PHY driver"
+       depends on OF
+       select GENERIC_PHY
+       default SOC_SPACEMIT_K1X && USB_DWC3_SPACEMIT
+       help
+         USB3 and PCIE Combo PHY Support for Spacemit k1-x Soc
diff --git a/drivers/phy/spacemit/Makefile b/drivers/phy/spacemit/Makefile
new file mode 100644 (file)
index 0000000..e28d038
--- /dev/null
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_SPACEMIT_K1X_COMBPHY) += phy-spacemit-k1x-combphy.o
diff --git a/drivers/phy/spacemit/phy-spacemit-k1x-combphy.c b/drivers/phy/spacemit/phy-spacemit-k1x-combphy.c
new file mode 100644 (file)
index 0000000..374c621
--- /dev/null
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * phy-spacemit-k1x-combphy.c - Spacemit k1-x combo PHY for USB3 and PCIE
+ *
+ * Copyright (c) 2023 Spacemit Co., Ltd.
+ *
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/of_device.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/delay.h>
+
+#define SPACEMIT_COMBPHY_WAIT_TIMEOUT 1000
+#define SPACEMIT_COMBPHY_MODE_SEL BIT(3)
+
+// Registers for USB3 PHY
+#define SPACEMIT_COMBPHY_USB_REG1 0x68
+#define SPACEMIT_COMBPHY_USB_REG1_VAL 0x0
+#define SPACEMIT_COMBPHY_USB_REG2 (0x12 << 2)
+#define SPACEMIT_COMBPHY_USB_REG2_VAL 0x603a2276
+#define SPACEMIT_COMBPHY_USB_REG3 (0x02 << 2)
+#define SPACEMIT_COMBPHY_USB_REG3_VAL 0x97c
+#define SPACEMIT_COMBPHY_USB_REG4 (0x06 << 2)
+#define SPACEMIT_COMBPHY_USB_REG4_VAL 0x0
+#define SPACEMIT_COMBPHY_USB_PLL_REG 0x8
+#define SPACEMIT_COMBPHY_USB_PLL_MASK 0x1
+#define SPACEMIT_COMBPHY_USB_PLL_VAL 0x1
+
+struct spacemit_combphy_priv;
+
+struct spacemit_combphy_priv {
+       struct device *dev;
+       int num_clks;
+       struct clk_bulk_data *clks;
+       struct reset_control *phy_rst;
+       void __iomem *phy_sel;
+       void __iomem *puphy_base;
+       struct phy *phy;
+       u8 type;
+};
+
+static inline void spacemit_reg_updatel(void __iomem *reg, u32 offset, u32 mask,
+                                       u32 val)
+{
+       u32 tmp;
+       tmp = readl(reg + offset);
+       tmp = (tmp & ~(mask)) | val;
+       writel(tmp, reg + offset);
+}
+
+static int spacemit_combphy_wait_ready(struct spacemit_combphy_priv *priv,
+                                          u32 offset, u32 mask, u32 val)
+{
+       int timeout = SPACEMIT_COMBPHY_WAIT_TIMEOUT;
+       while (((readl(priv->puphy_base + offset) & mask) != val) && --timeout)
+               ;
+       if (!timeout) {
+               return -ETIMEDOUT;
+       }
+       dev_dbg(priv->dev, "phy init timeout remain: %d", timeout);
+       return 0;
+}
+
+static int spacemit_combphy_set_mode(struct spacemit_combphy_priv *priv)
+{
+       u8 mode = priv->type;
+       if (mode == PHY_TYPE_USB3) {
+               spacemit_reg_updatel(priv->phy_sel, 0, 0,
+                                        SPACEMIT_COMBPHY_MODE_SEL);
+       } else {
+               dev_err(priv->dev, "PHY type %x not supported\n", mode);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int spacemit_combphy_init_usb(struct spacemit_combphy_priv *priv)
+{
+       int ret;
+       void __iomem *base = priv->puphy_base;
+       dev_info(priv->dev, "USB3 PHY init.\n");
+
+       writel(SPACEMIT_COMBPHY_USB_REG1_VAL, base + SPACEMIT_COMBPHY_USB_REG1);
+       writel(SPACEMIT_COMBPHY_USB_REG2_VAL, base + SPACEMIT_COMBPHY_USB_REG2);
+       writel(SPACEMIT_COMBPHY_USB_REG3_VAL, base + SPACEMIT_COMBPHY_USB_REG3);
+       writel(SPACEMIT_COMBPHY_USB_REG4_VAL, base + SPACEMIT_COMBPHY_USB_REG4);
+
+       ret = spacemit_combphy_wait_ready(priv, SPACEMIT_COMBPHY_USB_PLL_REG,
+                                         SPACEMIT_COMBPHY_USB_PLL_MASK,
+                                         SPACEMIT_COMBPHY_USB_PLL_VAL);
+
+       if (ret)
+               dev_err(priv->dev, "USB3 PHY init timeout!\n");
+
+       return ret;
+}
+
+static int spacemit_combphy_init(struct phy *phy)
+{
+       struct spacemit_combphy_priv *priv = phy_get_drvdata(phy);
+       int ret;
+
+       ret = spacemit_combphy_set_mode(priv);
+
+       if (ret) {
+               dev_err(priv->dev, "failed to set mode for PHY type %x\n",
+                       priv->type);
+               goto out;
+       }
+
+       ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
+       if (ret) {
+               dev_err(priv->dev, "failed to enable clks\n");
+               goto out;
+       }
+
+       ret = reset_control_deassert(priv->phy_rst);
+       if (ret) {
+               dev_err(priv->dev, "failed to deassert rst\n");
+               goto err_clk;
+       }
+
+       switch (priv->type) {
+       case PHY_TYPE_USB3:
+               ret = spacemit_combphy_init_usb(priv);
+               break;
+       default:
+               dev_err(priv->dev, "PHY type %x not supported\n", priv->type);
+               ret = -EINVAL;
+               break;
+       }
+
+       if (ret)
+               goto err_rst;
+
+       return 0;
+
+err_rst:
+       reset_control_assert(priv->phy_rst);
+err_clk:
+       clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
+out:
+       return ret;
+}
+
+static int spacemit_combphy_exit(struct phy *phy)
+{
+       struct spacemit_combphy_priv *priv = phy_get_drvdata(phy);
+
+       clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
+       reset_control_assert(priv->phy_rst);
+
+       return 0;
+}
+
+static struct phy *spacemit_combphy_xlate(struct device *dev,
+                                         struct of_phandle_args *args)
+{
+       struct spacemit_combphy_priv *priv = dev_get_drvdata(dev);
+
+       if (args->args_count != 1) {
+               dev_err(dev, "invalid number of arguments\n");
+               return ERR_PTR(-EINVAL);
+       }
+
+       if (priv->type != PHY_NONE && priv->type != args->args[0])
+               dev_warn(dev, "PHY type %d is selected to override %d\n",
+                        args->args[0], priv->type);
+
+       priv->type = args->args[0];
+
+       if (args->args_count > 1) {
+               dev_dbg(dev, "combo phy idx: %d selected",  args->args[1]);
+       }
+
+       return priv->phy;
+}
+
+static const struct phy_ops spacemit_combphy_ops = {
+       .init = spacemit_combphy_init,
+       .exit = spacemit_combphy_exit,
+       .owner = THIS_MODULE,
+};
+
+static int spacemit_combphy_probe(struct platform_device *pdev)
+{
+       struct phy_provider *phy_provider;
+       struct device *dev = &pdev->dev;
+       struct spacemit_combphy_priv *priv;
+       int ret;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->puphy_base = devm_platform_ioremap_resource_byname(pdev, "puphy");
+       if (IS_ERR(priv->puphy_base)) {
+               ret = PTR_ERR(priv->puphy_base);
+               return ret;
+       }
+
+       priv->phy_sel = devm_platform_ioremap_resource_byname(pdev, "phy_sel");
+       if (IS_ERR(priv->phy_sel)) {
+               ret = PTR_ERR(priv->phy_sel);
+               return ret;
+       }
+
+       priv->dev = dev;
+       priv->type = PHY_NONE;
+
+       priv->num_clks = devm_clk_bulk_get_all(dev, &priv->clks);
+
+       priv->phy_rst = devm_reset_control_array_get_shared(dev);
+       if (IS_ERR(priv->phy_rst))
+               return dev_err_probe(dev, PTR_ERR(priv->phy_rst),
+                                        "failed to get phy reset\n");
+
+       priv->phy = devm_phy_create(dev, NULL, &spacemit_combphy_ops);
+       if (IS_ERR(priv->phy)) {
+               dev_err(dev, "failed to create combphy\n");
+               return PTR_ERR(priv->phy);
+       }
+
+       dev_set_drvdata(dev, priv);
+       phy_set_drvdata(priv->phy, priv);
+
+       phy_provider =
+               devm_of_phy_provider_register(dev, spacemit_combphy_xlate);
+
+       return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id spacemit_combphy_of_match[] = {
+       {
+               .compatible = "spacemit,k1x-combphy",
+       },
+       {},
+};
+MODULE_DEVICE_TABLE(of, spacemit_combphy_of_match);
+
+static struct platform_driver spacemit_combphy_driver = {
+       .probe  = spacemit_combphy_probe,
+       .driver = {
+               .name = "spacemit-k1x-combphy",
+               .of_match_table = spacemit_combphy_of_match,
+       },
+};
+module_platform_driver(spacemit_combphy_driver);
index 98efcbb76c882147f17981ed61dcb360f50c1739..bb8b7679d8e9745938a235c42e75623651563158 100644 (file)
@@ -178,4 +178,11 @@ config USB_DWC3_OCTEON
          Only the host mode is currently supported.
          Say 'Y' or 'M' here if you have one such device.
 
+config USB_DWC3_SPACEMIT
+       tristate "Spacemit Platforms"
+       default USB_DWC3
+       help
+         Support SPACEMIT platforms with DesignWare Core USB3 IP.
+         Say 'Y' or 'M' here if you have one such device
+
 endif
index fe1493d4bbe58e5c4de0adf477e023ac386cd236..b0ef85f5abf57c87314f751f0675196f6b20d10c 100644 (file)
@@ -55,3 +55,4 @@ obj-$(CONFIG_USB_DWC3_QCOM)           += dwc3-qcom.o
 obj-$(CONFIG_USB_DWC3_IMX8MP)          += dwc3-imx8mp.o
 obj-$(CONFIG_USB_DWC3_XILINX)          += dwc3-xilinx.o
 obj-$(CONFIG_USB_DWC3_OCTEON)          += dwc3-octeon.o
+obj-$(CONFIG_USB_DWC3_SPACEMIT)                += dwc3-spacemit.o
diff --git a/drivers/usb/dwc3/dwc3-spacemit.c b/drivers/usb/dwc3/dwc3-spacemit.c
new file mode 100644 (file)
index 0000000..6da8db1
--- /dev/null
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * dwc3-spacemit.c - Spacemit DWC3 Specific Glue layer
+ *
+ * Copyright (c) 2023 Spacemit Co., Ltd.
+ *
+ * Author: Wilson <long.wan@spacemit.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/reset.h>
+#include <linux/of_address.h>
+
+#define DWC3_SPACEMIT_MAX_CLOCKS       4
+
+struct dwc3_spacemit_driverdata {
+       const char              *clk_names[DWC3_SPACEMIT_MAX_CLOCKS];
+       int                     num_clks;
+       int                     suspend_clk_idx;
+};
+
+struct dwc3_spacemit {
+       struct device           *dev;
+       struct reset_control    *resets;
+
+       const char              **clk_names;
+       struct clk              *clks[DWC3_SPACEMIT_MAX_CLOCKS];
+       int                     num_clks;
+       int                     suspend_clk_idx;
+};
+
+static int dwc3_spacemit_init(struct dwc3_spacemit *data)
+{
+       struct device *dev = data->dev;
+       int     ret = 0;
+
+       data->resets = devm_reset_control_array_get_optional_exclusive(dev);
+       if (IS_ERR(data->resets)) {
+               ret = PTR_ERR(data->resets);
+               dev_err(dev, "failed to get resets, err=%d\n", ret);
+               return ret;
+       }
+
+       ret = reset_control_assert(data->resets);
+       if (ret) {
+               dev_err(dev, "failed to assert resets, err=%d\n", ret);
+               return ret;
+       }
+
+       ret = reset_control_deassert(data->resets);
+       if (ret) {
+               dev_err(dev, "failed to deassert resets, err=%d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int dwc3_spacemit_probe(struct platform_device *pdev)
+{
+       struct dwc3_spacemit    *spacemit;
+       struct device           *dev = &pdev->dev;
+       struct device_node      *node = dev->of_node;
+       const struct dwc3_spacemit_driverdata *driver_data;
+       int                     i, ret;
+
+       spacemit = devm_kzalloc(dev, sizeof(*spacemit), GFP_KERNEL);
+       if (!spacemit)
+               return -ENOMEM;
+
+       driver_data = of_device_get_match_data(dev);
+       spacemit->dev = dev;
+       spacemit->num_clks = driver_data->num_clks;
+       spacemit->clk_names = (const char **)driver_data->clk_names;
+       spacemit->suspend_clk_idx = driver_data->suspend_clk_idx;
+
+       platform_set_drvdata(pdev, spacemit);
+
+       for (i = 0; i < spacemit->num_clks; i++) {
+               spacemit->clks[i] = devm_clk_get(dev, spacemit->clk_names[i]);
+               if (IS_ERR(spacemit->clks[i])) {
+                       dev_err(dev, "failed to get clock: %s\n",
+                               spacemit->clk_names[i]);
+                       return PTR_ERR(spacemit->clks[i]);
+               }
+       }
+
+       for (i = 0; i < spacemit->num_clks; i++) {
+               ret = clk_prepare_enable(spacemit->clks[i]);
+               if (ret) {
+                       while (i-- > 0)
+                               clk_disable_unprepare(spacemit->clks[i]);
+                       return ret;
+               }
+       }
+
+       if (spacemit->suspend_clk_idx >= 0)
+               clk_prepare_enable(spacemit->clks[spacemit->suspend_clk_idx]);
+
+       ret = dwc3_spacemit_init(spacemit);
+       if (ret) {
+               dev_err(dev, "failed to init spacemit\n");
+               goto populate_err;
+       }
+
+       if (node) {
+               ret = of_platform_populate(node, NULL, NULL, dev);
+               if (ret) {
+                       dev_err(dev, "failed to add dwc3 core\n");
+                       goto populate_err;
+               }
+       } else {
+               dev_err(dev, "no device node, failed to add dwc3 core\n");
+               ret = -ENODEV;
+               goto populate_err;
+       }
+
+       return 0;
+
+populate_err:
+       for (i = spacemit->num_clks - 1; i >= 0; i--)
+               clk_disable_unprepare(spacemit->clks[i]);
+
+       if (spacemit->suspend_clk_idx >= 0)
+               clk_disable_unprepare(spacemit->clks[spacemit->suspend_clk_idx]);
+
+       return ret;
+}
+
+static int dwc3_spacemit_remove(struct platform_device *pdev)
+{
+       struct dwc3_spacemit    *spacemit = platform_get_drvdata(pdev);
+       int i;
+
+       of_platform_depopulate(&pdev->dev);
+
+       for (i = spacemit->num_clks - 1; i >= 0; i--)
+               clk_disable_unprepare(spacemit->clks[i]);
+
+       if (spacemit->suspend_clk_idx >= 0)
+               clk_disable_unprepare(spacemit->clks[spacemit->suspend_clk_idx]);
+
+       return 0;
+}
+
+static const struct dwc3_spacemit_driverdata spacemit_k1pro_drvdata = {
+       .clk_names = { "usbdrd30" },
+       .num_clks = 0,
+       .suspend_clk_idx = -1,
+};
+
+static const struct dwc3_spacemit_driverdata spacemit_k1x_drvdata = {
+       .clk_names = { "usbdrd30" },
+       .num_clks = 1,
+       .suspend_clk_idx = -1,
+};
+
+static const struct of_device_id spacemit_dwc3_match[] = {
+       {
+               .compatible = "spacemit,k1-pro-dwc3",
+               .data = &spacemit_k1pro_drvdata,
+       },
+       {
+               .compatible = "spacemit,k1-x-dwc3",
+               .data = &spacemit_k1x_drvdata,
+       },
+       { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, spacemit_dwc3_match);
+
+#ifdef CONFIG_PM_SLEEP
+static int dwc3_spacemit_suspend(struct device *dev)
+{
+       struct dwc3_spacemit *spacemit = dev_get_drvdata(dev);
+       int i;
+
+       for (i = spacemit->num_clks - 1; i >= 0; i--)
+               clk_disable_unprepare(spacemit->clks[i]);
+
+       return 0;
+}
+
+static int dwc3_spacemit_resume(struct device *dev)
+{
+       struct dwc3_spacemit *spacemit = dev_get_drvdata(dev);
+       int i, ret;
+
+       for (i = 0; i < spacemit->num_clks; i++) {
+               ret = clk_prepare_enable(spacemit->clks[i]);
+               if (ret) {
+                       while (i-- > 0)
+                               clk_disable_unprepare(spacemit->clks[i]);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static const struct dev_pm_ops dwc3_spacemit_dev_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(dwc3_spacemit_suspend, dwc3_spacemit_resume)
+};
+
+#define DEV_PM_OPS     (&dwc3_spacemit_dev_pm_ops)
+#else
+#define DEV_PM_OPS     NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct platform_driver dwc3_spacemit_driver = {
+       .probe          = dwc3_spacemit_probe,
+       .remove         = dwc3_spacemit_remove,
+       .driver         = {
+               .name   = "spacemit-dwc3",
+               .of_match_table = spacemit_dwc3_match,
+               .pm     = DEV_PM_OPS,
+       },
+};
+
+module_platform_driver(dwc3_spacemit_driver);
+
+MODULE_AUTHOR("Wilson <long.wan@spacemit.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("DesignWare USB3 Spacemit Glue Layer");
index 99b15b77dfd576dace539b7166dd574fe134fcdf..40a93ea313cf7c5c858fe692d844b66ee37b51c6 100644 (file)
@@ -318,3 +318,11 @@ config USB_ONBOARD_HUB
          this config will enable the driver and it will automatically
          match the state of the USB subsystem. If this driver is a
          module it will be called onboard_usb_hub.
+
+config SPACEMIT_ONBOARD_USB_HUB
+       tristate "Spacemit onboard USB hub support"
+       depends on ARCH_SPACEMIT || COMPILE_TEST
+       default SOC_SPACEMIT_K1X && USB_DWC3_SPACEMIT
+       help
+         Say Y here if you want to support onboard usb hubs on Spacemit
+         platform. If unsure, say Y when compile for Spacemit platform.
index 1992cc284d8aba91f2229810d6b1d9e3b663ef2e..d6f305959babd4b569184b648f2d6ec540da6d7d 100644 (file)
@@ -33,3 +33,4 @@ obj-$(CONFIG_USB_SISUSBVGA)           += sisusbvga/
 obj-$(CONFIG_USB_LINK_LAYER_TEST)      += lvstest.o
 obj-$(CONFIG_BRCM_USB_PINMAP)          += brcmstb-usb-pinmap.o
 obj-$(CONFIG_USB_ONBOARD_HUB)          += onboard_usb_hub.o
+obj-$(CONFIG_SPACEMIT_ONBOARD_USB_HUB)         += spacemit_onboard_hub.o
diff --git a/drivers/usb/misc/spacemit_onboard_hub.c b/drivers/usb/misc/spacemit_onboard_hub.c
new file mode 100644 (file)
index 0000000..522c7e7
--- /dev/null
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Onboard USB Hub support for Spacemit platform
+ *
+ * Copyright (c) 2023 Spacemit Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/resource.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/of_address.h>
+#include <linux/gpio/consumer.h>
+
+#include "spacemit_onboard_hub.h"
+
+#define DRIVER_VERSION "v1.0.2"
+
+static void spacemit_hub_enable(struct spacemit_hub_priv *spacemit, bool on)
+{
+       unsigned i;
+       int active_val = spacemit->hub_gpio_active_low ? 0 : 1;
+
+       if (!spacemit->hub_gpios)
+               return;
+
+       dev_dbg(spacemit->dev, "do hub enable %s\n", on ? "on" : "off");
+
+       if (on) {
+               for (i = 0; i < spacemit->hub_gpios->ndescs; i++) {
+                       gpiod_set_value(spacemit->hub_gpios->desc[i],
+                                       active_val);
+                       if (spacemit->hub_inter_delay_ms) {
+                               msleep(spacemit->hub_inter_delay_ms);
+                       }
+               }
+       } else {
+               for (i = spacemit->hub_gpios->ndescs; i > 0; --i) {
+                       gpiod_set_value(spacemit->hub_gpios->desc[i - 1],
+                                       !active_val);
+                       if (spacemit->hub_inter_delay_ms) {
+                               msleep(spacemit->hub_inter_delay_ms);
+                       }
+               }
+       }
+       spacemit->is_hub_on = on;
+}
+
+static void spacemit_hub_vbus_enable(struct spacemit_hub_priv *spacemit,
+                                        bool on)
+{
+       unsigned i;
+       int active_val = spacemit->vbus_gpio_active_low ? 0 : 1;
+
+       if (!spacemit->vbus_gpios)
+               return;
+
+       dev_dbg(spacemit->dev, "do hub vbus on %s\n", on ? "on" : "off");
+       if (on) {
+               for (i = 0; i < spacemit->vbus_gpios->ndescs; i++) {
+                       gpiod_set_value(spacemit->vbus_gpios->desc[i],
+                                       active_val);
+                       if (spacemit->vbus_inter_delay_ms) {
+                               msleep(spacemit->vbus_inter_delay_ms);
+                       }
+               }
+       } else {
+               for (i = spacemit->vbus_gpios->ndescs; i > 0; --i) {
+                       gpiod_set_value(spacemit->vbus_gpios->desc[i - 1],
+                                       !active_val);
+                       if (spacemit->vbus_inter_delay_ms) {
+                               msleep(spacemit->vbus_inter_delay_ms);
+                       }
+               }
+       }
+       spacemit->is_vbus_on = on;
+}
+
+static void spacemit_hub_configure(struct spacemit_hub_priv *spacemit, bool on)
+{
+       dev_dbg(spacemit->dev, "do hub configure %s\n", on ? "on" : "off");
+       if (on) {
+               spacemit_hub_enable(spacemit, true);
+               if (spacemit->vbus_delay_ms && spacemit->vbus_gpios) {
+                       msleep(spacemit->vbus_delay_ms);
+               }
+               spacemit_hub_vbus_enable(spacemit, true);
+       } else {
+               spacemit_hub_vbus_enable(spacemit, false);
+               if (spacemit->vbus_delay_ms && spacemit->vbus_gpios) {
+                       msleep(spacemit->vbus_delay_ms);
+               }
+               spacemit_hub_enable(spacemit, false);
+       }
+}
+
+static void spacemit_read_u32_prop(struct device *dev, const char *name,
+                                  u32 init_val, u32 *pval)
+{
+       if (device_property_read_u32(dev, name, pval))
+               *pval = init_val;
+       dev_dbg(dev, "hub %s, delay: %u ms\n", name, *pval);
+}
+
+static int spacemit_hub_probe(struct platform_device *pdev)
+{
+       struct spacemit_hub_priv *spacemit;
+       struct device *dev = &pdev->dev;
+
+       dev_info(&pdev->dev, "%s\n", DRIVER_VERSION);
+
+       spacemit = devm_kzalloc(&pdev->dev, sizeof(*spacemit), GFP_KERNEL);
+       if (!spacemit)
+               return -ENOMEM;
+
+       spacemit_read_u32_prop(dev, "hub_inter_delay_ms", 0,
+                                  &spacemit->hub_inter_delay_ms);
+       spacemit_read_u32_prop(dev, "vbus_inter_delay_ms", 0,
+                                  &spacemit->vbus_inter_delay_ms);
+       spacemit_read_u32_prop(dev, "vbus_delay_ms", 10,
+                                  &spacemit->vbus_delay_ms);
+
+       spacemit->hub_gpio_active_low =
+               device_property_read_bool(dev, "hub_gpio_active_low");
+       spacemit->vbus_gpio_active_low =
+               device_property_read_bool(dev, "vbus_gpio_active_low");
+       spacemit->suspend_power_on =
+               device_property_read_bool(dev, "suspend_power_on");
+
+       spacemit->hub_gpios = devm_gpiod_get_array_optional(
+               &pdev->dev, "hub",
+               spacemit->hub_gpio_active_low ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW);
+       if (IS_ERR(spacemit->hub_gpios)) {
+               dev_err(&pdev->dev, "failed to retrieve hub-gpios from dts\n");
+               return PTR_ERR(spacemit->hub_gpios);
+       }
+
+       spacemit->vbus_gpios = devm_gpiod_get_array_optional(
+               &pdev->dev, "vbus",
+               spacemit->vbus_gpio_active_low ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW);
+       if (IS_ERR(spacemit->vbus_gpios)) {
+               dev_err(&pdev->dev, "failed to retrieve vbus-gpios from dts\n");
+               return PTR_ERR(spacemit->vbus_gpios);
+       }
+
+       platform_set_drvdata(pdev, spacemit);
+       spacemit->dev = &pdev->dev;
+       mutex_init(&spacemit->hub_mutex);
+
+       spacemit_hub_configure(spacemit, true);
+
+       dev_info(&pdev->dev, "onboard usb hub driver probed, hub configured\n");
+
+       spacemit_hub_debugfs_init(spacemit);
+
+       return 0;
+}
+
+static int spacemit_hub_remove(struct platform_device *pdev)
+{
+       struct spacemit_hub_priv *spacemit = platform_get_drvdata(pdev);
+
+       debugfs_remove(debugfs_lookup(dev_name(&pdev->dev), usb_debug_root));
+       spacemit_hub_configure(spacemit, false);
+       mutex_destroy(&spacemit->hub_mutex);
+       dev_info(&pdev->dev, "onboard usb hub driver exit, disable hub\n");
+       return 0;
+}
+
+static const struct of_device_id spacemit_hub_dt_match[] = {
+       { .compatible = "spacemit,usb3-hub",},
+       {},
+};
+MODULE_DEVICE_TABLE(of, spacemit_hub_dt_match);
+
+#ifdef CONFIG_PM_SLEEP
+static int spacemit_hub_suspend(struct device *dev)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct spacemit_hub_priv *spacemit = platform_get_drvdata(pdev);
+       mutex_lock(&spacemit->hub_mutex);
+       if (!spacemit->suspend_power_on) {
+               spacemit_hub_configure(spacemit, false);
+               dev_info(dev, "turn off hub power supply\n");
+       }
+       mutex_unlock(&spacemit->hub_mutex);
+       return 0;
+}
+
+static int spacemit_hub_resume(struct device *dev)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct spacemit_hub_priv *spacemit = platform_get_drvdata(pdev);
+       mutex_lock(&spacemit->hub_mutex);
+       if (!spacemit->suspend_power_on) {
+               spacemit_hub_configure(spacemit, true);
+               dev_info(dev, "resume hub power supply\n");
+       }
+       mutex_unlock(&spacemit->hub_mutex);
+       return 0;
+}
+
+static const struct dev_pm_ops spacemit_onboard_hub_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(spacemit_hub_suspend, spacemit_hub_resume)
+};
+#define DEV_PM_OPS     (&spacemit_onboard_hub_pm_ops)
+#else
+#define DEV_PM_OPS     NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct platform_driver spacemit_hub_driver = {
+       .probe  = spacemit_hub_probe,
+       .remove = spacemit_hub_remove,
+       .driver = {
+               .name   = "spacemit-usb3-hub",
+               .owner  = THIS_MODULE,
+               .of_match_table = of_match_ptr(spacemit_hub_dt_match),
+               .pm = DEV_PM_OPS,
+       },
+};
+
+module_platform_driver(spacemit_hub_driver);
+MODULE_DESCRIPTION("Spacemit Onboard USB Hub driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/misc/spacemit_onboard_hub.h b/drivers/usb/misc/spacemit_onboard_hub.h
new file mode 100644 (file)
index 0000000..dec341e
--- /dev/null
@@ -0,0 +1,186 @@
+#include <linux/property.h>
+#include <linux/delay.h>
+#include <linux/usb.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/mutex.h>
+
+
+struct spacemit_hub_priv {
+       struct device *dev;
+       bool is_hub_on;
+       bool is_vbus_on;
+
+       struct gpio_descs *hub_gpios;
+       struct gpio_descs *vbus_gpios;
+       bool hub_gpio_active_low;
+       bool vbus_gpio_active_low;
+
+       u32 hub_inter_delay_ms;
+       u32 vbus_delay_ms;
+       u32 vbus_inter_delay_ms;
+
+       bool suspend_power_on;
+
+       struct mutex hub_mutex;
+};
+
+static void spacemit_hub_enable(struct spacemit_hub_priv *spacemit, bool on);
+
+static void spacemit_hub_vbus_enable(struct spacemit_hub_priv *spacemit,
+                                       bool on);
+
+static int spacemit_hub_enable_show(struct seq_file *s, void *unused)
+{
+       struct spacemit_hub_priv *spacemit = s->private;
+       mutex_lock(&spacemit->hub_mutex);
+       seq_puts(s, spacemit->is_hub_on ? "true\n" : "false\n");
+       mutex_unlock(&spacemit->hub_mutex);
+       return 0;
+}
+
+static ssize_t spacemit_hub_enable_write(struct file *file,
+                                          const char __user *ubuf, size_t count,
+                                          loff_t *ppos)
+{
+       struct seq_file *s = file->private_data;
+       struct spacemit_hub_priv *spacemit = s->private;
+       bool on = false;
+       char buf[32];
+
+       if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+               return -EFAULT;
+
+       if ((!strncmp(buf, "true", 4)) || (!strncmp(buf, "1", 1)))
+               on = true;
+       if ((!strncmp(buf, "false", 5)) || !strncmp(buf, "0", 1))
+               on = false;
+
+       mutex_lock(&spacemit->hub_mutex);
+       if (on != spacemit->is_hub_on) {
+               spacemit_hub_enable(spacemit, on);
+       }
+       mutex_unlock(&spacemit->hub_mutex);
+
+       return count;
+}
+
+static int spacemit_hub_enable_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, spacemit_hub_enable_show, inode->i_private);
+}
+
+struct file_operations spacemit_hub_enable_fops = {
+       .open = spacemit_hub_enable_open,
+       .write = spacemit_hub_enable_write,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
+static int spacemit_hub_vbus_show(struct seq_file *s, void *unused)
+{
+       struct spacemit_hub_priv *spacemit = s->private;
+       mutex_lock(&spacemit->hub_mutex);
+       seq_puts(s, spacemit->is_vbus_on ? "true\n" : "false\n");
+       mutex_unlock(&spacemit->hub_mutex);
+       return 0;
+}
+
+static ssize_t spacemit_hub_vbus_write(struct file *file,
+                                          const char __user *ubuf, size_t count,
+                                          loff_t *ppos)
+{
+       struct seq_file *s = file->private_data;
+       struct spacemit_hub_priv *spacemit = s->private;
+       bool on = false;
+       char buf[32];
+
+       if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+               return -EFAULT;
+
+       if ((!strncmp(buf, "true", 4)) || (!strncmp(buf, "1", 1)))
+               on = true;
+       if ((!strncmp(buf, "false", 5)) || !strncmp(buf, "0", 1))
+               on = false;
+
+       mutex_lock(&spacemit->hub_mutex);
+       if (on != spacemit->is_vbus_on) {
+               spacemit_hub_vbus_enable(spacemit, on);
+       }
+       mutex_unlock(&spacemit->hub_mutex);
+
+       return count;
+}
+
+static int spacemit_hub_vbus_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, spacemit_hub_vbus_show, inode->i_private);
+}
+
+struct file_operations spacemit_hub_vbus_fops = {
+       .open = spacemit_hub_vbus_open,
+       .write = spacemit_hub_vbus_write,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
+static int spacemit_hub_suspend_show(struct seq_file *s, void *unused)
+{
+       struct spacemit_hub_priv *spacemit = s->private;
+       mutex_lock(&spacemit->hub_mutex);
+       seq_puts(s, spacemit->suspend_power_on ? "true\n" : "false\n");
+       mutex_unlock(&spacemit->hub_mutex);
+       return 0;
+}
+
+static ssize_t spacemit_hub_suspend_write(struct file *file,
+                                          const char __user *ubuf, size_t count,
+                                          loff_t *ppos)
+{
+       struct seq_file *s = file->private_data;
+       struct spacemit_hub_priv *spacemit = s->private;
+       bool on = false;
+       char buf[32];
+
+       if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+               return -EFAULT;
+
+       if ((!strncmp(buf, "true", 4)) || (!strncmp(buf, "1", 1)))
+               on = true;
+       if ((!strncmp(buf, "false", 5)) || !strncmp(buf, "0", 1))
+               on = false;
+
+       mutex_lock(&spacemit->hub_mutex);
+       spacemit->suspend_power_on = on;
+       mutex_unlock(&spacemit->hub_mutex);
+
+       return count;
+}
+
+static int spacemit_hub_suspend_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, spacemit_hub_suspend_show, inode->i_private);
+}
+
+struct file_operations spacemit_hub_suspend_fops = {
+       .open = spacemit_hub_suspend_open,
+       .write = spacemit_hub_suspend_write,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
+static void spacemit_hub_debugfs_init(struct spacemit_hub_priv *spacemit)
+{
+       struct dentry *root;
+
+       root = debugfs_create_dir(dev_name(spacemit->dev), usb_debug_root);
+       debugfs_create_file("vbus_on", 0644, root, spacemit,
+                               &spacemit_hub_vbus_fops);
+       debugfs_create_file("hub_on", 0644, root, spacemit,
+                               &spacemit_hub_enable_fops);
+       debugfs_create_file("suspend_power_on", 0644, root, spacemit,
+                               &spacemit_hub_suspend_fops);
+}
index 5f629d7cad64648ce380886bcbff795b89421bce..2a2600cddafe0476b12c6f1084760dbc1d1b0928 100644 (file)
@@ -172,4 +172,12 @@ config USB_ULPI_VIEWPORT
          Provides read/write operations to the ULPI phy register set for
          controllers with a viewport register (e.g. Chipidea/ARC controllers).
 
+config K1XCI_USB2_PHY
+       tristate "K1x ci USB 2.0 PHY Driver"
+       depends on USB || USB_GADGET
+       select USB_PHY
+       help
+         Enable this to support USB 2.0 PHY driver. This driver will do the PHY
+         initialization and shutdown. The PHY driver will be used by K1x udc/ehci/otg driver.
+
 endmenu
index e5d619b4d8f68bbf3ba5cbb834c82f803728312d..15f15891e5eb87cb468a48a0f3209ae6a9c17d99 100644 (file)
@@ -23,3 +23,4 @@ obj-$(CONFIG_USB_MXS_PHY)             += phy-mxs-usb.o
 obj-$(CONFIG_USB_ULPI)                 += phy-ulpi.o
 obj-$(CONFIG_USB_ULPI_VIEWPORT)                += phy-ulpi-viewport.o
 obj-$(CONFIG_KEYSTONE_USB_PHY)         += phy-keystone.o
+obj-$(CONFIG_K1XCI_USB2_PHY)           += phy-k1x-ci-usb2.o
diff --git a/drivers/usb/phy/phy-k1x-ci-usb2.c b/drivers/usb/phy/phy-k1x-ci-usb2.c
new file mode 100644 (file)
index 0000000..116e8fd
--- /dev/null
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * UDC Phy support for Spacemit k1x SoCs
+ *
+ * Copyright (c) 2023 Spacemit Inc.
+ */
+
+#include <linux/resource.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/k1x_ci_usb.h>
+#include <linux/of_address.h>
+#include "phy-k1x-ci-usb2.h"
+
+static int mv_usb2_phy_init(struct usb_phy *phy)
+{
+       struct mv_usb2_phy *mv_phy = container_of(phy, struct mv_usb2_phy, phy);
+       void __iomem *base = mv_phy->base;
+       uint32_t loops, temp;
+
+       clk_enable(mv_phy->clk);
+
+       // make sure the usb controller is not under reset process before any configuration
+       udelay(50);
+       writel(0xbec4, base + USB2_PHY_REG26); //24M ref clk
+       udelay(150);
+
+       loops = USB2D_CTRL_RESET_TIME_MS * 1000;
+
+       //wait for usb2 phy PLL ready
+       do {
+               temp = readl(base + USB2_PHY_REG01);
+               if (temp & USB2_PHY_REG01_PLL_IS_READY)
+                       break;
+               udelay(50);
+       } while(--loops);
+
+       if (loops == 0)
+               pr_info("Wait PHY_REG01[PLLREADY] timeout\n");
+
+       //release usb2 phy internal reset and enable clock gating
+       writel(0x60ef, base + USB2_PHY_REG01);
+       writel(0x1c, base + USB2_PHY_REG0D);
+
+       //select HS parallel data path
+       temp = readl(base + USB2_PHY_REG06);
+       // temp |= USB2_CFG_HS_SRC_SEL;
+       temp &= ~(USB2_CFG_HS_SRC_SEL);
+       writel(temp, base + USB2_PHY_REG06);
+
+       /* auto clear host disc*/
+       temp = readl(base + USB2_PHY_REG04);
+       temp |= USB2_PHY_REG04_AUTO_CLEAR_DIS;
+       writel(temp, base + USB2_PHY_REG04);
+
+       return 0;
+}
+
+static void mv_usb2_phy_shutdown(struct usb_phy *phy)
+{
+       struct mv_usb2_phy *mv_phy = container_of(phy, struct mv_usb2_phy, phy);
+
+       clk_disable(mv_phy->clk);
+}
+
+static int mv_usb2_phy_connect_change(struct usb_phy *phy,
+                                         enum usb_device_speed speed)
+{
+       struct mv_usb2_phy *mv_phy = container_of(phy, struct mv_usb2_phy, phy);
+       uint32_t reg;
+       if (!mv_phy->handle_connect_change)
+               return 0;
+       reg = readl(mv_phy->base + USB2_PHY_REG40);
+       reg |= USB2_PHY_REG40_CLR_DISC;
+       writel(reg, mv_phy->base + USB2_PHY_REG40);
+       return 0;
+}
+
+static int mv_usb2_phy_probe(struct platform_device *pdev)
+{
+       struct mv_usb2_phy *mv_phy;
+       struct resource *r;
+
+       dev_dbg(&pdev->dev, "k1x-ci-usb-phy-probe: Enter...\n");
+       mv_phy = devm_kzalloc(&pdev->dev, sizeof(*mv_phy), GFP_KERNEL);
+       if (mv_phy == NULL) {
+               dev_err(&pdev->dev, "failed to allocate memory\n");
+               return -ENOMEM;
+       }
+
+       mv_phy->pdev = pdev;
+
+       mv_phy->clk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(mv_phy->clk)) {
+               dev_err(&pdev->dev, "failed to get clock.\n");
+               return PTR_ERR(mv_phy->clk);
+       }
+       clk_prepare(mv_phy->clk);
+
+       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (r == NULL) {
+               dev_err(&pdev->dev, "no phy I/O memory resource defined\n");
+               return -ENODEV;
+       }
+       mv_phy->base = devm_ioremap_resource(&pdev->dev, r);
+       if (mv_phy->base == NULL) {
+               dev_err(&pdev->dev, "error map register base\n");
+               return -EBUSY;
+       }
+
+       mv_phy->handle_connect_change = device_property_read_bool(&pdev->dev,
+               "spacemit,handle_connect_change");
+
+       mv_phy->phy.dev = &pdev->dev;
+       mv_phy->phy.label = "mv-usb2";
+       mv_phy->phy.type = USB_PHY_TYPE_USB2;
+       mv_phy->phy.init = mv_usb2_phy_init;
+       mv_phy->phy.shutdown = mv_usb2_phy_shutdown;
+       mv_phy->phy.notify_disconnect = mv_usb2_phy_connect_change;
+       mv_phy->phy.notify_connect = mv_usb2_phy_connect_change;
+
+       usb_add_phy_dev(&mv_phy->phy);
+
+       platform_set_drvdata(pdev, mv_phy);
+
+       return 0;
+}
+
+static int mv_usb2_phy_remove(struct platform_device *pdev)
+{
+       struct mv_usb2_phy *mv_phy = platform_get_drvdata(pdev);
+
+       usb_remove_phy(&mv_phy->phy);
+
+       clk_unprepare(mv_phy->clk);
+
+       platform_set_drvdata(pdev, NULL);
+
+       return 0;
+}
+
+static const struct of_device_id mv_usbphy_dt_match[] = {
+       { .compatible = "spacemit,usb2-phy",},
+       {},
+};
+MODULE_DEVICE_TABLE(of, mv_usbphy_dt_match);
+
+static struct platform_driver mv_usb2_phy_driver = {
+       .probe  = mv_usb2_phy_probe,
+       .remove = mv_usb2_phy_remove,
+       .driver = {
+               .name   = "mv-usb2-phy",
+               .owner  = THIS_MODULE,
+               .of_match_table = of_match_ptr(mv_usbphy_dt_match),
+       },
+};
+
+module_platform_driver(mv_usb2_phy_driver);
+MODULE_DESCRIPTION("Spacemit USB2 phy driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/phy/phy-k1x-ci-usb2.h b/drivers/usb/phy/phy-k1x-ci-usb2.h
new file mode 100644 (file)
index 0000000..2a001de
--- /dev/null
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifndef __MV_USB2_H
+#define __MV_USB2_H
+#include <linux/usb/phy.h>
+
+/* phy regs */
+#define USB2_PHY_REG01                 0x4
+#define USB2_PHY_REG01_PLL_IS_READY    (0x1 << 0)
+#define USB2_PHY_REG04                 0x10
+#define USB2_PHY_REG04_EN_HSTSOF       (0x1 << 0)
+#define USB2_PHY_REG04_AUTO_CLEAR_DIS  (0x1 << 2)
+#define USB2_PHY_REG08                 0x20
+#define USB2_PHY_REG08_DISCON_DET      (0x1 << 9)
+#define USB2_PHY_REG0D                 0x34
+#define USB2_PHY_REG40                 0x40
+#define USB2_PHY_REG40_CLR_DISC        (0x1 << 0)
+#define USB2_PHY_REG26                 0x98
+#define USB2_PHY_REG22                 0x88
+#define USB2_CFG_FORCE_CDRCLK          (0x1 << 6)
+#define USB2_PHY_REG06                 0x18
+#define USB2_CFG_HS_SRC_SEL            (0x1 << 0)
+
+#define USB2D_CTRL_RESET_TIME_MS       50
+
+struct mv_usb2_phy {
+       struct usb_phy          phy;
+       struct platform_device  *pdev;
+       void __iomem            *base;
+       struct clk              *clk;
+       bool            handle_connect_change;
+};
+
+#endif
diff --git a/include/linux/platform_data/k1x_ci_usb.h b/include/linux/platform_data/k1x_ci_usb.h
new file mode 100755 (executable)
index 0000000..1272c1f
--- /dev/null
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifndef __MV_PLATFORM_USB_H
+#define __MV_PLATFORM_USB_H
+
+#include <linux/notifier.h>
+
+enum pxa_ehci_type {
+       EHCI_UNDEFINED = 0,
+       PXA_U2OEHCI,    /* pxa 168, 9xx */
+       PXA_SPH,        /* pxa 168, 9xx SPH */
+       MMP3_HSIC,      /* mmp3 hsic */
+       MMP3_FSIC,      /* mmp3 fsic */
+};
+
+/* for usb middle layer support */
+enum {
+       PXA_USB_DEV_OTG,
+       PXA_USB_DEV_SPH1,
+       PXA_USB_DEV_SPH2,
+       PXA_USB_DEV_SPH3,
+       PXA_USB_DEV_MAX,
+};
+
+enum {
+       EVENT_VBUS,
+       EVENT_ID,
+};
+
+struct pxa_usb_vbus_ops {
+       int (*get_vbus)(unsigned int *level);
+       int (*set_vbus)(unsigned int level);
+       int (*init)(void);
+};
+
+enum {
+       VBUS_LOW        = 0,
+       VBUS_HIGH       = 1 << 0,
+};
+
+struct pxa_usb_idpin_ops {
+       int (*get_idpin)(unsigned int *level);
+       int (*init)(void);
+ };
+
+struct pxa_usb_extern_ops {
+       struct pxa_usb_vbus_ops         vbus;
+       struct pxa_usb_idpin_ops        idpin;
+};
+
+#define pxa_usb_has_extern_call(id, o, f, arg...)      ( \
+{ \
+       struct pxa_usb_extern_ops *ops;                 \
+       int ret;                                        \
+       ops  = pxa_usb_get_extern_ops(id);              \
+       ret = (!ops ? 0 : ((ops->o.f) ?                 \
+               1 : 0));                                \
+       ret;                                            \
+} \
+)
+
+#define pxa_usb_extern_call(id, o, f, args...) ( \
+{ \
+       struct pxa_usb_extern_ops *ops;                 \
+       int ret;                                        \
+       ops  = pxa_usb_get_extern_ops(id);              \
+       ret = (!ops ? -ENODEV : ((ops->o.f) ?           \
+               ops->o.f(args) : -ENOIOCTLCMD));        \
+       ret;                                            \
+} \
+)
+
+#define pxa_usb_set_extern_call(id, o, f, p) ( \
+{ \
+       struct pxa_usb_extern_ops *ops;         \
+       int ret;                                \
+       ops = pxa_usb_get_extern_ops(id);       \
+       ret = !ops ? -ENODEV : ((ops->o.f) ?    \
+               -EINVAL : ({ops->o.f = p; 0; }));\
+       ret;                                    \
+} \
+)
+
+extern int mv_udc_register_client(struct notifier_block *nb);
+extern int mv_udc_unregister_client(struct notifier_block *nb);
+
+#ifdef CONFIG_MV_USB_CONNECTOR
+extern struct pxa_usb_extern_ops *pxa_usb_get_extern_ops(unsigned int id);
+extern int pxa_usb_register_notifier(unsigned int id,
+                                       struct notifier_block *nb);
+extern int pxa_usb_unregister_notifier(unsigned int id,
+                                       struct notifier_block *nb);
+extern int pxa_usb_notify(unsigned int id, unsigned long val, void *v);
+#else
+static inline struct pxa_usb_extern_ops *pxa_usb_get_extern_ops(unsigned int id) {return NULL;}
+static inline int pxa_usb_register_notifier(unsigned int id, struct notifier_block *nb) {return 0;}
+static inline int pxa_usb_unregister_notifier(unsigned int id, struct notifier_block *nb) {return 0;}
+static inline int pxa_usb_notify(unsigned int id, unsigned long val, void *v) {return 0;}
+/* end of usb middle layer support */
+#endif
+
+struct mv_usb_platform_data {
+       unsigned int            clknum;
+       char                    **clkname;
+       /*
+        * select from PXA_USB_DEV_OTG to PXA_USB_DEV_MAX.
+        * It indicates the index of usb device.
+        */
+       unsigned int            id;
+       unsigned int            extern_attr;
+
+       /* only valid for HCD. OTG or Host only*/
+       unsigned int            mode;
+
+       /* This flag is used for that needs id pin checked by otg */
+       unsigned int    disable_otg_clock_gating:1;
+       /* Force a_bus_req to be asserted */
+       unsigned int    otg_force_a_bus_req:1;
+};
+
+enum charger_type {
+       NULL_CHARGER    = 0,
+       DEFAULT_CHARGER,
+       DCP_CHARGER,            /* standard wall charger */
+       CDP_CHARGER,            /* Charging Downstream Port */
+       SDP_CHARGER,            /* standard PC charger */
+       NONE_STANDARD_CHARGER,  /* none-standard charger */
+       MAX_CHARGER
+};
+
+#endif