realtek: mdio: backport upstream patches

This backports the upstream efforts for the mdio driver. No downstream
functionality is affected as OpenWrt still uses the mdio-realtek-otto
driver.

Link: https://github.com/openwrt/openwrt/pull/23678
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
This commit is contained in:
Markus Stockhausen committed 2026-06-06 15:03:16 +02:00
1 parent 1ba7b38cfc
commit 32bcc2986c
20 files changed
+2628

No files matched your search

@@ -0,0 +1,381 @@
From d15c0d1df49480b8f7d7c6182880e353574c871d Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Thu, 21 May 2026 19:59:10 +0200
Subject: [PATCH 01/20] net: mdio: realtek-rtl9300: enhance documentation &
naming
The Realtek ethernet MDIO driver currently only serves SOCs from the
Realtek RTL930x series. This is only one lineup of the Realtek Otto
switch series that also knows RTL838x, RTL839x, RTL931x devices.
All of these share similar hardware with comparable MMIO access logic
but have individual variations. Important to note
- Controller works on switch ports instead of buses and addresses.
- Devices incorporate additional MDIO hardware. E.g.
- an auxiliary MDIO controller for GPIO expanders [1]
- a MDIO style SerDes controller [2]
To avoid future confusion enhance the driver documentation and
function naming. Make clear what this driver is about and what
parts are generic and what parts are device specific. For this
rename the function and structure prefix as follows:
- for generic functions use otto_emdio_
- for device specific helpers use e.g. otto_emdio_9300_
This prefix naming tries to align with the watchdog timer [3].
It paves the way so that drivers for the other Realtek Otto MDIO
controllers can be added in future commits using the same naming
convention.
Remark 1: The read/write functions are kept device specific for now
because they will only fit the RTL930x SOCs. Renaming will take place
as soon as the I/O handling will be generalized.
Remark 2: The driver name "mdio-rtl9300" is kept for now.
[1] https://git.openwrt.org/openwrt/openwrt/tree/target/linux/realtek/patches-6.18/723-net-mdio-Add-Realtek-Otto-auxiliary-controller.patch
[2] https://git.openwrt.org/openwrt/openwrt/tree/target/linux/realtek/files-6.18/drivers/net/mdio/mdio-realtek-otto-serdes.c
[3] https://elixir.bootlin.com/linux/v7.0/source/drivers/watchdog/realtek_otto_wdt.c
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260521175918.1494797-2-markus.stockhausen@gmx.de
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 139 ++++++++++++++----------
1 file changed, 84 insertions(+), 55 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -1,11 +1,40 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * MDIO controller for RTL9300 switches with integrated SoC.
+ * Realtek switches of the Otto series (RTL838x, RTL839x, RTL930x and RTL931x SoCs) have multiple
+ * integrated MDIO controllers. This driver targets the ethernet MDIO controller. It serves only
+ * 1G/2.5G/10G ethernet PHYs attached to up to 4 individual buses.
*
- * The MDIO communication is abstracted by the switch. At the software level
- * communication uses the switch port to address the PHY. We work out the
- * mapping based on the MDIO bus described in device tree and phandles on the
- * ethernet-ports property.
+ * The controller is programmed through MMIO. The MDIO communication is abstracted by the hardware
+ * and uses the switch port number for its addressing. For this to work, mapping registers need to
+ * be setup in advance. With that the controller translates each port based I/O operation into the
+ * physical bus and address. This gives the following end-to-end communication
+ *
+ * +----------+ +----------+ +----------+ +----------+
+ * | phydev | ... | phydev | | phydev | ... | phydev |
+ * +----------+ +----------+ +----------+ +----------+
+ * | | | |
+ * mii_bus 0 +------------------+ +------------------+ mii_bus 1
+ * | |
+ * +-----------------------------------------------------+
+ * | MDIO driver |
+ * | translate bus/address -> port |
+ * +-----------------------------------------------------+
+ * | Software
+ * - - - - - - - - - - - - - - - - - - - - - - - - -
+ * | Hardware
+ * +-----------------------------------------------------+
+ * | MDIO controller |
+ * | translate port -> bus/address |
+ * +-----------------------------------------------------+
+ * | |
+ * bus 0 +------------------+ +------------------+ bus 1
+ * | | | |
+ * +----------+ +----------+ +----------+ +----------+
+ * | PHY 0/1 | ... | PHY 0/31 | | PHY 1/1 | ... | PHY 1/31 |
+ * +----------+ +----------+ +----------+ +----------+
+ *
+ * The driver works out the mapping based on the MDIO bus described in device tree and phandles on
+ * the ethernet-ports property.
*/
#include <linux/bitfield.h>
@@ -48,7 +77,7 @@
#define MAX_SMI_BUSSES 4
#define MAX_SMI_ADDR 0x1f
-struct rtl9300_mdio_priv {
+struct otto_emdio_priv {
struct regmap *regmap;
struct mutex lock; /* protect HW access */
DECLARE_BITMAP(valid_ports, MAX_PORTS);
@@ -58,15 +87,15 @@ struct rtl9300_mdio_priv {
struct mii_bus *bus[MAX_SMI_BUSSES];
};
-struct rtl9300_mdio_chan {
- struct rtl9300_mdio_priv *priv;
+struct otto_emdio_chan {
+ struct otto_emdio_priv *priv;
u8 mdio_bus;
};
-static int rtl9300_mdio_phy_to_port(struct mii_bus *bus, int phy_id)
+static int otto_emdio_phy_to_port(struct mii_bus *bus, int phy_id)
{
- struct rtl9300_mdio_chan *chan = bus->priv;
- struct rtl9300_mdio_priv *priv;
+ struct otto_emdio_chan *chan = bus->priv;
+ struct otto_emdio_priv *priv;
int i;
priv = chan->priv;
@@ -79,7 +108,7 @@ static int rtl9300_mdio_phy_to_port(stru
return -ENOENT;
}
-static int rtl9300_mdio_wait_ready(struct rtl9300_mdio_priv *priv)
+static int otto_emdio_wait_ready(struct otto_emdio_priv *priv)
{
struct regmap *regmap = priv->regmap;
u32 val;
@@ -90,10 +119,10 @@ static int rtl9300_mdio_wait_ready(struc
val, !(val & PHY_CTRL_CMD), 10, 1000);
}
-static int rtl9300_mdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)
+static int otto_emdio_9300_read_c22(struct mii_bus *bus, int phy_id, int regnum)
{
- struct rtl9300_mdio_chan *chan = bus->priv;
- struct rtl9300_mdio_priv *priv;
+ struct otto_emdio_chan *chan = bus->priv;
+ struct otto_emdio_priv *priv;
struct regmap *regmap;
int port;
u32 val;
@@ -102,12 +131,12 @@ static int rtl9300_mdio_read_c22(struct
priv = chan->priv;
regmap = priv->regmap;
- port = rtl9300_mdio_phy_to_port(bus, phy_id);
+ port = otto_emdio_phy_to_port(bus, phy_id);
if (port < 0)
return port;
mutex_lock(&priv->lock);
- err = rtl9300_mdio_wait_ready(priv);
+ err = otto_emdio_wait_ready(priv);
if (err)
goto out_err;
@@ -123,7 +152,7 @@ static int rtl9300_mdio_read_c22(struct
if (err)
goto out_err;
- err = rtl9300_mdio_wait_ready(priv);
+ err = otto_emdio_wait_ready(priv);
if (err)
goto out_err;
@@ -139,10 +168,10 @@ out_err:
return err;
}
-static int rtl9300_mdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16 value)
+static int otto_emdio_9300_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16 value)
{
- struct rtl9300_mdio_chan *chan = bus->priv;
- struct rtl9300_mdio_priv *priv;
+ struct otto_emdio_chan *chan = bus->priv;
+ struct otto_emdio_priv *priv;
struct regmap *regmap;
int port;
u32 val;
@@ -151,12 +180,12 @@ static int rtl9300_mdio_write_c22(struct
priv = chan->priv;
regmap = priv->regmap;
- port = rtl9300_mdio_phy_to_port(bus, phy_id);
+ port = otto_emdio_phy_to_port(bus, phy_id);
if (port < 0)
return port;
mutex_lock(&priv->lock);
- err = rtl9300_mdio_wait_ready(priv);
+ err = otto_emdio_wait_ready(priv);
if (err)
goto out_err;
@@ -194,10 +223,10 @@ out_err:
return err;
}
-static int rtl9300_mdio_read_c45(struct mii_bus *bus, int phy_id, int dev_addr, int regnum)
+static int otto_emdio_9300_read_c45(struct mii_bus *bus, int phy_id, int dev_addr, int regnum)
{
- struct rtl9300_mdio_chan *chan = bus->priv;
- struct rtl9300_mdio_priv *priv;
+ struct otto_emdio_chan *chan = bus->priv;
+ struct otto_emdio_priv *priv;
struct regmap *regmap;
int port;
u32 val;
@@ -206,12 +235,12 @@ static int rtl9300_mdio_read_c45(struct
priv = chan->priv;
regmap = priv->regmap;
- port = rtl9300_mdio_phy_to_port(bus, phy_id);
+ port = otto_emdio_phy_to_port(bus, phy_id);
if (port < 0)
return port;
mutex_lock(&priv->lock);
- err = rtl9300_mdio_wait_ready(priv);
+ err = otto_emdio_wait_ready(priv);
if (err)
goto out_err;
@@ -231,7 +260,7 @@ static int rtl9300_mdio_read_c45(struct
if (err)
goto out_err;
- err = rtl9300_mdio_wait_ready(priv);
+ err = otto_emdio_wait_ready(priv);
if (err)
goto out_err;
@@ -247,11 +276,11 @@ out_err:
return err;
}
-static int rtl9300_mdio_write_c45(struct mii_bus *bus, int phy_id, int dev_addr,
+static int otto_emdio_9300_write_c45(struct mii_bus *bus, int phy_id, int dev_addr,
int regnum, u16 value)
{
- struct rtl9300_mdio_chan *chan = bus->priv;
- struct rtl9300_mdio_priv *priv;
+ struct otto_emdio_chan *chan = bus->priv;
+ struct otto_emdio_priv *priv;
struct regmap *regmap;
int port;
u32 val;
@@ -260,12 +289,12 @@ static int rtl9300_mdio_write_c45(struct
priv = chan->priv;
regmap = priv->regmap;
- port = rtl9300_mdio_phy_to_port(bus, phy_id);
+ port = otto_emdio_phy_to_port(bus, phy_id);
if (port < 0)
return port;
mutex_lock(&priv->lock);
- err = rtl9300_mdio_wait_ready(priv);
+ err = otto_emdio_wait_ready(priv);
if (err)
goto out_err;
@@ -307,7 +336,7 @@ out_err:
return err;
}
-static int rtl9300_mdiobus_init(struct rtl9300_mdio_priv *priv)
+static int otto_emdio_9300_mdiobus_init(struct otto_emdio_priv *priv)
{
u32 glb_ctrl_mask = 0, glb_ctrl_val = 0;
struct regmap *regmap = priv->regmap;
@@ -350,10 +379,10 @@ static int rtl9300_mdiobus_init(struct r
return 0;
}
-static int rtl9300_mdiobus_probe_one(struct device *dev, struct rtl9300_mdio_priv *priv,
- struct fwnode_handle *node)
+static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv,
+ struct fwnode_handle *node)
{
- struct rtl9300_mdio_chan *chan;
+ struct otto_emdio_chan *chan;
struct mii_bus *bus;
u32 mdio_bus;
int err;
@@ -380,11 +409,11 @@ static int rtl9300_mdiobus_probe_one(str
bus->name = "Realtek Switch MDIO Bus";
if (priv->smi_bus_is_c45[mdio_bus]) {
- bus->read_c45 = rtl9300_mdio_read_c45;
- bus->write_c45 = rtl9300_mdio_write_c45;
+ bus->read_c45 = otto_emdio_9300_read_c45;
+ bus->write_c45 = otto_emdio_9300_write_c45;
} else {
- bus->read = rtl9300_mdio_read_c22;
- bus->write = rtl9300_mdio_write_c22;
+ bus->read = otto_emdio_9300_read_c22;
+ bus->write = otto_emdio_9300_write_c22;
}
bus->parent = dev;
chan = bus->priv;
@@ -404,9 +433,9 @@ static int rtl9300_mdiobus_probe_one(str
* ethernet-ports node and build a mapping of the switch port to MDIO bus/addr
* based on the phy-handle.
*/
-static int rtl9300_mdiobus_map_ports(struct device *dev)
+static int otto_emdio_map_ports(struct device *dev)
{
- struct rtl9300_mdio_priv *priv = dev_get_drvdata(dev);
+ struct otto_emdio_priv *priv = dev_get_drvdata(dev);
struct device *parent = dev->parent;
int err;
@@ -462,10 +491,10 @@ static int rtl9300_mdiobus_map_ports(str
return 0;
}
-static int rtl9300_mdiobus_probe(struct platform_device *pdev)
+static int otto_emdio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct rtl9300_mdio_priv *priv;
+ struct otto_emdio_priv *priv;
int err;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
@@ -482,38 +511,38 @@ static int rtl9300_mdiobus_probe(struct
platform_set_drvdata(pdev, priv);
- err = rtl9300_mdiobus_map_ports(dev);
+ err = otto_emdio_map_ports(dev);
if (err)
return err;
device_for_each_child_node_scoped(dev, child) {
- err = rtl9300_mdiobus_probe_one(dev, priv, child);
+ err = otto_emdio_probe_one(dev, priv, child);
if (err)
return err;
}
- err = rtl9300_mdiobus_init(priv);
+ err = otto_emdio_9300_mdiobus_init(priv);
if (err)
return dev_err_probe(dev, err, "failed to initialise MDIO bus controller\n");
return 0;
}
-static const struct of_device_id rtl9300_mdio_ids[] = {
+static const struct of_device_id otto_emdio_ids[] = {
{ .compatible = "realtek,rtl9301-mdio" },
{}
};
-MODULE_DEVICE_TABLE(of, rtl9300_mdio_ids);
+MODULE_DEVICE_TABLE(of, otto_emdio_ids);
-static struct platform_driver rtl9300_mdio_driver = {
- .probe = rtl9300_mdiobus_probe,
+static struct platform_driver otto_emdio_driver = {
+ .probe = otto_emdio_probe,
.driver = {
.name = "mdio-rtl9300",
- .of_match_table = rtl9300_mdio_ids,
+ .of_match_table = otto_emdio_ids,
},
};
-module_platform_driver(rtl9300_mdio_driver);
+module_platform_driver(otto_emdio_driver);
MODULE_DESCRIPTION("RTL9300 MDIO driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,84 @@
From 315a02b39e8c4d1e8aebc8c3cc1e14e193f116ae Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Thu, 21 May 2026 19:59:11 +0200
Subject: [PATCH 02/20] net: mdio: realtek-rtl9300: Add device specific info
structure
Device properties of the RTL930x SOCs are hardcoded into the MDIO driver.
This must be relaxed to support additional devices like the RTL838x or
RTL839x. These do not have 4 SMI buses but 1 or 2 instead.
To support multiple devices establish an info structure that contains
individual variations of each series. As a first use case add the number
of buses into this structure and use it where needed.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260521175918.1494797-3-markus.stockhausen@gmx.de
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -51,6 +51,7 @@
#include <linux/property.h>
#include <linux/regmap.h>
+#define RTL9300_NUM_BUSES 4
#define SMI_GLB_CTRL 0xca00
#define GLB_CTRL_INTF_SEL(intf) BIT(16 + (intf))
#define SMI_PORT0_15_POLLING_SEL 0xca08
@@ -77,7 +78,12 @@
#define MAX_SMI_BUSSES 4
#define MAX_SMI_ADDR 0x1f
+struct otto_emdio_info {
+ u8 num_buses;
+};
+
struct otto_emdio_priv {
+ const struct otto_emdio_info *info;
struct regmap *regmap;
struct mutex lock; /* protect HW access */
DECLARE_BITMAP(valid_ports, MAX_PORTS);
@@ -357,7 +363,7 @@ static int otto_emdio_9300_mdiobus_init(
/* Put the interfaces into C45 mode if required */
glb_ctrl_mask = GENMASK(19, 16);
- for (i = 0; i < MAX_SMI_BUSSES; i++)
+ for (i = 0; i < priv->info->num_buses; i++)
if (priv->smi_bus_is_c45[i])
glb_ctrl_val |= GLB_CTRL_INTF_SEL(i);
@@ -476,7 +482,7 @@ static int otto_emdio_map_ports(struct d
if (err)
return err;
- if (bus >= MAX_SMI_BUSSES)
+ if (bus >= priv->info->num_buses)
return dev_err_probe(dev, -EINVAL, "illegal smi bus number %d\n", bus);
err = of_property_read_u32(phy_dn, "reg", &addr);
@@ -505,6 +511,7 @@ static int otto_emdio_probe(struct platf
if (err)
return err;
+ priv->info = device_get_match_data(dev);
priv->regmap = syscon_node_to_regmap(dev->parent->of_node);
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
@@ -528,8 +535,12 @@ static int otto_emdio_probe(struct platf
return 0;
}
+static const struct otto_emdio_info otto_emdio_9300_info = {
+ .num_buses = RTL9300_NUM_BUSES,
+};
+
static const struct of_device_id otto_emdio_ids[] = {
- { .compatible = "realtek,rtl9301-mdio" },
+ { .compatible = "realtek,rtl9301-mdio", .data = &otto_emdio_9300_info },
{}
};
MODULE_DEVICE_TABLE(of, otto_emdio_ids);
@@ -0,0 +1,79 @@
From 215701873a7ed1214bba82c8fadcd2583d0246c3 Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Thu, 21 May 2026 19:59:12 +0200
Subject: [PATCH 03/20] net: mdio: realtek-rtl9300: Add ports to info structure
The ethernet MDIO controller in the Realtek Otto series has a very special
command register style. Instead of working with bus/address it works on
ethernet port numbers. For this the controller is initialized via mapping
registers that tell which port is mapped to which bus/address. Every
request to the driver is then converted as follows
1. Kernel calls driver with bus/address
2. Driver converts bus/address to port and issues command
3. Hardware maps port back to bus/address
The number of ports is different for each device. Make this configurable
by adding a property to the info structure. Switch the existing usage of
MAX_PORTS to this new property where needed.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260521175918.1494797-4-markus.stockhausen@gmx.de
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -52,6 +52,7 @@
#include <linux/regmap.h>
#define RTL9300_NUM_BUSES 4
+#define RTL9300_NUM_PORTS 28
#define SMI_GLB_CTRL 0xca00
#define GLB_CTRL_INTF_SEL(intf) BIT(16 + (intf))
#define SMI_PORT0_15_POLLING_SEL 0xca08
@@ -80,6 +81,7 @@
struct otto_emdio_info {
u8 num_buses;
+ u8 num_ports;
};
struct otto_emdio_priv {
@@ -106,7 +108,7 @@ static int otto_emdio_phy_to_port(struct
priv = chan->priv;
- for_each_set_bit(i, priv->valid_ports, MAX_PORTS)
+ for_each_set_bit(i, priv->valid_ports, priv->info->num_ports)
if (priv->smi_bus[i] == chan->mdio_bus &&
priv->smi_addr[i] == phy_id)
return i;
@@ -351,7 +353,7 @@ static int otto_emdio_9300_mdiobus_init(
int i, err;
/* Associate the port with the SMI interface and PHY */
- for_each_set_bit(i, priv->valid_ports, MAX_PORTS) {
+ for_each_set_bit(i, priv->valid_ports, priv->info->num_ports) {
int pos;
pos = (i % 6) * 5;
@@ -472,7 +474,7 @@ static int otto_emdio_map_ports(struct d
if (err)
return err;
- if (pn >= MAX_PORTS)
+ if (pn >= priv->info->num_ports)
return dev_err_probe(dev, -EINVAL, "illegal port number %d\n", pn);
if (test_bit(pn, priv->valid_ports))
@@ -537,6 +539,7 @@ static int otto_emdio_probe(struct platf
static const struct otto_emdio_info otto_emdio_9300_info = {
.num_buses = RTL9300_NUM_BUSES,
+ .num_ports = RTL9300_NUM_PORTS,
};
static const struct of_device_id otto_emdio_ids[] = {
@@ -0,0 +1,90 @@
From 38c9d5b644049e9e78fafa385d420b2ae8485b81 Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Thu, 21 May 2026 19:59:13 +0200
Subject: [PATCH 04/20] net: mdio: realtek-rtl9300: Add pages to info structure
The Realtek ethernet MDIO controller has a proprietary paging feature
that is closely aligned with Realtek based PHYs. These PHY know "pages"
for C22 access. Those can be switched via reads/writes to register 31.
Usually the paged access must be programmed in four steps.
1. read/save page register
2. change "page" register 31
3. read/write data register (on the given page)
4. restore page register
The controller can run all this in hardware with one single request
from the driver. It is given the page, the register and the data
and takes care of all the rest. This reduces CPU load. The number
of supported pages depend on the model. This is either 4096 for low
port count SOCs (up to 28 ports) or 8192 for high port count SOCs
(up to 56 ports).
There is however one special page that allows to pass through all C22
commands directly to the PHY - without any caching. This so called raw
page is dependent of the hardware. It is the highest supported page
number minus 1.
Provide the number of supported pages as a device specific property.
This new "num_pages" aligns with the existing properties and gives
an better insight into the hardware layout than just defining the
number of the raw page. The later directly derives from that and
can be accessed with the new RAW_PAGE() macro. Make use of it where
needed.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260521175918.1494797-5-markus.stockhausen@gmx.de
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -52,6 +52,7 @@
#include <linux/regmap.h>
#define RTL9300_NUM_BUSES 4
+#define RTL9300_NUM_PAGES 4096
#define RTL9300_NUM_PORTS 28
#define SMI_GLB_CTRL 0xca00
#define GLB_CTRL_INTF_SEL(intf) BIT(16 + (intf))
@@ -78,10 +79,12 @@
#define MAX_PORTS 28
#define MAX_SMI_BUSSES 4
#define MAX_SMI_ADDR 0x1f
+#define RAW_PAGE(priv) ((priv)->info->num_pages - 1)
struct otto_emdio_info {
u8 num_buses;
u8 num_ports;
+ u16 num_pages;
};
struct otto_emdio_priv {
@@ -154,7 +157,7 @@ static int otto_emdio_9300_read_c22(stru
val = FIELD_PREP(PHY_CTRL_REG_ADDR, regnum) |
FIELD_PREP(PHY_CTRL_PARK_PAGE, 0x1f) |
- FIELD_PREP(PHY_CTRL_MAIN_PAGE, 0xfff) |
+ FIELD_PREP(PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)) |
PHY_CTRL_READ | PHY_CTRL_TYPE_C22 | PHY_CTRL_CMD;
err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_1, val);
if (err)
@@ -207,7 +210,7 @@ static int otto_emdio_9300_write_c22(str
val = FIELD_PREP(PHY_CTRL_REG_ADDR, regnum) |
FIELD_PREP(PHY_CTRL_PARK_PAGE, 0x1f) |
- FIELD_PREP(PHY_CTRL_MAIN_PAGE, 0xfff) |
+ FIELD_PREP(PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)) |
PHY_CTRL_WRITE | PHY_CTRL_TYPE_C22 | PHY_CTRL_CMD;
err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_1, val);
if (err)
@@ -540,6 +543,7 @@ static int otto_emdio_probe(struct platf
static const struct otto_emdio_info otto_emdio_9300_info = {
.num_buses = RTL9300_NUM_BUSES,
.num_ports = RTL9300_NUM_PORTS,
+ .num_pages = RTL9300_NUM_PAGES,
};
static const struct of_device_id otto_emdio_ids[] = {
@@ -0,0 +1,109 @@
From 62baf5f1d80fe4f83099d52ac12e1337dd9fa9fc Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Thu, 21 May 2026 19:59:14 +0200
Subject: [PATCH 05/20] net: mdio: realtek-rtl9300: Add register structure
The MDIO controller of the Realtek Otto switches has either 4 or 7 command
registers. This depends on the number of supported ports. These registers
are "scattered" around the MMIO block and their addresses depend on the
specific model.
Nevertheless all command registers share a common pattern:
- A mask register with one bit per addressed port
(remark: the driver internally works on ports instead of bus/address)
- A I/O data register that transfers the to be read/written data
- A C45 registers that takes devnum and regnum
- A C22 register that also includes run and status bits
(remark: this also takes the Realtek proprietary C22 PHY page)
Provide an additional structure for these command registers so it can be
reused in two places.
1. For defining the register addresses in the regmap.
2. For defining the to be read/written register data
This will finally result in access patterns like
static int otto_emdio_run_cmd(u32 cmd,
struct rtl_mdio_cmd_regs *cmd_regs, ...)
{
regmap_write(regmap, priv->info->reg->cmd_regs.c45_data,
cmd_regs->c45_data);
...
}
static int otto_emdio_9300_write_c45(...)
{
struct otto_emdio_cmd_regs cmd_regs = {
.c45_data = ...
.io_data = ...,
.port_mask = ...,
};
return otto_emdio_run_cmd(RTL9300_CMD_WRITE_C45, &cmd_regs, ...);
}
As a first step start with the C45 register. This one takes the
devnum/regnum data that is stored in the high/low 16 bits.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260521175918.1494797-6-markus.stockhausen@gmx.de
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -71,7 +71,7 @@
#define SMI_ACCESS_PHY_CTRL_2 0xcb78
#define PHY_CTRL_INDATA GENMASK(31, 16)
#define PHY_CTRL_DATA GENMASK(15, 0)
-#define SMI_ACCESS_PHY_CTRL_3 0xcb7c
+#define RTL9300_SMI_ACCESS_PHY_CTRL_3 0xcb7c
#define PHY_CTRL_MMD_DEVAD GENMASK(20, 16)
#define PHY_CTRL_MMD_REG GENMASK(15, 0)
#define SMI_PORT0_5_ADDR_CTRL 0xcb80
@@ -81,7 +81,13 @@
#define MAX_SMI_ADDR 0x1f
#define RAW_PAGE(priv) ((priv)->info->num_pages - 1)
+
+struct otto_emdio_cmd_regs {
+ u32 c45_data;
+};
+
struct otto_emdio_info {
+ struct otto_emdio_cmd_regs cmd_regs;
u8 num_buses;
u8 num_ports;
u16 num_pages;
@@ -262,7 +268,7 @@ static int otto_emdio_9300_read_c45(stru
val = FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
FIELD_PREP(PHY_CTRL_MMD_REG, regnum);
- err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_3, val);
+ err = regmap_write(regmap, priv->info->cmd_regs.c45_data, val);
if (err)
goto out_err;
@@ -320,7 +326,7 @@ static int otto_emdio_9300_write_c45(str
val = FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
FIELD_PREP(PHY_CTRL_MMD_REG, regnum);
- err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_3, val);
+ err = regmap_write(regmap, priv->info->cmd_regs.c45_data, val);
if (err)
goto out_err;
@@ -541,6 +547,9 @@ static int otto_emdio_probe(struct platf
}
static const struct otto_emdio_info otto_emdio_9300_info = {
+ .cmd_regs = {
+ .c45_data = RTL9300_SMI_ACCESS_PHY_CTRL_3,
+ },
.num_buses = RTL9300_NUM_BUSES,
.num_ports = RTL9300_NUM_PORTS,
.num_pages = RTL9300_NUM_PAGES,
@@ -0,0 +1,178 @@
From 8bf7e2b133db0ec690268a0afb0ee9471f4fbb52 Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Thu, 21 May 2026 19:59:15 +0200
Subject: [PATCH 06/20] net: mdio: realtek-rtl9300: Add command/C22 register
Command issuing/status bits and C22 data share the same register. In the
future the number of places where this register is used will be:
- One generic command helper/runner for all devices that will access the
command bits of the register
- 8 device specific C22 read/write functions that will access the C22
data fields.
Thus name the register c22_data to align with the existing c45_data
register. This way all device specific helpers will have a common
view on the to-be-fed data. Add the register to the existing structure
and make use of it where needed.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260521175918.1494797-7-markus.stockhausen@gmx.de
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 38 +++++++++++++------------
1 file changed, 20 insertions(+), 18 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -58,7 +58,7 @@
#define GLB_CTRL_INTF_SEL(intf) BIT(16 + (intf))
#define SMI_PORT0_15_POLLING_SEL 0xca08
#define SMI_ACCESS_PHY_CTRL_0 0xcb70
-#define SMI_ACCESS_PHY_CTRL_1 0xcb74
+#define RTL9300_SMI_ACCESS_PHY_CTRL_1 0xcb74
#define PHY_CTRL_REG_ADDR GENMASK(24, 20)
#define PHY_CTRL_PARK_PAGE GENMASK(19, 15)
#define PHY_CTRL_MAIN_PAGE GENMASK(14, 3)
@@ -83,6 +83,7 @@
struct otto_emdio_cmd_regs {
+ u32 c22_data;
u32 c45_data;
};
@@ -128,12 +129,12 @@ static int otto_emdio_phy_to_port(struct
static int otto_emdio_wait_ready(struct otto_emdio_priv *priv)
{
struct regmap *regmap = priv->regmap;
- u32 val;
+ u32 cmd_reg, val;
lockdep_assert_held(&priv->lock);
+ cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
- return regmap_read_poll_timeout(regmap, SMI_ACCESS_PHY_CTRL_1,
- val, !(val & PHY_CTRL_CMD), 10, 1000);
+ return regmap_read_poll_timeout(regmap, cmd_reg, val, !(val & PHY_CTRL_CMD), 10, 1000);
}
static int otto_emdio_9300_read_c22(struct mii_bus *bus, int phy_id, int regnum)
@@ -141,12 +142,13 @@ static int otto_emdio_9300_read_c22(stru
struct otto_emdio_chan *chan = bus->priv;
struct otto_emdio_priv *priv;
struct regmap *regmap;
+ u32 cmd_reg, val;
int port;
- u32 val;
int err;
priv = chan->priv;
regmap = priv->regmap;
+ cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
port = otto_emdio_phy_to_port(bus, phy_id);
if (port < 0)
@@ -165,7 +167,7 @@ static int otto_emdio_9300_read_c22(stru
FIELD_PREP(PHY_CTRL_PARK_PAGE, 0x1f) |
FIELD_PREP(PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)) |
PHY_CTRL_READ | PHY_CTRL_TYPE_C22 | PHY_CTRL_CMD;
- err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_1, val);
+ err = regmap_write(regmap, cmd_reg, val);
if (err)
goto out_err;
@@ -190,12 +192,13 @@ static int otto_emdio_9300_write_c22(str
struct otto_emdio_chan *chan = bus->priv;
struct otto_emdio_priv *priv;
struct regmap *regmap;
+ u32 cmd_reg, val;
int port;
- u32 val;
int err;
priv = chan->priv;
regmap = priv->regmap;
+ cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
port = otto_emdio_phy_to_port(bus, phy_id);
if (port < 0)
@@ -218,12 +221,11 @@ static int otto_emdio_9300_write_c22(str
FIELD_PREP(PHY_CTRL_PARK_PAGE, 0x1f) |
FIELD_PREP(PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)) |
PHY_CTRL_WRITE | PHY_CTRL_TYPE_C22 | PHY_CTRL_CMD;
- err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_1, val);
+ err = regmap_write(regmap, cmd_reg, val);
if (err)
goto out_err;
- err = regmap_read_poll_timeout(regmap, SMI_ACCESS_PHY_CTRL_1,
- val, !(val & PHY_CTRL_CMD), 10, 100);
+ err = regmap_read_poll_timeout(regmap, cmd_reg, val, !(val & PHY_CTRL_CMD), 10, 100);
if (err)
goto out_err;
@@ -245,12 +247,13 @@ static int otto_emdio_9300_read_c45(stru
struct otto_emdio_chan *chan = bus->priv;
struct otto_emdio_priv *priv;
struct regmap *regmap;
+ u32 cmd_reg, val;
int port;
- u32 val;
int err;
priv = chan->priv;
regmap = priv->regmap;
+ cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
port = otto_emdio_phy_to_port(bus, phy_id);
if (port < 0)
@@ -272,8 +275,7 @@ static int otto_emdio_9300_read_c45(stru
if (err)
goto out_err;
- err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_1,
- PHY_CTRL_READ | PHY_CTRL_TYPE_C45 | PHY_CTRL_CMD);
+ err = regmap_write(regmap, cmd_reg, PHY_CTRL_READ | PHY_CTRL_TYPE_C45 | PHY_CTRL_CMD);
if (err)
goto out_err;
@@ -299,12 +301,13 @@ static int otto_emdio_9300_write_c45(str
struct otto_emdio_chan *chan = bus->priv;
struct otto_emdio_priv *priv;
struct regmap *regmap;
+ u32 cmd_reg, val;
int port;
- u32 val;
int err;
priv = chan->priv;
regmap = priv->regmap;
+ cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
port = otto_emdio_phy_to_port(bus, phy_id);
if (port < 0)
@@ -330,13 +333,11 @@ static int otto_emdio_9300_write_c45(str
if (err)
goto out_err;
- err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_1,
- PHY_CTRL_TYPE_C45 | PHY_CTRL_WRITE | PHY_CTRL_CMD);
+ err = regmap_write(regmap, cmd_reg, PHY_CTRL_TYPE_C45 | PHY_CTRL_WRITE | PHY_CTRL_CMD);
if (err)
goto out_err;
- err = regmap_read_poll_timeout(regmap, SMI_ACCESS_PHY_CTRL_1,
- val, !(val & PHY_CTRL_CMD), 10, 100);
+ err = regmap_read_poll_timeout(regmap, cmd_reg, val, !(val & PHY_CTRL_CMD), 10, 100);
if (err)
goto out_err;
@@ -548,6 +549,7 @@ static int otto_emdio_probe(struct platf
static const struct otto_emdio_info otto_emdio_9300_info = {
.cmd_regs = {
+ .c22_data = RTL9300_SMI_ACCESS_PHY_CTRL_1,
.c45_data = RTL9300_SMI_ACCESS_PHY_CTRL_3,
},
.num_buses = RTL9300_NUM_BUSES,
@@ -0,0 +1,161 @@
From ffc92cd009145eb5f85d913fbbc67fd24b601080 Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Thu, 21 May 2026 19:59:16 +0200
Subject: [PATCH 07/20] net: mdio: realtek-rtl9300: Add I/O register
The MDIO data that needs to be written or read to registers of the
controller is handled by an I/O register. Add that to the register
structure and make use of it where needed.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260521175918.1494797-8-markus.stockhausen@gmx.de
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 28 +++++++++++++++----------
1 file changed, 17 insertions(+), 11 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -68,7 +68,7 @@
#define PHY_CTRL_TYPE_C22 0
#define PHY_CTRL_CMD BIT(0)
#define PHY_CTRL_FAIL BIT(25)
-#define SMI_ACCESS_PHY_CTRL_2 0xcb78
+#define RTL9300_SMI_ACCESS_PHY_CTRL_2 0xcb78
#define PHY_CTRL_INDATA GENMASK(31, 16)
#define PHY_CTRL_DATA GENMASK(15, 0)
#define RTL9300_SMI_ACCESS_PHY_CTRL_3 0xcb7c
@@ -85,6 +85,7 @@
struct otto_emdio_cmd_regs {
u32 c22_data;
u32 c45_data;
+ u32 io_data;
};
struct otto_emdio_info {
@@ -141,13 +142,14 @@ static int otto_emdio_9300_read_c22(stru
{
struct otto_emdio_chan *chan = bus->priv;
struct otto_emdio_priv *priv;
+ u32 io_reg, cmd_reg, val;
struct regmap *regmap;
- u32 cmd_reg, val;
int port;
int err;
priv = chan->priv;
regmap = priv->regmap;
+ io_reg = priv->info->cmd_regs.io_data;
cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
port = otto_emdio_phy_to_port(bus, phy_id);
@@ -159,7 +161,7 @@ static int otto_emdio_9300_read_c22(stru
if (err)
goto out_err;
- err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_2, FIELD_PREP(PHY_CTRL_INDATA, port));
+ err = regmap_write(regmap, io_reg, FIELD_PREP(PHY_CTRL_INDATA, port));
if (err)
goto out_err;
@@ -175,7 +177,7 @@ static int otto_emdio_9300_read_c22(stru
if (err)
goto out_err;
- err = regmap_read(regmap, SMI_ACCESS_PHY_CTRL_2, &val);
+ err = regmap_read(regmap, io_reg, &val);
if (err)
goto out_err;
@@ -191,13 +193,14 @@ static int otto_emdio_9300_write_c22(str
{
struct otto_emdio_chan *chan = bus->priv;
struct otto_emdio_priv *priv;
+ u32 io_reg, cmd_reg, val;
struct regmap *regmap;
- u32 cmd_reg, val;
int port;
int err;
priv = chan->priv;
regmap = priv->regmap;
+ io_reg = priv->info->cmd_regs.io_data;
cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
port = otto_emdio_phy_to_port(bus, phy_id);
@@ -213,7 +216,7 @@ static int otto_emdio_9300_write_c22(str
if (err)
goto out_err;
- err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_2, FIELD_PREP(PHY_CTRL_INDATA, value));
+ err = regmap_write(regmap, io_reg, FIELD_PREP(PHY_CTRL_INDATA, value));
if (err)
goto out_err;
@@ -246,13 +249,14 @@ static int otto_emdio_9300_read_c45(stru
{
struct otto_emdio_chan *chan = bus->priv;
struct otto_emdio_priv *priv;
+ u32 io_reg, cmd_reg, val;
struct regmap *regmap;
- u32 cmd_reg, val;
int port;
int err;
priv = chan->priv;
regmap = priv->regmap;
+ io_reg = priv->info->cmd_regs.io_data;
cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
port = otto_emdio_phy_to_port(bus, phy_id);
@@ -265,7 +269,7 @@ static int otto_emdio_9300_read_c45(stru
goto out_err;
val = FIELD_PREP(PHY_CTRL_INDATA, port);
- err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_2, val);
+ err = regmap_write(regmap, io_reg, val);
if (err)
goto out_err;
@@ -283,7 +287,7 @@ static int otto_emdio_9300_read_c45(stru
if (err)
goto out_err;
- err = regmap_read(regmap, SMI_ACCESS_PHY_CTRL_2, &val);
+ err = regmap_read(regmap, io_reg, &val);
if (err)
goto out_err;
@@ -300,13 +304,14 @@ static int otto_emdio_9300_write_c45(str
{
struct otto_emdio_chan *chan = bus->priv;
struct otto_emdio_priv *priv;
+ u32 io_reg, cmd_reg, val;
struct regmap *regmap;
- u32 cmd_reg, val;
int port;
int err;
priv = chan->priv;
regmap = priv->regmap;
+ io_reg = priv->info->cmd_regs.io_data;
cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
port = otto_emdio_phy_to_port(bus, phy_id);
@@ -323,7 +328,7 @@ static int otto_emdio_9300_write_c45(str
goto out_err;
val = FIELD_PREP(PHY_CTRL_INDATA, value);
- err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_2, val);
+ err = regmap_write(regmap, io_reg, val);
if (err)
goto out_err;
@@ -551,6 +556,7 @@ static const struct otto_emdio_info otto
.cmd_regs = {
.c22_data = RTL9300_SMI_ACCESS_PHY_CTRL_1,
.c45_data = RTL9300_SMI_ACCESS_PHY_CTRL_3,
+ .io_data = RTL9300_SMI_ACCESS_PHY_CTRL_2,
},
.num_buses = RTL9300_NUM_BUSES,
.num_ports = RTL9300_NUM_PORTS,
@@ -0,0 +1,71 @@
From cad6a373f7f69254cb57e76e06f7734777575555 Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Thu, 21 May 2026 19:59:17 +0200
Subject: [PATCH 08/20] net: mdio: realtek-rtl9300: Add port mask register
MDIO controller commands work on ports. These are converted by
the driver and hardware forth and back to bus/address. For write
commands a port mask register needs to be filled. Each bit tells the
controller to which PHY the write will be issued. Setting multiple
bits allows to program multiple PHYs in one step. The driver will
not make use of this parallel write feature. But it must at least
fill the bit of the target port that it wants to write to.
Depending on the SOC type and the number of supported PHYs this is
either one or two 32 bit port mask registers. The driver currently only
supports the 28 port RTL930x SOCs. So provide only the mask register
for the lower 32 ports. Add it to the register structure and make use
of it where needed.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260521175918.1494797-9-markus.stockhausen@gmx.de
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -57,7 +57,7 @@
#define SMI_GLB_CTRL 0xca00
#define GLB_CTRL_INTF_SEL(intf) BIT(16 + (intf))
#define SMI_PORT0_15_POLLING_SEL 0xca08
-#define SMI_ACCESS_PHY_CTRL_0 0xcb70
+#define RTL9300_SMI_ACCESS_PHY_CTRL_0 0xcb70
#define RTL9300_SMI_ACCESS_PHY_CTRL_1 0xcb74
#define PHY_CTRL_REG_ADDR GENMASK(24, 20)
#define PHY_CTRL_PARK_PAGE GENMASK(19, 15)
@@ -86,6 +86,7 @@ struct otto_emdio_cmd_regs {
u32 c22_data;
u32 c45_data;
u32 io_data;
+ u32 port_mask_low;
};
struct otto_emdio_info {
@@ -212,7 +213,7 @@ static int otto_emdio_9300_write_c22(str
if (err)
goto out_err;
- err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_0, BIT(port));
+ err = regmap_write(regmap, priv->info->cmd_regs.port_mask_low, BIT(port));
if (err)
goto out_err;
@@ -323,7 +324,7 @@ static int otto_emdio_9300_write_c45(str
if (err)
goto out_err;
- err = regmap_write(regmap, SMI_ACCESS_PHY_CTRL_0, BIT(port));
+ err = regmap_write(regmap, priv->info->cmd_regs.port_mask_low, BIT(port));
if (err)
goto out_err;
@@ -557,6 +558,7 @@ static const struct otto_emdio_info otto
.c22_data = RTL9300_SMI_ACCESS_PHY_CTRL_1,
.c45_data = RTL9300_SMI_ACCESS_PHY_CTRL_3,
.io_data = RTL9300_SMI_ACCESS_PHY_CTRL_2,
+ .port_mask_low = RTL9300_SMI_ACCESS_PHY_CTRL_0,
},
.num_buses = RTL9300_NUM_BUSES,
.num_ports = RTL9300_NUM_PORTS,
@@ -0,0 +1,66 @@
From 826a1926f084037b91415a53a29ce93264f08ada Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Thu, 21 May 2026 19:59:18 +0200
Subject: [PATCH 09/20] net: mdio: realtek-rtl9300: Link I/O functions in info
structure
The MDIO controller registers of the different devices of the
Realtek Otto switch series are very similar. Nevertheless each
device will need to feed the whole command data distributed over
the controller registers slightly different.
E.g. the combined C22/command register has different field layouts.
On RTL930x bits 24-20 define the to-be-accessed C22 register number
while on RTL839x this is stored in bits 9-5.
Thus there need to be device specific read/write functions that
are called dynamically. Add them into the info structure and make
use of them where needed.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260521175918.1494797-10-markus.stockhausen@gmx.de
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -94,6 +94,10 @@ struct otto_emdio_info {
u8 num_buses;
u8 num_ports;
u16 num_pages;
+ int (*read_c22)(struct mii_bus *bus, int phy_id, int regnum);
+ int (*read_c45)(struct mii_bus *bus, int phy_id, int dev_addr, int regnum);
+ int (*write_c22)(struct mii_bus *bus, int phy_id, int regnum, u16 value);
+ int (*write_c45)(struct mii_bus *bus, int phy_id, int dev_addr, int regnum, u16 value);
};
struct otto_emdio_priv {
@@ -433,11 +437,11 @@ static int otto_emdio_probe_one(struct d
bus->name = "Realtek Switch MDIO Bus";
if (priv->smi_bus_is_c45[mdio_bus]) {
- bus->read_c45 = otto_emdio_9300_read_c45;
- bus->write_c45 = otto_emdio_9300_write_c45;
+ bus->read_c45 = priv->info->read_c45;
+ bus->write_c45 = priv->info->write_c45;
} else {
- bus->read = otto_emdio_9300_read_c22;
- bus->write = otto_emdio_9300_write_c22;
+ bus->read = priv->info->read_c22;
+ bus->write = priv->info->write_c22;
}
bus->parent = dev;
chan = bus->priv;
@@ -563,6 +567,10 @@ static const struct otto_emdio_info otto
.num_buses = RTL9300_NUM_BUSES,
.num_ports = RTL9300_NUM_PORTS,
.num_pages = RTL9300_NUM_PAGES,
+ .read_c22 = otto_emdio_9300_read_c22,
+ .read_c45 = otto_emdio_9300_read_c45,
+ .write_c22 = otto_emdio_9300_write_c22,
+ .write_c45 = otto_emdio_9300_write_c45,
};
static const struct of_device_id otto_emdio_ids[] = {
@@ -0,0 +1,241 @@
From 9ccb438210c4838962dfa1a10d4c198391dcc284 Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Wed, 27 May 2026 18:34:46 +0200
Subject: [PATCH 10/20] net: mdio: realtek-rtl9300: provide generic command
runner
The current bus read/write commands for C22/C45 are RTL930x specific.
Avoid to duplicate those 200 lines of code for the RTL838x, RTL839x and
RTL931x targets. Instead provide a generic command runner that is SoC
independent. The implementation works as follows:
The runner will take a prepared list of the four MDIO registers. It will
feed the data into the registers. This generic write to all registers
(or to say "a little bit too much") is no issue. The hardware looks at
the to be executed command and will only take the pieces of data that
are really required. No side effects have been observed on any of the
four SoCs during the time this mechanism exists in downstream OpenWrt.
The last fed register is the C22/command register. This will be enriched
with the proper command flags from the caller. The hardware issues the
command and the runner will wait for its finalization.
Besides from feeding all registers the runner emulates the behaviour of
the old code as best as possible
- check defensively for a running command in advance
- Before this commit the driver had different MMIO timeout values.
1000s for command preparation, 100us after writes and 1000us after
reads. The new version uses a consistent 1000us timeout for all
of these.
- return -ENXIO in case of hardware failure (fail bit)
As a first consumer of this runner convert the write_c45() function.
This is realized in a multi stage approach
- a generic otto_emdio_write_c45() will be called by the bus
- this will forward the request to the device specific writer. In this
case otto_emdio_9300_write_c45().
- There the command data is filled in and the additional helper
otto_emdio_write_cmd() will be called
- That adds the write flag and issues the generic command runner.
With all the above mentioned in place, there is not much left to do in
otto_emdio_9300_write_c45(). It just fills the register fields and
calls the write helper with the right command bits.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/20260527163449.1294961-2-markus.stockhausen@gmx.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 135 +++++++++++++++---------
1 file changed, 83 insertions(+), 52 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -90,6 +90,8 @@ struct otto_emdio_cmd_regs {
};
struct otto_emdio_info {
+ u32 cmd_fail;
+ u32 cmd_write;
struct otto_emdio_cmd_regs cmd_regs;
u8 num_buses;
u8 num_ports;
@@ -97,7 +99,7 @@ struct otto_emdio_info {
int (*read_c22)(struct mii_bus *bus, int phy_id, int regnum);
int (*read_c45)(struct mii_bus *bus, int phy_id, int dev_addr, int regnum);
int (*write_c22)(struct mii_bus *bus, int phy_id, int regnum, u16 value);
- int (*write_c45)(struct mii_bus *bus, int phy_id, int dev_addr, int regnum, u16 value);
+ int (*write_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u16 value);
};
struct otto_emdio_priv {
@@ -132,6 +134,64 @@ static int otto_emdio_phy_to_port(struct
return -ENOENT;
}
+static struct otto_emdio_priv *otto_emdio_bus_to_priv(struct mii_bus *bus)
+{
+ struct otto_emdio_chan *chan = bus->priv;
+
+ return chan->priv;
+}
+
+static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
+ struct otto_emdio_cmd_regs *cmd_data)
+{
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+ const struct otto_emdio_info *info = priv->info;
+ u32 cmdstate;
+ int ret;
+
+ /* Defensive pre check just in case something goes horrible wrong */
+ ret = regmap_read_poll_timeout(priv->regmap, info->cmd_regs.c22_data,
+ cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 1000);
+ if (ret)
+ return ret;
+
+ /* Fill all registers. Hardware will read only the needed bits depending on command */
+ ret = regmap_write(priv->regmap, info->cmd_regs.port_mask_low, cmd_data->port_mask_low);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->regmap, info->cmd_regs.io_data, cmd_data->io_data);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->regmap, info->cmd_regs.c45_data, cmd_data->c45_data);
+ if (ret)
+ return ret;
+
+ /* C22 data and command bits share the same register. */
+ ret = regmap_write(priv->regmap, info->cmd_regs.c22_data,
+ cmd_data->c22_data | cmd | PHY_CTRL_CMD);
+ if (ret)
+ return ret;
+
+ ret = regmap_read_poll_timeout(priv->regmap, info->cmd_regs.c22_data,
+ cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 1000);
+ if (ret)
+ return ret;
+
+ return cmdstate & info->cmd_fail ? -ENXIO : 0;
+}
+
+static int otto_emdio_write_cmd(struct mii_bus *bus, u32 cmd,
+ struct otto_emdio_cmd_regs *cmd_data)
+{
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+
+ lockdep_assert_held(&priv->lock);
+
+ return otto_emdio_run_cmd(bus, cmd | priv->info->cmd_write, cmd_data);
+}
+
static int otto_emdio_wait_ready(struct otto_emdio_priv *priv)
{
struct regmap *regmap = priv->regmap;
@@ -304,64 +364,33 @@ out_err:
return err;
}
-static int otto_emdio_9300_write_c45(struct mii_bus *bus, int phy_id, int dev_addr,
- int regnum, u16 value)
+static int otto_emdio_9300_write_c45(struct mii_bus *bus, int port,
+ int dev_addr, int regnum, u16 value)
{
- struct otto_emdio_chan *chan = bus->priv;
- struct otto_emdio_priv *priv;
- u32 io_reg, cmd_reg, val;
- struct regmap *regmap;
- int port;
- int err;
+ struct otto_emdio_cmd_regs cmd_data = {
+ .c45_data = FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
+ FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
+ .io_data = FIELD_PREP(PHY_CTRL_INDATA, value),
+ .port_mask_low = BIT(port),
+ };
- priv = chan->priv;
- regmap = priv->regmap;
- io_reg = priv->info->cmd_regs.io_data;
- cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
+ return otto_emdio_write_cmd(bus, PHY_CTRL_TYPE_C45, &cmd_data);
+}
+
+static int otto_emdio_write_c45(struct mii_bus *bus, int phy_id,
+ int dev_addr, int regnum, u16 value)
+{
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+ int ret, port;
port = otto_emdio_phy_to_port(bus, phy_id);
if (port < 0)
return port;
- mutex_lock(&priv->lock);
- err = otto_emdio_wait_ready(priv);
- if (err)
- goto out_err;
-
- err = regmap_write(regmap, priv->info->cmd_regs.port_mask_low, BIT(port));
- if (err)
- goto out_err;
-
- val = FIELD_PREP(PHY_CTRL_INDATA, value);
- err = regmap_write(regmap, io_reg, val);
- if (err)
- goto out_err;
-
- val = FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
- FIELD_PREP(PHY_CTRL_MMD_REG, regnum);
- err = regmap_write(regmap, priv->info->cmd_regs.c45_data, val);
- if (err)
- goto out_err;
-
- err = regmap_write(regmap, cmd_reg, PHY_CTRL_TYPE_C45 | PHY_CTRL_WRITE | PHY_CTRL_CMD);
- if (err)
- goto out_err;
-
- err = regmap_read_poll_timeout(regmap, cmd_reg, val, !(val & PHY_CTRL_CMD), 10, 100);
- if (err)
- goto out_err;
-
- if (val & PHY_CTRL_FAIL) {
- err = -ENXIO;
- goto out_err;
- }
-
- mutex_unlock(&priv->lock);
- return 0;
+ scoped_guard(mutex, &priv->lock)
+ ret = priv->info->write_c45(bus, port, dev_addr, regnum, value);
-out_err:
- mutex_unlock(&priv->lock);
- return err;
+ return ret;
}
static int otto_emdio_9300_mdiobus_init(struct otto_emdio_priv *priv)
@@ -438,7 +467,7 @@ static int otto_emdio_probe_one(struct d
bus->name = "Realtek Switch MDIO Bus";
if (priv->smi_bus_is_c45[mdio_bus]) {
bus->read_c45 = priv->info->read_c45;
- bus->write_c45 = priv->info->write_c45;
+ bus->write_c45 = otto_emdio_write_c45;
} else {
bus->read = priv->info->read_c22;
bus->write = priv->info->write_c22;
@@ -558,6 +587,8 @@ static int otto_emdio_probe(struct platf
}
static const struct otto_emdio_info otto_emdio_9300_info = {
+ .cmd_fail = PHY_CTRL_FAIL,
+ .cmd_write = PHY_CTRL_WRITE,
.cmd_regs = {
.c22_data = RTL9300_SMI_ACCESS_PHY_CTRL_1,
.c45_data = RTL9300_SMI_ACCESS_PHY_CTRL_3,
@@ -0,0 +1,133 @@
From 9cd59932f2d2a09ef239228671020dfd1ae7f4af Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Wed, 27 May 2026 18:34:47 +0200
Subject: [PATCH 11/20] net: mdio: realtek-rtl9300: use command runner for
write_c22()
Now that the driver has a generic command runner make use of it in the
write_c22() path. For this.
- add generic otto_emdio_write_c22() helper that will be called by bus
- convert otto_emdio_9300_write_c22() to new command runner logic
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/20260527163449.1294961-3-markus.stockhausen@gmx.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 81 +++++++++----------------
1 file changed, 27 insertions(+), 54 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -98,7 +98,7 @@ struct otto_emdio_info {
u16 num_pages;
int (*read_c22)(struct mii_bus *bus, int phy_id, int regnum);
int (*read_c45)(struct mii_bus *bus, int phy_id, int dev_addr, int regnum);
- int (*write_c22)(struct mii_bus *bus, int phy_id, int regnum, u16 value);
+ int (*write_c22)(struct mii_bus *bus, int port, int regnum, u16 value);
int (*write_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u16 value);
};
@@ -254,60 +254,18 @@ out_err:
return err;
}
-static int otto_emdio_9300_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16 value)
+static int otto_emdio_9300_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)
{
- struct otto_emdio_chan *chan = bus->priv;
- struct otto_emdio_priv *priv;
- u32 io_reg, cmd_reg, val;
- struct regmap *regmap;
- int port;
- int err;
-
- priv = chan->priv;
- regmap = priv->regmap;
- io_reg = priv->info->cmd_regs.io_data;
- cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
-
- port = otto_emdio_phy_to_port(bus, phy_id);
- if (port < 0)
- return port;
-
- mutex_lock(&priv->lock);
- err = otto_emdio_wait_ready(priv);
- if (err)
- goto out_err;
-
- err = regmap_write(regmap, priv->info->cmd_regs.port_mask_low, BIT(port));
- if (err)
- goto out_err;
-
- err = regmap_write(regmap, io_reg, FIELD_PREP(PHY_CTRL_INDATA, value));
- if (err)
- goto out_err;
-
- val = FIELD_PREP(PHY_CTRL_REG_ADDR, regnum) |
- FIELD_PREP(PHY_CTRL_PARK_PAGE, 0x1f) |
- FIELD_PREP(PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)) |
- PHY_CTRL_WRITE | PHY_CTRL_TYPE_C22 | PHY_CTRL_CMD;
- err = regmap_write(regmap, cmd_reg, val);
- if (err)
- goto out_err;
-
- err = regmap_read_poll_timeout(regmap, cmd_reg, val, !(val & PHY_CTRL_CMD), 10, 100);
- if (err)
- goto out_err;
-
- if (val & PHY_CTRL_FAIL) {
- err = -ENXIO;
- goto out_err;
- }
-
- mutex_unlock(&priv->lock);
- return 0;
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+ struct otto_emdio_cmd_regs cmd_data = {
+ .c22_data = FIELD_PREP(PHY_CTRL_REG_ADDR, regnum) |
+ FIELD_PREP(PHY_CTRL_PARK_PAGE, 0x1f) |
+ FIELD_PREP(PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+ .io_data = FIELD_PREP(PHY_CTRL_INDATA, value),
+ .port_mask_low = BIT(port),
+ };
-out_err:
- mutex_unlock(&priv->lock);
- return err;
+ return otto_emdio_write_cmd(bus, PHY_CTRL_TYPE_C22, &cmd_data);
}
static int otto_emdio_9300_read_c45(struct mii_bus *bus, int phy_id, int dev_addr, int regnum)
@@ -377,6 +335,21 @@ static int otto_emdio_9300_write_c45(str
return otto_emdio_write_cmd(bus, PHY_CTRL_TYPE_C45, &cmd_data);
}
+static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16 value)
+{
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+ int ret, port;
+
+ port = otto_emdio_phy_to_port(bus, phy_id);
+ if (port < 0)
+ return port;
+
+ scoped_guard(mutex, &priv->lock)
+ ret = priv->info->write_c22(bus, port, regnum, value);
+
+ return ret;
+}
+
static int otto_emdio_write_c45(struct mii_bus *bus, int phy_id,
int dev_addr, int regnum, u16 value)
{
@@ -470,7 +443,7 @@ static int otto_emdio_probe_one(struct d
bus->write_c45 = otto_emdio_write_c45;
} else {
bus->read = priv->info->read_c22;
- bus->write = priv->info->write_c22;
+ bus->write = otto_emdio_write_c22;
}
bus->parent = dev;
chan = bus->priv;
@@ -0,0 +1,182 @@
From fcce51bfd4edc5cd4059f835315d227657bcf62e Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Wed, 27 May 2026 18:34:48 +0200
Subject: [PATCH 12/20] net: mdio: realtek-rtl9300: use command runner for
read_c45()
Convert the read_c45() path to the new command runner. This needs the
additional helper otto_emdio_read_cmd() that can issue the command runner
and process a read operation. It is basically nothing more than
- run the command
- read the command result thorugh the I/O register
With this in place convert the read_c45() like the alread existing write
C22/C45 implementation.
- bus calls otto_emdio_read_c45()
- this handed over to SoC specific otto_emdio_9300_read_c45()
- the registers are filled
- the otto_emdio_read_cmd() is issued
- that calls the command runner
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/20260527163449.1294961-4-markus.stockhausen@gmx.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 100 ++++++++++++------------
1 file changed, 48 insertions(+), 52 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -91,13 +91,14 @@ struct otto_emdio_cmd_regs {
struct otto_emdio_info {
u32 cmd_fail;
+ u32 cmd_read;
u32 cmd_write;
struct otto_emdio_cmd_regs cmd_regs;
u8 num_buses;
u8 num_ports;
u16 num_pages;
int (*read_c22)(struct mii_bus *bus, int phy_id, int regnum);
- int (*read_c45)(struct mii_bus *bus, int phy_id, int dev_addr, int regnum);
+ int (*read_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u32 *value);
int (*write_c22)(struct mii_bus *bus, int port, int regnum, u16 value);
int (*write_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u16 value);
};
@@ -182,6 +183,26 @@ static int otto_emdio_run_cmd(struct mii
return cmdstate & info->cmd_fail ? -ENXIO : 0;
}
+static int otto_emdio_read_cmd(struct mii_bus *bus, u32 cmd,
+ struct otto_emdio_cmd_regs *cmd_data, u32 *value)
+{
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+ int ret;
+
+ lockdep_assert_held(&priv->lock);
+ ret = otto_emdio_run_cmd(bus, cmd | priv->info->cmd_read, cmd_data);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(priv->regmap, priv->info->cmd_regs.io_data, value);
+ if (ret)
+ return ret;
+
+ *value = FIELD_GET(PHY_CTRL_DATA, *value);
+
+ return 0;
+}
+
static int otto_emdio_write_cmd(struct mii_bus *bus, u32 cmd,
struct otto_emdio_cmd_regs *cmd_data)
{
@@ -268,58 +289,16 @@ static int otto_emdio_9300_write_c22(str
return otto_emdio_write_cmd(bus, PHY_CTRL_TYPE_C22, &cmd_data);
}
-static int otto_emdio_9300_read_c45(struct mii_bus *bus, int phy_id, int dev_addr, int regnum)
+static int otto_emdio_9300_read_c45(struct mii_bus *bus, int port,
+ int dev_addr, int regnum, u32 *value)
{
- struct otto_emdio_chan *chan = bus->priv;
- struct otto_emdio_priv *priv;
- u32 io_reg, cmd_reg, val;
- struct regmap *regmap;
- int port;
- int err;
-
- priv = chan->priv;
- regmap = priv->regmap;
- io_reg = priv->info->cmd_regs.io_data;
- cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
-
- port = otto_emdio_phy_to_port(bus, phy_id);
- if (port < 0)
- return port;
-
- mutex_lock(&priv->lock);
- err = otto_emdio_wait_ready(priv);
- if (err)
- goto out_err;
-
- val = FIELD_PREP(PHY_CTRL_INDATA, port);
- err = regmap_write(regmap, io_reg, val);
- if (err)
- goto out_err;
-
- val = FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
- FIELD_PREP(PHY_CTRL_MMD_REG, regnum);
- err = regmap_write(regmap, priv->info->cmd_regs.c45_data, val);
- if (err)
- goto out_err;
-
- err = regmap_write(regmap, cmd_reg, PHY_CTRL_READ | PHY_CTRL_TYPE_C45 | PHY_CTRL_CMD);
- if (err)
- goto out_err;
-
- err = otto_emdio_wait_ready(priv);
- if (err)
- goto out_err;
-
- err = regmap_read(regmap, io_reg, &val);
- if (err)
- goto out_err;
-
- mutex_unlock(&priv->lock);
- return FIELD_GET(PHY_CTRL_DATA, val);
+ struct otto_emdio_cmd_regs cmd_data = {
+ .c45_data = FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
+ FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
+ .io_data = FIELD_PREP(PHY_CTRL_INDATA, port),
+ };
-out_err:
- mutex_unlock(&priv->lock);
- return err;
+ return otto_emdio_read_cmd(bus, PHY_CTRL_TYPE_C45, &cmd_data, value);
}
static int otto_emdio_9300_write_c45(struct mii_bus *bus, int port,
@@ -350,6 +329,22 @@ static int otto_emdio_write_c22(struct m
return ret;
}
+static int otto_emdio_read_c45(struct mii_bus *bus, int phy_id, int dev_addr, int regnum)
+{
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+ int ret, port;
+ u32 value;
+
+ port = otto_emdio_phy_to_port(bus, phy_id);
+ if (port < 0)
+ return port;
+
+ scoped_guard(mutex, &priv->lock)
+ ret = priv->info->read_c45(bus, port, dev_addr, regnum, &value);
+
+ return ret ? ret : value;
+}
+
static int otto_emdio_write_c45(struct mii_bus *bus, int phy_id,
int dev_addr, int regnum, u16 value)
{
@@ -439,7 +434,7 @@ static int otto_emdio_probe_one(struct d
bus->name = "Realtek Switch MDIO Bus";
if (priv->smi_bus_is_c45[mdio_bus]) {
- bus->read_c45 = priv->info->read_c45;
+ bus->read_c45 = otto_emdio_read_c45;
bus->write_c45 = otto_emdio_write_c45;
} else {
bus->read = priv->info->read_c22;
@@ -561,6 +556,7 @@ static int otto_emdio_probe(struct platf
static const struct otto_emdio_info otto_emdio_9300_info = {
.cmd_fail = PHY_CTRL_FAIL,
+ .cmd_read = PHY_CTRL_READ,
.cmd_write = PHY_CTRL_WRITE,
.cmd_regs = {
.c22_data = RTL9300_SMI_ACCESS_PHY_CTRL_1,
@@ -0,0 +1,142 @@
From 2e3c28c715e913decf228dcd656bbb0dfc18133b Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Wed, 27 May 2026 18:34:49 +0200
Subject: [PATCH 13/20] net: mdio: realtek-rtl9300: use command runner for
read_c22()
Convert the final missing read_c22() path to the new read enabled command
runner. Do it the same way as other implementations.
- bus calls otto_emdio_read_c22()
- this hands over to SoC specific otto_emdio_9300_read_c22()
- finally the registers are filled and the runner issued
With this cleanup remove the obsolete helper otto_emdio_wait_ready()
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/20260527163449.1294961-5-markus.stockhausen@gmx.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 87 ++++++++-----------------
1 file changed, 27 insertions(+), 60 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -97,7 +97,7 @@ struct otto_emdio_info {
u8 num_buses;
u8 num_ports;
u16 num_pages;
- int (*read_c22)(struct mii_bus *bus, int phy_id, int regnum);
+ int (*read_c22)(struct mii_bus *bus, int port, int regnum, u32 *value);
int (*read_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u32 *value);
int (*write_c22)(struct mii_bus *bus, int port, int regnum, u16 value);
int (*write_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u16 value);
@@ -213,66 +213,17 @@ static int otto_emdio_write_cmd(struct m
return otto_emdio_run_cmd(bus, cmd | priv->info->cmd_write, cmd_data);
}
-static int otto_emdio_wait_ready(struct otto_emdio_priv *priv)
+static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
{
- struct regmap *regmap = priv->regmap;
- u32 cmd_reg, val;
-
- lockdep_assert_held(&priv->lock);
- cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
-
- return regmap_read_poll_timeout(regmap, cmd_reg, val, !(val & PHY_CTRL_CMD), 10, 1000);
-}
-
-static int otto_emdio_9300_read_c22(struct mii_bus *bus, int phy_id, int regnum)
-{
- struct otto_emdio_chan *chan = bus->priv;
- struct otto_emdio_priv *priv;
- u32 io_reg, cmd_reg, val;
- struct regmap *regmap;
- int port;
- int err;
-
- priv = chan->priv;
- regmap = priv->regmap;
- io_reg = priv->info->cmd_regs.io_data;
- cmd_reg = priv->info->cmd_regs.c22_data; /* shared command/C22 register */
-
- port = otto_emdio_phy_to_port(bus, phy_id);
- if (port < 0)
- return port;
-
- mutex_lock(&priv->lock);
- err = otto_emdio_wait_ready(priv);
- if (err)
- goto out_err;
-
- err = regmap_write(regmap, io_reg, FIELD_PREP(PHY_CTRL_INDATA, port));
- if (err)
- goto out_err;
-
- val = FIELD_PREP(PHY_CTRL_REG_ADDR, regnum) |
- FIELD_PREP(PHY_CTRL_PARK_PAGE, 0x1f) |
- FIELD_PREP(PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)) |
- PHY_CTRL_READ | PHY_CTRL_TYPE_C22 | PHY_CTRL_CMD;
- err = regmap_write(regmap, cmd_reg, val);
- if (err)
- goto out_err;
-
- err = otto_emdio_wait_ready(priv);
- if (err)
- goto out_err;
-
- err = regmap_read(regmap, io_reg, &val);
- if (err)
- goto out_err;
-
- mutex_unlock(&priv->lock);
- return FIELD_GET(PHY_CTRL_DATA, val);
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+ struct otto_emdio_cmd_regs cmd_data = {
+ .c22_data = FIELD_PREP(PHY_CTRL_REG_ADDR, regnum) |
+ FIELD_PREP(PHY_CTRL_PARK_PAGE, 0x1f) |
+ FIELD_PREP(PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+ .io_data = FIELD_PREP(PHY_CTRL_INDATA, port),
+ };
-out_err:
- mutex_unlock(&priv->lock);
- return err;
+ return otto_emdio_read_cmd(bus, PHY_CTRL_TYPE_C22, &cmd_data, value);
}
static int otto_emdio_9300_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)
@@ -314,6 +265,22 @@ static int otto_emdio_9300_write_c45(str
return otto_emdio_write_cmd(bus, PHY_CTRL_TYPE_C45, &cmd_data);
}
+static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)
+{
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+ int ret, port;
+ u32 value;
+
+ port = otto_emdio_phy_to_port(bus, phy_id);
+ if (port < 0)
+ return port;
+
+ scoped_guard(mutex, &priv->lock)
+ ret = priv->info->read_c22(bus, port, regnum, &value);
+
+ return ret ? ret : value;
+}
+
static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16 value)
{
struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
@@ -437,7 +404,7 @@ static int otto_emdio_probe_one(struct d
bus->read_c45 = otto_emdio_read_c45;
bus->write_c45 = otto_emdio_write_c45;
} else {
- bus->read = priv->info->read_c22;
+ bus->read = otto_emdio_read_c22;
bus->write = otto_emdio_write_c22;
}
bus->parent = dev;
@@ -0,0 +1,136 @@
From f369dd8ccf2dbce9166544e266e2b6ca974f98d9 Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Wed, 3 Jun 2026 19:59:18 +0200
Subject: [PATCH 14/20] net: mdio: realtek-rtl9300: Refactor
otto_emdio_map_ports()
This function has multiple issues:
- It uses __free low level cleanups
- It mixes "fwnode" and "of" functions
Convert this to a uniform "of" usage and manual reference counting
cleanup. With that also fix two subtle lookup bugs in the original
code.
mdio_dn = phy_dn->parent;
if (mdio_dn->parent != dev->of_node)
continue;
This skips an API access and therefore misses reference counting.
Additionally in the case of a very buggy device tree, phy_dn might
be a root node. Looking up its grandparent leads to a NULL pointer
access.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260603175924.123019-2-markus.stockhausen@gmx.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 69 +++++++++++++++----------
1 file changed, 42 insertions(+), 27 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -427,60 +427,75 @@ static int otto_emdio_probe_one(struct d
*/
static int otto_emdio_map_ports(struct device *dev)
{
+ struct device_node *ports_dn, *phy_dn, *bus_dn, *ctrl_dn;
struct otto_emdio_priv *priv = dev_get_drvdata(dev);
struct device *parent = dev->parent;
- int err;
+ u32 addr, bus, pn;
+ int err = 0;
- struct fwnode_handle *ports __free(fwnode_handle) =
- device_get_named_child_node(parent, "ethernet-ports");
- if (!ports)
+ ports_dn = of_get_child_by_name(parent->of_node, "ethernet-ports");
+ if (!ports_dn)
return dev_err_probe(dev, -EINVAL, "%pfwP missing ethernet-ports\n",
dev_fwnode(parent));
- fwnode_for_each_child_node_scoped(ports, port) {
- struct device_node *mdio_dn;
- u32 addr;
- u32 bus;
- u32 pn;
-
- struct device_node *phy_dn __free(device_node) =
- of_parse_phandle(to_of_node(port), "phy-handle", 0);
+ for_each_available_child_of_node_scoped(ports_dn, port_dn) {
+ ctrl_dn = NULL;
+ bus_dn = NULL;
+ phy_dn = of_parse_phandle(port_dn, "phy-handle", 0);
/* skip ports without phys */
if (!phy_dn)
continue;
- mdio_dn = phy_dn->parent;
+ bus_dn = of_get_parent(phy_dn);
+ if (!bus_dn)
+ goto put_nodes;
+
+ ctrl_dn = of_get_parent(bus_dn);
/* only map ports that are connected to this mdio-controller */
- if (mdio_dn->parent != dev->of_node)
- continue;
+ if (ctrl_dn != dev->of_node)
+ goto put_nodes;
- err = fwnode_property_read_u32(port, "reg", &pn);
+ err = of_property_read_u32(port_dn, "reg", &pn);
if (err)
- return err;
+ goto put_nodes;
- if (pn >= priv->info->num_ports)
- return dev_err_probe(dev, -EINVAL, "illegal port number %d\n", pn);
+ if (pn >= priv->info->num_ports) {
+ err = dev_err_probe(dev, -EINVAL, "illegal port number %d\n", pn);
+ goto put_nodes;
+ }
+
+ if (test_bit(pn, priv->valid_ports)) {
+ err = dev_err_probe(dev, -EINVAL, "duplicated port number %d\n", pn);
+ goto put_nodes;
+ }
- if (test_bit(pn, priv->valid_ports))
- return dev_err_probe(dev, -EINVAL, "duplicated port number %d\n", pn);
-
- err = of_property_read_u32(mdio_dn, "reg", &bus);
+ err = of_property_read_u32(bus_dn, "reg", &bus);
if (err)
- return err;
+ goto put_nodes;
- if (bus >= priv->info->num_buses)
- return dev_err_probe(dev, -EINVAL, "illegal smi bus number %d\n", bus);
+ if (bus >= priv->info->num_buses) {
+ err = dev_err_probe(dev, -EINVAL, "illegal smi bus number %d\n", bus);
+ goto put_nodes;
+ }
err = of_property_read_u32(phy_dn, "reg", &addr);
if (err)
- return err;
+ goto put_nodes;
__set_bit(pn, priv->valid_ports);
priv->smi_bus[pn] = bus;
priv->smi_addr[pn] = addr;
+put_nodes:
+ of_node_put(bus_dn);
+ of_node_put(phy_dn);
+ of_node_put(ctrl_dn);
+ if (err)
+ break;
}
- return 0;
+ of_node_put(ports_dn);
+
+ return err;
}
static int otto_emdio_probe(struct platform_device *pdev)
@@ -0,0 +1,56 @@
From 829ee0f8bc3920092b06d32d9e05328395ed5b77 Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Wed, 3 Jun 2026 19:59:19 +0200
Subject: [PATCH 15/20] net: mdio: realtek-rtl9300: harden
otto_emdio_map_ports()
Due to its design the MDIO driver needs to set up a port to bus/address
mapping during probing. The "ethernet-ports" subnodes are scanned and
from the "phy-handle" property the MDIO nodes are looked up. In case of
a malformed device tree the driver might produce out-of-bounds accesses.
The PHY address is not checked against the maximum supported address.
Add a sanity check and drop the unneeded MAX_SMI_ADDR define.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260603175924.123019-3-markus.stockhausen@gmx.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -78,7 +78,6 @@
#define MAX_PORTS 28
#define MAX_SMI_BUSSES 4
-#define MAX_SMI_ADDR 0x1f
#define RAW_PAGE(priv) ((priv)->info->num_pages - 1)
@@ -430,8 +429,8 @@ static int otto_emdio_map_ports(struct d
struct device_node *ports_dn, *phy_dn, *bus_dn, *ctrl_dn;
struct otto_emdio_priv *priv = dev_get_drvdata(dev);
struct device *parent = dev->parent;
- u32 addr, bus, pn;
- int err = 0;
+ int addr, err = 0;
+ u32 bus, pn;
ports_dn = of_get_child_by_name(parent->of_node, "ethernet-ports");
if (!ports_dn)
@@ -478,9 +477,11 @@ static int otto_emdio_map_ports(struct d
goto put_nodes;
}
- err = of_property_read_u32(phy_dn, "reg", &addr);
- if (err)
+ addr = of_mdio_parse_addr(dev, phy_dn);
+ if (addr < 0) {
+ err = addr;
goto put_nodes;
+ }
__set_bit(pn, priv->valid_ports);
priv->smi_bus[pn] = bus;
@@ -0,0 +1,43 @@
From 67885c0e3919be0f39d8f69e4eb446c9df8fa5fc Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Wed, 3 Jun 2026 19:59:20 +0200
Subject: [PATCH 16/20] net: mdio: realtek-rtl9300: harden
otto_emdio_probe_one()
The bus probing of the MDIO driver uses a two stage approach.
1. The device tree "ethernet-ports" node is scanned to build a mapping
between ports and PHYs.
2. The children of the device tree "controller" are scanned to create
the individual MDIO buses.
The first step already checks the consistency of the PHY and bus nodes
that are linked via the ports. But it might miss a dangling bus child
node that is not linked. Step two simply iterates over all bus child
nodes and might read malformed data from nodes not checked in step one.
Harden this and return a meaningful error message.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260603175924.123019-4-markus.stockhausen@gmx.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -380,7 +380,11 @@ static int otto_emdio_probe_one(struct d
err = fwnode_property_read_u32(node, "reg", &mdio_bus);
if (err)
- return err;
+ return dev_err_probe(dev, err, "undefined smi bus number\n");
+
+ if (mdio_bus >= priv->info->num_buses)
+ return dev_err_probe(dev, -EINVAL,
+ "illegal (dangling) smi bus number %d\n", mdio_bus);
/* The MDIO accesses from the kernel work with the PHY polling unit in
* the switch. We need to tell the PPU to operate either in GPHY (i.e.
@@ -0,0 +1,219 @@
From 94f05740ad16c5b2bf738af69b99cd0c48afd19e Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Wed, 3 Jun 2026 19:59:21 +0200
Subject: [PATCH 17/20] net: mdio: realtek-rtl9300: relocate topology setup
Until now the driver sets up the port to bus/address topology of the
controller after all buses are set up via otto_emdio_probe_one(). This
does not work for devices where U-Boot skips this setup. It is not
only needed for the hardware internal background PHY polling engine
but it is essential for access to the PHYs during probing.
Depending on the SoC type there exist two different register arrays
- Bus mapping registers (RTL930x, RTL931x) define to which bus the port
is attached. E.g. [1]
- Address mapping registers (RTL838x, RTL930x, RTL931x) define to which
address of the bus the port is attached. E.g. [2]
Relocate the topology setup and make it generic. For this
- Define device-specific bus_base/addr_base attributes that give the
register base address where the mapping lives. In case one or both are
not given the SoC does not support this specific type of mapping.
- Create a helper otto_emdio_setup_topology() that writes the detected
topology to the registers.
- Call this helper prior to otto_emdio_probe_one().
- Remove unneeded code from otto_emdio_9300_mdiobus_init().
- Due to the added prefixes, increase define indentation
Subtle change: The old coding used regmap_bulk_write and silently wrote
bus=0/address=0 to mapping registers for ports that are out of scope.
The new coding leaves those untouched.
[1] https://svanheule.net/realtek/longan/register/smi_port0_15_polling_sel
[2] https://svanheule.net/realtek/longan/register/smi_port0_5_addr_ctrl
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260603175924.123019-5-markus.stockhausen@gmx.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 127 ++++++++++++++----------
1 file changed, 76 insertions(+), 51 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -51,34 +51,38 @@
#include <linux/property.h>
#include <linux/regmap.h>
-#define RTL9300_NUM_BUSES 4
-#define RTL9300_NUM_PAGES 4096
-#define RTL9300_NUM_PORTS 28
-#define SMI_GLB_CTRL 0xca00
-#define GLB_CTRL_INTF_SEL(intf) BIT(16 + (intf))
-#define SMI_PORT0_15_POLLING_SEL 0xca08
-#define RTL9300_SMI_ACCESS_PHY_CTRL_0 0xcb70
-#define RTL9300_SMI_ACCESS_PHY_CTRL_1 0xcb74
-#define PHY_CTRL_REG_ADDR GENMASK(24, 20)
-#define PHY_CTRL_PARK_PAGE GENMASK(19, 15)
-#define PHY_CTRL_MAIN_PAGE GENMASK(14, 3)
-#define PHY_CTRL_WRITE BIT(2)
-#define PHY_CTRL_READ 0
-#define PHY_CTRL_TYPE_C45 BIT(1)
-#define PHY_CTRL_TYPE_C22 0
-#define PHY_CTRL_CMD BIT(0)
-#define PHY_CTRL_FAIL BIT(25)
-#define RTL9300_SMI_ACCESS_PHY_CTRL_2 0xcb78
-#define PHY_CTRL_INDATA GENMASK(31, 16)
-#define PHY_CTRL_DATA GENMASK(15, 0)
-#define RTL9300_SMI_ACCESS_PHY_CTRL_3 0xcb7c
-#define PHY_CTRL_MMD_DEVAD GENMASK(20, 16)
-#define PHY_CTRL_MMD_REG GENMASK(15, 0)
-#define SMI_PORT0_5_ADDR_CTRL 0xcb80
-
-#define MAX_PORTS 28
-#define MAX_SMI_BUSSES 4
-#define RAW_PAGE(priv) ((priv)->info->num_pages - 1)
+#define RTL9300_NUM_BUSES 4
+#define RTL9300_NUM_PAGES 4096
+#define RTL9300_NUM_PORTS 28
+#define SMI_GLB_CTRL 0xca00
+#define GLB_CTRL_INTF_SEL(intf) BIT(16 + (intf))
+#define RTL9300_SMI_PORT0_15_POLLING_SEL 0xca08
+#define RTL9300_SMI_ACCESS_PHY_CTRL_0 0xcb70
+#define RTL9300_SMI_ACCESS_PHY_CTRL_1 0xcb74
+#define PHY_CTRL_REG_ADDR GENMASK(24, 20)
+#define PHY_CTRL_PARK_PAGE GENMASK(19, 15)
+#define PHY_CTRL_MAIN_PAGE GENMASK(14, 3)
+#define PHY_CTRL_WRITE BIT(2)
+#define PHY_CTRL_READ 0
+#define PHY_CTRL_TYPE_C45 BIT(1)
+#define PHY_CTRL_TYPE_C22 0
+#define PHY_CTRL_CMD BIT(0)
+#define PHY_CTRL_FAIL BIT(25)
+#define RTL9300_SMI_ACCESS_PHY_CTRL_2 0xcb78
+#define PHY_CTRL_INDATA GENMASK(31, 16)
+#define PHY_CTRL_DATA GENMASK(15, 0)
+#define RTL9300_SMI_ACCESS_PHY_CTRL_3 0xcb7c
+#define PHY_CTRL_MMD_DEVAD GENMASK(20, 16)
+#define PHY_CTRL_MMD_REG GENMASK(15, 0)
+#define RTL9300_SMI_PORT0_5_ADDR_CTRL 0xcb80
+
+#define MAP_ADDRS_PER_REG 6
+#define MAP_BITS_PER_ADDR 5
+#define MAP_BITS_PER_BUS 2
+#define MAP_BUSES_PER_REG 16
+#define MAX_PORTS 28
+#define MAX_SMI_BUSSES 4
+#define RAW_PAGE(priv) ((priv)->info->num_pages - 1)
struct otto_emdio_cmd_regs {
@@ -89,6 +93,8 @@ struct otto_emdio_cmd_regs {
};
struct otto_emdio_info {
+ u32 addr_map_base;
+ u32 bus_map_base;
u32 cmd_fail;
u32 cmd_read;
u32 cmd_write;
@@ -327,41 +333,54 @@ static int otto_emdio_write_c45(struct m
return ret;
}
-static int otto_emdio_9300_mdiobus_init(struct otto_emdio_priv *priv)
+static int otto_emdio_write_mapping(struct otto_emdio_priv *priv, u32 base, u32 port,
+ u32 vals_per_reg, u32 bits_per_val, u32 value)
{
- u32 glb_ctrl_mask = 0, glb_ctrl_val = 0;
- struct regmap *regmap = priv->regmap;
- u32 port_addr[5] = { 0 };
- u32 poll_sel[2] = { 0 };
- int i, err;
+ u32 shift = (port % vals_per_reg) * bits_per_val;
+ u32 reg = base + (port / vals_per_reg) * 4;
+ u32 mask = GENMASK(bits_per_val - 1, 0);
- /* Associate the port with the SMI interface and PHY */
- for_each_set_bit(i, priv->valid_ports, priv->info->num_ports) {
- int pos;
+ return regmap_update_bits(priv->regmap, reg, mask << shift, value << shift);
+}
- pos = (i % 6) * 5;
- port_addr[i / 6] |= (priv->smi_addr[i] & 0x1f) << pos;
+static int otto_emdio_setup_topology(struct otto_emdio_priv *priv)
+{
+ const struct otto_emdio_info *info = priv->info;
+ u32 port;
+ int ret;
- pos = (i % 16) * 2;
- poll_sel[i / 16] |= (priv->smi_bus[i] & 0x3) << pos;
+ for_each_set_bit(port, priv->valid_ports, info->num_ports) {
+ if (info->bus_map_base) {
+ ret = otto_emdio_write_mapping(priv, info->bus_map_base, port,
+ MAP_BUSES_PER_REG, MAP_BITS_PER_BUS,
+ priv->smi_bus[port]);
+ if (ret)
+ return ret;
+ }
+ if (info->addr_map_base) {
+ ret = otto_emdio_write_mapping(priv, info->addr_map_base, port,
+ MAP_ADDRS_PER_REG, MAP_BITS_PER_ADDR,
+ priv->smi_addr[port]);
+ if (ret)
+ return ret;
+ }
}
+ return 0;
+}
+
+static int otto_emdio_9300_mdiobus_init(struct otto_emdio_priv *priv)
+{
+ u32 glb_ctrl_mask = 0, glb_ctrl_val = 0;
+ struct regmap *regmap = priv->regmap;
+ int i, err;
+
/* Put the interfaces into C45 mode if required */
glb_ctrl_mask = GENMASK(19, 16);
for (i = 0; i < priv->info->num_buses; i++)
if (priv->smi_bus_is_c45[i])
glb_ctrl_val |= GLB_CTRL_INTF_SEL(i);
- err = regmap_bulk_write(regmap, SMI_PORT0_5_ADDR_CTRL,
- port_addr, 5);
- if (err)
- return err;
-
- err = regmap_bulk_write(regmap, SMI_PORT0_15_POLLING_SEL,
- poll_sel, 2);
- if (err)
- return err;
-
err = regmap_update_bits(regmap, SMI_GLB_CTRL,
glb_ctrl_mask, glb_ctrl_val);
if (err)
@@ -528,6 +547,10 @@ static int otto_emdio_probe(struct platf
if (err)
return err;
+ err = otto_emdio_setup_topology(priv);
+ if (err)
+ return err;
+
device_for_each_child_node_scoped(dev, child) {
err = otto_emdio_probe_one(dev, priv, child);
if (err)
@@ -542,6 +565,8 @@ static int otto_emdio_probe(struct platf
}
static const struct otto_emdio_info otto_emdio_9300_info = {
+ .addr_map_base = RTL9300_SMI_PORT0_5_ADDR_CTRL,
+ .bus_map_base = RTL9300_SMI_PORT0_15_POLLING_SEL,
.cmd_fail = PHY_CTRL_FAIL,
.cmd_read = PHY_CTRL_READ,
.cmd_write = PHY_CTRL_WRITE,
@@ -0,0 +1,64 @@
From 439aba443a8012b5a61bf327f75bec69be56ddd3 Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Wed, 3 Jun 2026 19:59:22 +0200
Subject: [PATCH 18/20] net: mdio: realtek-rtl9300: relocate c22/c45 device
tree readout
otto_emdio_map_ports() is the central place to lookup the topology and the
properties of the Realtek ethernet MDIO controller from the device tree.
Deviating from this the c22/c45 detection via "ethernet-phy-ieee802.3-c45"
is running separately in otto_emdio_probe_one(). It loops over the same
nodes, just at a later point in time.
There is no benefit to divide this setup and to have a time window where
the data structure is only filled partially. Additionally it uses the
"fwnode" API. Consolidate the setup and convert it to the "of" API.
Remark. This is a subtle change for dangling PHY nodes (not referenced
by ethernet-ports). Before this commit all PHY nodes were evaluated for
c45 setup, now only the referenced ones.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260603175924.123019-6-markus.stockhausen@gmx.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 21 +++++++++------------
1 file changed, 9 insertions(+), 12 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -405,18 +405,6 @@ static int otto_emdio_probe_one(struct d
return dev_err_probe(dev, -EINVAL,
"illegal (dangling) smi bus number %d\n", mdio_bus);
- /* The MDIO accesses from the kernel work with the PHY polling unit in
- * the switch. We need to tell the PPU to operate either in GPHY (i.e.
- * clause 22) or 10GPHY mode (i.e. clause 45).
- *
- * We select 10GPHY mode if there is at least one PHY that declares
- * compatible = "ethernet-phy-ieee802.3-c45". This does mean we can't
- * support both c45 and c22 on the same MDIO bus.
- */
- fwnode_for_each_child_node_scoped(node, child)
- if (fwnode_device_is_compatible(child, "ethernet-phy-ieee802.3-c45"))
- priv->smi_bus_is_c45[mdio_bus] = true;
-
bus = devm_mdiobus_alloc_size(dev, sizeof(*chan));
if (!bus)
return -ENOMEM;
@@ -506,6 +494,15 @@ static int otto_emdio_map_ports(struct d
goto put_nodes;
}
+ /*
+ * The MDIO accesses from the kernel work with the PHY polling unit in the
+ * switch. The PPU either operates in GPHY (i.e. clause 22) or 10GPHY mode
+ * (i.e. clause 45). Select 10GPHY mode if there is at least one PHY that
+ * declares compatible = "ethernet-phy-ieee802.3-c45".
+ */
+ if (of_device_is_compatible(phy_dn, "ethernet-phy-ieee802.3-c45"))
+ priv->smi_bus_is_c45[bus] = true;
+
__set_bit(pn, priv->valid_ports);
priv->smi_bus[pn] = bus;
priv->smi_addr[pn] = addr;
@@ -0,0 +1,136 @@
From 56b4864150970ba834367fe74b37ce250419347e Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Wed, 3 Jun 2026 19:59:23 +0200
Subject: [PATCH 19/20] net: mdio: realtek-rtl9300: reorder controller setup
After the former refactoring the existing otto_emdio_9300_mdiobus_init()
contains only the c22/c45 bus mode setup. Like the topology setup this
must run before bus registration. Otherwise the bus does not "speak" the
right protocol for PHY setup.
This setup is device-specific and other SoCs will need to set up other
register bits in the controller in the future. Therefore
- Relocate c22/c45 device tree readout to the very beginning of the probing
- Add a new device-specific setup_controller() into the info structure.
- Relocate otto_emdio_priv to satisfy the new info structure dependency.
- Rename otto_emdio_9300_mdiobus_init accordingly and add it to the
RTL9300 info structure. At the same time, adapt register naming
for the function to make it clear that it only applies to this SoC.
- Call setup_controller() prior to bus registration.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260603175924.123019-7-markus.stockhausen@gmx.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 44 ++++++++++++++-----------
1 file changed, 24 insertions(+), 20 deletions(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -54,8 +54,8 @@
#define RTL9300_NUM_BUSES 4
#define RTL9300_NUM_PAGES 4096
#define RTL9300_NUM_PORTS 28
-#define SMI_GLB_CTRL 0xca00
-#define GLB_CTRL_INTF_SEL(intf) BIT(16 + (intf))
+#define RTL9300_SMI_GLB_CTRL 0xca00
+#define RTL9300_GLB_CTRL_INTF_SEL(intf) BIT(16 + (intf))
#define RTL9300_SMI_PORT0_15_POLLING_SEL 0xca08
#define RTL9300_SMI_ACCESS_PHY_CTRL_0 0xcb70
#define RTL9300_SMI_ACCESS_PHY_CTRL_1 0xcb74
@@ -92,6 +92,17 @@ struct otto_emdio_cmd_regs {
u32 port_mask_low;
};
+struct otto_emdio_priv {
+ const struct otto_emdio_info *info;
+ struct regmap *regmap;
+ struct mutex lock; /* protect HW access */
+ DECLARE_BITMAP(valid_ports, MAX_PORTS);
+ u8 smi_bus[MAX_PORTS];
+ u8 smi_addr[MAX_PORTS];
+ bool smi_bus_is_c45[MAX_SMI_BUSSES];
+ struct mii_bus *bus[MAX_SMI_BUSSES];
+};
+
struct otto_emdio_info {
u32 addr_map_base;
u32 bus_map_base;
@@ -102,23 +113,13 @@ struct otto_emdio_info {
u8 num_buses;
u8 num_ports;
u16 num_pages;
+ int (*setup_controller)(struct otto_emdio_priv *priv);
int (*read_c22)(struct mii_bus *bus, int port, int regnum, u32 *value);
int (*read_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u32 *value);
int (*write_c22)(struct mii_bus *bus, int port, int regnum, u16 value);
int (*write_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u16 value);
};
-struct otto_emdio_priv {
- const struct otto_emdio_info *info;
- struct regmap *regmap;
- struct mutex lock; /* protect HW access */
- DECLARE_BITMAP(valid_ports, MAX_PORTS);
- u8 smi_bus[MAX_PORTS];
- u8 smi_addr[MAX_PORTS];
- bool smi_bus_is_c45[MAX_SMI_BUSSES];
- struct mii_bus *bus[MAX_SMI_BUSSES];
-};
-
struct otto_emdio_chan {
struct otto_emdio_priv *priv;
u8 mdio_bus;
@@ -369,7 +370,7 @@ static int otto_emdio_setup_topology(str
return 0;
}
-static int otto_emdio_9300_mdiobus_init(struct otto_emdio_priv *priv)
+static int otto_emdio_9300_setup_controller(struct otto_emdio_priv *priv)
{
u32 glb_ctrl_mask = 0, glb_ctrl_val = 0;
struct regmap *regmap = priv->regmap;
@@ -379,9 +380,9 @@ static int otto_emdio_9300_mdiobus_init(
glb_ctrl_mask = GENMASK(19, 16);
for (i = 0; i < priv->info->num_buses; i++)
if (priv->smi_bus_is_c45[i])
- glb_ctrl_val |= GLB_CTRL_INTF_SEL(i);
+ glb_ctrl_val |= RTL9300_GLB_CTRL_INTF_SEL(i);
- err = regmap_update_bits(regmap, SMI_GLB_CTRL,
+ err = regmap_update_bits(regmap, RTL9300_SMI_GLB_CTRL,
glb_ctrl_mask, glb_ctrl_val);
if (err)
return err;
@@ -548,16 +549,18 @@ static int otto_emdio_probe(struct platf
if (err)
return err;
+ if (priv->info->setup_controller) {
+ err = priv->info->setup_controller(priv);
+ if (err)
+ return dev_err_probe(dev, err, "failed to setup MDIO bus controller\n");
+ }
+
device_for_each_child_node_scoped(dev, child) {
err = otto_emdio_probe_one(dev, priv, child);
if (err)
return err;
}
- err = otto_emdio_9300_mdiobus_init(priv);
- if (err)
- return dev_err_probe(dev, err, "failed to initialise MDIO bus controller\n");
-
return 0;
}
@@ -576,6 +579,7 @@ static const struct otto_emdio_info otto
.num_buses = RTL9300_NUM_BUSES,
.num_ports = RTL9300_NUM_PORTS,
.num_pages = RTL9300_NUM_PAGES,
+ .setup_controller = otto_emdio_9300_setup_controller,
.read_c22 = otto_emdio_9300_read_c22,
.read_c45 = otto_emdio_9300_read_c45,
.write_c22 = otto_emdio_9300_write_c22,
@@ -0,0 +1,57 @@
From 50b682a5aed70847e0746db7f723d22bef1801d1 Mon Sep 17 00:00:00 2001
From: Manuel Stocker <mensi@mensi.ch>
Date: Wed, 3 Jun 2026 19:59:24 +0200
Subject: [PATCH 20/20] net: mdio: realtek-rtl9300: Correctly handle
ethernet-phy-package
Realtek Otto switches usually make use of multiport PHYs (e.g. 8 port
1G RTL8218D or 4 port 2.5G RTL8224). The device tree can describe this
fact via an "ethernet-phy-package" node that resides between the bus
and the PHY node.
When looking up the device tree bus node via the chain port->phy->parent
the driver totally ignores the existence of a PHY package. Enhance the
lookup to take care of this feature.
Link: https://github.com/openwrt/openwrt/pull/23591
Signed-off-by: Manuel Stocker <mensi@mensi.ch>
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://patch.msgid.link/20260603175924.123019-8-markus.stockhausen@gmx.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -432,6 +432,21 @@ static int otto_emdio_probe_one(struct d
return 0;
}
+static struct device_node *otto_emdio_get_bus_node(struct device_node *dn)
+{
+ struct device_node *parent = of_get_parent(dn);
+ struct device_node *grandparent;
+
+ if (parent && of_node_name_eq(parent, "ethernet-phy-package")) {
+ grandparent = of_get_parent(parent);
+ of_node_put(parent);
+
+ return grandparent;
+ }
+
+ return parent;
+}
+
/* The mdio-controller is part of a switch block so we parse the sibling
* ethernet-ports node and build a mapping of the switch port to MDIO bus/addr
* based on the phy-handle.
@@ -457,7 +472,7 @@ static int otto_emdio_map_ports(struct d
if (!phy_dn)
continue;
- bus_dn = of_get_parent(phy_dn);
+ bus_dn = otto_emdio_get_bus_node(phy_dn);
if (!bus_dn)
goto put_nodes;