[openwrt/openwrt] realtek: add SerDes PCS driver

LEDE Commits lede-commits at lists.infradead.org
Sat Sep 20 03:51:28 PDT 2025


robimarko pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/fe27cce1ecb78d3f26137d44d8a20845a18af3ec

commit fe27cce1ecb78d3f26137d44d8a20845a18af3ec
Author: Markus Stockhausen <markus.stockhausen at gmx.de>
AuthorDate: Wed Sep 17 14:22:25 2025 -0400

    realtek: add SerDes PCS driver
    
    Until now the the SerDes configuration is realized with helper functions
    scattered around the DSA and PHY driver. Give them a new home as a PCS
    driver.
    
    The target design is as follows:
    
    - dsa driver manages switch
    - pcs driver manages SerDes on high level (this commit)
    - mdio driver manages SerDes on low level
    
    This driver adds the high level SerDes access via PCS. It makes use of
    the low level mdio SerDes driver to access the registers.
    
    Remark: This initial version provides exactly all phylink_pcs_ops that
    are currently part of the DSA driver. So this can be swapped in one of
    the next commits as a drop in replacement. To make use of it something
    like this is needed:
    
    ...
    ports = of_get_child_by_name(node, "ethernet-ports");
    if (!ports)
            return -EINVAL;
    
    for_each_available_child_of_node(ports, port) {
            pcs_node = of_parse_phandle(port, "pcs-handle", 0);
            of_property_read_u32(port, "reg", &port_nr)) {
    
            priv->pcs[port_nr] = rtpcs_create(dev, pcs_node, port_nr);
    }
    ...
    
    Signed-off-by: Markus Stockhausen <markus.stockhausen at gmx.de>
    Link: https://github.com/openwrt/openwrt/pull/20075
    Signed-off-by: Robert Marko <robimarko at gmail.com>
---
 target/linux/realtek/dts/rtl838x.dtsi              |  25 ++
 target/linux/realtek/dts/rtl839x.dtsi              |  49 +++
 target/linux/realtek/dts/rtl930x.dtsi              |  43 ++
 target/linux/realtek/dts/rtl931x.dtsi              |  48 +++
 .../files-6.12/drivers/net/pcs/pcs-rtl-otto.c      | 440 +++++++++++++++++++++
 .../patches-6.12/730-add-pcs-rtl-otto.patch        |  37 ++
 target/linux/realtek/rtl838x/config-6.12           |   1 +
 target/linux/realtek/rtl839x/config-6.12           |   1 +
 target/linux/realtek/rtl930x/config-6.12           |   1 +
 target/linux/realtek/rtl930x_nand/config-6.12      |   1 +
 target/linux/realtek/rtl931x/config-6.12           |   1 +
 target/linux/realtek/rtl931x_nand/config-6.12      |   1 +
 12 files changed, 648 insertions(+)

diff --git a/target/linux/realtek/dts/rtl838x.dtsi b/target/linux/realtek/dts/rtl838x.dtsi
index ab81c49f8a..6a872bcb0f 100644
--- a/target/linux/realtek/dts/rtl838x.dtsi
+++ b/target/linux/realtek/dts/rtl838x.dtsi
@@ -232,6 +232,31 @@
 			compatible = "realtek,rtl8380-serdes-mdio", "realtek,otto-serdes-mdio";
 		};
 
+		pcs {
+			compatible = "realtek,rtl8380-pcs", "realtek,otto-pcs";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			serdes0: serdes at 0 {
+				reg = <0>;
+			};
+			serdes1: serdes at 1 {
+				reg = <1>;
+			};
+			serdes2: serdes at 2 {
+				reg = <2>;
+			};
+			serdes3: serdes at 3 {
+				reg = <3>;
+			};
+			serdes4: serdes at 4 {
+				reg = <4>;
+			};
+			serdes5: serdes at 5 {
+				reg = <5>;
+			};
+		};
+
 		soc_thermal: thermal {
 			compatible = "realtek,rtl8380-thermal";
 			#thermal-sensor-cells = <0>;
diff --git a/target/linux/realtek/dts/rtl839x.dtsi b/target/linux/realtek/dts/rtl839x.dtsi
index 6b4f4821a2..5e92c5071d 100644
--- a/target/linux/realtek/dts/rtl839x.dtsi
+++ b/target/linux/realtek/dts/rtl839x.dtsi
@@ -240,6 +240,55 @@
 			compatible = "realtek,rtl8392-serdes-mdio", "realtek,otto-serdes-mdio";
 		};
 
+		pcs {
+			compatible = "realtek,rtl8392-pcs", "realtek,otto-pcs";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			serdes0: serdes at 0 {
+				reg = <0>;
+			};
+			serdes1: serdes at 1 {
+				reg = <1>;
+			};
+			serdes2: serdes at 2 {
+				reg = <2>;
+			};
+			serdes3: serdes at 3 {
+				reg = <3>;
+			};
+			serdes4: serdes at 4 {
+				reg = <4>;
+			};
+			serdes5: serdes at 5 {
+				reg = <5>;
+			};
+			serdes6: serdes at 6 {
+				reg = <6>;
+			};
+			serdes7: serdes at 7 {
+				reg = <7>;
+			};
+			serdes8: serdes at 8 {
+				reg = <8>;
+			};
+			serdes9: serdes at 9 {
+				reg = <9>;
+			};
+			serdes10: serdes at 10 {
+				reg = <10>;
+			};
+			serdes11: serdes at 11 {
+				reg = <11>;
+			};
+			serdes12: serdes at 12 {
+				reg = <12>;
+			};
+			serdes13: serdes at 13 {
+				reg = <13>;
+			};
+		};
+
 		soc_thermal: thermal {
 			compatible = "realtek,rtl8390-thermal";
 			#thermal-sensor-cells = <0>;
diff --git a/target/linux/realtek/dts/rtl930x.dtsi b/target/linux/realtek/dts/rtl930x.dtsi
index ad06cef6cd..d3c6a2dd6c 100644
--- a/target/linux/realtek/dts/rtl930x.dtsi
+++ b/target/linux/realtek/dts/rtl930x.dtsi
@@ -207,6 +207,49 @@
 			compatible = "realtek,rtl9301-serdes-mdio", "realtek,otto-serdes-mdio";
 		};
 
+		pcs {
+			compatible = "realtek,rtl9301-pcs", "realtek,otto-pcs";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			serdes0: serdes at 0 {
+				reg = <0>;
+			};
+			serdes1: serdes at 1 {
+				reg = <1>;
+			};
+			serdes2: serdes at 2 {
+				reg = <2>;
+			};
+			serdes3: serdes at 3 {
+				reg = <3>;
+			};
+			serdes4: serdes at 4 {
+				reg = <4>;
+			};
+			serdes5: serdes at 5 {
+				reg = <5>;
+			};
+			serdes6: serdes at 6 {
+				reg = <6>;
+			};
+			serdes7: serdes at 7 {
+				reg = <7>;
+			};
+			serdes8: serdes at 8 {
+				reg = <8>;
+			};
+			serdes9: serdes at 9 {
+				reg = <9>;
+			};
+			serdes10: serdes at 10 {
+				reg = <10>;
+			};
+			serdes11: serdes at 11 {
+				reg = <11>;
+			};
+		};
+
 		soc_thermal: thermal {
 			compatible = "realtek,rtl9300-thermal";
 			#thermal-sensor-cells = <0>;
diff --git a/target/linux/realtek/dts/rtl931x.dtsi b/target/linux/realtek/dts/rtl931x.dtsi
index d7b789b9bb..58121bea2e 100644
--- a/target/linux/realtek/dts/rtl931x.dtsi
+++ b/target/linux/realtek/dts/rtl931x.dtsi
@@ -239,6 +239,54 @@
 			compatible = "realtek,rtl9311-serdes-mdio", "realtek,otto-serdes-mdio";
 		};
 
+		pcs {
+			compatible = "realtek,rtl9311-pcs", "realtek,otto-pcs";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			serdes0: serdes at 0 {
+				reg = <0>;
+			};
+			serdes1: serdes at 1 {
+				reg = <1>;
+			};
+			serdes2: serdes at 2 {
+				reg = <2>;
+			};
+			serdes3: serdes at 3 {
+				reg = <3>;
+			};
+			serdes4: serdes at 4 {
+				reg = <4>;
+			};
+			serdes5: serdes at 5 {
+				reg = <5>;
+			};
+			serdes6: serdes at 6 {
+				reg = <6>;
+			};
+			serdes7: serdes at 7 {
+				reg = <7>;
+			};
+			serdes8: serdes at 8 {
+				reg = <8>;
+			};
+			serdes9: serdes at 9 {
+				reg = <9>;
+			};
+			serdes10: serdes at 10 {
+				reg = <10>;
+			};
+			serdes11: serdes at 11 {
+				reg = <11>;
+			};
+			serdes12: serdes at 12 {
+				reg = <12>;
+			};
+			serdes13: serdes at 13 {
+				reg = <13>;
+			};
+		};
 	};
 
 	pinmux at 1b001358 {
diff --git a/target/linux/realtek/files-6.12/drivers/net/pcs/pcs-rtl-otto.c b/target/linux/realtek/files-6.12/drivers/net/pcs/pcs-rtl-otto.c
new file mode 100644
index 0000000000..ceedf209f5
--- /dev/null
+++ b/target/linux/realtek/files-6.12/drivers/net/pcs/pcs-rtl-otto.c
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/mdio.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/phylink.h>
+#include <linux/regmap.h>
+
+#define RTPCS_PORT_CNT				57
+
+#define RTPCS_SPEED_10				0
+#define RTPCS_SPEED_100				1
+#define RTPCS_SPEED_1000			2
+#define RTPCS_SPEED_10000_LEGACY		3
+#define RTPCS_SPEED_10000			4
+#define RTPCS_SPEED_2500			5
+#define RTPCS_SPEED_5000			6
+
+#define RTPCS_838X_CPU_PORT			28
+#define RTPCS_838X_MAC_LINK_DUP_STS		0xa19c
+#define RTPCS_838X_MAC_LINK_SPD_STS		0xa190
+#define RTPCS_838X_MAC_LINK_STS			0xa188
+#define RTPCS_838X_MAC_RX_PAUSE_STS		0xa1a4
+#define RTPCS_838X_MAC_TX_PAUSE_STS		0xa1a0
+
+#define RTPCS_839X_CPU_PORT			52
+#define RTPCS_839X_MAC_LINK_DUP_STS		0x03b0
+#define RTPCS_839X_MAC_LINK_SPD_STS		0x03a0
+#define RTPCS_839X_MAC_LINK_STS			0x0390
+#define RTPCS_839X_MAC_RX_PAUSE_STS		0x03c0
+#define RTPCS_839X_MAC_TX_PAUSE_STS		0x03b8
+
+#define RTPCS_83XX_MAC_LINK_SPD_BITS		2
+
+#define RTPCS_930X_CPU_PORT			28
+#define RTPCS_930X_MAC_LINK_DUP_STS		0xcb28
+#define RTPCS_930X_MAC_LINK_SPD_STS		0xcb18
+#define RTPCS_930X_MAC_LINK_STS			0xcb10
+#define RTPCS_930X_MAC_RX_PAUSE_STS		0xcb30
+#define RTPCS_930X_MAC_TX_PAUSE_STS		0xcb2c
+
+#define RTPCS_931X_CPU_PORT			56
+#define RTPCS_931X_MAC_LINK_DUP_STS		0x0ef0
+#define RTPCS_931X_MAC_LINK_SPD_STS		0x0ed0
+#define RTPCS_931X_MAC_LINK_STS			0x0ec0
+#define RTPCS_931X_MAC_RX_PAUSE_STS		0x0f00
+#define RTPCS_931X_MAC_TX_PAUSE_STS		0x0ef8
+
+#define RTPCS_93XX_MAC_LINK_SPD_BITS		4
+
+struct rtpcs_config {
+	int cpu_port;
+	int mac_link_dup_sts;
+	int mac_link_spd_bits;
+	int mac_link_spd_sts;
+	int mac_link_sts;
+	int mac_rx_pause_sts;
+	int mac_tx_pause_sts;
+	const struct phylink_pcs_ops *pcs_ops;
+};
+
+struct rtpcs_ctrl {
+	struct device *dev;
+	struct regmap *map;
+	struct mii_bus *bus;
+	const struct rtpcs_config *cfg;
+	struct rtpcs_link *link[RTPCS_PORT_CNT];
+};
+
+struct rtpcs_link {
+	struct rtpcs_ctrl *ctrl;
+	struct phylink_pcs pcs;
+	int sds;
+	int port;
+};
+
+static int rtpcs_sds_to_mmd(int sds_page, int sds_regnum)
+{
+	return (sds_page << 8) + sds_regnum;
+}
+
+static int rtpcs_sds_read(struct rtpcs_ctrl *ctrl, int sds, int page, int regnum)
+{
+	int mmd_regnum = rtpcs_sds_to_mmd(page, regnum);
+
+	return mdiobus_c45_read(ctrl->bus, sds, MDIO_MMD_VEND1, mmd_regnum);
+}
+
+/*
+ * For later use, when the SerDes registers need to be written ...
+ *
+ * static int rtpcs_sds_write(struct rtpcs_ctrl *ctrl, int sds, int page, int regnum, u16 value)
+ * {
+ *	int mmd_regnum = rtpcs_sds_to_mmd(page, regnum);
+ *
+ *	return mdiobus_c45_write(ctrl->bus, sds, MDIO_MMD_VEND1, mmd_regnum, value);
+ * }
+ */
+
+static int rtpcs_regmap_read_bits(struct rtpcs_ctrl *ctrl, int base, int bithigh, int bitlow)
+{
+	int offset = base + (bitlow / 32) * 4;
+	int bits = bithigh + 1 - bitlow;
+	int shift = bitlow % 32;
+	int value;
+
+	regmap_read(ctrl->map, offset, &value);
+	value = (value >> shift) & (BIT(bits) - 1);
+
+	return value;
+}
+
+static struct rtpcs_link *rtpcs_phylink_pcs_to_link(struct phylink_pcs *pcs)
+{
+	return container_of(pcs, struct rtpcs_link, pcs);
+}
+
+static void rtpcs_pcs_get_state(struct phylink_pcs *pcs, struct phylink_link_state *state)
+{
+	struct rtpcs_link *link = rtpcs_phylink_pcs_to_link(pcs);
+	struct rtpcs_ctrl *ctrl = link->ctrl;
+	int port = link->port;
+	int linkup, speed;
+
+	state->link = 0;
+	state->speed = SPEED_UNKNOWN;
+	state->duplex = DUPLEX_UNKNOWN;
+	state->pause &= ~(MLO_PAUSE_RX | MLO_PAUSE_TX);
+
+	/* Read MAC side link twice */
+	for (int i = 0; i < 2; i++)
+		linkup = rtpcs_regmap_read_bits(ctrl, ctrl->cfg->mac_link_sts, port, port);
+
+	if (!linkup)
+		return;
+
+	state->link = 1;
+	state->duplex = rtpcs_regmap_read_bits(ctrl, ctrl->cfg->mac_link_dup_sts, port, port);
+
+	speed = rtpcs_regmap_read_bits(ctrl, ctrl->cfg->mac_link_spd_sts,
+				       ctrl->cfg->mac_link_spd_bits * (port + 1) - 1,
+				       ctrl->cfg->mac_link_spd_bits * port);
+	switch (speed) {
+	case RTPCS_SPEED_10:
+		state->speed = SPEED_10;
+		break;
+	case RTPCS_SPEED_100:
+		state->speed = SPEED_100;
+		break;
+	case RTPCS_SPEED_1000:
+		state->speed = SPEED_1000;
+		break;
+	case RTPCS_SPEED_10000:
+	case RTPCS_SPEED_10000_LEGACY:
+		/*
+		 * The legacy mode is ok so far with minor inconsistencies. On RTL838x this flag
+		 * is either 500M or 2G. It might be that MAC_GLITE_STS register tells more. On
+		 * RTL839x this is either 500M or 10G. More info might be in MAC_LINK_500M_STS.
+		 * Without support for the 500M modes simply resolve to 10G.
+		 */
+		state->speed = SPEED_10000;
+		break;
+	case RTPCS_SPEED_2500:
+		state->speed = SPEED_2500;
+		break;
+	case RTPCS_SPEED_5000:
+		state->speed = SPEED_5000;
+		break;
+	default:
+		dev_err(ctrl->dev, "unknown speed %d\n", speed);
+	}
+
+	if (rtpcs_regmap_read_bits(ctrl, ctrl->cfg->mac_rx_pause_sts, port, port))
+		state->pause |= MLO_PAUSE_RX;
+	if (rtpcs_regmap_read_bits(ctrl, ctrl->cfg->mac_tx_pause_sts, port, port))
+		state->pause |= MLO_PAUSE_TX;
+}
+
+static void rtpcs_pcs_an_restart(struct phylink_pcs *pcs)
+{
+	struct rtpcs_link *link = rtpcs_phylink_pcs_to_link(pcs);
+	struct rtpcs_ctrl *ctrl = link->ctrl;
+
+	dev_warn(ctrl->dev, "an_restart() for port %d and sds %d not yet implemented\n",
+		 link->port, link->sds);
+}
+
+static int rtpcs_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+			    phy_interface_t interface, const unsigned long *advertising,
+			    bool permit_pause_to_mac)
+{
+	struct rtpcs_link *link = rtpcs_phylink_pcs_to_link(pcs);
+	struct rtpcs_ctrl *ctrl = link->ctrl;
+
+	/*
+	 * TODO: This (or copies of this) will be the central function for configuring the
+	 * link between PHY and SerDes. As of now a lot of the code is scattered throughout
+	 * all the other Realtek drivers. Maybe some day this will live up to the expectations.
+	 */
+
+	dev_warn(ctrl->dev, "pcs_config(%s) for port %d and sds %d not yet implemented\n",
+		 phy_modes(interface), link->port, link->sds);
+
+	return 0;
+}
+
+struct phylink_pcs *rtpcs_create(struct device *dev, struct device_node *np, int port);
+struct phylink_pcs *rtpcs_create(struct device *dev, struct device_node *np, int port)
+{
+	struct platform_device *pdev;
+	struct device_node *pcs_np;
+	struct rtpcs_ctrl *ctrl;
+	struct rtpcs_link *link;
+	int sds;
+
+	/*
+	 * RTL838x devices have a built-in octa port RTL8218B PHY that is not attached via
+	 * a SerDes. Allow to be called with an empty SerDes device node. In this case lookup
+	 * the parent/driver node directly.
+	 */
+	if (np) {
+		if (!of_device_is_available(np))
+			return ERR_PTR(-ENODEV);
+
+		if (of_property_read_u32(np, "reg", &sds))
+			return ERR_PTR(-EINVAL);
+
+		pcs_np = of_get_parent(np);
+	} else {
+		pcs_np = of_find_compatible_node(NULL, NULL, "realtek,otto-pcs");
+		sds = -1;
+	}
+
+	if (!pcs_np)
+		return ERR_PTR(-ENODEV);
+
+	if (!of_device_is_available(pcs_np)) {
+		of_node_put(pcs_np);
+		return ERR_PTR(-ENODEV);
+	}
+
+	pdev = of_find_device_by_node(pcs_np);
+	of_node_put(pcs_np);
+	if (!pdev)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	ctrl = platform_get_drvdata(pdev);
+	if (!ctrl) {
+		put_device(&pdev->dev);
+		return ERR_PTR(-EPROBE_DEFER);
+	}
+
+	if (port < 0 || port > ctrl->cfg->cpu_port)
+		return ERR_PTR(-EINVAL);
+
+	if (sds !=-1 && rtpcs_sds_read(ctrl, sds, 0 , 0) < 0)
+		return ERR_PTR(-EINVAL);
+
+	link = kzalloc(sizeof(*link), GFP_KERNEL);
+	if (!link) {
+		put_device(&pdev->dev);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	device_link_add(dev, ctrl->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
+
+	link->ctrl = ctrl;
+	link->port = port;
+	link->sds = sds;
+	link->pcs.ops = ctrl->cfg->pcs_ops;
+
+	ctrl->link[port] = link;
+
+	dev_dbg(ctrl->dev, "phylink_pcs created, port %d, sds %d\n", port, sds);
+
+	return &link->pcs;
+}
+EXPORT_SYMBOL(rtpcs_create);
+
+static struct mii_bus *rtpcs_probe_serdes_bus(struct rtpcs_ctrl *ctrl)
+{
+	struct device_node *np;
+	struct mii_bus *bus;
+
+	np = of_find_compatible_node(NULL, NULL, "realtek,otto-serdes-mdio");
+	if (!np) {
+		dev_err(ctrl->dev, "SerDes mdio bus not found in DT");
+		return ERR_PTR(-ENODEV);
+	}
+
+	bus = of_mdio_find_bus(np);
+	of_node_put(np);
+	if (!bus) {
+		dev_warn(ctrl->dev, "SerDes mdio bus not (yet) active");
+		return ERR_PTR(-EPROBE_DEFER);
+	}
+
+	if (!of_device_is_available(np)) {
+		dev_err(ctrl->dev, "SerDes mdio bus not usable");
+		return ERR_PTR(-ENODEV);
+	}
+
+	return bus;
+}
+
+static int rtpcs_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device *dev = &pdev->dev;
+	struct rtpcs_ctrl *ctrl;
+
+	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+	if (!ctrl)
+		return -ENOMEM;
+
+	ctrl->dev = dev;
+	ctrl->cfg = (const struct rtpcs_config *)device_get_match_data(ctrl->dev);
+	ctrl->map = syscon_node_to_regmap(np->parent);
+	if (IS_ERR(ctrl->map))
+		return PTR_ERR(ctrl->map);
+
+	ctrl->bus = rtpcs_probe_serdes_bus(ctrl);
+	if (IS_ERR(ctrl->bus))
+		return PTR_ERR(ctrl->bus);
+	/*
+	 * rtpcs_create() relies on that fact that data is attached to the platform device to
+	 * determine if the driver is ready. Do this after everything is initialized properly.
+	 */
+	platform_set_drvdata(pdev, ctrl);
+
+	dev_info(dev, "Realtek PCS driver initialized\n");
+
+	return 0;
+}
+
+static const struct phylink_pcs_ops rtpcs_838x_pcs_ops = {
+	.pcs_an_restart		= rtpcs_pcs_an_restart,
+	.pcs_config		= rtpcs_pcs_config,
+	.pcs_get_state		= rtpcs_pcs_get_state,
+};
+
+static const struct rtpcs_config rtpcs_838x_cfg = {
+	.cpu_port		= RTPCS_838X_CPU_PORT,
+	.mac_link_dup_sts	= RTPCS_838X_MAC_LINK_DUP_STS,
+	.mac_link_spd_sts	= RTPCS_838X_MAC_LINK_SPD_STS,
+	.mac_link_spd_bits	= RTPCS_83XX_MAC_LINK_SPD_BITS,
+	.mac_link_sts		= RTPCS_838X_MAC_LINK_STS,
+	.mac_rx_pause_sts	= RTPCS_838X_MAC_RX_PAUSE_STS,
+	.mac_tx_pause_sts	= RTPCS_838X_MAC_TX_PAUSE_STS,
+	.pcs_ops		= &rtpcs_838x_pcs_ops,
+};
+
+static const struct phylink_pcs_ops rtpcs_839x_pcs_ops = {
+	.pcs_an_restart		= rtpcs_pcs_an_restart,
+	.pcs_config		= rtpcs_pcs_config,
+	.pcs_get_state		= rtpcs_pcs_get_state,
+};
+
+static const struct rtpcs_config rtpcs_839x_cfg = {
+	.cpu_port		= RTPCS_839X_CPU_PORT,
+	.mac_link_dup_sts	= RTPCS_839X_MAC_LINK_DUP_STS,
+	.mac_link_spd_sts	= RTPCS_839X_MAC_LINK_SPD_STS,
+	.mac_link_spd_bits	= RTPCS_83XX_MAC_LINK_SPD_BITS,
+	.mac_link_sts		= RTPCS_839X_MAC_LINK_STS,
+	.mac_rx_pause_sts	= RTPCS_839X_MAC_RX_PAUSE_STS,
+	.mac_tx_pause_sts	= RTPCS_839X_MAC_TX_PAUSE_STS,
+	.pcs_ops		= &rtpcs_839x_pcs_ops,
+};
+
+static const struct phylink_pcs_ops rtpcs_930x_pcs_ops = {
+	.pcs_an_restart		= rtpcs_pcs_an_restart,
+	.pcs_config		= rtpcs_pcs_config,
+	.pcs_get_state		= rtpcs_pcs_get_state,
+};
+
+static const struct rtpcs_config rtpcs_930x_cfg = {
+	.cpu_port		= RTPCS_930X_CPU_PORT,
+	.mac_link_dup_sts	= RTPCS_930X_MAC_LINK_DUP_STS,
+	.mac_link_spd_sts	= RTPCS_930X_MAC_LINK_SPD_STS,
+	.mac_link_spd_bits	= RTPCS_93XX_MAC_LINK_SPD_BITS,
+	.mac_link_sts		= RTPCS_930X_MAC_LINK_STS,
+	.mac_rx_pause_sts	= RTPCS_930X_MAC_RX_PAUSE_STS,
+	.mac_tx_pause_sts	= RTPCS_930X_MAC_TX_PAUSE_STS,
+	.pcs_ops		= &rtpcs_930x_pcs_ops,
+};
+
+static const struct phylink_pcs_ops rtpcs_931x_pcs_ops = {
+	.pcs_an_restart		= rtpcs_pcs_an_restart,
+	.pcs_config		= rtpcs_pcs_config,
+	.pcs_get_state		= rtpcs_pcs_get_state,
+};
+
+static const struct rtpcs_config rtpcs_931x_cfg = {
+	.cpu_port		= RTPCS_931X_CPU_PORT,
+	.mac_link_dup_sts	= RTPCS_931X_MAC_LINK_DUP_STS,
+	.mac_link_spd_sts	= RTPCS_931X_MAC_LINK_SPD_STS,
+	.mac_link_spd_bits	= RTPCS_93XX_MAC_LINK_SPD_BITS,
+	.mac_link_sts		= RTPCS_931X_MAC_LINK_STS,
+	.mac_rx_pause_sts	= RTPCS_931X_MAC_RX_PAUSE_STS,
+	.mac_tx_pause_sts	= RTPCS_931X_MAC_TX_PAUSE_STS,
+	.pcs_ops		= &rtpcs_931x_pcs_ops,
+};
+
+static const struct of_device_id rtpcs_of_match[] = {
+	{
+		.compatible = "realtek,rtl8380-pcs",
+		.data = &rtpcs_838x_cfg,
+	},
+	{
+		.compatible = "realtek,rtl8392-pcs",
+		.data = &rtpcs_839x_cfg,
+	},
+	{
+		.compatible = "realtek,rtl9301-pcs",
+		.data = &rtpcs_930x_cfg,
+	},
+	{
+		.compatible = "realtek,rtl9311-pcs",
+		.data = &rtpcs_931x_cfg,
+	},
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rtpcs_of_match);
+
+static struct platform_driver rtpcs_driver = {
+	.driver = {
+		.name = "realtek-otto-pcs",
+		.of_match_table = rtpcs_of_match
+	},
+	.probe = rtpcs_probe,
+};
+module_platform_driver(rtpcs_driver);
+
+MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen at gmx.de>");
+MODULE_DESCRIPTION("Realtek Otto SerDes PCS driver");
+MODULE_LICENSE("GPL v2");
\ No newline at end of file
diff --git a/target/linux/realtek/patches-6.12/730-add-pcs-rtl-otto.patch b/target/linux/realtek/patches-6.12/730-add-pcs-rtl-otto.patch
new file mode 100644
index 0000000000..27927bd891
--- /dev/null
+++ b/target/linux/realtek/patches-6.12/730-add-pcs-rtl-otto.patch
@@ -0,0 +1,37 @@
+From ad75da9aaa8765b2115e7b40ee4f6dbcd60c3321 Mon Sep 17 00:00:00 2001
+From: Markus Stockhausen <markus.stockhausen at gmx.de>
+Date: Weg, 17 Sep 2025 20:23:31 +0200
+Subject: [PATCH] net: pcs: Add Realtek Otto SerDes controller
+
+SoCs in Realtek's Otto platform such as the RTL83xx and RTL93xx
+have multiple SerDes to drive the PHYs. Provide a PCS driver
+to configure them.
+
+Signed-off-by: Markus Stockhausen <markus.stockhausen at gmx.de>
+---
+--- a/drivers/net/pcs/Kconfig
++++ b/drivers/net/pcs/Kconfig
+@@ -36,6 +36,14 @@ config PCS_MTK_USXGMII
+ 	  1000Base-X, 2500Base-X and Cisco SGMII are supported on the same
+ 	  differential pairs via an embedded LynxI PHY.
+ 
++config PCS_RTL_OTTO
++	tristate "Realtek Otto SerDes PCS"
++	depends on MACH_REALTEK_RTL || COMPILE_TEST
++	select PHYLINK
++	select REGMAP
++	help
++	  This module provides a driver for the Realtek SerDes PCS
++
+ config PCS_RZN1_MIIC
+ 	tristate "Renesas RZ/N1 MII converter"
+ 	depends on OF && (ARCH_RZN1 || COMPILE_TEST)
+--- a/drivers/net/pcs/Makefile
++++ b/drivers/net/pcs/Makefile
+@@ -7,5 +7,6 @@ pcs_xpcs-$(CONFIG_PCS_XPCS)	:= pcs-xpcs.
+ obj-$(CONFIG_PCS_XPCS)		+= pcs_xpcs.o
+ obj-$(CONFIG_PCS_LYNX)		+= pcs-lynx.o
+ obj-$(CONFIG_PCS_MTK_LYNXI)	+= pcs-mtk-lynxi.o
++obj-$(CONFIG_PCS_RTL_OTTO)	+= pcs-rtl-otto.o
+ obj-$(CONFIG_PCS_RZN1_MIIC)	+= pcs-rzn1-miic.o
+ obj-$(CONFIG_PCS_MTK_USXGMII)	+= pcs-mtk-usxgmii.o
diff --git a/target/linux/realtek/rtl838x/config-6.12 b/target/linux/realtek/rtl838x/config-6.12
index d156ce1a17..8afd239b3b 100644
--- a/target/linux/realtek/rtl838x/config-6.12
+++ b/target/linux/realtek/rtl838x/config-6.12
@@ -193,6 +193,7 @@ CONFIG_PAGE_POOL=y
 CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
 CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
 CONFIG_PCI_DRIVERS_LEGACY=y
+CONFIG_PCS_RTL_OTTO=y
 CONFIG_PERF_USE_VMALLOC=y
 CONFIG_PGTABLE_LEVELS=2
 CONFIG_PHYLIB=y
diff --git a/target/linux/realtek/rtl839x/config-6.12 b/target/linux/realtek/rtl839x/config-6.12
index 56ae0bfe77..022cafea46 100644
--- a/target/linux/realtek/rtl839x/config-6.12
+++ b/target/linux/realtek/rtl839x/config-6.12
@@ -199,6 +199,7 @@ CONFIG_PAGE_POOL=y
 CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
 CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
 CONFIG_PCI_DRIVERS_LEGACY=y
+CONFIG_PCS_RTL_OTTO=y
 CONFIG_PERF_USE_VMALLOC=y
 CONFIG_PGTABLE_LEVELS=2
 CONFIG_PHYLIB=y
diff --git a/target/linux/realtek/rtl930x/config-6.12 b/target/linux/realtek/rtl930x/config-6.12
index e160b060ac..5b3b426019 100644
--- a/target/linux/realtek/rtl930x/config-6.12
+++ b/target/linux/realtek/rtl930x/config-6.12
@@ -182,6 +182,7 @@ CONFIG_PAGE_POOL=y
 CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
 CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
 CONFIG_PCI_DRIVERS_LEGACY=y
+CONFIG_PCS_RTL_OTTO=y
 CONFIG_PERF_USE_VMALLOC=y
 CONFIG_PGTABLE_LEVELS=2
 CONFIG_PHYLIB=y
diff --git a/target/linux/realtek/rtl930x_nand/config-6.12 b/target/linux/realtek/rtl930x_nand/config-6.12
index 075b06f362..c4a2806421 100644
--- a/target/linux/realtek/rtl930x_nand/config-6.12
+++ b/target/linux/realtek/rtl930x_nand/config-6.12
@@ -185,6 +185,7 @@ CONFIG_PAGE_POOL=y
 CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
 CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
 CONFIG_PCI_DRIVERS_LEGACY=y
+CONFIG_PCS_RTL_OTTO=y
 CONFIG_PERF_USE_VMALLOC=y
 CONFIG_PGTABLE_LEVELS=2
 CONFIG_PHYLIB=y
diff --git a/target/linux/realtek/rtl931x/config-6.12 b/target/linux/realtek/rtl931x/config-6.12
index 9c64303ec2..ee51cdb256 100644
--- a/target/linux/realtek/rtl931x/config-6.12
+++ b/target/linux/realtek/rtl931x/config-6.12
@@ -193,6 +193,7 @@ CONFIG_PAGE_POOL=y
 CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
 CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
 CONFIG_PCI_DRIVERS_LEGACY=y
+CONFIG_PCS_RTL_OTTO=y
 CONFIG_PERF_USE_VMALLOC=y
 CONFIG_PGTABLE_LEVELS=2
 CONFIG_PHYLIB=y
diff --git a/target/linux/realtek/rtl931x_nand/config-6.12 b/target/linux/realtek/rtl931x_nand/config-6.12
index 25e8b8ff4f..529739d7ac 100644
--- a/target/linux/realtek/rtl931x_nand/config-6.12
+++ b/target/linux/realtek/rtl931x_nand/config-6.12
@@ -197,6 +197,7 @@ CONFIG_PAGE_POOL=y
 CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
 CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
 CONFIG_PCI_DRIVERS_LEGACY=y
+CONFIG_PCS_RTL_OTTO=y
 CONFIG_PERF_USE_VMALLOC=y
 CONFIG_PGTABLE_LEVELS=2
 CONFIG_PHYLIB=y




More information about the lede-commits mailing list