[PATCH net-next v4 6/8] net: mdio: Add SoC-e SWIP MDIO controller driver

From: Vasilij Strassheim

Date: Thu Nov 05 2026 - 09:25:03 EST


Add a driver for the MDIO controller integrated into SoC-e Ethernet
switch IP cores. Support Clause 22 transactions and the separate address
and data cycles required for Clause 45 accesses.

The transaction control register is shared with the external MDIO bus
selector. The selector occupies bits 26:16 and is managed by a generic
MMIO MDIO mux through the upper 16-bit halfword. Access transaction
control through the lower halfword to preserve the selector bits.

Check that the controller is idle before programming transaction
registers and poll for completion after each transaction cycle. Return
-ETIMEDOUT if the controller remains busy. No abort or reset recovery is
implemented.

The idle check does not cover selector writes: the generic mux changes
the selector before invoking the controller callback. After a timeout, a
subsequent access to a different child bus may therefore change the
selector while the previous transaction is still active. Whether the
hardware latches the selector at transaction start is not documented, so
such a bus change is not guaranteed to be safe. This limitation is
accepted when using the generic MMIO MDIO mux.

Signed-off-by: Vasilij Strassheim <v.strassheim@xxxxxxxxxxxxx>
---
drivers/net/mdio/Kconfig | 10 ++
drivers/net/mdio/Makefile | 1 +
drivers/net/mdio/mdio-soce.c | 213 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 224 insertions(+)

diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig
index d44278f26fab..b9bb9016b925 100644
--- a/drivers/net/mdio/Kconfig
+++ b/drivers/net/mdio/Kconfig
@@ -189,6 +189,16 @@ config MDIO_REGMAP
regmap. Users willing to use this driver must explicitly select
REGMAP.

+config MDIO_SOCE_SWIP
+ tristate
+ depends on OF_MDIO && HAS_IOMEM
+ depends on MFD_SOCE_SWIP
+ help
+ This driver provides support for the MDIO controller
+ integrated into SoC-e SWIP Ethernet switch IP cores. This option
+ will be selected if the configuration for the main switch
+ functionality of the FPGA IP core is selected.
+
config MDIO_THUNDER
tristate "ThunderX SOCs MDIO buses"
depends on 64BIT
diff --git a/drivers/net/mdio/Makefile b/drivers/net/mdio/Makefile
index 048586746026..d69f918313d3 100644
--- a/drivers/net/mdio/Makefile
+++ b/drivers/net/mdio/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
obj-$(CONFIG_MDIO_PIC64HPSC) += mdio-pic64hpsc.o
obj-$(CONFIG_MDIO_REALTEK_RTL9300) += mdio-realtek-rtl9300.o
obj-$(CONFIG_MDIO_REGMAP) += mdio-regmap.o
+obj-$(CONFIG_MDIO_SOCE_SWIP) += mdio-soce.o
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o
obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o
diff --git a/drivers/net/mdio/mdio-soce.c b/drivers/net/mdio/mdio-soce.c
new file mode 100644
index 000000000000..5350feb92d55
--- /dev/null
+++ b/drivers/net/mdio/mdio-soce.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020-2026 System on Chip engineering, S.L.
+ * Copyright (c) 2026 Linutronix GmbH
+ * Author: Vasilij Strassheim <v.strassheim@xxxxxxxxxxxxx>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_mdio.h>
+#include <linux/platform_device.h>
+
+#define SWIP_MDIO_TIMEOUT_US 1000
+
+#define SWIP_MDIO_PARAMS_OFFSET 0x0000
+#define SWIP_MDIO_WRITE_OFFSET 0x0004
+#define SWIP_MDIO_READ_OFFSET 0x0008
+
+#define SWIP_MDIO_CTRL_TRANSTYPE_MASK GENMASK(4, 3)
+#define SWIP_MDIO_CTRL_TRANSTYPE_WRITE 0x1
+#define SWIP_MDIO_CTRL_TRANSTYPE_READ 0x3
+#define SWIP_MDIO_CTRL_CLAUSE BIT(1)
+#define SWIP_MDIO_CTRL_OPSTATUS BIT(0)
+#define SWIP_MDIO_PARAMS_REGDEV_MASK GENMASK(12, 8)
+#define SWIP_MDIO_PARAMS_PHYADDR_MASK GENMASK(4, 0)
+#define SWIP_MDIO_READ_DATA_MASK GENMASK(15, 0)
+
+struct swip_mdio {
+ void __iomem *ctrl;
+ void __iomem *data;
+};
+
+static int swip_mdio_wait_for_idle(struct swip_mdio *priv)
+{
+ void __iomem *ctrl = priv->ctrl;
+ u16 val;
+
+ return readw_poll_timeout(ctrl, val, !(val & SWIP_MDIO_CTRL_OPSTATUS),
+ 10, SWIP_MDIO_TIMEOUT_US);
+}
+
+static int swip_mdio_read(struct mii_bus *bus, int phy_addr, int regnum)
+{
+ struct swip_mdio *priv = bus->priv;
+ void __iomem *data = priv->data;
+ u32 command;
+ int ret;
+
+ ret = swip_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ writel(FIELD_PREP(SWIP_MDIO_PARAMS_REGDEV_MASK, regnum) |
+ FIELD_PREP(SWIP_MDIO_PARAMS_PHYADDR_MASK, phy_addr),
+ data + SWIP_MDIO_PARAMS_OFFSET);
+
+ command = FIELD_PREP(SWIP_MDIO_CTRL_TRANSTYPE_MASK,
+ SWIP_MDIO_CTRL_TRANSTYPE_READ) |
+ SWIP_MDIO_CTRL_OPSTATUS;
+ writew(command, priv->ctrl);
+
+ ret = swip_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ return readl(data + SWIP_MDIO_READ_OFFSET) & SWIP_MDIO_READ_DATA_MASK;
+}
+
+static int swip_mdio_read_c45(struct mii_bus *bus, int phy_addr, int devad,
+ int regnum)
+{
+ struct swip_mdio *priv = bus->priv;
+ void __iomem *data = priv->data;
+ u32 command;
+ int ret;
+
+ ret = swip_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ writel(FIELD_PREP(SWIP_MDIO_PARAMS_REGDEV_MASK, devad) |
+ FIELD_PREP(SWIP_MDIO_PARAMS_PHYADDR_MASK, phy_addr),
+ data + SWIP_MDIO_PARAMS_OFFSET);
+ writel(regnum, data + SWIP_MDIO_WRITE_OFFSET);
+
+ command = SWIP_MDIO_CTRL_CLAUSE | SWIP_MDIO_CTRL_OPSTATUS;
+ writew(command, priv->ctrl);
+
+ ret = swip_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ command = FIELD_PREP(SWIP_MDIO_CTRL_TRANSTYPE_MASK,
+ SWIP_MDIO_CTRL_TRANSTYPE_READ) |
+ SWIP_MDIO_CTRL_CLAUSE | SWIP_MDIO_CTRL_OPSTATUS;
+ writew(command, priv->ctrl);
+
+ ret = swip_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ return readl(data + SWIP_MDIO_READ_OFFSET) & SWIP_MDIO_READ_DATA_MASK;
+}
+
+static int swip_mdio_write(struct mii_bus *bus, int phy_addr, int regnum,
+ u16 val)
+{
+ struct swip_mdio *priv = bus->priv;
+ void __iomem *data = priv->data;
+ u32 command;
+ int ret;
+
+ ret = swip_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ writel(FIELD_PREP(SWIP_MDIO_PARAMS_REGDEV_MASK, regnum) |
+ FIELD_PREP(SWIP_MDIO_PARAMS_PHYADDR_MASK, phy_addr),
+ data + SWIP_MDIO_PARAMS_OFFSET);
+ writel(val, data + SWIP_MDIO_WRITE_OFFSET);
+
+ command = FIELD_PREP(SWIP_MDIO_CTRL_TRANSTYPE_MASK,
+ SWIP_MDIO_CTRL_TRANSTYPE_WRITE) |
+ SWIP_MDIO_CTRL_OPSTATUS;
+ writew(command, priv->ctrl);
+
+ return swip_mdio_wait_for_idle(priv);
+}
+
+static int swip_mdio_write_c45(struct mii_bus *bus, int phy_addr, int devad,
+ int regnum, u16 val)
+{
+ struct swip_mdio *priv = bus->priv;
+ void __iomem *data = priv->data;
+ u32 command;
+ int ret;
+
+ ret = swip_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ writel(FIELD_PREP(SWIP_MDIO_PARAMS_REGDEV_MASK, devad) |
+ FIELD_PREP(SWIP_MDIO_PARAMS_PHYADDR_MASK, phy_addr),
+ data + SWIP_MDIO_PARAMS_OFFSET);
+ writel(regnum, data + SWIP_MDIO_WRITE_OFFSET);
+
+ command = SWIP_MDIO_CTRL_CLAUSE | SWIP_MDIO_CTRL_OPSTATUS;
+ writew(command, priv->ctrl);
+
+ ret = swip_mdio_wait_for_idle(priv);
+ if (ret)
+ return ret;
+
+ writel(val, data + SWIP_MDIO_WRITE_OFFSET);
+
+ command = FIELD_PREP(SWIP_MDIO_CTRL_TRANSTYPE_MASK,
+ SWIP_MDIO_CTRL_TRANSTYPE_WRITE) |
+ SWIP_MDIO_CTRL_CLAUSE | SWIP_MDIO_CTRL_OPSTATUS;
+ writew(command, priv->ctrl);
+
+ return swip_mdio_wait_for_idle(priv);
+}
+
+static int swip_mdio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct swip_mdio *priv;
+ struct mii_bus *bus;
+
+ bus = devm_mdiobus_alloc_size(dev, sizeof(*priv));
+ if (!bus)
+ return -ENOMEM;
+
+ priv = bus->priv;
+ priv->data = devm_platform_ioremap_resource_byname(pdev, "data");
+ if (IS_ERR(priv->data))
+ return PTR_ERR(priv->data);
+
+ priv->ctrl = devm_platform_ioremap_resource_byname(pdev, "control");
+ if (IS_ERR(priv->ctrl))
+ return PTR_ERR(priv->ctrl);
+
+ bus->name = "soce mdio";
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
+ bus->parent = dev;
+ bus->read = swip_mdio_read;
+ bus->write = swip_mdio_write;
+ bus->read_c45 = swip_mdio_read_c45;
+ bus->write_c45 = swip_mdio_write_c45;
+
+ return devm_of_mdiobus_register(dev, bus, dev->of_node);
+}
+
+static const struct of_device_id swip_mdio_of_match[] = {
+ { .compatible = "soce,swip-mdio" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, swip_mdio_of_match);
+
+static struct platform_driver swip_mdio_driver = {
+ .probe = swip_mdio_probe,
+ .driver = {
+ .name = "soce-mdio",
+ .of_match_table = swip_mdio_of_match,
+ },
+};
+module_platform_driver(swip_mdio_driver);
+
+MODULE_AUTHOR("Vasilij Strassheim <v.strassheim@xxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("SoC-e MDIO controller driver");
+MODULE_LICENSE("GPL");

--
2.39.5