* [PATCH net-next 0/8] net: mdio: realtek-rtl9300: Add RTL83xx support
@ 2026-06-13 11:29 Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 1/8] dt-bindings: net: realtek,rtl9301-mdio: Add RTL83xx series Markus Stockhausen
` (7 more replies)
0 siblings, 8 replies; 13+ messages in thread
From: Markus Stockhausen @ 2026-06-13 11:29 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
Cc: Markus Stockhausen
The Realtek Otto switch platform consists of four different series
- RTL838x aka maple : 28 port 1G Switches
- RTL839x aka cypress : 52 port 1G Switches
- RTL930x aka longan : 28 port 1G/2.5G/10G Switches
- RTL931x aka mango : 56 port 1G/2.5G/10G Switches
While there always was a good knowledge about the MDIO hardware
polling unit and its necessity for the MAC layer, there was no
detailed documentation available. For this series the MDIO bus was
inspected with a logic analyzer for a better understanding how
polling and kernel access interact on the bus. All this is now
explained in the driver comments.
This patch series adds support for the RTL83xx devices. For this
- Enhance device tree binding.
- Add special handling for limitations enforced by hardware polling.
These already have minor side effects on RTL93xx devices but are even
more critical for the RTL83xx hardware.
- Add RTL83xx coding.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
Markus Stockhausen (8):
dt-bindings: net: realtek,rtl9301-mdio: Add RTL83xx series
net: mdio: realtek-rtl9300: Add polling documentation
net: mdio: realtek-rtl9300: Add page tracking
net: mdio: realtek-rtl9300: Configure hardware polling during probing
net: mdio: realtek-rtl9300: Add c45 over c22 mitigation
net: mdio: realtek-rtl9300: Increase MDIO timeout
net: mdio: realtek-rtl9300: Add support for RTL838x
net: mdio: realtek-rtl9300: Add support for RTL839x
.../bindings/net/realtek,rtl9301-mdio.yaml | 12 +
drivers/net/mdio/mdio-realtek-rtl9300.c | 399 +++++++++++++++++-
2 files changed, 398 insertions(+), 13 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH net-next 1/8] dt-bindings: net: realtek,rtl9301-mdio: Add RTL83xx series
2026-06-13 11:29 [PATCH net-next 0/8] net: mdio: realtek-rtl9300: Add RTL83xx support Markus Stockhausen
@ 2026-06-13 11:29 ` Markus Stockhausen
2026-06-13 19:07 ` Krzysztof Kozlowski
2026-06-13 11:29 ` [PATCH net-next 2/8] net: mdio: realtek-rtl9300: Add polling documentation Markus Stockhausen
` (6 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Markus Stockhausen @ 2026-06-13 11:29 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
Cc: Markus Stockhausen
The lower end Realtek Otto switches provide 1G only and are divided into
two series:
- Maple : RTL838x up to 28 ports
- Cypress: RTL839x up to 56 ports
The Maple based devices have 3 different SoCs: RTL8380, RTL8381 and
RTL8382. The Cypress series consists of the RTL8391, RTL8392 and
RTL8393 SoCs. The MDIO controller of these switches works like the
existing RTL93xx logic but has different characteristics and different
registers. Add new compatibles in the device tree.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
.../bindings/net/realtek,rtl9301-mdio.yaml | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml b/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml
index 271e05bae9c5..de33364b67ef 100644
--- a/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml
+++ b/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml
@@ -12,6 +12,16 @@ maintainers:
properties:
compatible:
oneOf:
+ - items:
+ - enum:
+ - realtek,rtl8381-mdio
+ - realtek,rtl8382-mdio
+ - const: realtek,rtl8380-mdio
+ - items:
+ - enum:
+ - realtek,rtl8392-mdio
+ - realtek,rtl8393-mdio
+ - const: realtek,rtl8391-mdio
- items:
- enum:
- realtek,rtl9302b-mdio
@@ -24,6 +34,8 @@ properties:
- realtek,rtl9313-mdio
- const: realtek,rtl9311-mdio
- enum:
+ - realtek,rtl8380-mdio
+ - realtek,rtl8391-mdio
- realtek,rtl9301-mdio
- realtek,rtl9311-mdio
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next 2/8] net: mdio: realtek-rtl9300: Add polling documentation
2026-06-13 11:29 [PATCH net-next 0/8] net: mdio: realtek-rtl9300: Add RTL83xx support Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 1/8] dt-bindings: net: realtek,rtl9301-mdio: Add RTL83xx series Markus Stockhausen
@ 2026-06-13 11:29 ` Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 3/8] net: mdio: realtek-rtl9300: Add page tracking Markus Stockhausen
` (5 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Markus Stockhausen @ 2026-06-13 11:29 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
Cc: Markus Stockhausen
Add a detailed explanation how the hardware polling unit in the
Realtek Otto switches works. This simplifies developing future
patches and reviewing them.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 66 +++++++++++++++++++++++++
1 file changed, 66 insertions(+)
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index 892ed3780a65..da2864c94d2c 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -35,6 +35,72 @@
*
* The driver works out the mapping based on the MDIO bus described in device tree and phandles on
* the ethernet-ports property.
+ *
+ * The devices have a hardware polling unit that runs in the background without any CPU load. It
+ * constantly scans the MDIO bus and the attached PHYs and updates the MAC status registers.
+ *
+ * How does the polling work?
+ *
+ * Each device has a SMI_POLL_CTRL register. A per-port bitmask decides if the hardware polling of
+ * the associated bus/address is active or not. The hardware runs a tight loop over this and for
+ * each set polling bit it issues a status check for the PHY. Attaching a logic analyzer to the
+ * MDIO bus of an RTL8380 and RTL8393 gives the following commands (in kernel notation):
+ *
+ * RTL8380 RTL8393
+ * --------------------------- ---------------------------
+ * phy_write(phy, 31, 0x0); phy_read(phy, 0);
+ * phy_write(phy, 13, 0x7); phy_read(phy, 1);
+ * phy_write(phy, 14, 0x3c); phy_read(phy, 4);
+ * phy_write(phy, 13, 0x8007); phy_read(phy, 5);
+ * phy_read(phy, 14); phy_read(phy, 6);
+ * phy_write(phy, 13, 0x7); phy_read(phy, 9);
+ * phy_write(phy, 14, 0x3d); phy_read(phy, 10);
+ * phy_write(phy, 13, 0x8007); phy_read(phy, 15);
+ * phy_read(phy, 14); phy_write(phy, 13, 0x7);
+ * phy_read(phy, 9); phy_write(phy, 14, 0x3c);
+ * phy_read(phy, 10); phy_write(phy, 13, 0x4007);
+ * phy_read(phy, 15); phy_read(phy, 14);
+ * phy_read(phy, 0); phy_write(phy, 13, 0x7);
+ * phy_read(phy, 1); phy_write(phy, 14, 0x3d);
+ * phy_read(phy, 4); phy_write(phy, 13, 0x4007);
+ * phy_read(phy, 5); phy_read(phy, 14);
+ * phy_read(phy, 6);
+ *
+ * The c22 over c45 register 13/14 sequences read MDIO_AN_EEE_ADV and MDIO_AN_EEE_LPABLE. As soon
+ * as one PHY status is read, the polling engine goes over to the next PHY. Basically the bus is
+ * always busy and the MAC status is updated in realtime.
+ *
+ * How does MDIO access from kernel work?
+ *
+ * When issuing MDIO accesses via an MMIO based interface the final write to the command register
+ * sets a "run command now" bit. Between two polling sequences for different PHYs the hardware
+ * checks if a user command needs to run and sends it onto the bus. Afterwards it simply continues
+ * its polling work. Inspecting the command sequence for a paged read on the logic analyzer gives:
+ *
+ * RTL8380 RTL8393
+ * --------------------------- ---------------------------
+ * phy_write(phy, 31, page); phy_write(phy, 31, page);
+ * phy_write(phy, reg, value); phy_write(phy, reg, value);
+ * phy_write(phy, 31, 0);
+ *
+ * What does this mean?
+ *
+ * There are slight differences in polling and PHY access between the models but the challenge
+ * stays the same. On the one hand that greatly simplifies the MAC layer, on the other hand it
+ * has some implications for the kernel PHY subsystem.
+ *
+ * - Without the polling and a proper MAC status, some of the link handling features do not work.
+ * Especially an unpopulated MAC_LINK_STS register cancels operations to other MAC registers.
+ * - The Realtek page register 31 is magically modified in the background. On the RTL838x it is
+ * simply reset. Other devices have hardware mitigations for this in place.
+ * - A c45 over c22 kernel access sequence is most likely to fail because chances are high that
+ * the polling engine overwrites registers 13/14 in between.
+ * - PHY firmware loading can have issues. Especially if a PHY is designed to expect a clean
+ * sequence of registers and values without deviation.
+ * - An access to one PHY will need to wait for the next free slot of the polling engine.
+ *
+ * Conclusion: Kernel access to the PHYs must know and handle any interference that arises from
+ * the above described hardware polling.
*/
#include <linux/bitfield.h>
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next 3/8] net: mdio: realtek-rtl9300: Add page tracking
2026-06-13 11:29 [PATCH net-next 0/8] net: mdio: realtek-rtl9300: Add RTL83xx support Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 1/8] dt-bindings: net: realtek,rtl9301-mdio: Add RTL83xx series Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 2/8] net: mdio: realtek-rtl9300: Add polling documentation Markus Stockhausen
@ 2026-06-13 11:29 ` Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 4/8] net: mdio: realtek-rtl9300: Configure hardware polling during probing Markus Stockhausen
` (4 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Markus Stockhausen @ 2026-06-13 11:29 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
Cc: Markus Stockhausen
The hardware polling unit of the Realtek switches has a very special
handling for PHY register 31 (aka Realtek page register) in place.
- On the RTL838x it is permanently reset to zero.
- On other devices there is some magic saving/restoring (aka parking)
in the background in place.
This makes access to PHYs a gamble.
As of now all known existing hardware designs have Realtek based 1G PHYs.
Otherwise the polling engine and the MAC status update will not work at
all and the vendor SDK would fail totally.
This driver differentiates clearly between c22 and c45 buses. During
probing it enables only one of the protocols for a bus. So it is safe
to assume that any c22 access will only target a Realtek based 1G PHY.
Intercept access to register 31 and store the desired value for each port
in the driver. When issuing access to other registers add the saved page.
This given, the hardware will run two consecutive c22 commands that are
not interrupted by polling.
... hardware poll ...
phy_write(phy, 31, page)
phy_write(phy, reg, value)
... hardware poll ...
Remark! To keep this simple, writes to register 31 are only accepted
if they are lower than the device specific raw page - 0..4094/8190.
Otherwise -EINVAL is returned. Under the above assumption (Only 1G
Realtek PHYs on c22 bus) this is no limitation.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 32 +++++++++++++++++--------
1 file changed, 22 insertions(+), 10 deletions(-)
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index da2864c94d2c..c3a9eeca3154 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -193,6 +193,7 @@ struct otto_emdio_priv {
struct regmap *regmap;
struct mutex lock; /* protect HW access */
DECLARE_BITMAP(valid_ports, MAX_PORTS);
+ u16 page[MAX_PORTS];
u8 smi_bus[MAX_PORTS];
u8 smi_addr[MAX_PORTS];
bool smi_bus_is_c45[MAX_SMI_BUSSES];
@@ -337,7 +338,7 @@ static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u
struct otto_emdio_cmd_regs cmd_data = {
.c22_data = FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) |
FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) |
- FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+ FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, priv->page[port]),
.io_data = FIELD_PREP(RTL9300_PHY_CTRL_INDATA, port),
};
@@ -351,7 +352,7 @@ static int otto_emdio_9300_write_c22(struct mii_bus *bus, int port, int regnum,
struct otto_emdio_cmd_regs cmd_data = {
.c22_data = FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) |
FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) |
- FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+ FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, priv->page[port]),
.io_data = FIELD_PREP(RTL9300_PHY_CTRL_INDATA, value),
.port_mask_low = BIT(port),
};
@@ -391,7 +392,7 @@ static int otto_emdio_9310_read_c22(struct mii_bus *bus, int port, int regnum, u
struct otto_emdio_cmd_regs cmd_data = {
.broadcast = FIELD_PREP(RTL9310_BC_PORT_ID, port),
.c22_data = FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) |
- FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+ FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, priv->page[port]),
};
return otto_emdio_read_cmd(bus, RTL9310_PHY_CTRL_TYPE_C22, &cmd_data,
@@ -403,7 +404,7 @@ static int otto_emdio_9310_write_c22(struct mii_bus *bus, int port, int regnum,
struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
struct otto_emdio_cmd_regs cmd_data = {
.c22_data = FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) |
- FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+ FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, priv->page[port]),
.io_data = FIELD_PREP(RTL9310_PHY_CTRL_INDATA, value),
.port_mask_high = (u32)(BIT_ULL(port) >> 32),
.port_mask_low = (u32)(BIT_ULL(port)),
@@ -442,15 +443,19 @@ static int otto_emdio_9310_write_c45(struct mii_bus *bus, int port,
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;
+ int port, ret = 0;
u32 value;
port = otto_emdio_phy_to_port(bus, phy_id);
if (port < 0)
return port;
- scoped_guard(mutex, &priv->lock)
+ scoped_guard(mutex, &priv->lock) {
+ if (regnum == 31)
+ return priv->page[port];
+
ret = priv->info->read_c22(bus, port, regnum, &value);
+ }
return ret ? ret : value;
}
@@ -458,16 +463,23 @@ static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)
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;
+ int 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);
+ scoped_guard(mutex, &priv->lock) {
+ if (regnum == 31) {
+ if (value >= RAW_PAGE(priv))
+ return -EINVAL;
- return ret;
+ priv->page[port] = value;
+ return 0;
+ }
+
+ return priv->info->write_c22(bus, port, regnum, value);
+ }
}
static int otto_emdio_read_c45(struct mii_bus *bus, int phy_id, int dev_addr, int regnum)
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next 4/8] net: mdio: realtek-rtl9300: Configure hardware polling during probing
2026-06-13 11:29 [PATCH net-next 0/8] net: mdio: realtek-rtl9300: Add RTL83xx support Markus Stockhausen
` (2 preceding siblings ...)
2026-06-13 11:29 ` [PATCH net-next 3/8] net: mdio: realtek-rtl9300: Add page tracking Markus Stockhausen
@ 2026-06-13 11:29 ` Markus Stockhausen
2026-06-14 11:30 ` sashiko-bot
2026-06-13 11:29 ` [PATCH net-next 5/8] net: mdio: realtek-rtl9300: Add c45 over c22 mitigation Markus Stockhausen
` (3 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Markus Stockhausen @ 2026-06-13 11:29 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
Cc: Markus Stockhausen
During bus probing the PHYs are initialized and firmware might be
loaded. This often requires complex access sequences where the switch
hardware polling might interfere badly.
The polling can be configured with one or two 32 bit mask registers.
Each bit enables (=1) or disables (=0) the polling of the corresponding
port.
Provide a helper to enable/disable polling for a specific port. With
this disable hardware polling temporarily during bus probing and enable
it afterwards according to the device tree topology. Nice side effect:
This patch brings the hardware polling into a consistent state for
devices where U-Boot does not take care.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 26 ++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index c3a9eeca3154..a7fd075947b6 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -137,6 +137,7 @@
#define RTL9300_PHY_CTRL_INDATA GENMASK(31, 16)
#define RTL9300_PHY_CTRL_DATA GENMASK(15, 0)
#define RTL9300_SMI_ACCESS_PHY_CTRL_3 0xcb7c
+#define RTL9300_SMI_POLL_CTRL 0xca90
#define RTL9300_SMI_PORT0_5_ADDR_CTRL 0xcb80
#define RTL9310_NUM_BUSES 4
@@ -162,6 +163,7 @@
#define RTL9310_PHY_CTRL_INDATA GENMASK(15, 0)
#define RTL9310_SMI_INDRT_ACCESS_MMD_CTRL 0x0c18
#define RTL9310_SMI_PORT_ADDR_CTRL 0x0c74
+#define RTL9310_SMI_PORT_POLLING_CTRL 0x0ccc
#define RTL9310_SMI_PORT_POLLING_SEL 0x0c9c
#define PHY_CTRL_CMD BIT(0)
@@ -210,6 +212,7 @@ struct otto_emdio_info {
u8 num_buses;
u8 num_ports;
u16 num_pages;
+ u32 poll_ctrl;
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);
@@ -245,6 +248,12 @@ static struct otto_emdio_priv *otto_emdio_bus_to_priv(struct mii_bus *bus)
return chan->priv;
}
+static int otto_emdio_set_port_polling(struct otto_emdio_priv *priv, int port, bool active)
+{
+ return regmap_assign_bits(priv->regmap, priv->info->poll_ctrl + (port / 32) * 4,
+ BIT(port % 32), active);
+}
+
static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
struct otto_emdio_cmd_regs *cmd_data)
{
@@ -735,7 +744,7 @@ static int otto_emdio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct otto_emdio_priv *priv;
- int err;
+ int port, err;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
@@ -750,6 +759,13 @@ static int otto_emdio_probe(struct platform_device *pdev)
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
+ /* Avoid issues with complex firmware loads. */
+ for (port = 0; port < priv->info->num_ports; port++) {
+ err = otto_emdio_set_port_polling(priv, port, false);
+ if (err)
+ return err;
+ }
+
platform_set_drvdata(pdev, priv);
err = otto_emdio_map_ports(dev);
@@ -772,6 +788,12 @@ static int otto_emdio_probe(struct platform_device *pdev)
return err;
}
+ for_each_set_bit(port, priv->valid_ports, priv->info->num_ports) {
+ err = otto_emdio_set_port_polling(priv, port, true);
+ if (err)
+ return err;
+ }
+
return 0;
}
@@ -790,6 +812,7 @@ 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,
+ .poll_ctrl = RTL9300_SMI_POLL_CTRL,
.setup_controller = otto_emdio_9300_setup_controller,
.read_c22 = otto_emdio_9300_read_c22,
.read_c45 = otto_emdio_9300_read_c45,
@@ -815,6 +838,7 @@ static const struct otto_emdio_info otto_emdio_9310_info = {
.num_buses = RTL9310_NUM_BUSES,
.num_pages = RTL9310_NUM_PAGES,
.num_ports = RTL9310_NUM_PORTS,
+ .poll_ctrl = RTL9310_SMI_PORT_POLLING_CTRL,
.setup_controller = otto_emdio_9310_setup_controller,
.read_c22 = otto_emdio_9310_read_c22,
.read_c45 = otto_emdio_9310_read_c45,
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next 5/8] net: mdio: realtek-rtl9300: Add c45 over c22 mitigation
2026-06-13 11:29 [PATCH net-next 0/8] net: mdio: realtek-rtl9300: Add RTL83xx support Markus Stockhausen
` (3 preceding siblings ...)
2026-06-13 11:29 ` [PATCH net-next 4/8] net: mdio: realtek-rtl9300: Configure hardware polling during probing Markus Stockhausen
@ 2026-06-13 11:29 ` Markus Stockhausen
2026-06-14 11:30 ` sashiko-bot
2026-06-13 11:29 ` [PATCH net-next 6/8] net: mdio: realtek-rtl9300: Increase MDIO timeout Markus Stockhausen
` (2 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Markus Stockhausen @ 2026-06-13 11:29 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
Cc: Markus Stockhausen
When reading the PHY state on c22 based buses the hardware polling unit
reads the EEE status with a sequence similar to this:
...
phy_write(phy, 31, 0x0);
phy_write(phy, 13, 0x7); /* c22 over c45 MDIO_AN_EEE_ADV */
phy_write(phy, 14, 0x3c);
phy_write(phy, 13, 0x8007);
phy_read(phy, 14);
phy_write(phy, 13, 0x7); /* c22 over c45 MDIO_AN_EEE_LPABLE */
phy_write(phy, 14, 0x3d);
phy_write(phy, 13, 0x8007);
...
If the Linux kernel wants to do the same in mmd_phy_read() via a call to
mmd_phy_indirect() this most likely fails. The commands are issued in a
straight sequence but between two of them the hardware polling might run
a status check for the same PHY. This effectively breaks the kernel access
and makes use of c45 over c22 unusable.
Detailed analysis shows that for RTL838x, RTL839x and RTL931x polling
can be safely deactivated during operation. The MAC layer will continue
to show the last known state. RTL839x is an exception from this. As soon
as polling is disabled the MAC link status register shows "port down".
Enhance the driver to detect this register 13/14/13/14 access sequence.
Before the first access to register 13 of a PHY disable polling for the
corresponding port. Reenable polling as soon as the sequence is finished
or any other unexpected input is detected. Some details about the stop
and start timing:
- The stopping is issued inflight while the polling engine is working.
After it is finished no new polling for the port will be issued (tested
with only one port with active polling).
- Reenabling the polling engine happens within ~25us after the last
command of the MMD sequence. This is mostly due to MMIO overhead.
Technically speaking, add a simple state machine that increments a
per-port MMD counter for each successful step of the sequence. When the
first command starts (counter=1) stop polling. When the last command
finishes (counter=4) or unexpected data is sent start polling.
Additionally:
- Add a global "initialization done" tracker that stops the mechanism
from kicking in during bus probing.
- Add a global "link flapping" option that allows to disable the state
tracker for the to-be-added RTL839x series completely.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 62 ++++++++++++++++++++++++-
1 file changed, 60 insertions(+), 2 deletions(-)
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index a7fd075947b6..e206ee3e2b1c 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -196,10 +196,12 @@ struct otto_emdio_priv {
struct mutex lock; /* protect HW access */
DECLARE_BITMAP(valid_ports, MAX_PORTS);
u16 page[MAX_PORTS];
+ u8 mmd_state[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];
+ bool init_done;
};
struct otto_emdio_info {
@@ -209,6 +211,7 @@ struct otto_emdio_info {
u32 cmd_read;
u32 cmd_write;
struct otto_emdio_cmd_regs cmd_regs;
+ bool link_flap;
u8 num_buses;
u8 num_ports;
u16 num_pages;
@@ -254,6 +257,43 @@ static int otto_emdio_set_port_polling(struct otto_emdio_priv *priv, int port, b
BIT(port % 32), active);
}
+static int otto_emdio_mmd_prefix(struct otto_emdio_priv *priv, int port, int regnum)
+{
+ u8 newstate, *state = &priv->mmd_state[port];
+ int expected, ret = 0;
+
+ if (!priv->init_done)
+ return 0;
+ /*
+ * Disabled polling might produce link flapping and false notification interrupts on the
+ * MAC layer. In this case disable c45 over c22 MMD access because chances are high that
+ * the register 13/14/13/14 sequence is intercepted by a parallel hardware access. As
+ * a workaround the PHY must provide its own mmd read/write() callbacks and redirect to
+ * normal c22 registers. See rtlgen_read_mmd().
+ */
+ if (priv->info->link_flap)
+ return (regnum == MII_MMD_DATA || regnum == MII_MMD_CTRL) ? -EIO : 0;
+
+ expected = (*state & 1) ? MII_MMD_DATA : MII_MMD_CTRL;
+ newstate = regnum == expected ? *state + 1 : 0;
+
+ if (newstate == 1 || newstate < *state)
+ ret = otto_emdio_set_port_polling(priv, port, !newstate);
+ *state = newstate;
+
+ return ret;
+}
+
+static int otto_emdio_mmd_postfix(struct otto_emdio_priv *priv, int port, int regnum)
+{
+ if (priv->mmd_state[port] != 4)
+ return 0;
+
+ priv->mmd_state[port] = 0;
+
+ return otto_emdio_set_port_polling(priv, port, true);
+}
+
static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
struct otto_emdio_cmd_regs *cmd_data)
{
@@ -463,7 +503,15 @@ static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)
if (regnum == 31)
return priv->page[port];
+ ret = otto_emdio_mmd_prefix(priv, port, regnum);
+ if (ret)
+ return ret;
+
ret = priv->info->read_c22(bus, port, regnum, &value);
+ if (ret)
+ return ret;
+
+ ret = otto_emdio_mmd_postfix(priv, port, regnum);
}
return ret ? ret : value;
@@ -472,7 +520,7 @@ static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)
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 port;
+ int port, ret;
port = otto_emdio_phy_to_port(bus, phy_id);
if (port < 0)
@@ -487,7 +535,15 @@ static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16
return 0;
}
- return priv->info->write_c22(bus, port, regnum, value);
+ ret = otto_emdio_mmd_prefix(priv, port, regnum);
+ if (ret)
+ return ret;
+
+ ret = priv->info->write_c22(bus, port, regnum, value);
+ if (ret)
+ return ret;
+
+ return otto_emdio_mmd_postfix(priv, port, regnum);
}
}
@@ -794,6 +850,8 @@ static int otto_emdio_probe(struct platform_device *pdev)
return err;
}
+ priv->init_done = true;
+
return 0;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next 6/8] net: mdio: realtek-rtl9300: Increase MDIO timeout
2026-06-13 11:29 [PATCH net-next 0/8] net: mdio: realtek-rtl9300: Add RTL83xx support Markus Stockhausen
` (4 preceding siblings ...)
2026-06-13 11:29 ` [PATCH net-next 5/8] net: mdio: realtek-rtl9300: Add c45 over c22 mitigation Markus Stockhausen
@ 2026-06-13 11:29 ` Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 7/8] net: mdio: realtek-rtl9300: Add support for RTL838x Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 8/8] net: mdio: realtek-rtl9300: Add support for RTL839x Markus Stockhausen
7 siblings, 0 replies; 13+ messages in thread
From: Markus Stockhausen @ 2026-06-13 11:29 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
Cc: Markus Stockhausen
Access to the Realtek Otto ethernet MDIO bus must wait for a free slot
between two hardware polls. The polling sequence consists of at least
17 commands on the RTL8380 devices. This delay can be nicely seen when
disabling polling completely. The following times are measured from the
last register write that sets the command bit until hardware responds
with the command finished bit set.
- average c22 read with polling enabled on all ports: ~380us
- average c22 read with polling enabled on one port: ~380us
- average c22 read with polling completely disabled: ~180us
With a default MDIO bus frequency of 2.5Mhz the bare hardware runtime
for a single command (32 bit preamble + 32 bit data) is ~25us. So the
hardware adds quite some overhead. On top of this comes the fact that
especially the RTL838x devices are low on resources (500Mhz 4Kec core
with 16K cache).
Analysis on a RTL838x device with 28 ports gives PHY access timeouts
during one of three boots while waiting for command completion. This is
currently set to 1ms. From the above explanation one can see that there
is not much headroom left.
Increase the timeout to 5ms.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index e206ee3e2b1c..244af5fdeaf3 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -302,9 +302,9 @@ static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
u32 cmdstate;
int ret;
- /* Defensive pre check just in case something goes horrible wrong */
+ /* Defensive pre check just in case something goes horribly wrong */
ret = regmap_read_poll_timeout(priv->regmap, info->cmd_regs.c22_data,
- cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 1000);
+ cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 5000);
if (ret)
return ret;
@@ -344,7 +344,7 @@ static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
return ret;
ret = regmap_read_poll_timeout(priv->regmap, info->cmd_regs.c22_data,
- cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 1000);
+ cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 5000);
if (ret)
return ret;
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next 7/8] net: mdio: realtek-rtl9300: Add support for RTL838x
2026-06-13 11:29 [PATCH net-next 0/8] net: mdio: realtek-rtl9300: Add RTL83xx support Markus Stockhausen
` (5 preceding siblings ...)
2026-06-13 11:29 ` [PATCH net-next 6/8] net: mdio: realtek-rtl9300: Increase MDIO timeout Markus Stockhausen
@ 2026-06-13 11:29 ` Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 8/8] net: mdio: realtek-rtl9300: Add support for RTL839x Markus Stockhausen
7 siblings, 0 replies; 13+ messages in thread
From: Markus Stockhausen @ 2026-06-13 11:29 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
Cc: Markus Stockhausen
The MDIO driver has been prepared for multiple device support. Add all
required bits for the RTL838x (aka maple) series. This is straightforward
but some things are worth mentioning.
- The device has a lot in common with the RTL930x series. 28 ports, 4096
(Realtek) pages, 4 MMIO registers
- The MDIO engine has no fail bit. Thus the mask is set to zero
- There is only one SMI bus for 1G PHYs. No bus_map_base register exists.
- The setup_controller() function needs no c45 setup but must activate
the PHY access.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 109 ++++++++++++++++++++++++
1 file changed, 109 insertions(+)
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index 244af5fdeaf3..d9ff0b0aecbb 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -117,6 +117,28 @@
#include <linux/property.h>
#include <linux/regmap.h>
+#define RTL8380_NUM_BUSES 1
+#define RTL8380_NUM_PAGES 4096
+#define RTL8380_NUM_PORTS 28
+#define RTL8380_SMI_GLB_CTRL 0xa100
+#define RTL8380_SMI_PHY_PATCH_DONE BIT(15)
+#define RTL8380_SMI_ACCESS_PHY_CTRL_0 0xa1b8
+#define RTL8380_SMI_ACCESS_PHY_CTRL_1 0xa1bc
+#define RTL8380_PHY_CTRL_REG_ADDR GENMASK(24, 20)
+#define RTL8380_PHY_CTRL_PARK_PAGE GENMASK(19, 15)
+#define RTL8380_PHY_CTRL_MAIN_PAGE GENMASK(14, 3)
+#define RTL8380_PHY_CTRL_WRITE BIT(2)
+#define RTL8380_PHY_CTRL_READ 0
+#define RTL8380_PHY_CTRL_TYPE_C45 BIT(1)
+#define RTL8380_PHY_CTRL_TYPE_C22 0
+#define RTL8380_PHY_CTRL_FAIL 0 /* no fail indicator */
+#define RTL8380_SMI_ACCESS_PHY_CTRL_2 0xa1c0
+#define RTL8380_PHY_CTRL_INDATA GENMASK(31, 16)
+#define RTL8380_PHY_CTRL_DATA GENMASK(15, 0)
+#define RTL8380_SMI_ACCESS_PHY_CTRL_3 0xa1c4
+#define RTL8380_SMI_POLL_CTRL 0xa17c
+#define RTL8380_SMI_PORT0_5_ADDR_CTRL 0xa1c8
+
#define RTL9300_NUM_BUSES 4
#define RTL9300_NUM_PAGES 4096
#define RTL9300_NUM_PORTS 28
@@ -381,6 +403,60 @@ static int otto_emdio_write_cmd(struct mii_bus *bus, u32 cmd,
return otto_emdio_run_cmd(bus, cmd | priv->info->cmd_write, cmd_data);
}
+static int otto_emdio_8380_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
+{
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+ struct otto_emdio_cmd_regs cmd_data = {
+ .c22_data = FIELD_PREP(RTL8380_PHY_CTRL_REG_ADDR, regnum) |
+ FIELD_PREP(RTL8380_PHY_CTRL_PARK_PAGE, 0x1f) |
+ FIELD_PREP(RTL8380_PHY_CTRL_MAIN_PAGE, priv->page[port]),
+ .io_data = FIELD_PREP(RTL8380_PHY_CTRL_INDATA, port),
+ };
+
+ return otto_emdio_read_cmd(bus, RTL8380_PHY_CTRL_TYPE_C22, &cmd_data,
+ RTL8380_PHY_CTRL_DATA, value);
+}
+
+static int otto_emdio_8380_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)
+{
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+ struct otto_emdio_cmd_regs cmd_data = {
+ .c22_data = FIELD_PREP(RTL8380_PHY_CTRL_REG_ADDR, regnum) |
+ FIELD_PREP(RTL8380_PHY_CTRL_PARK_PAGE, 0x1f) |
+ FIELD_PREP(RTL8380_PHY_CTRL_MAIN_PAGE, priv->page[port]),
+ .io_data = FIELD_PREP(RTL8380_PHY_CTRL_INDATA, value),
+ .port_mask_low = BIT(port),
+ };
+
+ return otto_emdio_write_cmd(bus, RTL8380_PHY_CTRL_TYPE_C22, &cmd_data);
+}
+
+static int otto_emdio_8380_read_c45(struct mii_bus *bus, int port,
+ int dev_addr, int regnum, u32 *value)
+{
+ 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(RTL8380_PHY_CTRL_INDATA, port),
+ };
+
+ return otto_emdio_read_cmd(bus, RTL8380_PHY_CTRL_TYPE_C45, &cmd_data,
+ RTL8380_PHY_CTRL_DATA, value);
+}
+
+static int otto_emdio_8380_write_c45(struct mii_bus *bus, int port,
+ int dev_addr, int regnum, u16 value)
+{
+ 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(RTL8380_PHY_CTRL_INDATA, value),
+ .port_mask_low = BIT(port),
+ };
+
+ return otto_emdio_write_cmd(bus, RTL8380_PHY_CTRL_TYPE_C45, &cmd_data);
+}
+
static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
{
struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
@@ -615,6 +691,16 @@ static int otto_emdio_setup_topology(struct otto_emdio_priv *priv)
return 0;
}
+static int otto_emdio_8380_setup_controller(struct otto_emdio_priv *priv)
+{
+ /*
+ * PHY_PATCH_DONE enables PHY control via SoC. This is required for PHY access, including
+ * patching and must be set before the PHYs are probed.
+ */
+ return regmap_set_bits(priv->regmap, RTL8380_SMI_GLB_CTRL,
+ RTL8380_SMI_PHY_PATCH_DONE);
+}
+
static int otto_emdio_9300_setup_controller(struct otto_emdio_priv *priv)
{
u32 glb_ctrl_mask = 0, glb_ctrl_val = 0;
@@ -855,6 +941,28 @@ static int otto_emdio_probe(struct platform_device *pdev)
return 0;
}
+static const struct otto_emdio_info otto_emdio_8380_info = {
+ .addr_map_base = RTL8380_SMI_PORT0_5_ADDR_CTRL,
+ .cmd_fail = RTL8380_PHY_CTRL_FAIL,
+ .cmd_read = RTL8380_PHY_CTRL_READ,
+ .cmd_write = RTL8380_PHY_CTRL_WRITE,
+ .cmd_regs = {
+ .c22_data = RTL8380_SMI_ACCESS_PHY_CTRL_1,
+ .c45_data = RTL8380_SMI_ACCESS_PHY_CTRL_3,
+ .io_data = RTL8380_SMI_ACCESS_PHY_CTRL_2,
+ .port_mask_low = RTL8380_SMI_ACCESS_PHY_CTRL_0,
+ },
+ .num_buses = RTL8380_NUM_BUSES,
+ .num_pages = RTL8380_NUM_PAGES,
+ .num_ports = RTL8380_NUM_PORTS,
+ .poll_ctrl = RTL8380_SMI_POLL_CTRL,
+ .setup_controller = otto_emdio_8380_setup_controller,
+ .read_c22 = otto_emdio_8380_read_c22,
+ .read_c45 = otto_emdio_8380_read_c45,
+ .write_c22 = otto_emdio_8380_write_c22,
+ .write_c45 = otto_emdio_8380_write_c45,
+};
+
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,
@@ -905,6 +1013,7 @@ static const struct otto_emdio_info otto_emdio_9310_info = {
};
static const struct of_device_id otto_emdio_ids[] = {
+ { .compatible = "realtek,rtl8380-mdio", .data = &otto_emdio_8380_info },
{ .compatible = "realtek,rtl9301-mdio", .data = &otto_emdio_9300_info },
{ .compatible = "realtek,rtl9311-mdio", .data = &otto_emdio_9310_info },
{}
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next 8/8] net: mdio: realtek-rtl9300: Add support for RTL839x
2026-06-13 11:29 [PATCH net-next 0/8] net: mdio: realtek-rtl9300: Add RTL83xx support Markus Stockhausen
` (6 preceding siblings ...)
2026-06-13 11:29 ` [PATCH net-next 7/8] net: mdio: realtek-rtl9300: Add support for RTL838x Markus Stockhausen
@ 2026-06-13 11:29 ` Markus Stockhausen
2026-06-14 11:30 ` sashiko-bot
7 siblings, 1 reply; 13+ messages in thread
From: Markus Stockhausen @ 2026-06-13 11:29 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
Cc: Markus Stockhausen
The MDIO driver has been prepared for multiple device support. Add all
required bits for the RTL839x (aka cypress) series. This is straightforward
but some things are worth mentioning.
- The device has a lot in common with the RTL931x series. 8192 (Realtek)
pages and 7 MMIO registers
- There are two SMI buses for 1G PHYs. Neither the bus nor address map
register exists.
- The MAC layer shows link flapping when temporarily deactivating the
hardware polling for one port. Mark this in the info structure.
- The hardware has not much to configure. So the setup_controller()
function is not needed.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 104 ++++++++++++++++++++++++
1 file changed, 104 insertions(+)
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index d9ff0b0aecbb..2ab1aeb85eed 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -139,6 +139,29 @@
#define RTL8380_SMI_POLL_CTRL 0xa17c
#define RTL8380_SMI_PORT0_5_ADDR_CTRL 0xa1c8
+#define RTL8390_NUM_BUSES 2
+#define RTL8390_NUM_PAGES 8192
+#define RTL8390_NUM_PORTS 52
+#define RTL8390_BCAST_PHYID_CTRL 0x03ec
+#define RTL8390_PHYREG_ACCESS_CTRL 0x03dc
+#define RTL8390_PHY_CTRL_REG_ADDR GENMASK(9, 5)
+#define RTL8390_PHY_CTRL_PARK_PAGE GENMASK(27, 23)
+#define RTL8390_PHY_CTRL_MAIN_PAGE GENMASK(22, 10)
+#define RTL8390_PHY_CTRL_FAIL BIT(1)
+#define RTL8390_PHY_CTRL_WRITE BIT(3)
+#define RTL8390_PHY_CTRL_READ 0
+#define RTL8390_PHY_CTRL_TYPE_C45 BIT(2)
+#define RTL8390_PHY_CTRL_TYPE_C22 0
+#define RTL8390_PHYREG_CTRL 0x03e0
+#define RTL8390_PHY_CTRL_EXT_PAGE GENMASK(8, 0)
+#define RTL8390_PHYREG_DATA_CTRL 0x03f0
+#define RTL8390_PHY_CTRL_INDATA GENMASK(31, 16)
+#define RTL8390_PHY_CTRL_DATA GENMASK(15, 0)
+#define RTL8390_PHYREG_MMD_CTRL 0x03f4
+#define RTL8390_PHYREG_PORT_CTRL_LOW 0x03e4
+#define RTL8390_PHYREG_PORT_CTRL_HIGH 0x03e8
+#define RTL8390_SMI_PORT_POLLING_CTRL 0x03fc
+
#define RTL9300_NUM_BUSES 4
#define RTL9300_NUM_PAGES 4096
#define RTL9300_NUM_PORTS 28
@@ -457,6 +480,62 @@ static int otto_emdio_8380_write_c45(struct mii_bus *bus, int port,
return otto_emdio_write_cmd(bus, RTL8380_PHY_CTRL_TYPE_C45, &cmd_data);
}
+static int otto_emdio_8390_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
+{
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+ struct otto_emdio_cmd_regs cmd_data = {
+ .c22_data = FIELD_PREP(RTL8390_PHY_CTRL_REG_ADDR, regnum) |
+ FIELD_PREP(RTL8390_PHY_CTRL_MAIN_PAGE, priv->page[port]),
+ .ext_page = FIELD_PREP(RTL8390_PHY_CTRL_EXT_PAGE, 0x1ff),
+ .io_data = FIELD_PREP(RTL8390_PHY_CTRL_INDATA, port),
+ };
+
+ return otto_emdio_read_cmd(bus, RTL8390_PHY_CTRL_TYPE_C22, &cmd_data,
+ RTL8390_PHY_CTRL_DATA, value);
+}
+
+static int otto_emdio_8390_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)
+{
+ struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+ struct otto_emdio_cmd_regs cmd_data = {
+ .c22_data = FIELD_PREP(RTL8390_PHY_CTRL_REG_ADDR, regnum) |
+ FIELD_PREP(RTL8390_PHY_CTRL_MAIN_PAGE, priv->page[port]),
+ .ext_page = FIELD_PREP(RTL8390_PHY_CTRL_EXT_PAGE, 0x1ff),
+ .io_data = FIELD_PREP(RTL8390_PHY_CTRL_INDATA, value),
+ .port_mask_high = (u32)(BIT_ULL(port) >> 32),
+ .port_mask_low = (u32)(BIT_ULL(port)),
+ };
+
+ return otto_emdio_write_cmd(bus, RTL8390_PHY_CTRL_TYPE_C22, &cmd_data);
+}
+
+static int otto_emdio_8390_read_c45(struct mii_bus *bus, int port,
+ int dev_addr, int regnum, u32 *value)
+{
+ 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(RTL8390_PHY_CTRL_INDATA, port),
+ };
+
+ return otto_emdio_read_cmd(bus, RTL8390_PHY_CTRL_TYPE_C45, &cmd_data,
+ RTL8390_PHY_CTRL_DATA, value);
+}
+
+static int otto_emdio_8390_write_c45(struct mii_bus *bus, int port,
+ int dev_addr, int regnum, u16 value)
+{
+ 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(RTL8390_PHY_CTRL_INDATA, value),
+ .port_mask_high = (u32)(BIT_ULL(port) >> 32),
+ .port_mask_low = (u32)(BIT_ULL(port)),
+ };
+
+ return otto_emdio_write_cmd(bus, RTL8390_PHY_CTRL_TYPE_C45, &cmd_data);
+}
+
static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
{
struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
@@ -963,6 +1042,30 @@ static const struct otto_emdio_info otto_emdio_8380_info = {
.write_c45 = otto_emdio_8380_write_c45,
};
+static const struct otto_emdio_info otto_emdio_8390_info = {
+ .cmd_fail = RTL8390_PHY_CTRL_FAIL,
+ .cmd_read = RTL8390_PHY_CTRL_READ,
+ .cmd_write = RTL8390_PHY_CTRL_WRITE,
+ .cmd_regs = {
+ .broadcast = RTL8390_BCAST_PHYID_CTRL,
+ .c22_data = RTL8390_PHYREG_ACCESS_CTRL,
+ .c45_data = RTL8390_PHYREG_MMD_CTRL,
+ .ext_page = RTL8390_PHYREG_CTRL,
+ .io_data = RTL8390_PHYREG_DATA_CTRL,
+ .port_mask_low = RTL8390_PHYREG_PORT_CTRL_LOW,
+ .port_mask_high = RTL8390_PHYREG_PORT_CTRL_HIGH,
+ },
+ .link_flap = true,
+ .num_buses = RTL8390_NUM_BUSES,
+ .num_pages = RTL8390_NUM_PAGES,
+ .num_ports = RTL8390_NUM_PORTS,
+ .poll_ctrl = RTL8390_SMI_PORT_POLLING_CTRL,
+ .read_c22 = otto_emdio_8390_read_c22,
+ .read_c45 = otto_emdio_8390_read_c45,
+ .write_c22 = otto_emdio_8390_write_c22,
+ .write_c45 = otto_emdio_8390_write_c45,
+};
+
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,
@@ -1014,6 +1117,7 @@ static const struct otto_emdio_info otto_emdio_9310_info = {
static const struct of_device_id otto_emdio_ids[] = {
{ .compatible = "realtek,rtl8380-mdio", .data = &otto_emdio_8380_info },
+ { .compatible = "realtek,rtl8391-mdio", .data = &otto_emdio_8390_info },
{ .compatible = "realtek,rtl9301-mdio", .data = &otto_emdio_9300_info },
{ .compatible = "realtek,rtl9311-mdio", .data = &otto_emdio_9310_info },
{}
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH net-next 1/8] dt-bindings: net: realtek,rtl9301-mdio: Add RTL83xx series
2026-06-13 11:29 ` [PATCH net-next 1/8] dt-bindings: net: realtek,rtl9301-mdio: Add RTL83xx series Markus Stockhausen
@ 2026-06-13 19:07 ` Krzysztof Kozlowski
0 siblings, 0 replies; 13+ messages in thread
From: Krzysztof Kozlowski @ 2026-06-13 19:07 UTC (permalink / raw)
To: Markus Stockhausen
Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
On Sat, Jun 13, 2026 at 01:29:39PM +0200, Markus Stockhausen wrote:
- realtek,rtl9302b-mdio
> @@ -24,6 +34,8 @@ properties:
> - realtek,rtl9313-mdio
> - const: realtek,rtl9311-mdio
> - enum:
> + - realtek,rtl8380-mdio
> + - realtek,rtl8391-mdio
> - realtek,rtl9301-mdio
I don't have that context in my recent next, but anyway looks correct.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next 5/8] net: mdio: realtek-rtl9300: Add c45 over c22 mitigation
2026-06-13 11:29 ` [PATCH net-next 5/8] net: mdio: realtek-rtl9300: Add c45 over c22 mitigation Markus Stockhausen
@ 2026-06-14 11:30 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-14 11:30 UTC (permalink / raw)
To: Markus Stockhausen; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] State Machine Desynchronization and Hardware Polling Corruption on MDIO Error
--
commit 6336036b9a3d5d0b25bcff57860a0355d0dabd49
Author: Markus Stockhausen <markus.stockhausen@gmx.de>
net: mdio: realtek-rtl9300: Add c45 over c22 mitigation
Enhances the RTL9300 MDIO driver to detect the 4-step c45-over-c22 MMD
sequence. Disables hardware polling during the sequence to prevent concurrent
access from breaking the kernel's read/writes. Adds a state machine to track
sequence progress.
> diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
> index a7fd075947b67..e206ee3e2b1cc 100644
> --- a/drivers/net/mdio/mdio-realtek-rtl9300.c
> +++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
[ ... ]
> @@ -254,6 +257,43 @@ static int otto_emdio_set_port_polling(struct otto_emdio_priv *priv, int port, b
> BIT(port % 32), active);
> }
>
> +static int otto_emdio_mmd_prefix(struct otto_emdio_priv *priv, int port, int regnum)
> +{
> + u8 newstate, *state = &priv->mmd_state[port];
> + int expected, ret = 0;
> +
[ ... ]
> + expected = (*state & 1) ? MII_MMD_DATA : MII_MMD_CTRL;
> + newstate = regnum == expected ? *state + 1 : 0;
> +
> + if (newstate == 1 || newstate < *state)
> + ret = otto_emdio_set_port_polling(priv, port, !newstate);
> + *state = newstate;
> +
> + return ret;
> +}
> +
> +static int otto_emdio_mmd_postfix(struct otto_emdio_priv *priv, int port, int regnum)
> +{
> + if (priv->mmd_state[port] != 4)
> + return 0;
> +
> + priv->mmd_state[port] = 0;
> +
> + return otto_emdio_set_port_polling(priv, port, true);
> +}
> +
[ ... ]
> @@ -463,7 +503,15 @@ static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)
> if (regnum == 31)
> return priv->page[port];
>
> + ret = otto_emdio_mmd_prefix(priv, port, regnum);
> + if (ret)
> + return ret;
> +
> ret = priv->info->read_c22(bus, port, regnum, &value);
> + if (ret)
> + return ret;
[Severity: High]
If priv->info->read_c22() returns an error, the function returns early and
skips otto_emdio_mmd_postfix(), leaving priv->mmd_state[port] in an
intermediate state.
If an error occurs on the final step, the state is left at 4. A new MMD
sequence starting with register 13 will match the expected register logic
in otto_emdio_mmd_prefix() and increment the state to 5, then 6, etc.
Since otto_emdio_mmd_postfix() only re-enables polling when the state is
exactly 4, will hardware polling remain permanently disabled for this port
if an error occurs mid-sequence?
> +
> + ret = otto_emdio_mmd_postfix(priv, port, regnum);
> }
>
> return ret ? ret : value;
> @@ -472,7 +520,7 @@ static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)
> 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 port;
> + int port, ret;
>
> port = otto_emdio_phy_to_port(bus, phy_id);
> if (port < 0)
> @@ -487,7 +535,15 @@ static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16
> return 0;
> }
>
> - return priv->info->write_c22(bus, port, regnum, value);
> + ret = otto_emdio_mmd_prefix(priv, port, regnum);
> + if (ret)
> + return ret;
> +
> + ret = priv->info->write_c22(bus, port, regnum, value);
> + if (ret)
> + return ret;
[Severity: High]
Similarly, does returning early here on error skip otto_emdio_mmd_postfix()
and desynchronize the state machine?
> +
> + return otto_emdio_mmd_postfix(priv, port, regnum);
> }
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260613112946.1071411-1-markus.stockhausen@gmx.de?part=5
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next 4/8] net: mdio: realtek-rtl9300: Configure hardware polling during probing
2026-06-13 11:29 ` [PATCH net-next 4/8] net: mdio: realtek-rtl9300: Configure hardware polling during probing Markus Stockhausen
@ 2026-06-14 11:30 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-14 11:30 UTC (permalink / raw)
To: Markus Stockhausen; +Cc: conor+dt, robh, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Hardware polling mitigation is bypassed by deferred, asynchronous, or late PHY driver probing.
--
commit f401bbac0c9ccd3010cafd736b0916dccaad8dca
Author: Markus Stockhausen <markus.stockhausen@gmx.de>
net: mdio: realtek-rtl9300: Configure hardware polling during probing
This patch provides helpers to enable or disable hardware polling for a
specific port. It uses these to temporarily disable hardware polling during
MDIO bus probing to prevent interference with complex PHY initialization
sequences, re-enabling it afterwards.
> diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
> index c3a9eeca31540..a7fd075947b67 100644
> --- a/drivers/net/mdio/mdio-realtek-rtl9300.c
> +++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
[ ... ]
> @@ -750,6 +759,13 @@ static int otto_emdio_probe(struct platform_device *pdev)
> if (IS_ERR(priv->regmap))
> return PTR_ERR(priv->regmap);
>
> + /* Avoid issues with complex firmware loads. */
> + for (port = 0; port < priv->info->num_ports; port++) {
> + err = otto_emdio_set_port_polling(priv, port, false);
> + if (err)
> + return err;
> + }
> +
> platform_set_drvdata(pdev, priv);
>
> err = otto_emdio_map_ports(dev);
[ ... ]
> @@ -772,6 +788,12 @@ static int otto_emdio_probe(struct platform_device *pdev)
> return err;
> }
>
> + for_each_set_bit(port, priv->valid_ports, priv->info->num_ports) {
> + err = otto_emdio_set_port_polling(priv, port, true);
> + if (err)
> + return err;
> + }
> +
[Severity: Medium]
Could this hardware polling mitigation be bypassed if a PHY driver defers
probing or loads asynchronously?
The mitigation temporarily disables polling across the MDIO bus registration
and re-enables it here unconditionally. Since PHY devices are registered as
children of the MDIO bus, they might bind to their drivers asynchronously,
return -EPROBE_DEFER, or be bound later via sysfs.
If that happens, wouldn't the PHY initialize while the hardware polling is
already re-enabled, potentially causing the interference this patch is trying
to avoid?
> return 0;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260613112946.1071411-1-markus.stockhausen@gmx.de?part=4
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next 8/8] net: mdio: realtek-rtl9300: Add support for RTL839x
2026-06-13 11:29 ` [PATCH net-next 8/8] net: mdio: realtek-rtl9300: Add support for RTL839x Markus Stockhausen
@ 2026-06-14 11:30 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-14 11:30 UTC (permalink / raw)
To: Markus Stockhausen; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing RTL8390_PHY_CTRL_PARK_PAGE initialization corrupts PHY BMCR
--
commit af49d45856a275a9bfebf688b76101516b5e946d
Author: Markus Stockhausen <markus.stockhausen@gmx.de>
net: mdio: realtek-rtl9300: Add support for RTL839x
The MDIO driver is prepared for multiple device support by adding
required bits for the RTL839x (aka cypress) series. The hardware
shares features with the RTL931x series, including 8192 pages and
7 MMIO registers.
> diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
> index d9ff0b0aecbb1..2ab1aeb85eedb 100644
> --- a/drivers/net/mdio/mdio-realtek-rtl9300.c
> +++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
[ ... ]
> @@ -457,6 +480,62 @@ static int otto_emdio_8380_write_c45(struct mii_bus *bus, int port,
> return otto_emdio_write_cmd(bus, RTL8380_PHY_CTRL_TYPE_C45, &cmd_data);
> }
>
> +static int otto_emdio_8390_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
> +{
> + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
> + struct otto_emdio_cmd_regs cmd_data = {
> + .c22_data = FIELD_PREP(RTL8390_PHY_CTRL_REG_ADDR, regnum) |
> + FIELD_PREP(RTL8390_PHY_CTRL_MAIN_PAGE, priv->page[port]),
[Severity: High]
Does this code inadvertently omit the PARK_PAGE configuration?
Without setting FIELD_PREP(RTL8390_PHY_CTRL_PARK_PAGE, 0x1f), this field
implicitly defaults to 0. Would this cause the hardware polling engine
to write the PHY page value to register 0 (the Basic Mode Control Register)
instead of register 31?
If so, this could overwrite the BMCR on every C22 command and corrupt the
PHY's core configuration.
> + .ext_page = FIELD_PREP(RTL8390_PHY_CTRL_EXT_PAGE, 0x1ff),
> + .io_data = FIELD_PREP(RTL8390_PHY_CTRL_INDATA, port),
> + };
> +
> + return otto_emdio_read_cmd(bus, RTL8390_PHY_CTRL_TYPE_C22, &cmd_data,
> + RTL8390_PHY_CTRL_DATA, value);
> +}
> +
> +static int otto_emdio_8390_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)
> +{
> + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
> + struct otto_emdio_cmd_regs cmd_data = {
> + .c22_data = FIELD_PREP(RTL8390_PHY_CTRL_REG_ADDR, regnum) |
> + FIELD_PREP(RTL8390_PHY_CTRL_MAIN_PAGE, priv->page[port]),
[Severity: High]
Are we missing the FIELD_PREP(RTL8390_PHY_CTRL_PARK_PAGE, 0x1f) configuration
here as well?
> + .ext_page = FIELD_PREP(RTL8390_PHY_CTRL_EXT_PAGE, 0x1ff),
> + .io_data = FIELD_PREP(RTL8390_PHY_CTRL_INDATA, value),
> + .port_mask_high = (u32)(BIT_ULL(port) >> 32),
> + .port_mask_low = (u32)(BIT_ULL(port)),
> + };
> +
> + return otto_emdio_write_cmd(bus, RTL8390_PHY_CTRL_TYPE_C22, &cmd_data);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260613112946.1071411-1-markus.stockhausen@gmx.de?part=8
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-06-14 11:30 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-13 11:29 [PATCH net-next 0/8] net: mdio: realtek-rtl9300: Add RTL83xx support Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 1/8] dt-bindings: net: realtek,rtl9301-mdio: Add RTL83xx series Markus Stockhausen
2026-06-13 19:07 ` Krzysztof Kozlowski
2026-06-13 11:29 ` [PATCH net-next 2/8] net: mdio: realtek-rtl9300: Add polling documentation Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 3/8] net: mdio: realtek-rtl9300: Add page tracking Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 4/8] net: mdio: realtek-rtl9300: Configure hardware polling during probing Markus Stockhausen
2026-06-14 11:30 ` sashiko-bot
2026-06-13 11:29 ` [PATCH net-next 5/8] net: mdio: realtek-rtl9300: Add c45 over c22 mitigation Markus Stockhausen
2026-06-14 11:30 ` sashiko-bot
2026-06-13 11:29 ` [PATCH net-next 6/8] net: mdio: realtek-rtl9300: Increase MDIO timeout Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 7/8] net: mdio: realtek-rtl9300: Add support for RTL838x Markus Stockhausen
2026-06-13 11:29 ` [PATCH net-next 8/8] net: mdio: realtek-rtl9300: Add support for RTL839x Markus Stockhausen
2026-06-14 11:30 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox