* [PATCH net-next 0/3] net: phy: realtek: SerDes lane polarity and LEDs for RTL8261C/D
@ 2026-09-21 16:18 Simon Polack
2026-09-21 16:18 ` [PATCH net-next 1/3] dt-bindings: net: realtek,rtl82xx: Document RTL8261C/D PHYs Simon Polack
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Simon Polack @ 2026-09-21 16:18 UTC (permalink / raw)
To: andrew+netdev, hkallweit1, linux, davem, edumazet, kuba, pabeni,
robh, krzk+dt, conor+dt
Cc: f.fainelli, javen_xu, chad, vladimir.oltean, netdev, devicetree,
linux-kernel
The RTL8261CE on the Gemtek W1700K (Airoha AN7581) binds, but the 10G
ports don't come up: the board routes the USXGMII lanes inverted and the
driver never applies tx-polarity/rx-polarity from the device tree.
Patch 2 fixes that; the link then comes up at 1G and 10G.
The inversion is done in the PHY because the AN7581 PCS has no known
polarity control - nor does its MT7988 XFI T-PHY sibling - and Airoha's
own EN8811H driver handles it the same way.
Patch 3 is Chad Monroe's LED support, rebased onto the merged driver
with the RTL822X_VND2 LED definitions reused and a polarity bit fix.
Patch 1 adds the bindings for both.
Tested on an RTL8261CE (W1700K). Polarity is applied unchanged to the
RTL8261C, RTL8261D and RTL8261D_VM, which share config_init() but are
untested with inverted lanes. LEDs were also tested on RTL8261C
and RTL8261D boards.
The LED callbacks duplicate rtl822xb_led_*(); sharing the code means
touching the RTL8221B path, so that is left for a follow-up.
An LLM assistant (Claude) was used for rebasing, restructuring and the
changelogs. The register usage comes from existing driver sources and
was verified on hardware; the code has been reviewed by me.
Chad Monroe (1):
net: phy: realtek: add LED support for RTL8261C/D
Simon Polack (2):
dt-bindings: net: realtek,rtl82xx: Document RTL8261C/D PHYs
net: phy: realtek: apply SerDes lane polarity on RTL8261C/D
.../bindings/net/realtek,rtl82xx.yaml | 52 ++-
drivers/net/phy/realtek/Kconfig | 1 +
drivers/net/phy/realtek/realtek_main.c | 402 ++++++++++++++++--
3 files changed, 427 insertions(+), 28 deletions(-)
base-commit: 8830e65ed46de41f849eefb8ba227d4852c460f6
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH net-next 1/3] dt-bindings: net: realtek,rtl82xx: Document RTL8261C/D PHYs
2026-09-21 16:18 [PATCH net-next 0/3] net: phy: realtek: SerDes lane polarity and LEDs for RTL8261C/D Simon Polack
@ 2026-09-21 16:18 ` Simon Polack
2026-09-21 16:18 ` [PATCH net-next 2/3] net: phy: realtek: apply SerDes lane polarity on RTL8261C/D Simon Polack
2026-09-21 16:18 ` [PATCH net-next 3/3] net: phy: realtek: add LED support for RTL8261C/D Simon Polack
2 siblings, 0 replies; 4+ messages in thread
From: Simon Polack @ 2026-09-21 16:18 UTC (permalink / raw)
To: andrew+netdev, hkallweit1, linux, davem, edumazet, kuba, pabeni,
robh, krzk+dt, conor+dt
Cc: f.fainelli, javen_xu, chad, vladimir.oltean, netdev, devicetree,
linux-kernel
The RTL8261C/D family reports PHY IDs 0x001cc898 (RTL8261C/D),
0x001cc899 (RTL8261CE) and 0x001cc89a (RTL8261D_VM). Add the three
corresponding compatible strings.
These parts drive four LEDs from the VEND2 LED register block, so allow
the leds node for them the way it is already allowed for the RTL8221B.
They also accept the generic tx-polarity and rx-polarity properties to
invert the host-side SerDes lanes, which is needed on boards that route
the USXGMII lanes inverted between the SoC and the PHY. Reference
phy-common-props.yaml so those properties are described, restrict them
to the compatibles that implement them, and add an example using both.
Signed-off-by: Simon Polack <spolack+git@mailbox.org>
Assisted-by: LLM
---
.../bindings/net/realtek,rtl82xx.yaml | 52 ++++++++++++++++++-
1 file changed, 51 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/net/realtek,rtl82xx.yaml b/Documentation/devicetree/bindings/net/realtek,rtl82xx.yaml
index 07b39ef1fc62..b21efcb9b255 100644
--- a/Documentation/devicetree/bindings/net/realtek,rtl82xx.yaml
+++ b/Documentation/devicetree/bindings/net/realtek,rtl82xx.yaml
@@ -27,6 +27,9 @@ properties:
- ethernet-phy-id001c.c862
- ethernet-phy-id001c.c878
- ethernet-phy-id001c.c880
+ - ethernet-phy-id001c.c898
+ - ethernet-phy-id001c.c899
+ - ethernet-phy-id001c.c89a
- ethernet-phy-id001c.c910
- ethernet-phy-id001c.c912
- ethernet-phy-id001c.c913
@@ -74,15 +77,35 @@ unevaluatedProperties: false
allOf:
- $ref: ethernet-phy.yaml#
+ - $ref: /schemas/phy/phy-common-props.yaml#
- if:
not:
properties:
compatible:
contains:
- const: ethernet-phy-id001c.c916
+ enum:
+ - ethernet-phy-id001c.c898
+ - ethernet-phy-id001c.c899
+ - ethernet-phy-id001c.c89a
+ - ethernet-phy-id001c.c916
then:
properties:
leds: false
+ - if:
+ not:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - ethernet-phy-id001c.c898
+ - ethernet-phy-id001c.c899
+ - ethernet-phy-id001c.c89a
+ then:
+ properties:
+ rx-polarity: false
+ rx-polarity-names: false
+ tx-polarity: false
+ tx-polarity-names: false
examples:
- |
@@ -96,3 +119,30 @@ examples:
realtek,aldps-enable;
};
};
+
+ - |
+ #include <dt-bindings/leds/common.h>
+ #include <dt-bindings/phy/phy.h>
+
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-phy@0 {
+ compatible = "ethernet-phy-id001c.c899";
+ reg = <0>;
+ tx-polarity = <PHY_POL_INVERT>;
+ rx-polarity = <PHY_POL_INVERT>;
+
+ leds {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led@3 {
+ reg = <3>;
+ color = <LED_COLOR_ID_GREEN>;
+ function = LED_FUNCTION_LAN;
+ };
+ };
+ };
+ };
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH net-next 2/3] net: phy: realtek: apply SerDes lane polarity on RTL8261C/D
2026-09-21 16:18 [PATCH net-next 0/3] net: phy: realtek: SerDes lane polarity and LEDs for RTL8261C/D Simon Polack
2026-09-21 16:18 ` [PATCH net-next 1/3] dt-bindings: net: realtek,rtl82xx: Document RTL8261C/D PHYs Simon Polack
@ 2026-09-21 16:18 ` Simon Polack
2026-09-21 16:18 ` [PATCH net-next 3/3] net: phy: realtek: add LED support for RTL8261C/D Simon Polack
2 siblings, 0 replies; 4+ messages in thread
From: Simon Polack @ 2026-09-21 16:18 UTC (permalink / raw)
To: andrew+netdev, hkallweit1, linux, davem, edumazet, kuba, pabeni,
robh, krzk+dt, conor+dt
Cc: f.fainelli, javen_xu, chad, vladimir.oltean, netdev, devicetree,
linux-kernel
Boards such as the Gemtek W1700K (Airoha AN7581) route the USXGMII
lanes between the SoC and the PHY inverted and describe that in the
device tree with the generic tx-polarity/rx-polarity properties.
Nothing in the RTL8261C/D driver reads them and the Airoha PCS has no
polarity handling of its own, so the inversion is never programmed.
The copper side negotiates normally while the SerDes never trains and
the netdev stays NO-CARRIER.
Lane polarity on this part lives behind the same VEND1 SerDes command
window the RTL822x code already uses for in-band autoneg, in SerDes
registers 0x0000 (bits 9:8) and 0x00c2 (bits 14:13). The register at
VEND1 0xc1 that some other Realtek 10G PHYs use for the same purpose
is not implemented on this die and reads back as zero after a write.
Add a read side and a read-modify-write helper to the command window,
move rtl822x_serdes_write() up next to them so all three are visible
from the RTL8261 code, apply the polarity from config_init() and select
PHY_COMMON_PROPS for the property helpers. Only lanes actually described
in the firmware node are touched; a board without the properties keeps
whatever the boot loader and the PHY firmware left in place.
The bit assignment has been verified on an RTL8261CE (sub-model 0x00,
PHY ID 0x001cc899): on the W1700K, SerDes register 0x0000 goes from
0x1403 to 0x1703 and 0x00c2 from 0x0000 to 0x6000, after which VEND1
0x758d reports the SerDes linked (0x0010 -> 0x001e) and the link comes
up at 1G and 10G and passes traffic. The RTL8261C, RTL8261D and
RTL8261D_VM share rtl8261x_config_init() and get the same treatment,
but have not been tested with inverted lanes.
Assisted-by: LLM
Signed-off-by: Simon Polack <spolack+git@mailbox.org>
---
drivers/net/phy/realtek/Kconfig | 1 +
drivers/net/phy/realtek/realtek_main.c | 174 +++++++++++++++++++++----
2 files changed, 148 insertions(+), 27 deletions(-)
diff --git a/drivers/net/phy/realtek/Kconfig b/drivers/net/phy/realtek/Kconfig
index a741b34d193e..a9272aebe26d 100644
--- a/drivers/net/phy/realtek/Kconfig
+++ b/drivers/net/phy/realtek/Kconfig
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
config REALTEK_PHY
tristate "Realtek PHYs"
+ select PHY_COMMON_PROPS
select PHY_PACKAGE
help
Currently supports RTL821x/RTL822x and fast ethernet PHYs
diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index 1e670638dd1c..04d397f0a2d8 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -13,9 +13,11 @@
#include <linux/firmware.h>
#include <linux/of.h>
#include <linux/phy.h>
+#include <linux/phy/phy-common-props.h>
#include <linux/pm_wakeirq.h>
#include <linux/netdevice.h>
#include <linux/module.h>
+#include <linux/property.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/string_choices.h>
@@ -164,6 +166,7 @@
#define RTL822X_VND1_SERDES_INBAND_DISABLE 0x71d0
#define RTL822X_VND1_SERDES_INBAND_ENABLE 0x70d0
#define RTL822X_VND1_SERDES_DATA 0x7589
+#define RTL822X_VND1_SERDES_RDATA 0x758a
#define RTL822X_VND2_TO_PAGE(reg) ((reg) >> 4)
#define RTL822X_VND2_TO_PAGE_REG(reg) (16 + (((reg) & GENMASK(3, 0)) >> 1))
@@ -271,6 +274,18 @@
#define RTL8261X_INT_ALDPS_CHG BIT(9)
#define RTL8261X_INT_JABBER BIT(10)
+/* SerDes lane polarity, behind the VEND1 SerDes command window. This is not
+ * the global inversion bit that other Realtek 10G PHYs use; the bit
+ * assignment below has only been verified on an RTL8261CE reporting PHY ID
+ * 0x001cc899.
+ */
+#define RTL8261X_SERDES_POL_REG0 0x0000
+#define RTL8261X_SERDES_POL_REG0_TX BIT(8)
+#define RTL8261X_SERDES_POL_REG0_RX BIT(9)
+#define RTL8261X_SERDES_POL_REGC2 0x00c2
+#define RTL8261X_SERDES_POL_REGC2_TX BIT(14)
+#define RTL8261X_SERDES_POL_REGC2_RX BIT(13)
+
#define RTL8261X_INT_MASK_DEFAULT (RTL8261X_INT_AUTONEG_DONE | \
RTL8261X_INT_LINK_CHG | \
RTL8261X_INT_AUTONEG_ERROR | \
@@ -708,18 +723,147 @@ static int rtl8261x_config_aneg(struct phy_device *phydev)
return 0;
}
+static int rtl822x_serdes_wait(struct phy_device *phydev)
+{
+ int poll;
+
+ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+ RTL822X_VND1_SERDES_CMD, poll,
+ !(poll & RTL822X_VND1_SERDES_CMD_BUSY),
+ 500, 100000, false);
+}
+
+static int rtl822x_serdes_read(struct phy_device *phydev, u16 reg)
+{
+ int ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_ADDR, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_CMD,
+ RTL822X_VND1_SERDES_CMD_BUSY);
+ if (ret < 0)
+ return ret;
+
+ ret = rtl822x_serdes_wait(phydev);
+ if (ret < 0)
+ return ret;
+
+ return phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_RDATA);
+}
+
+static int rtl822x_serdes_write(struct phy_device *phydev, u16 reg, u16 val)
+{
+ int ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_ADDR, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_DATA, val);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_CMD,
+ RTL822X_VND1_SERDES_CMD_WRITE |
+ RTL822X_VND1_SERDES_CMD_BUSY);
+ if (ret < 0)
+ return ret;
+
+ return rtl822x_serdes_wait(phydev);
+}
+
+static int rtl822x_serdes_modify(struct phy_device *phydev, u16 reg,
+ u16 mask, u16 set)
+{
+ int val;
+
+ val = rtl822x_serdes_read(phydev, reg);
+ if (val < 0)
+ return val;
+
+ if ((val & mask) == set)
+ return 0;
+
+ return rtl822x_serdes_write(phydev, reg, (val & ~mask) | set);
+}
+
+/* The host-side SerDes lanes are only inverted when the board says so.
+ * Lanes the firmware node does not describe are left as the boot loader
+ * and PHY firmware left them.
+ */
+static int rtl8261x_config_serdes_polarity(struct phy_device *phydev)
+{
+ bool tx_present, rx_present, tx_invert = false, rx_invert = false;
+ u16 reg0_mask = 0, reg0_set = 0, regc2_mask = 0, regc2_set = 0;
+ struct fwnode_handle *fwnode = dev_fwnode(&phydev->mdio.dev);
+ const char *mode = phy_modes(phydev->interface);
+ unsigned int pol;
+ int ret;
+
+ tx_present = fwnode_property_present(fwnode, "tx-polarity");
+ rx_present = fwnode_property_present(fwnode, "rx-polarity");
+ if (!tx_present && !rx_present)
+ return 0;
+
+ if (tx_present) {
+ ret = phy_get_manual_tx_polarity(fwnode, mode, &pol);
+ if (ret)
+ return ret;
+ tx_invert = pol == PHY_POL_INVERT;
+ }
+
+ if (rx_present) {
+ ret = phy_get_manual_rx_polarity(fwnode, mode, &pol);
+ if (ret)
+ return ret;
+ rx_invert = pol == PHY_POL_INVERT;
+ }
+
+ if (tx_present) {
+ reg0_mask |= RTL8261X_SERDES_POL_REG0_TX;
+ regc2_mask |= RTL8261X_SERDES_POL_REGC2_TX;
+ if (tx_invert) {
+ reg0_set |= RTL8261X_SERDES_POL_REG0_TX;
+ regc2_set |= RTL8261X_SERDES_POL_REGC2_TX;
+ }
+ }
+
+ if (rx_present) {
+ reg0_mask |= RTL8261X_SERDES_POL_REG0_RX;
+ regc2_mask |= RTL8261X_SERDES_POL_REGC2_RX;
+ if (rx_invert) {
+ reg0_set |= RTL8261X_SERDES_POL_REG0_RX;
+ regc2_set |= RTL8261X_SERDES_POL_REGC2_RX;
+ }
+ }
+
+ ret = rtl822x_serdes_modify(phydev, RTL8261X_SERDES_POL_REG0,
+ reg0_mask, reg0_set);
+ if (ret < 0)
+ return ret;
+
+ return rtl822x_serdes_modify(phydev, RTL8261X_SERDES_POL_REGC2,
+ regc2_mask, regc2_set);
+}
+
static int rtl8261x_config_init(struct phy_device *phydev)
{
struct rtl8261x_priv *priv = phydev->priv;
+ int ret;
/* The firmware parameters are preserved across IEEE soft resets and
* suspend/resume cycles. Reloading is only necessary after a power
* cycle or hard reset.
*/
- if (priv->fw_name && !priv->fw_loaded)
- return rtl8261x_fw_load(phydev);
+ if (priv->fw_name && !priv->fw_loaded) {
+ ret = rtl8261x_fw_load(phydev);
+ if (ret)
+ return ret;
+ }
- return 0;
+ return rtl8261x_config_serdes_polarity(phydev);
}
static int rtl821x_probe(struct phy_device *phydev)
@@ -2120,30 +2264,6 @@ static int rtl822xb_config_init(struct phy_device *phydev)
return rtl822x_set_serdes_option_mode(phydev, false);
}
-static int rtl822x_serdes_write(struct phy_device *phydev, u16 reg, u16 val)
-{
- int ret, poll;
-
- ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_ADDR, reg);
- if (ret < 0)
- return ret;
-
- ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_DATA, val);
- if (ret < 0)
- return ret;
-
- ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_CMD,
- RTL822X_VND1_SERDES_CMD_WRITE |
- RTL822X_VND1_SERDES_CMD_BUSY);
- if (ret < 0)
- return ret;
-
- return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
- RTL822X_VND1_SERDES_CMD, poll,
- !(poll & RTL822X_VND1_SERDES_CMD_BUSY),
- 500, 100000, false);
-}
-
static int rtl822x_config_inband(struct phy_device *phydev, unsigned int modes)
{
return rtl822x_serdes_write(phydev, RTL822X_VND1_SERDES_ADDR_AUTONEG,
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH net-next 3/3] net: phy: realtek: add LED support for RTL8261C/D
2026-09-21 16:18 [PATCH net-next 0/3] net: phy: realtek: SerDes lane polarity and LEDs for RTL8261C/D Simon Polack
2026-09-21 16:18 ` [PATCH net-next 1/3] dt-bindings: net: realtek,rtl82xx: Document RTL8261C/D PHYs Simon Polack
2026-09-21 16:18 ` [PATCH net-next 2/3] net: phy: realtek: apply SerDes lane polarity on RTL8261C/D Simon Polack
@ 2026-09-21 16:18 ` Simon Polack
2 siblings, 0 replies; 4+ messages in thread
From: Simon Polack @ 2026-09-21 16:18 UTC (permalink / raw)
To: andrew+netdev, hkallweit1, linux, davem, edumazet, kuba, pabeni,
robh, krzk+dt, conor+dt
Cc: f.fainelli, javen_xu, chad, vladimir.oltean, netdev, devicetree,
linux-kernel
From: Chad Monroe <chad@monroe.io>
The RTL8261C/D use the same LED register block as the RTL8221B: one
control register per LED at VEND2 0xd032 + 2 * index selecting the link
speeds that light it, activity blink bits in LCR6 and per-LED polarity
and enable bits in LCR7. Compared to the RTL8221B there are four LEDs
instead of three, and two more speed bits for 5G and 10G.
Reuse the existing RTL822X_VND2_* definitions and add the two speed bits
and the enable bit. The callbacks follow rtl822xb_led_*() closely, with
two additions:
- led_polarity_set() honours active-low LEDs. The polarity bit in LCR7
doubles as the manual on/off level in led_brightness_set(), so the
configured polarity is cached in the private data and restored by
led_hw_control_set().
- led_hw_control_set() and led_brightness_set() set the output enable
bit, which firmware normally leaves set already.
The callbacks duplicate most of rtl822xb_led_*(), differing only in the
LED count, the speed mask and the LCR7 handling. Making the RTL8221B code
table-driven and sharing it would be the right end state, but that
touches a working driver and wants testing on an RTL8221B, so it is left
for a follow-up series.
The callbacks are wired to the RTL8261C_CG and RTL8261CE_CG entries,
which between them cover the parts this was tested on. RTL8261D_VM is
left out for lack of hardware.
Tested on a Gemtek W1700K (Airoha AN7581) with an RTL8261CE: index 2
drives a yellow and index 3 a green LED on each 10G port. Under hardware
control the link speed bits, the activity blink and the combined
TRIGGER_NETDEV_LINK all take effect and offloading is confirmed through
the netdev trigger. The polarity bit was verified to be active-high when
clear on that board. Also tested on AN7581 and MT7988A boards with an
RTL8261C and RTL8261D.
Signed-off-by: Chad Monroe <chad@monroe.io>
Co-developed-by: Simon Polack <spolack+git@mailbox.org>
Signed-off-by: Simon Polack <spolack+git@mailbox.org>
Assisted-by: LLM
---
drivers/net/phy/realtek/realtek_main.c | 228 +++++++++++++++++++++++++
1 file changed, 228 insertions(+)
diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index 04d397f0a2d8..b1f020aa7760 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -183,12 +183,15 @@
#define RTL822X_VND2_LCR_LINK_100 BIT(1)
#define RTL822X_VND2_LCR_LINK_1000 BIT(2)
#define RTL822X_VND2_LCR_LINK_2500 BIT(5)
+#define RTL822X_VND2_LCR_LINK_5000 BIT(6)
+#define RTL822X_VND2_LCR_LINK_10000 BIT(4)
#define RTL822X_VND2_LCR6 0xd040
#define RTL822X_VND2_LED_ACT(x) BIT(x)
#define RTL822X_VND2_LCR7 0xd044
#define RTL822X_VND2_LED_POLAR(x) BIT(x)
+#define RTL822X_VND2_LED_EN(x) BIT((x) + 4)
#define RTL8224_MII_RTCT 0x11
#define RTL8224_MII_RTCT_ENABLE BIT(0)
@@ -305,6 +308,7 @@
#define FW_SUB_MAGIC_8261D 0x32363144
#define RTL8261X_POLL_TIMEOUT_MS 100
#define RTL8261X_MAX_MMD_DEV 31
+#define RTL8261X_LED_COUNT 4
#define RTL8261C_CE_FW_NAME "rtl_nic/rtl8261c.bin"
#define RTL8261D_FW_NAME "rtl_nic/rtl8261d.bin"
@@ -364,6 +368,11 @@ struct rtl8261x_priv {
const char *fw_name;
u32 fw_sub_magic;
bool fw_loaded;
+ /* LCR7's polarity bit doubles as the manual on/off level in
+ * led_brightness_set(), so the configured polarity is kept here
+ * to restore it whenever hardware control is set up again.
+ */
+ u8 led_active_low;
};
static int rtl821x_read_page(struct phy_device *phydev)
@@ -2530,6 +2539,215 @@ static int rtl822xb_led_brightness_set(struct phy_device *phydev, u8 index,
RTL822X_VND2_LED_POLAR(index));
}
+static int rtl8261x_led_hw_is_supported(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ const unsigned long act_mask = BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX);
+ const unsigned long link_mask = BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_10) |
+ BIT(TRIGGER_NETDEV_LINK_100) |
+ BIT(TRIGGER_NETDEV_LINK_1000) |
+ BIT(TRIGGER_NETDEV_LINK_2500) |
+ BIT(TRIGGER_NETDEV_LINK_5000) |
+ BIT(TRIGGER_NETDEV_LINK_10000);
+
+ if (index >= RTL8261X_LED_COUNT)
+ return -EINVAL;
+
+ /* Filter out any other unsupported triggers. */
+ if (rules & ~(link_mask | act_mask))
+ return -EOPNOTSUPP;
+
+ /* RX and TX are not differentiated, they are not possible
+ * without combination with a link trigger.
+ */
+ if ((rules & act_mask) && !(rules & link_mask))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int rtl8261x_led_hw_control_get(struct phy_device *phydev, u8 index,
+ unsigned long *rules)
+{
+ const u16 all_links = RTL822X_VND2_LCR_LINK_10 |
+ RTL822X_VND2_LCR_LINK_100 |
+ RTL822X_VND2_LCR_LINK_1000 |
+ RTL822X_VND2_LCR_LINK_2500 |
+ RTL822X_VND2_LCR_LINK_5000 |
+ RTL822X_VND2_LCR_LINK_10000;
+ int val;
+
+ if (index >= RTL8261X_LED_COUNT)
+ return -EINVAL;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LED(index));
+ if (val < 0)
+ return val;
+
+ if (val & RTL822X_VND2_LCR_LINK_10)
+ __set_bit(TRIGGER_NETDEV_LINK_10, rules);
+
+ if (val & RTL822X_VND2_LCR_LINK_100)
+ __set_bit(TRIGGER_NETDEV_LINK_100, rules);
+
+ if (val & RTL822X_VND2_LCR_LINK_1000)
+ __set_bit(TRIGGER_NETDEV_LINK_1000, rules);
+
+ if (val & RTL822X_VND2_LCR_LINK_2500)
+ __set_bit(TRIGGER_NETDEV_LINK_2500, rules);
+
+ if (val & RTL822X_VND2_LCR_LINK_5000)
+ __set_bit(TRIGGER_NETDEV_LINK_5000, rules);
+
+ if (val & RTL822X_VND2_LCR_LINK_10000)
+ __set_bit(TRIGGER_NETDEV_LINK_10000, rules);
+
+ if ((val & all_links) == all_links)
+ __set_bit(TRIGGER_NETDEV_LINK, rules);
+
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LCR6);
+ if (val < 0)
+ return val;
+
+ if (val & RTL822X_VND2_LED_ACT(index)) {
+ __set_bit(TRIGGER_NETDEV_RX, rules);
+ __set_bit(TRIGGER_NETDEV_TX, rules);
+ }
+
+ return 0;
+}
+
+static int rtl8261x_led_hw_control_set(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ struct rtl8261x_priv *priv = phydev->priv;
+ u16 lcr7 = RTL822X_VND2_LED_EN(index);
+ u16 val = 0;
+ bool act;
+ int ret;
+
+ if (index >= RTL8261X_LED_COUNT)
+ return -EINVAL;
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
+ test_bit(TRIGGER_NETDEV_LINK_10, &rules))
+ val |= RTL822X_VND2_LCR_LINK_10;
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
+ test_bit(TRIGGER_NETDEV_LINK_100, &rules))
+ val |= RTL822X_VND2_LCR_LINK_100;
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
+ test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
+ val |= RTL822X_VND2_LCR_LINK_1000;
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
+ test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
+ val |= RTL822X_VND2_LCR_LINK_2500;
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
+ test_bit(TRIGGER_NETDEV_LINK_5000, &rules))
+ val |= RTL822X_VND2_LCR_LINK_5000;
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
+ test_bit(TRIGGER_NETDEV_LINK_10000, &rules))
+ val |= RTL822X_VND2_LCR_LINK_10000;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ RTL822X_VND2_LED(index), val);
+ if (ret < 0)
+ return ret;
+
+ act = test_bit(TRIGGER_NETDEV_RX, &rules) ||
+ test_bit(TRIGGER_NETDEV_TX, &rules);
+
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LCR6,
+ RTL822X_VND2_LED_ACT(index), act ?
+ RTL822X_VND2_LED_ACT(index) : 0);
+ if (ret < 0)
+ return ret;
+
+ /* Enable the output and restore the configured polarity, which
+ * led_brightness_set() may have used as a level.
+ */
+ if (priv->led_active_low & BIT(index))
+ lcr7 |= RTL822X_VND2_LED_POLAR(index);
+
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LCR7,
+ RTL822X_VND2_LED_EN(index) |
+ RTL822X_VND2_LED_POLAR(index), lcr7);
+}
+
+static int rtl8261x_led_brightness_set(struct phy_device *phydev, u8 index,
+ enum led_brightness value)
+{
+ struct rtl8261x_priv *priv = phydev->priv;
+ u16 lcr7 = RTL822X_VND2_LED_EN(index);
+ bool active_low, on;
+ int ret;
+
+ if (index >= RTL8261X_LED_COUNT)
+ return -EINVAL;
+
+ /* clear HW LED setup */
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ RTL822X_VND2_LED(index), 0);
+ if (ret < 0)
+ return ret;
+
+ /* clear HW LED blink */
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LCR6,
+ RTL822X_VND2_LED_ACT(index));
+ if (ret < 0)
+ return ret;
+
+ /* With no link condition selected the output sits at its idle
+ * level, so inverting the polarity turns the LED on.
+ */
+ active_low = priv->led_active_low & BIT(index);
+ on = value != LED_OFF;
+ if (on != active_low)
+ lcr7 |= RTL822X_VND2_LED_POLAR(index);
+
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LCR7,
+ RTL822X_VND2_LED_EN(index) |
+ RTL822X_VND2_LED_POLAR(index), lcr7);
+}
+
+static int rtl8261x_led_polarity_set(struct phy_device *phydev, int index,
+ unsigned long modes)
+{
+ struct rtl8261x_priv *priv = phydev->priv;
+ bool active_low = false;
+ u32 mode;
+
+ if (index >= RTL8261X_LED_COUNT)
+ return -EINVAL;
+
+ for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
+ switch (mode) {
+ case PHY_LED_ACTIVE_LOW:
+ active_low = true;
+ break;
+ case PHY_LED_ACTIVE_HIGH:
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (active_low)
+ priv->led_active_low |= BIT(index);
+ else
+ priv->led_active_low &= ~BIT(index);
+
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LCR7,
+ RTL822X_VND2_LED_POLAR(index),
+ active_low ? RTL822X_VND2_LED_POLAR(index) : 0);
+}
+
static int rtl822xb_led_hw_is_supported(struct phy_device *phydev, u8 index,
unsigned long rules)
{
@@ -3494,6 +3712,11 @@ static struct phy_driver realtek_drvs[] = {
.soft_reset = genphy_c45_pma_soft_reset,
.suspend = genphy_c45_pma_suspend,
.resume = genphy_c45_pma_resume,
+ .led_brightness_set = rtl8261x_led_brightness_set,
+ .led_hw_is_supported = rtl8261x_led_hw_is_supported,
+ .led_hw_control_get = rtl8261x_led_hw_control_get,
+ .led_hw_control_set = rtl8261x_led_hw_control_set,
+ .led_polarity_set = rtl8261x_led_polarity_set,
}, {
PHY_ID_MATCH_EXACT(RTL_8261CE_CG),
.name = "Realtek RTL8261CE 10Gbps PHY",
@@ -3507,6 +3730,11 @@ static struct phy_driver realtek_drvs[] = {
.soft_reset = genphy_c45_pma_soft_reset,
.suspend = genphy_c45_pma_suspend,
.resume = genphy_c45_pma_resume,
+ .led_brightness_set = rtl8261x_led_brightness_set,
+ .led_hw_is_supported = rtl8261x_led_hw_is_supported,
+ .led_hw_control_get = rtl8261x_led_hw_control_get,
+ .led_hw_control_set = rtl8261x_led_hw_control_set,
+ .led_polarity_set = rtl8261x_led_polarity_set,
}, {
PHY_ID_MATCH_EXACT(RTL_8261D_VM),
.name = "Realtek RTL8261D_VM 10Gbps PHY",
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-21 16:19 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-21 16:18 [PATCH net-next 0/3] net: phy: realtek: SerDes lane polarity and LEDs for RTL8261C/D Simon Polack
2026-09-21 16:18 ` [PATCH net-next 1/3] dt-bindings: net: realtek,rtl82xx: Document RTL8261C/D PHYs Simon Polack
2026-09-21 16:18 ` [PATCH net-next 2/3] net: phy: realtek: apply SerDes lane polarity on RTL8261C/D Simon Polack
2026-09-21 16:18 ` [PATCH net-next 3/3] net: phy: realtek: add LED support for RTL8261C/D Simon Polack
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®