--- /dev/null
+// 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