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"
rockchip/ \
samsung/ \
socionext/ \
+ spacemit/ \
st/ \
starfive/ \
sunplus/ \
--- /dev/null
+# 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
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_SPACEMIT_K1X_COMBPHY) += phy-spacemit-k1x-combphy.o
--- /dev/null
+// 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);
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
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
--- /dev/null
+// 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");
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.
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
--- /dev/null
+// 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");
--- /dev/null
+#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);
+}
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
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
--- /dev/null
+// 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");
--- /dev/null
+// 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
--- /dev/null
+// 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