[PATCH v2 11/24] phy: zte: Add ZTE ZX279128S PCIe PHY driver

Navid Ghahremani ghahramani.navid at gmail.com
Wed Oct 14 00:20:51 PDT 2026


Add a generic PHY driver for the PCIe SerDes PHY inside the Top CRM
block of the ZTE ZX279128S SoC. This driver encapsulates PLL power
and analog SerDes calibration for the two PCIe ports, isolating
these hardware details from the PCIe host controller driver.

Signed-off-by: Navid Ghahremani <ghahramani.navid at gmail.com>
---
 drivers/phy/Kconfig              |  12 +++
 drivers/phy/Makefile             |   1 +
 drivers/phy/phy-zx279128s-pcie.c | 156 +++++++++++++++++++++++++++++++
 3 files changed, 169 insertions(+)
 create mode 100644 drivers/phy/phy-zx279128s-pcie.c

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 19f3b7d12b..3518d384be 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -91,6 +91,18 @@ config PHY_ECONET_PCIE
 	  callback for PCIe GEN1 and GEN2 ports. This PHY is found on
 	  EcoNet SoCs including EN751221 and EN7528.
 
+config PHY_ZX279128S_PCIE
+	tristate "ZTE ZX279128S PCIe PHY driver"
+	depends on ARCH_ZTE || COMPILE_TEST
+	depends on OF
+	select GENERIC_PHY
+	select MFD_SYSCON
+	help
+	  Enable support for the PCIe SerDes PHY found inside the Top CRM
+	  of ZTE ZX279128S SoCs. This driver configures PLL power and analog
+	  calibration for the two PCIe ports.
+	  If you say Y or M here, you will enable PCIe PHY initialization.
+
 config PHY_GOOGLE_USB
 	tristate "Google Tensor SoC USB PHY driver"
 	select GENERIC_PHY
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index d7aa516bcc..1323aa817f 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_PHY_NXP_TJA1145)		+= phy-nxp-tja1145.o
 obj-$(CONFIG_PHY_PISTACHIO_USB)		+= phy-pistachio-usb.o
 obj-$(CONFIG_PHY_SNPS_EUSB2)		+= phy-snps-eusb2.o
 obj-$(CONFIG_PHY_XGENE)			+= phy-xgene.o
+obj-$(CONFIG_PHY_ZX279128S_PCIE)	+= phy-zx279128s-pcie.o
 
 obj-$(CONFIG_GENERIC_PHY)		+= allwinner/	\
 					   amlogic/	\
diff --git a/drivers/phy/phy-zx279128s-pcie.c b/drivers/phy/phy-zx279128s-pcie.c
new file mode 100644
index 0000000000..33bf0eaac8
--- /dev/null
+++ b/drivers/phy/phy-zx279128s-pcie.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ZTE zx279128s PCIe PHY driver
+ *
+ * Controls the analog SerDes and PLL for the two PCIe ports inside the
+ * Top CRM block.
+ *
+ * Copyright (C) 2026 Navid Ghahremani <ghahramani.navid at gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define CRM1_PHY_CTRL		0x008
+#define CRM1_MISC		0x024
+#define CRM1_MISC_BIT26		BIT(26)
+#define CRM1_PHY1_CAL0		0x1b0
+#define CRM1_PHY1_CAL1		0x1b4
+
+struct zx_pcie_phy_port {
+	u32 phy_on[3];
+	bool has_cal;
+};
+
+static const struct zx_pcie_phy_port zx_phy_ports[] = {
+	[0] = {
+		.phy_on = { BIT(15), BIT(13), BIT(14) },
+	},
+	[1] = {
+		.phy_on = { BIT(27), BIT(25), BIT(26) },
+		.has_cal = true,
+	},
+};
+
+struct zx_pcie_phy {
+	struct regmap *regmap;
+	const struct zx_pcie_phy_port *port;
+	unsigned int port_id;
+};
+
+static int zx_pcie_phy_init(struct phy *phy)
+{
+	struct zx_pcie_phy *zphy = phy_get_drvdata(phy);
+	const struct zx_pcie_phy_port *port = zphy->port;
+	int i, ret;
+
+	for (i = 0; i < ARRAY_SIZE(port->phy_on); i++) {
+		ret = regmap_write_bits(zphy->regmap, CRM1_PHY_CTRL,
+					port->phy_on[i], port->phy_on[i]);
+		if (ret)
+			return ret;
+		fsleep(50);
+
+		if (i == 0 && port->has_cal) {
+			ret = regmap_write(zphy->regmap, CRM1_PHY1_CAL1, 0x00202d5a);
+			if (ret)
+				return ret;
+			ret = regmap_write(zphy->regmap, CRM1_PHY1_CAL0, 0x0046c24a);
+			if (ret)
+				return ret;
+		}
+	}
+
+	ret = regmap_write_bits(zphy->regmap, CRM1_MISC, CRM1_MISC_BIT26, 0);
+	if (ret)
+		return ret;
+	fsleep(50);
+
+	return 0;
+}
+
+static int zx_pcie_phy_exit(struct phy *phy)
+{
+	struct zx_pcie_phy *zphy = phy_get_drvdata(phy);
+	const struct zx_pcie_phy_port *port = zphy->port;
+	int i, ret;
+
+	for (i = ARRAY_SIZE(port->phy_on) - 1; i >= 0; i--) {
+		ret = regmap_write_bits(zphy->regmap, CRM1_PHY_CTRL,
+					port->phy_on[i], 0);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct phy_ops zx_pcie_phy_ops = {
+	.init = zx_pcie_phy_init,
+	.exit = zx_pcie_phy_exit,
+	.owner = THIS_MODULE,
+};
+
+static int zx_pcie_phy_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct phy_provider *provider;
+	struct zx_pcie_phy *zphy;
+	struct phy *phy;
+	u32 port_id;
+	int ret;
+
+	zphy = devm_kzalloc(dev, sizeof(*zphy), GFP_KERNEL);
+	if (!zphy)
+		return -ENOMEM;
+
+	ret = of_property_read_u32(dev->of_node, "reg", &port_id);
+	if (ret)
+		return dev_err_probe(dev, ret, "missing reg property\n");
+
+	if (port_id >= ARRAY_SIZE(zx_phy_ports))
+		return dev_err_probe(dev, -EINVAL, "invalid port id %u\n", port_id);
+
+	zphy->port_id = port_id;
+	zphy->port = &zx_phy_ports[port_id];
+
+	zphy->regmap = syscon_node_to_regmap(dev->parent->of_node);
+	if (IS_ERR(zphy->regmap))
+		return dev_err_probe(dev, PTR_ERR(zphy->regmap),
+				     "failed to get parent topcrm regmap\n");
+
+	phy = devm_phy_create(dev, dev->of_node, &zx_pcie_phy_ops);
+	if (IS_ERR(phy))
+		return dev_err_probe(dev, PTR_ERR(phy), "failed to create phy\n");
+
+	phy_set_drvdata(phy, zphy);
+
+	provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	return PTR_ERR_OR_ZERO(provider);
+}
+
+static const struct of_device_id zx_pcie_phy_of_match[] = {
+	{ .compatible = "zte,zx279128s-pcie-phy" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, zx_pcie_phy_of_match);
+
+static struct platform_driver zx_pcie_phy_driver = {
+	.probe = zx_pcie_phy_probe,
+	.driver = {
+		.name = "zx279128s-pcie-phy",
+		.of_match_table = zx_pcie_phy_of_match,
+		.suppress_bind_attrs = true,
+	},
+};
+builtin_platform_driver(zx_pcie_phy_driver);
+
+MODULE_AUTHOR("Navid Ghahremani <ghahramani.navid at gmail.com>");
+MODULE_DESCRIPTION("ZTE ZX279128S PCIe PHY driver");
+MODULE_LICENSE("GPL");
-- 
2.55.0




More information about the linux-arm-kernel mailing list