]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
realtek: mdio: add SerDes driver 20062/head
authorMarkus Stockhausen <markus.stockhausen@gmx.de>
Mon, 15 Sep 2025 16:08:53 +0000 (12:08 -0400)
committerRobert Marko <robimarko@gmail.com>
Wed, 17 Sep 2025 17:23:15 +0000 (19:23 +0200)
Until now the SerDes access is realized with some helper functions
in the mdio bus. These were moved around a lot and had no real home.
End that temporary solution to move them where they belong.

The target design for the different Realtek drivers is as follows:

- dsa driver manages switch
- pcs driver manages SerDes on high level (to be developed)
- mdio driver manages SerDes on low level (this commit)

This driver adds the low level SerDes access via mdio. For debugging
purposes the user can interact with the SerDes in different ways.

First, there is a debug interface in
/sys/kernel/debug/realtek_otto_serdes/serdes.X/registers.
With that a dump of all registers can be shown.

> cat /sys/kernel/debug/realtek_otto_serdes/serdes.4/registers
Back SDS  4:   00   01   02   03   04   05   06   07   08
SDS        : 0C03 0F00 7060 7106 074D 0EBF 0F0F 0359 5248
SDS_EXT    : 0000 0000 85FA 8C6D 5CCC 0000 20D8 0003 79AA
...

Second, one can read/write registers via the mmd functions of the
mdio command line tool. Important to know: The registers are accessed
on the vendor specific MDIO_MMD_VEND1 device address (=30). Additionally
the SerDes page and register are concatenated into the the mmd register.
Top 8 bits are SerDes page and bottom 8 bits are SerDEs register.
E.g.

- mmd 0x0206 : SerDes page 0x02, SerDes register 0x06
- mmd 0x041f : SerDes page 0x04, SerDes register 0x1f

Read register 0x02 on page 0x03 of SerDes 0
> mdio realtek-serdes-mdio mmd 0:30 raw 0x0302

Write register 0x12 on page 0x02 of SerDes 1
> mdio realtek-serdes-mdio mmd 1:30 raw 0x0212 0x2222

For now this driver is only defined in the devicetree and activated
in the kernel build. There is no current consumer but at least
the debugging interface is available. Cleanup of the currently used
SerDes functions will come later.

Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://github.com/openwrt/openwrt/pull/20062
Signed-off-by: Robert Marko <robimarko@gmail.com>
12 files changed:
target/linux/realtek/dts/rtl838x.dtsi
target/linux/realtek/dts/rtl839x.dtsi
target/linux/realtek/dts/rtl930x.dtsi
target/linux/realtek/dts/rtl931x.dtsi
target/linux/realtek/files-6.12/drivers/net/mdio/mdio-realtek-otto-serdes.c [new file with mode: 0644]
target/linux/realtek/patches-6.12/808-add-serdes-mdio-driver.patch [new file with mode: 0644]
target/linux/realtek/rtl838x/config-6.12
target/linux/realtek/rtl839x/config-6.12
target/linux/realtek/rtl930x/config-6.12
target/linux/realtek/rtl930x_nand/config-6.12
target/linux/realtek/rtl931x/config-6.12
target/linux/realtek/rtl931x_nand/config-6.12

index f4b629033b277a794994492517061324b4793a0a..3b17c66c0b4ea33c0e773d50611e874975b6c247 100644 (file)
                        pinctrl-0 = <&mdio_aux_mdx>, <&aux_mode_mdio>;
                };
 
+               mdio_serdes: mdio-serdes {
+                       compatible = "realtek,rtl8380-serdes-mdio", "realtek,otto-serdes-mdio";
+               };
+
                soc_thermal: thermal {
                        compatible = "realtek,rtl8380-thermal";
                        #thermal-sensor-cells = <0>;
index 256ee0bc3056ae248b5c0915a765eedc07a02448..9eb4be8af801da202a1137b49aa2b44b4eb188b0 100644 (file)
                        pinctrl-0 = <&mdio_aux_mdx>;
                };
 
+               mdio_serdes: mdio-serdes {
+                       compatible = "realtek,rtl8392-serdes-mdio", "realtek,otto-serdes-mdio";
+               };
+
                soc_thermal: thermal {
                        compatible = "realtek,rtl8390-thermal";
                        #thermal-sensor-cells = <0>;
index 13c6fad5a116de83a820e9892aff61d6cbb122e3..97bf6abe5d0edfe6a3ffa8d1b87713a3c33fbe22 100644 (file)
                        status = "disabled";
                };
 
+               mdio_serdes: mdio-serdes {
+                       compatible = "realtek,rtl9301-serdes-mdio", "realtek,otto-serdes-mdio";
+               };
+
                soc_thermal: thermal {
                        compatible = "realtek,rtl9300-thermal";
                        #thermal-sensor-cells = <0>;
index 27f12df2b80130b4e9565c524a947b83bc3e4f19..352e9547f01559ccf5e2741ce03dd9cd594147a1 100644 (file)
 
                        status = "disabled";
                };
+
+               mdio_serdes: mdio-serdes {
+                       compatible = "realtek,rtl9311-serdes-mdio", "realtek,otto-serdes-mdio";
+               };
+
        };
 
        pinmux@1b001358 {
diff --git a/target/linux/realtek/files-6.12/drivers/net/mdio/mdio-realtek-otto-serdes.c b/target/linux/realtek/files-6.12/drivers/net/mdio/mdio-realtek-otto-serdes.c
new file mode 100644 (file)
index 0000000..6296d1b
--- /dev/null
@@ -0,0 +1,532 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/debugfs.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.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/regmap.h>
+
+#define RTSDS_REG_CNT                  32
+#define RTSDS_VAL_MASK                 GENMASK(15, 0)
+#define RTSDS_SUBPAGE(page)            (page % 64)
+
+#define RTSDS_MMD_PAGE_MASK            GENMASK(15, 8)
+#define RTSDS_MMD_REG_MASK             GENMASK(7, 0)
+
+#define RTSDS_838X_SDS_CNT             6
+#define RTSDS_838X_PAGE_CNT            4
+#define RTSDS_838X_BASE                        0xe780
+
+#define RTSDS_839X_SDS_CNT             14
+#define RTSDS_839X_PAGE_CNT            12
+#define RTSDS_839X_BASE                        0xa000
+
+#define RTSDS_930X_SDS_CNT             12
+#define RTSDS_930X_PAGE_CNT            64
+#define RTSDS_930X_BASE                        0x03b0
+
+#define RTSDS_931X_SDS_CNT             14
+#define RTSDS_931X_PAGE_CNT            192
+#define RTSDS_931X_BASE                        0x5638
+
+#define RTSDS_93XX_CMD_READ            0
+#define RTSDS_93XX_CMD_WRITE           BIT(1)
+#define RTSDS_93XX_CMD_BUSY            BIT(0)
+#define RTSDS_93XX_CMD_SDS_MASK                GENMASK(6, 2)
+#define RTSDS_93XX_CMD_PAGE_MASK       GENMASK(12, 7)
+#define RTSDS_93XX_CMD_REG_MASK                GENMASK(17, 13)
+
+struct rtsds_ctrl {
+       struct device *dev;
+       struct regmap *map;
+       struct mii_bus *bus;
+       const struct rtsds_config *cfg;
+};
+
+struct rtsds_config {
+       int sds_cnt;
+       int page_cnt;
+       int base;
+       int (*read)(struct rtsds_ctrl *ctrl, int sds, int page, int regnum);
+       int (*write)(struct rtsds_ctrl *ctrl, int sds, int page, int regnum, u16 value);
+       int (*backing_sds)(int sds, int page);
+};
+
+static bool rtsds_mmd_to_sds(struct rtsds_ctrl *ctrl, int addr, int devad, int mmd_regnum,
+                            int *sds_page, int *sds_regnum)
+{
+       *sds_page = FIELD_GET(RTSDS_MMD_PAGE_MASK, mmd_regnum);
+       *sds_regnum = FIELD_GET(RTSDS_MMD_REG_MASK, mmd_regnum);
+
+       return !(addr < 0 || addr >= ctrl->cfg->sds_cnt ||
+                *sds_page < 0 || *sds_page >= ctrl->cfg->page_cnt ||
+                *sds_regnum < 0 || *sds_regnum >= RTSDS_REG_CNT ||
+                devad != MDIO_MMD_VEND1);
+}
+
+static int rtsds_sds_to_mmd(int sds_page, int sds_regnum)
+{
+       return FIELD_PREP(RTSDS_MMD_PAGE_MASK, sds_page) |
+              FIELD_PREP(RTSDS_MMD_REG_MASK, sds_regnum);
+}
+
+static int rtsds_get_backing_sds(int sds, int page)
+{
+       /* non RTL931x devices have 1:1 frontend/backend mapping */
+       return sds;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+/*
+ * The SerDes offer a lot of magic that sill needs to be uncovered. Getting this info manually
+ * with mdio command line tools can be time consuming. Provide a convenient register dump.
+ */
+
+#define RTSDS_DBG_PAGE_NAMES   48
+#define RTSDS_DBG_ROOT_DIR     "realtek_otto_serdes"
+
+struct rtsds_debug_info {
+       struct rtsds_ctrl *ctrl;
+       int sds;
+};
+
+static const char * const rtsds_page_name[RTSDS_DBG_PAGE_NAMES] = {
+       [0] = "SDS",            [1] = "SDS_EXT",
+       [2] = "FIB",            [3] = "FIB_EXT",
+       [4] = "DTE",            [5] = "DTE_EXT",
+       [6] = "TGX",            [7] = "TGX_EXT",
+       [8] = "ANA_RG",         [9] = "ANA_RG_EXT",
+       [10] = "ANA_TG",        [11] = "ANA_TG_EXT",
+       [31] = "ANA_WDIG",
+       [32] = "ANA_MISC",      [33] = "ANA_COM",
+       [34] = "ANA_SP",        [35] = "ANA_SP_EXT",
+       [36] = "ANA_1G",        [37] = "ANA_1G_EXT",
+       [38] = "ANA_2G",        [39] = "ANA_2G_EXT",
+       [40] = "ANA_3G",        [41] = "ANA_3G_EXT",
+       [42] = "ANA_5G",        [43] = "ANA_5G_EXT",
+       [44] = "ANA_6G",        [45] = "ANA_6G_EXT",
+       [46] = "ANA_10G",       [47] = "ANA_10G_EXT",
+};
+
+static int rtsds_dbg_registers_show(struct seq_file *seqf, void *unused)
+{
+       struct rtsds_debug_info *dbg_info = seqf->private;
+       struct rtsds_ctrl *ctrl = dbg_info->ctrl;
+       struct mii_bus *bus = ctrl->bus;
+       int sds = dbg_info->sds;
+       int regnum, page = 0;
+       int subpage;
+
+       do {
+               subpage = RTSDS_SUBPAGE(page);
+               if (!subpage) {
+                       seq_printf(seqf, "Back SDS %02d:", ctrl->cfg->backing_sds(sds, page));
+                       for (regnum = 0; regnum < RTSDS_REG_CNT; regnum++)
+                               seq_printf(seqf, "   %02X", regnum);
+                       seq_puts(seqf, "\n");
+               }
+
+               if (subpage < RTSDS_DBG_PAGE_NAMES && rtsds_page_name[subpage])
+                       seq_printf(seqf, "%*s: ", -11, rtsds_page_name[subpage]);
+               else
+                       seq_printf(seqf, "PAGE %02X    : ", page);
+
+               for (regnum = 0; regnum < RTSDS_REG_CNT; regnum++)
+                       seq_printf(seqf, "%04X ",
+                                  mdiobus_c45_read(bus, sds, MDIO_MMD_VEND1,
+                                                   rtsds_sds_to_mmd(page, regnum)));
+               seq_puts(seqf, "\n");
+       } while (++page < ctrl->cfg->page_cnt);
+
+       return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(rtsds_dbg_registers);
+
+static int rtsds_debug_init(struct rtsds_ctrl *ctrl, u32 sds)
+{
+       struct rtsds_debug_info *dbg_info;
+       struct dentry *dir, *root;
+       char dirname[32];
+
+       dbg_info = kzalloc(sizeof(*dbg_info), GFP_KERNEL);
+       if (!dbg_info)
+               return -ENOMEM;
+
+       dbg_info->ctrl = ctrl;
+       dbg_info->sds = sds;
+
+       root = debugfs_lookup(RTSDS_DBG_ROOT_DIR, NULL);
+       if (!root)
+               root = debugfs_create_dir(RTSDS_DBG_ROOT_DIR, NULL);
+
+       snprintf(dirname, sizeof(dirname), "serdes.%d", sds);
+       dir = debugfs_create_dir(dirname, root);
+
+       debugfs_create_file("registers", 0600, dir, dbg_info, &rtsds_dbg_registers_fops);
+
+       return 0;
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+/*
+ * The RTL838x has 6 SerDes. The 16 bit registers start at 0xbb00e780 and are mapped directly into
+ * 32 bit memory addresses. High 16 bits are always empty. A "lower" memory block serves pages 0/3
+ * a "higher" memory block pages 1/2.
+ */
+
+static int rtsds_838x_reg_offset(int sds, int page, int regnum)
+{
+       if (page == 0 || page == 3)
+               return (sds << 9) + (page << 7) + (regnum << 2);
+
+       /* (page == 1 || page == 2) */
+       return 0xb80 + (sds << 8) + (page << 7) + (regnum << 2);
+}
+
+static int rtsds_838x_read(struct rtsds_ctrl *ctrl, int sds, int page, int regnum)
+{
+       int offset = rtsds_838x_reg_offset(sds, page, regnum);
+       int ret, value;
+
+       ret = regmap_read(ctrl->map, ctrl->cfg->base + offset, &value);
+
+       return ret ? ret : value & RTSDS_VAL_MASK;
+}
+
+static int rtsds_838x_write(struct rtsds_ctrl *ctrl, int sds, int page, int regnum, u16 value)
+{
+       int offset = rtsds_838x_reg_offset(sds, page, regnum);
+
+       return regmap_write(ctrl->map, ctrl->cfg->base + offset, value);
+}
+
+/*
+ * The RTL839x has 14 SerDes starting at 0xbb00a000. 0-7, 10, 11 are 5GBit, 8, 9, 12, 13 are
+ * 10 GBit. Two adjacent SerDes are tightly coupled and share a 1024 bytes register area. Per 32
+ * bit address two registers are stored. The first register is stored in the lower 2 bytes ("on
+ * the right" due to big endian) and the second register in the upper 2 bytes. The following
+ * register areas are known:
+ *
+ * - XSG0      (4 pages @ offset 0x000): for even SerDes
+ * - XSG1      (4 pages @ offset 0x100): for odd SerDes
+ * - TGRX      (4 pages @ offset 0x200): for even 10G SerDes
+ * - ANA_RG    (2 pages @ offset 0x300): for even 5G SerDes
+ * - ANA_RG    (2 pages @ offset 0x380): for odd 5G SerDes
+ * - ANA_TG    (2 pages @ offset 0x300): for even 10G SerDes
+ * - ANA_TG    (2 pages @ offset 0x380): for odd 10G SerDes
+ *
+ * The most consistent mapping that aligns to the RTL93xx devices is:
+ *
+ *             even 5G SerDes  odd 5G SerDes   even 10G SerDes odd 10G SerDes
+ * Page 0:     XSG0/0          XSG1/0          XSG0/0          XSG1/0
+ * Page 1:     XSG0/1          XSG1/1          XSG0/1          XSG1/1
+ * Page 2:     XSG0/2          XSG1/2          XSG0/2          XSG1/2
+ * Page 3:     XSG0/3          XSG1/3          XSG0/3          XSG1/3
+ * Page 4:     <zero>          <zero>          TGRX/0          <zero>
+ * Page 5:     <zero>          <zero>          TGRX/1          <zero>
+ * Page 6:     <zero>          <zero>          TGRX/2          <zero>
+ * Page 7:     <zero>          <zero>          TGRX/3          <zero>
+ * Page 8:     ANA_RG          ANA_RG          <zero>          <zero>
+ * Page 9:     ANA_RG_EXT      ANA_RG_EXT      <zero>          <zero>
+ * Page 10:    <zero>          <zero>          ANA_TG          ANA_TG
+ * Page 11:    <zero>          <zero>          ANA_TG_EXT      ANA_TG_EXT
+ */
+
+static int rtsds_839x_reg_offset(int sds, int page, int regnum)
+{
+       int offset = ((sds & 0xfe) << 9) + ((regnum & 0xfe) << 1) + (page << 6);
+       int sds5g = (GENMASK(11, 10) | GENMASK(7, 0)) & BIT(sds);
+
+       if (page < 4)
+               return offset + ((sds & 1) << 8);
+       else if ((page & 4) && (sds == 8 || sds == 12))
+               return offset + 0x100;
+       else if (page >= 8 && page <= 9 && sds5g)
+               return offset + 0x100 + ((sds & 1) << 7);
+       else if (page >= 10 && !sds5g)
+               return offset + 0x80 + ((sds & 1) << 7);
+
+       return -EINVAL; /* hole */
+}
+
+static int rtsds_839x_read(struct rtsds_ctrl *ctrl, int sds, int page, int regnum)
+{
+       int offset = rtsds_839x_reg_offset(sds, page, regnum);
+       int shift = regnum & 1 ? 16 : 0;
+       int ret, value;
+
+       if (offset < 0)
+               return 0;
+
+       ret = regmap_read(ctrl->map, ctrl->cfg->base + offset, &value);
+
+       return ret ? ret : (value >> shift) & RTSDS_VAL_MASK;
+}
+
+static int rtsds_839x_write(struct rtsds_ctrl *ctrl, int sds, int page, int regnum, u16 value)
+{
+       int offset = rtsds_839x_reg_offset(sds, page, regnum);
+       int write_value = value;
+       int neigh_value;
+
+       if (offset < 0)
+               return 0;
+
+       neigh_value = rtsds_839x_read(ctrl, sds, page, regnum ^ 1);
+       if (neigh_value < 0)
+               return neigh_value;
+
+       if (regnum & 1)
+               write_value = (write_value << 16) + neigh_value;
+       else
+               write_value = (neigh_value << 16) + write_value;
+
+       return regmap_write(ctrl->map, ctrl->cfg->base + offset, write_value);
+}
+
+/*
+ * RTL93xx targets use a shared implementation. Their SerDes data is accessed through two IO
+ * registers which simulate commands to an internal MDIO bus.
+ *
+ * The RTL930x family has 12 SerDes of three types.
+ *
+ * - SerDes 0-1 exist on the RTL9301 and 9302B and are QSGMII capable
+ * - SerDes 2-9 are USXGMII capabable with either quad or single configuration
+ * - SerDes 10-11 are 10GBase-R capable
+ *
+ * The RTL931x family has 14 "frontend" SerDes that are cascaded. All operations (e.g. reset) work
+ * on this frontend view while their registers are distributed over a total of least 26 background
+ * SerDes with 64 pages and 32 registers. Three types of SerDes exist:
+ *
+ * - Serdes 0,1 are "simple" and work on one background serdes.
+ * - "Even" SerDes with numbers 2, 4, 6, 8, 10, 12 work on two background SerDes. One analog and
+ *   one digital.
+ * - "Odd" SerDes with numbers 3, 5, 7, 9, 11, 13 work on a total of 3 background SerDes (one
+ *   analog and two digital)
+ *
+ * This maps to:
+ *
+ * Frontend SerDes  |  0  1  2  3  4  5  6  7  8  9 10 11 12 13
+ * -----------------+------------------------------------------
+ * Backend SerDes 1 |  0  1  2  3  6  7 10 11 14 15 18 19 22 23
+ * Backend SerDes 2 |  0  1  2  4  6  8 10 12 14 16 18 20 22 24
+ * Backend SerDes 3 |  0  1  3  5  7  9 11 13 15 17 19 21 23 25
+ *
+ * Note: In Realtek proprietary XSGMII mode (10G pumped SGMII) the frontend SerDes works on the
+ * two digital SerDes while in all other modes it works on the analog and the first digital
+ * SerDes. Overlapping (e.g. backend SerDes 7 can be analog or digital 2) is avoided by the
+ * existing hardware designs.
+ *
+ * Align this for readability by simulating a total of 192 pages and mix them as follows.
+ *
+ * frontend page               "even" frontend SerDes          "odd" frontend SerDes
+ * page 0x00-0x3f (analog):    page 0x00-0x3f back SDS         page 0x00-0x3f back SDS
+ * page 0x40-0x7f (digi 1):    page 0x00-0x3f back SDS         page 0x00-0x3f back SDS+1
+ * page 0x80-0xbf (digi 2):    page 0x00-0x3f back SDS+1       page 0x00-0x3f back SDS+2
+ */
+
+static int rtsds_931x_get_backing_sds(int sds, int page)
+{
+       int map[] = { 0, 1, 2, 3, 6, 7, 10, 11, 14, 15, 18, 19, 22, 23 };
+       int backsds = map[sds];
+
+       if (sds < 2)
+               return backsds;
+       else if (sds & 1)
+               backsds += (page >> 6); /* distribute "odd" to 3 background SerDes */
+       else
+               backsds += (page >> 7); /* distribute "even" to 2 background SerDes */
+
+       return backsds;
+}
+
+static int rtsds_rt93xx_io(struct rtsds_ctrl *ctrl, int sds, int page, int regnum, int cmd)
+{
+       int ret, op, value;
+
+       op = FIELD_PREP(RTSDS_93XX_CMD_SDS_MASK, sds) |
+            FIELD_PREP(RTSDS_93XX_CMD_PAGE_MASK, page) |
+            FIELD_PREP(RTSDS_93XX_CMD_REG_MASK, regnum) |
+            RTSDS_93XX_CMD_BUSY | cmd;
+
+       regmap_write(ctrl->map, ctrl->cfg->base, op);
+       ret = regmap_read_poll_timeout(ctrl->map, ctrl->cfg->base, value,
+                                      !(value & RTSDS_93XX_CMD_BUSY), 30, 1000000);
+
+       if (ret < 0) {
+               dev_err(ctrl->dev, "SerDes I/O timed out\n");
+               return -ETIMEDOUT;
+       }
+
+       return 0;
+}
+
+static int rtsds_93xx_read(struct rtsds_ctrl *ctrl, int sds, int page, int regnum)
+{
+       int subpage = RTSDS_SUBPAGE(page);
+       int ret, backsds, value;
+
+       backsds = ctrl->cfg->backing_sds(sds, page);
+       ret = rtsds_rt93xx_io(ctrl, backsds, subpage, regnum, RTSDS_93XX_CMD_READ);
+       if (ret)
+               return ret;
+
+       ret = regmap_read(ctrl->map, ctrl->cfg->base + 4, &value);
+
+       return ret ? ret : value & RTSDS_VAL_MASK;
+}
+
+static int rtsds_93xx_write(struct rtsds_ctrl *ctrl, int sds, int page, int regnum, u16 value)
+{
+       int subpage = RTSDS_SUBPAGE(page);
+       int ret, backsds;
+
+       backsds = ctrl->cfg->backing_sds(sds, page);
+       ret = regmap_write(ctrl->map, ctrl->cfg->base + 4, value);
+       if (ret)
+               return ret;
+
+       return rtsds_rt93xx_io(ctrl, backsds, subpage, regnum, RTSDS_93XX_CMD_WRITE);
+}
+
+static int rtsds_read(struct mii_bus *bus, int addr, int devad, int regnum)
+{
+       struct rtsds_ctrl *ctrl = bus->priv;
+       int sds_page, sds_regnum;
+
+       if (!rtsds_mmd_to_sds(ctrl, addr, devad, regnum, &sds_page, &sds_regnum))
+               return -EINVAL;
+
+       return ctrl->cfg->read(ctrl, addr, sds_page, sds_regnum);
+}
+
+static int rtsds_write(struct mii_bus *bus, int addr, int devad, int regnum, u16 value)
+{
+       struct rtsds_ctrl *ctrl = bus->priv;
+       int sds_page, sds_regnum;
+
+       if (!rtsds_mmd_to_sds(ctrl, addr, devad, regnum, &sds_page, &sds_regnum))
+               return -EINVAL;
+
+       return ctrl->cfg->write(ctrl, addr, sds_page, sds_regnum, value);
+}
+
+static int rtsds_probe(struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct device *dev = &pdev->dev;
+       struct rtsds_ctrl *ctrl;
+       struct mii_bus *bus;
+       int ret;
+
+       bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*ctrl));
+       if (!bus)
+               return -ENOMEM;
+
+       ctrl = bus->priv;
+       ctrl->map = syscon_node_to_regmap(np->parent);
+       if (IS_ERR(ctrl->map))
+               return PTR_ERR(ctrl->map);
+
+       ctrl->dev = dev;
+       ctrl->cfg = (const struct rtsds_config *)device_get_match_data(ctrl->dev);
+       ctrl->bus = bus;
+
+       snprintf(bus->id, MII_BUS_ID_SIZE, "realtek-serdes-mdio") ;
+       bus->name = "Realtek SerDes MDIO bus";
+       bus->parent = dev;
+       bus->read_c45 = rtsds_read;
+       bus->write_c45 = rtsds_write;
+       bus->phy_mask = ~0ULL;
+
+       ret = devm_mdiobus_register(dev, bus);
+       if (ret)
+               return ret;
+
+#ifdef CONFIG_DEBUG_FS
+       for (int sds = 0; sds < ctrl->cfg->sds_cnt; sds++)
+               rtsds_debug_init(ctrl, sds);
+#endif
+
+       dev_info(dev, "Realtek SerDes mdio bus initialized. %d SerDes, %d pages, %d registers.",
+                ctrl->cfg->sds_cnt, ctrl->cfg->page_cnt, RTSDS_REG_CNT);
+
+       return 0;
+}
+
+static const struct rtsds_config rtsds_838x_cfg = {
+       .sds_cnt        = RTSDS_838X_SDS_CNT,
+       .page_cnt       = RTSDS_838X_PAGE_CNT,
+       .base           = RTSDS_838X_BASE,
+       .read           = rtsds_838x_read,
+       .write          = rtsds_838x_write,
+       .backing_sds    = rtsds_get_backing_sds,
+};
+
+static const struct rtsds_config rtsds_839x_cfg = {
+       .sds_cnt        = RTSDS_839X_SDS_CNT,
+       .page_cnt       = RTSDS_839X_PAGE_CNT,
+       .base           = RTSDS_839X_BASE,
+       .read           = rtsds_839x_read,
+       .write          = rtsds_839x_write,
+       .backing_sds    = rtsds_get_backing_sds,
+};
+
+static const struct rtsds_config rtsds_930x_cfg = {
+       .sds_cnt        = RTSDS_930X_SDS_CNT,
+       .page_cnt       = RTSDS_930X_PAGE_CNT,
+       .base           = RTSDS_930X_BASE,
+       .read           = rtsds_93xx_read,
+       .write          = rtsds_93xx_write,
+       .backing_sds    = rtsds_get_backing_sds,
+};
+
+static const struct rtsds_config rtsds_931x_cfg = {
+       .sds_cnt        = RTSDS_931X_SDS_CNT,
+       .page_cnt       = RTSDS_931X_PAGE_CNT,
+       .base           = RTSDS_931X_BASE,
+       .read           = rtsds_93xx_read,
+       .write          = rtsds_93xx_write,
+       .backing_sds    = rtsds_931x_get_backing_sds,
+};
+
+static const struct of_device_id rtsds_of_match[] = {
+       {
+               .compatible = "realtek,rtl8380-serdes-mdio",
+               .data = &rtsds_838x_cfg,
+       },
+       {
+               .compatible = "realtek,rtl8392-serdes-mdio",
+               .data = &rtsds_839x_cfg,
+       },
+       {
+               .compatible = "realtek,rtl9301-serdes-mdio",
+               .data = &rtsds_930x_cfg,
+       },
+       {
+               .compatible = "realtek,rtl9311-serdes-mdio",
+               .data = &rtsds_931x_cfg,
+       },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rtsds_mdio_of_match);
+
+static struct platform_driver rtsds_mdio_driver = {
+       .driver = {
+               .name = "realtek-otto-serdes-mdio",
+               .of_match_table = rtsds_of_match
+       },
+       .probe = rtsds_probe,
+};
+module_platform_driver(rtsds_mdio_driver);
+
+MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen@gmx.de>");
+MODULE_DESCRIPTION("Realtek Otto SerDes MDIO bus");
+MODULE_LICENSE("GPL v2");
\ No newline at end of file
diff --git a/target/linux/realtek/patches-6.12/808-add-serdes-mdio-driver.patch b/target/linux/realtek/patches-6.12/808-add-serdes-mdio-driver.patch
new file mode 100644 (file)
index 0000000..292683f
--- /dev/null
@@ -0,0 +1,37 @@
+From fc3eda9aa25765b2115e7427ee4f6dbcd60f8721 Mon Sep 17 00:00:00 2001
+From: Markus Stockhausen <markus.stockhausen@gmx.de>
+Date: Mon, 15 Sep 2025 17:23:31 +0200
+Subject: [PATCH] net: mdio: 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 mdio interface
+to access their registers.
+
+Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
+---
+--- a/drivers/net/mdio/Makefile
++++ b/drivers/net/mdio/Makefile
+@@ -22,6 +22,7 @@ obj-$(CONFIG_MDIO_OCTEON)            += mdio-octeo
+ obj-$(CONFIG_MDIO_REGMAP)             += mdio-regmap.o
+ obj-$(CONFIG_MDIO_REALTEK_OTTO)       += mdio-realtek-otto.o
+ obj-$(CONFIG_MDIO_REALTEK_OTTO_AUX)   += mdio-realtek-otto-aux.o
++obj-$(CONFIG_MDIO_REALTEK_OTTO_SERDES)        += mdio-realtek-otto-serdes.o
+ obj-$(CONFIG_MDIO_SMBUS)              += mdio-smbus.o
+ obj-$(CONFIG_MDIO_SUN4I)              += mdio-sun4i.o
+ obj-$(CONFIG_MDIO_THUNDER)            += mdio-thunder.o
+--- a/drivers/net/mdio/Kconfig
++++ b/drivers/net/mdio/Kconfig
+@@ -224,6 +224,13 @@ config MDIO_REALTEK_OTTO_AUX
+         This driver supports the auxilairy MDIO bus on RTL838x SoCs. This bus
+         is typically used to attach RTL8231 GPIO extenders.
++config MDIO_REALTEK_OTTO_SERDES
++        tristate "Realtek Otto MDIO SerDes interface support"
++        default MACH_REALTEK_RTL
++        depends on MACH_REALTEK_RTL || COMPILE_TEST
++        help
++          This driver provides MDIO access to the RTL83xx/RTL93xx SerDes.
++
+ config MDIO_THUNDER
+       tristate "ThunderX SOCs MDIO buses"
+       depends on 64BIT
index 287541feb2634ec46cb5bdea87a6ce223ceec048..d156ce1a175bb8ff25fc2a32f1395883011de667 100644 (file)
@@ -136,6 +136,7 @@ CONFIG_MDIO_GPIO=y
 CONFIG_MDIO_I2C=y
 CONFIG_MDIO_REALTEK_OTTO=y
 CONFIG_MDIO_REALTEK_OTTO_AUX=y
+CONFIG_MDIO_REALTEK_OTTO_SERDES=y
 CONFIG_MDIO_SMBUS=y
 CONFIG_MFD_CORE=y
 CONFIG_MFD_RTL8231=y
index 51c0336710e19466aa2ff8fc3afb7df304d32290..56ae0bfe77f36bd4fc8ecb65582f7bb46b741637 100644 (file)
@@ -134,6 +134,7 @@ CONFIG_MDIO_DEVRES=y
 CONFIG_MDIO_I2C=y
 CONFIG_MDIO_REALTEK_OTTO=y
 CONFIG_MDIO_REALTEK_OTTO_AUX=y
+CONFIG_MDIO_REALTEK_OTTO_SERDES=y
 CONFIG_MDIO_SMBUS=y
 CONFIG_MFD_CORE=y
 CONFIG_MFD_RTL8231=y
index 82880f3a1691a63935ff6b00b7f64696c19d4810..e160b060ac09b0a841faa7407c437cf7ca53f7bb 100644 (file)
@@ -118,6 +118,7 @@ CONFIG_MDIO_DEVRES=y
 CONFIG_MDIO_I2C=y
 CONFIG_MDIO_REALTEK_OTTO=y
 CONFIG_MDIO_REALTEK_OTTO_AUX=y
+CONFIG_MDIO_REALTEK_OTTO_SERDES=y
 CONFIG_MDIO_SMBUS=y
 CONFIG_MFD_RTL8231=y
 CONFIG_MFD_SYSCON=y
index 5ba7b2e613aead73f5aac58476495215e10ad7d7..075b06f3626e2c3989db952e9896e1a1e1ad3215 100644 (file)
@@ -118,6 +118,7 @@ CONFIG_MDIO_DEVRES=y
 CONFIG_MDIO_I2C=y
 CONFIG_MDIO_REALTEK_OTTO=y
 CONFIG_MDIO_REALTEK_OTTO_AUX=y
+CONFIG_MDIO_REALTEK_OTTO_SERDES=y
 CONFIG_MDIO_SMBUS=y
 CONFIG_MFD_RTL8231=y
 CONFIG_MFD_SYSCON=y
index f45a17fe7e49776bf8239e4b7a8763c1dcb8aa9c..9c64303ec2efb4032c6f83d51e763ff7a1827692 100644 (file)
@@ -123,6 +123,7 @@ CONFIG_MDIO_DEVRES=y
 CONFIG_MDIO_I2C=y
 CONFIG_MDIO_REALTEK_OTTO=y
 CONFIG_MDIO_REALTEK_OTTO_AUX=y
+CONFIG_MDIO_REALTEK_OTTO_SERDES=y
 CONFIG_MDIO_SMBUS=y
 CONFIG_MFD_RTL8231=y
 CONFIG_MFD_SYSCON=y
index 5bf65a5b8be7370559a9bfa9172e90152dfd625a..25e8b8ff4fcd8f031f1668c42dd38658dc0650b0 100644 (file)
@@ -123,6 +123,7 @@ CONFIG_MDIO_DEVRES=y
 CONFIG_MDIO_I2C=y
 CONFIG_MDIO_REALTEK_OTTO=y
 CONFIG_MDIO_REALTEK_OTTO_AUX=y
+CONFIG_MDIO_REALTEK_OTTO_SERDES=y
 CONFIG_MDIO_SMBUS=y
 CONFIG_MFD_RTL8231=y
 CONFIG_MFD_SYSCON=y