diff mbox series

[3/3] phy: sophgo: add usb phy driver for Sophgo CV1800 SoCs

Message ID 20240708120830.5785-4-ziyao@disroot.org
State Changes Requested
Headers show
Series Add USB support for Sophgo CV1800/SG200x SoCs | expand

Commit Message

Yao Zi July 8, 2024, 12:08 p.m. UTC
This adds a new driver for USB2 phys integrated in Sophgo CV1800 and
SG200x SoCs, which have the same design.

Most CV1800/SG200x boards have broken VBUS/ID detection, so we force
ID status to be specified in the device tree. This phy also supports
charger detection, which could be implemented later.

Unfortunately, there is no description about the phy in the public
datasheet. The driver was written by reading Sophgo GPL kernel source
and dumping registers on actual SG2002 devices.

Signed-off-by: Yao Zi <ziyao@disroot.org>
---
 drivers/phy/Kconfig                 |   1 +
 drivers/phy/Makefile                |   1 +
 drivers/phy/sophgo/Kconfig          |  10 ++
 drivers/phy/sophgo/Makefile         |   2 +
 drivers/phy/sophgo/phy-cv1800-usb.c | 213 ++++++++++++++++++++++++++++
 5 files changed, 227 insertions(+)
 create mode 100644 drivers/phy/sophgo/Kconfig
 create mode 100644 drivers/phy/sophgo/Makefile
 create mode 100644 drivers/phy/sophgo/phy-cv1800-usb.c
diff mbox series

Patch

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 787354b849c7..596b37ab3191 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -92,6 +92,7 @@  source "drivers/phy/renesas/Kconfig"
 source "drivers/phy/rockchip/Kconfig"
 source "drivers/phy/samsung/Kconfig"
 source "drivers/phy/socionext/Kconfig"
+source "drivers/phy/sophgo/Kconfig"
 source "drivers/phy/st/Kconfig"
 source "drivers/phy/starfive/Kconfig"
 source "drivers/phy/sunplus/Kconfig"
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 868a220ed0f6..7ff32f0ae08a 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -31,6 +31,7 @@  obj-y					+= allwinner/	\
 					   rockchip/	\
 					   samsung/	\
 					   socionext/	\
+					   sophgo/	\
 					   st/		\
 					   starfive/	\
 					   sunplus/	\
diff --git a/drivers/phy/sophgo/Kconfig b/drivers/phy/sophgo/Kconfig
new file mode 100644
index 000000000000..6feb5795f1fc
--- /dev/null
+++ b/drivers/phy/sophgo/Kconfig
@@ -0,0 +1,10 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+
+config PHY_SOPHGO_CV1800_USB
+	tristate "SOPHGO CV1800 USB 2.0 PHY driver"
+	depends on OF && (ARCH_SOPHGO || COMPILE_TEST)
+	select GENERIC_PHY
+	help
+	  Enable this to support the USB 2.0 PHY on Sophgo CV1800
+	  and SG200x SoCs.
+	  If unsure, say N.
diff --git a/drivers/phy/sophgo/Makefile b/drivers/phy/sophgo/Makefile
new file mode 100644
index 000000000000..b4b9de0697e7
--- /dev/null
+++ b/drivers/phy/sophgo/Makefile
@@ -0,0 +1,2 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_PHY_SOPHGO_CV1800_USB)	+= phy-cv1800-usb.o
diff --git a/drivers/phy/sophgo/phy-cv1800-usb.c b/drivers/phy/sophgo/phy-cv1800-usb.c
new file mode 100644
index 000000000000..873c72bd95cf
--- /dev/null
+++ b/drivers/phy/sophgo/phy-cv1800-usb.c
@@ -0,0 +1,213 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * USB PHY driver for Sophgo CV1800 SoCs.
+ *
+ * Copyright 2024 Yao Zi <ziyao@disroot.org>
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+
+#define CV1800_REG04				0x04
+#define CV1800_REG14				0x14
+#define  CV1800_REG14_UTMI_OVERRIDE		BIT(0)
+#define  CV1800_REG14_OPMODE_MASK		(0x3 << 1)
+#define  CV1800_REG14_OPMODE_SHIFT		1
+#define  CV1800_REG14_XCVRSEL_MASK		(0x3 << 3)
+#define  CV1800_REG14_XCVRSEL_SHIFT		3
+#define  CV1800_REG14_TERMSEL			BIT(5)
+#define  CV1800_REG14_DPPULLDOWN		BIT(6)
+#define  CV1800_REG14_DMPULLDOWN		BIT(7)
+#define  CV1800_REG14_UTMI_RESET		BIT(8)
+#define CV1800_REG20				0x20
+#define  CV1800_REG20_BC_EN			BIT(0)
+#define  CV1800_REG20_DCD_EN			BIT(1)
+#define  CV1800_REG20_DP_CMP_EN			BIT(2)
+#define  CV1800_REG20_DM_CMP_EN			BIT(3)
+#define  CV1800_REG20_VDP_SRC_EN		BIT(4)
+#define  CV1800_REG20_VDM_SRC_EN		BIT(5)
+#define  CV1800_REG20_CHG_DET			BIT(16)
+#define  CV1800_REG20_DP_DET			BIT(17)
+
+#define CV1800_PIN_ID_OVERWRITE_EN		BIT(6)
+#define CV1800_PIN_ID_OVERWRITE_VALUE(v)	((v) << 7)
+
+enum cv1800_usb_phy_role {
+	CV1800_USB_PHY_HOST	= 0,
+	CV1800_USB_PHY_DEVICE	= 1,
+};
+
+struct cv1800_usb_phy_priv {
+	void __iomem *regs;
+	void __iomem *pinreg;
+	struct clk *clk_apb;
+	struct clk *clk_125m;
+	struct clk *clk_33k;
+	struct clk *clk_12m;
+	enum cv1800_usb_phy_role role;
+};
+
+static void
+cv1800_usb_phy_set_role(struct cv1800_usb_phy_priv *priv,
+			enum cv1800_usb_phy_role role)
+{
+	writel(CV1800_PIN_ID_OVERWRITE_EN | CV1800_PIN_ID_OVERWRITE_VALUE(role),
+	       priv->pinreg);
+}
+
+static int cv1800_usb_phy_init(struct phy *phy)
+{
+	struct cv1800_usb_phy_priv *priv = phy_get_drvdata(phy);
+	int ret = 0;
+
+	ret = clk_prepare_enable(priv->clk_apb);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(priv->clk_125m);
+	if (ret)
+		goto err_clk_125m;
+
+	ret = clk_prepare_enable(priv->clk_33k);
+	if (ret)
+		goto err_clk_33k;
+
+	ret = clk_prepare_enable(priv->clk_12m);
+	if (ret)
+		goto err_clk_12m;
+
+	writel(0xa, priv->regs + CV1800_REG04);		/* magic number	*/
+	writel(0, priv->regs + CV1800_REG14);
+	writel(0, priv->regs + CV1800_REG20);
+
+	cv1800_usb_phy_set_role(priv, priv->role);
+
+	return 0;
+
+err_clk_12m:
+	clk_disable_unprepare(priv->clk_33k);
+err_clk_33k:
+	clk_disable_unprepare(priv->clk_125m);
+err_clk_125m:
+	clk_disable_unprepare(priv->clk_apb);
+
+	return ret;
+}
+
+static int cv1800_usb_phy_exit(struct phy *phy)
+{
+	struct cv1800_usb_phy_priv *priv = phy_get_drvdata(phy);
+
+	clk_disable_unprepare(priv->clk_33k);
+	clk_disable_unprepare(priv->clk_125m);
+	clk_disable_unprepare(priv->clk_apb);
+
+	return 0;
+}
+
+static const struct phy_ops cv1800_usb_phy_ops = {
+	.init = cv1800_usb_phy_init,
+	.exit = cv1800_usb_phy_exit,
+};
+
+static int
+cv1800b_usb_phy_parse_dt(struct cv1800_usb_phy_priv *priv, struct device *dev)
+{
+	const char *role;
+
+	if (!of_property_read_string(dev->of_node, "dr_role", &role)) {
+		if (!strcmp(role, "host")) {
+			priv->role = CV1800_USB_PHY_HOST;
+		} else if (!strcmp(role, "device")) {
+			priv->role = CV1800_USB_PHY_DEVICE;
+		} else {
+			dev_err(dev, "invalid dr_role %s", role);
+			return -EINVAL;
+		}
+	} else {
+		priv->role = CV1800_USB_PHY_DEVICE;
+	}
+
+	return 0;
+}
+
+static int cv1800_usb_phy_probe(struct platform_device *pdev)
+{
+	struct cv1800_usb_phy_priv *priv;
+	struct phy_provider *provider;
+	struct device *dev = &pdev->dev;
+	struct phy *phy;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	ret = cv1800b_usb_phy_parse_dt(priv, dev);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to parse dt");
+
+	priv->regs = devm_platform_ioremap_resource_byname(pdev, "phy");
+	if (IS_ERR(priv->regs))
+		return dev_err_probe(dev, PTR_ERR(priv->regs),
+				     "failed to map phy registers");
+
+	priv->pinreg = devm_platform_ioremap_resource_byname(pdev, "pin");
+	if (IS_ERR(priv->pinreg))
+		return dev_err_probe(dev, PTR_ERR(priv->pinreg),
+				     "failed to map pin register");
+
+	priv->clk_apb = devm_clk_get(dev, "apb");
+	if (IS_ERR(priv->clk_apb))
+		return dev_err_probe(dev, PTR_ERR(priv->clk_apb),
+				     "failed to get apb clock");
+
+	priv->clk_125m = devm_clk_get(dev, "125m");
+	if (IS_ERR(priv->clk_125m))
+		return dev_err_probe(dev, PTR_ERR(priv->clk_125m),
+				     "failed to get 125m clock");
+
+	priv->clk_33k = devm_clk_get(dev, "33k");
+	if (IS_ERR(priv->clk_33k))
+		return dev_err_probe(dev, PTR_ERR(priv->clk_33k),
+				     "failed to get 33k clock");
+
+	priv->clk_12m = devm_clk_get(dev, "12m");
+	if (IS_ERR(priv->clk_12m))
+		return dev_err_probe(dev, PTR_ERR(priv->clk_12m),
+				     "failed to get 12m clock");
+
+	phy = devm_phy_create(dev, NULL, &cv1800_usb_phy_ops);
+	if (IS_ERR(phy))
+		return dev_err_probe(dev, PTR_ERR(phy),
+				     "cannot create phy");
+
+	provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+	phy_set_drvdata(phy, priv);
+
+	return PTR_ERR_OR_ZERO(provider);
+}
+
+static const struct of_device_id cv1800_usb_phy_of_match_table[] = {
+	{ .compatible = "sophgo,cv1800-usb-phy" },
+	{ },
+};
+
+static struct platform_driver cv1800_usb_phy_platform_driver = {
+	.driver = {
+		.name = "cv1800-usb-phy",
+		.of_match_table = cv1800_usb_phy_of_match_table,
+	},
+	.probe = cv1800_usb_phy_probe,
+};
+
+module_platform_driver(cv1800_usb_phy_platform_driver);
+
+MODULE_DESCRIPTION("Sophgo CV1800 USB PHY Driver");
+MODULE_AUTHOR("Yao Zi <ziyao@disroot.org>");
+MODULE_LICENSE("GPL");