drivers/net/ethernet/realtek/r8169_main.c | 128 +++++++++++++++++++++- 1 file changed, 126 insertions(+), 2 deletions(-)
Add support for ATF variant of the RTL8127, this uses an SFP transceiver
instead of the twisted pair one and only runs at 1Gbps or 10Gbps, only
10Gbps is supported with this patch.
The change is based on the r8127 driver package version 11.015.00
available on the Realtek website and also on
https://github.com/openwrt/rtl8127.
There's no public datasheet for the chip so this is just porting over
the original vendor code to the API and style of the upstream one.
Signed-off-by: Fabio Baltieri <fabio.baltieri@gmail.com>
---
Hi, did more tests with 1g mode on the v1 of this patch, the setting
itself works but triggering it from ethtool reliably seems problematic
due to some weird behavior of the phy code which end up reporting a
specific link speed when the link is down, making it impossible to set
it with ethool as it thinks it's already set.
Since it seems like supporting 1g properly needs expanding the scope of
the patch, I'm taking the suggestion from Heiner in v1 review and
stripped this down so it only supports 10g, which is likely what the
vast majority of the users need anyway.
Also tested this on suspend/resume, this is also affected by the wol
issue but with that bit set it now works correctly.
v1 -> v2
- stripped out 1g support
- moved the sds settings in rtl8169_init_phy() so it get called on
resume
- renamed fiber_mode to sfp_mode to avoid confusion
Cheers,
Fabio
drivers/net/ethernet/realtek/r8169_main.c | 128 +++++++++++++++++++++-
1 file changed, 126 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index d18734fe12e..e19518c7b98 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -729,6 +729,7 @@ struct rtl8169_private {
unsigned supports_gmii:1;
unsigned aspm_manageable:1;
unsigned dash_enabled:1;
+ unsigned sfp_mode:1;
dma_addr_t counters_phys_addr;
struct rtl8169_counters *counters;
struct rtl8169_tc_offsets tc_offset;
@@ -842,7 +843,8 @@ static bool rtl_supports_eee(struct rtl8169_private *tp)
{
return tp->mac_version >= RTL_GIGA_MAC_VER_34 &&
tp->mac_version != RTL_GIGA_MAC_VER_37 &&
- tp->mac_version != RTL_GIGA_MAC_VER_39;
+ tp->mac_version != RTL_GIGA_MAC_VER_39 &&
+ !tp->sfp_mode;
}
static void rtl_read_mac_from_reg(struct rtl8169_private *tp, u8 *mac, int reg)
@@ -1399,6 +1401,95 @@ DECLARE_RTL_COND(rtl_ocp_tx_cond)
return RTL_R8(tp, IBISR0) & 0x20;
}
+#define R8127_SDS_CMD 0x2348
+#define R8127_SDS_ADDR 0x234a
+#define R8127_SDS_DATA_IN 0x234c
+#define R8127_SDS_DATA_OUT 0x234e
+
+#define R8127_MAKE_SDS_ADDR(_index, _page, _reg) \
+ (((_index) << 11) | ((_page) << 5) | (_reg))
+
+#define R8127_SDS_CMD_IN BIT(0)
+#define R8127_SDS_WE_IN BIT(1)
+
+DECLARE_RTL_COND(rtl_sds_cmd_done)
+{
+ return RTL_R16(tp, R8127_SDS_CMD) & R8127_SDS_CMD_IN;
+}
+
+static u16 rtl8127_sds_phy_read(struct rtl8169_private *tp,
+ u16 index, u16 page, u16 reg)
+{
+ RTL_W16(tp, R8127_SDS_ADDR, R8127_MAKE_SDS_ADDR(index, page, reg));
+ RTL_W16(tp, R8127_SDS_CMD, R8127_SDS_CMD_IN);
+
+ if (rtl_loop_wait_low(tp, &rtl_sds_cmd_done, 1, 100))
+ return RTL_R16(tp, R8127_SDS_DATA_OUT);
+ else
+ return 0xffff;
+}
+
+static void rtl8127_sds_phy_write(struct rtl8169_private *tp,
+ u16 index, u16 page, u16 reg, u16 val)
+{
+ RTL_W16(tp, R8127_SDS_DATA_IN, val);
+ RTL_W16(tp, R8127_SDS_ADDR, R8127_MAKE_SDS_ADDR(index, page, reg));
+ RTL_W16(tp, R8127_SDS_CMD, R8127_SDS_CMD_IN | R8127_SDS_WE_IN);
+
+ rtl_loop_wait_low(tp, &rtl_sds_cmd_done, 1, 100);
+}
+
+static void rtl8127_sds_phy_modify(struct rtl8169_private *tp,
+ u16 index, u16 page, u16 addr,
+ u16 mask, u16 set)
+{
+ u16 val;
+
+ val = rtl8127_sds_phy_read(tp, index, page, addr);
+ val = (val & ~mask) | set;
+ rtl8127_sds_phy_write(tp, index, page, addr, val);
+}
+
+static void rtl8127_sds_phy_reset(struct rtl8169_private *tp)
+{
+ RTL_W8(tp, 0x2350, RTL_R8(tp, 0x2350) & ~BIT(0));
+ udelay(1);
+
+ RTL_W16(tp, 0x233a, 0x801f);
+ RTL_W8(tp, 0x2350, RTL_R8(tp, 0x2350) | BIT(0));
+ udelay(10);
+}
+
+static void rtl8127_sds_phy_exit_1g(struct rtl8169_private *tp)
+{
+ rtl8127_sds_phy_modify(tp, 0, 1, 31, BIT(3), 0);
+ rtl8127_sds_phy_modify(tp, 0, 2, 0,
+ BIT(13) | BIT(12) | BIT(6),
+ BIT(6));
+
+ rtl8127_sds_phy_reset(tp);
+}
+
+static void rtl8127_set_sds_phy_caps_10g(struct rtl8169_private *tp)
+{
+ u16 val;
+
+ RTL_W16(tp, 0x233a, 0x801a);
+
+ val = RTL_R16(tp, 0x233e);
+ val &= BIT(13) | BIT(12) | BIT(1) | BIT(0);
+ val |= BIT(12);
+ RTL_W16(tp, 0x233e, val);
+
+ r8169_mdio_write(tp, 0xc40a, 0x0);
+ r8169_mdio_write(tp, 0xc466, 0x3);
+ r8169_mdio_write(tp, 0xc808, 0x0);
+ r8169_mdio_write(tp, 0xc80a, 0x0);
+
+ val = r8168_phy_ocp_read(tp, 0xc804);
+ r8168_phy_ocp_write(tp, 0xc804, (val & ~0x000f) | 0x000c);
+}
+
static void rtl8168ep_stop_cmac(struct rtl8169_private *tp)
{
RTL_W8(tp, IBCR2, RTL_R8(tp, IBCR2) & ~0x01);
@@ -1512,6 +1603,15 @@ static enum rtl_dash_type rtl_get_dash_type(struct rtl8169_private *tp)
}
}
+static bool rtl_sfp_mode(struct rtl8169_private *tp)
+{
+ if (tp->mac_version == RTL_GIGA_MAC_VER_80 &&
+ (r8168_mac_ocp_read(tp, 0xd006) & 0xff) == 0x07)
+ return true;
+
+ return false;
+}
+
static void rtl_set_d3_pll_down(struct rtl8169_private *tp, bool enable)
{
if (tp->mac_version >= RTL_GIGA_MAC_VER_25 &&
@@ -2390,7 +2490,10 @@ static void rtl8125a_config_eee_mac(struct rtl8169_private *tp)
static void rtl8125b_config_eee_mac(struct rtl8169_private *tp)
{
- r8168_mac_ocp_modify(tp, 0xe040, 0, BIT(1) | BIT(0));
+ if (tp->sfp_mode)
+ r8168_mac_ocp_modify(tp, 0xe040, BIT(1) | BIT(0), 0);
+ else
+ r8168_mac_ocp_modify(tp, 0xe040, 0, BIT(1) | BIT(0));
}
static void rtl_rar_exgmac_set(struct rtl8169_private *tp, const u8 *addr)
@@ -2440,6 +2543,25 @@ static void rtl8169_init_phy(struct rtl8169_private *tp)
tp->pci_dev->subsystem_device == 0xe000)
phy_write_paged(tp->phydev, 0x0001, 0x10, 0xf01b);
+ if (tp->sfp_mode) {
+ rtl8127_sds_phy_exit_1g(tp);
+ rtl8127_set_sds_phy_caps_10g(tp);
+
+ phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_10baseT_Half_BIT);
+ phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_10baseT_Full_BIT);
+ phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_100baseT_Half_BIT);
+ phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_100baseT_Full_BIT);
+ phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_1000baseT_Full_BIT);
+ phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_2500baseT_Full_BIT);
+ phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_5000baseT_Full_BIT);
+
+ phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_Autoneg_BIT);
+ phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_Pause_BIT);
+ phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_Asym_Pause_BIT);
+
+ tp->phydev->autoneg = 0;
+ }
+
/* We may have called phy_speed_down before */
phy_speed_up(tp->phydev);
@@ -5453,6 +5575,8 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
tp->dash_type = rtl_get_dash_type(tp);
tp->dash_enabled = rtl_dash_is_enabled(tp);
+ tp->sfp_mode = rtl_sfp_mode(tp);
+
tp->cp_cmd = RTL_R16(tp, CPlusCmd) & CPCMD_MASK;
if (sizeof(dma_addr_t) > 4 && tp->mac_version >= RTL_GIGA_MAC_VER_18 &&
--
2.47.3
On 11/20/2025 8:50 PM, Fabio Baltieri wrote:
> Add support for ATF variant of the RTL8127, this uses an SFP transceiver
> instead of the twisted pair one and only runs at 1Gbps or 10Gbps, only
> 10Gbps is supported with this patch.
>
> The change is based on the r8127 driver package version 11.015.00
> available on the Realtek website and also on
> https://github.com/openwrt/rtl8127.
>
> There's no public datasheet for the chip so this is just porting over
> the original vendor code to the API and style of the upstream one.
>
> Signed-off-by: Fabio Baltieri <fabio.baltieri@gmail.com>
> ---
>
> Hi, did more tests with 1g mode on the v1 of this patch, the setting
> itself works but triggering it from ethtool reliably seems problematic
> due to some weird behavior of the phy code which end up reporting a
> specific link speed when the link is down, making it impossible to set
> it with ethool as it thinks it's already set.
>
> Since it seems like supporting 1g properly needs expanding the scope of
> the patch, I'm taking the suggestion from Heiner in v1 review and
> stripped this down so it only supports 10g, which is likely what the
> vast majority of the users need anyway.
>
> Also tested this on suspend/resume, this is also affected by the wol
> issue but with that bit set it now works correctly.
>
> v1 -> v2
> - stripped out 1g support
> - moved the sds settings in rtl8169_init_phy() so it get called on
> resume
> - renamed fiber_mode to sfp_mode to avoid confusion
>
> Cheers,
> Fabio
>
> drivers/net/ethernet/realtek/r8169_main.c | 128 +++++++++++++++++++++-
> 1 file changed, 126 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index d18734fe12e..e19518c7b98 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
> @@ -729,6 +729,7 @@ struct rtl8169_private {
> unsigned supports_gmii:1;
> unsigned aspm_manageable:1;
> unsigned dash_enabled:1;
> + unsigned sfp_mode:1;
> dma_addr_t counters_phys_addr;
> struct rtl8169_counters *counters;
> struct rtl8169_tc_offsets tc_offset;
> @@ -842,7 +843,8 @@ static bool rtl_supports_eee(struct rtl8169_private *tp)
> {
> return tp->mac_version >= RTL_GIGA_MAC_VER_34 &&
> tp->mac_version != RTL_GIGA_MAC_VER_37 &&
> - tp->mac_version != RTL_GIGA_MAC_VER_39;
> + tp->mac_version != RTL_GIGA_MAC_VER_39 &&
> + !tp->sfp_mode;
> }
>
> static void rtl_read_mac_from_reg(struct rtl8169_private *tp, u8 *mac, int reg)
> @@ -1399,6 +1401,95 @@ DECLARE_RTL_COND(rtl_ocp_tx_cond)
> return RTL_R8(tp, IBISR0) & 0x20;
> }
>
> +#define R8127_SDS_CMD 0x2348
> +#define R8127_SDS_ADDR 0x234a
> +#define R8127_SDS_DATA_IN 0x234c
> +#define R8127_SDS_DATA_OUT 0x234e
> +
> +#define R8127_MAKE_SDS_ADDR(_index, _page, _reg) \
> + (((_index) << 11) | ((_page) << 5) | (_reg))
> +
> +#define R8127_SDS_CMD_IN BIT(0)
> +#define R8127_SDS_WE_IN BIT(1)
> +
> +DECLARE_RTL_COND(rtl_sds_cmd_done)
> +{
> + return RTL_R16(tp, R8127_SDS_CMD) & R8127_SDS_CMD_IN;
> +}
> +
> +static u16 rtl8127_sds_phy_read(struct rtl8169_private *tp,
> + u16 index, u16 page, u16 reg)
> +{
> + RTL_W16(tp, R8127_SDS_ADDR, R8127_MAKE_SDS_ADDR(index, page, reg));
> + RTL_W16(tp, R8127_SDS_CMD, R8127_SDS_CMD_IN);
> +
> + if (rtl_loop_wait_low(tp, &rtl_sds_cmd_done, 1, 100))
> + return RTL_R16(tp, R8127_SDS_DATA_OUT);
> + else
> + return 0xffff;
> +}
> +
> +static void rtl8127_sds_phy_write(struct rtl8169_private *tp,
> + u16 index, u16 page, u16 reg, u16 val)
> +{
> + RTL_W16(tp, R8127_SDS_DATA_IN, val);
> + RTL_W16(tp, R8127_SDS_ADDR, R8127_MAKE_SDS_ADDR(index, page, reg));
> + RTL_W16(tp, R8127_SDS_CMD, R8127_SDS_CMD_IN | R8127_SDS_WE_IN);
> +
> + rtl_loop_wait_low(tp, &rtl_sds_cmd_done, 1, 100);
> +}
> +
> +static void rtl8127_sds_phy_modify(struct rtl8169_private *tp,
> + u16 index, u16 page, u16 addr,
> + u16 mask, u16 set)
> +{
> + u16 val;
> +
> + val = rtl8127_sds_phy_read(tp, index, page, addr);
> + val = (val & ~mask) | set;
> + rtl8127_sds_phy_write(tp, index, page, addr, val);
> +}
> +
> +static void rtl8127_sds_phy_reset(struct rtl8169_private *tp)
> +{
> + RTL_W8(tp, 0x2350, RTL_R8(tp, 0x2350) & ~BIT(0));
> + udelay(1);
> +
> + RTL_W16(tp, 0x233a, 0x801f);
> + RTL_W8(tp, 0x2350, RTL_R8(tp, 0x2350) | BIT(0));
> + udelay(10);
> +}
> +
> +static void rtl8127_sds_phy_exit_1g(struct rtl8169_private *tp)
> +{
> + rtl8127_sds_phy_modify(tp, 0, 1, 31, BIT(3), 0);
> + rtl8127_sds_phy_modify(tp, 0, 2, 0,
> + BIT(13) | BIT(12) | BIT(6),
> + BIT(6));
> +
> + rtl8127_sds_phy_reset(tp);
> +}
> +
> +static void rtl8127_set_sds_phy_caps_10g(struct rtl8169_private *tp)
> +{
> + u16 val;
> +
> + RTL_W16(tp, 0x233a, 0x801a);
> +
> + val = RTL_R16(tp, 0x233e);
> + val &= BIT(13) | BIT(12) | BIT(1) | BIT(0);
> + val |= BIT(12);
> + RTL_W16(tp, 0x233e, val);
> +
> + r8169_mdio_write(tp, 0xc40a, 0x0);
> + r8169_mdio_write(tp, 0xc466, 0x3);
> + r8169_mdio_write(tp, 0xc808, 0x0);
> + r8169_mdio_write(tp, 0xc80a, 0x0);
> +
This function isn't usable on RTL8127.
> + val = r8168_phy_ocp_read(tp, 0xc804);
> + r8168_phy_ocp_write(tp, 0xc804, (val & ~0x000f) | 0x000c);
> +}
> +
> static void rtl8168ep_stop_cmac(struct rtl8169_private *tp)
> {
> RTL_W8(tp, IBCR2, RTL_R8(tp, IBCR2) & ~0x01);
> @@ -1512,6 +1603,15 @@ static enum rtl_dash_type rtl_get_dash_type(struct rtl8169_private *tp)
> }
> }
>
> +static bool rtl_sfp_mode(struct rtl8169_private *tp)
> +{
> + if (tp->mac_version == RTL_GIGA_MAC_VER_80 &&
> + (r8168_mac_ocp_read(tp, 0xd006) & 0xff) == 0x07)
> + return true;
> +
> + return false;
> +}
> +
> static void rtl_set_d3_pll_down(struct rtl8169_private *tp, bool enable)
> {
> if (tp->mac_version >= RTL_GIGA_MAC_VER_25 &&
> @@ -2390,7 +2490,10 @@ static void rtl8125a_config_eee_mac(struct rtl8169_private *tp)
>
> static void rtl8125b_config_eee_mac(struct rtl8169_private *tp)
> {
> - r8168_mac_ocp_modify(tp, 0xe040, 0, BIT(1) | BIT(0));
> + if (tp->sfp_mode)
> + r8168_mac_ocp_modify(tp, 0xe040, BIT(1) | BIT(0), 0);
> + else
> + r8168_mac_ocp_modify(tp, 0xe040, 0, BIT(1) | BIT(0));
> }
>
> static void rtl_rar_exgmac_set(struct rtl8169_private *tp, const u8 *addr)
> @@ -2440,6 +2543,25 @@ static void rtl8169_init_phy(struct rtl8169_private *tp)
> tp->pci_dev->subsystem_device == 0xe000)
> phy_write_paged(tp->phydev, 0x0001, 0x10, 0xf01b);
>
> + if (tp->sfp_mode) {
> + rtl8127_sds_phy_exit_1g(tp);
> + rtl8127_set_sds_phy_caps_10g(tp);
> +
> + phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_10baseT_Half_BIT);
> + phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_10baseT_Full_BIT);
> + phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_100baseT_Half_BIT);
> + phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_100baseT_Full_BIT);
> + phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_1000baseT_Full_BIT);
> + phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_2500baseT_Full_BIT);
> + phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_5000baseT_Full_BIT);
> +
> + phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_Autoneg_BIT);
> + phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_Pause_BIT);
> + phy_remove_link_mode(tp->phydev, ETHTOOL_LINK_MODE_Asym_Pause_BIT);
> +
> + tp->phydev->autoneg = 0;
> + }
> +
> /* We may have called phy_speed_down before */
> phy_speed_up(tp->phydev);
>
> @@ -5453,6 +5575,8 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
> tp->dash_type = rtl_get_dash_type(tp);
> tp->dash_enabled = rtl_dash_is_enabled(tp);
>
> + tp->sfp_mode = rtl_sfp_mode(tp);
> +
> tp->cp_cmd = RTL_R16(tp, CPlusCmd) & CPCMD_MASK;
>
> if (sizeof(dma_addr_t) > 4 && tp->mac_version >= RTL_GIGA_MAC_VER_18 &&
Your patch is a good starting point, however it needs more thoughts / work how to somewhat cleanly
integrate Realtek's design with phylib. E.g. you would want to set 10G and aneg off via ethtool,
but that's not supported by phy_ethtool_ksettings_set().
I'll prepare patches and, if you don't mind, would provide them to you for testing, as I don't
own this hw.
At least you have a working solution for the time being. Thanks!
On Thu, Nov 20, 2025 at 09:55:26PM +0100, Heiner Kallweit wrote: > Your patch is a good starting point, however it needs more thoughts / work how to somewhat cleanly > integrate Realtek's design with phylib. E.g. you would want to set 10G and aneg off via ethtool, > but that's not supported by phy_ethtool_ksettings_set(). > I'll prepare patches and, if you don't mind, would provide them to you for testing, as I don't > own this hw. > At least you have a working solution for the time being. Thanks! Sure thing, that works for me. Thanks for the help on this so far. Cheers, Fabio
On 11/21/2025 9:28 PM, Fabio Baltieri wrote:
> On Thu, Nov 20, 2025 at 09:55:26PM +0100, Heiner Kallweit wrote:
>> Your patch is a good starting point, however it needs more thoughts / work how to somewhat cleanly
>> integrate Realtek's design with phylib. E.g. you would want to set 10G and aneg off via ethtool,
>> but that's not supported by phy_ethtool_ksettings_set().
>> I'll prepare patches and, if you don't mind, would provide them to you for testing, as I don't
>> own this hw.
>> At least you have a working solution for the time being. Thanks!
>
> Sure thing, that works for me.
>
This is a version with better integration with phylib, and with 10G support only.
Maybe I simplified the PHY/Serdes initialization too much, we'll see.
A difference to your version is that via ethtool you now can and have to set autoneg to off.
I'd appreciate if you could give it a try and provide a full dmesg log and output of "ethtool <if>".
Note: This patch applies on top of net-next and linux-next. However, if you apply it on top
of some other recent kernel version, conflicts should be easy to resolve.
---
drivers/net/ethernet/realtek/r8169_main.c | 65 +++++++++++++++++++++--
drivers/net/phy/realtek/realtek_main.c | 26 +++++++++
2 files changed, 87 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 97dbe8f8933..dbcf3d26167 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -731,6 +731,7 @@ struct rtl8169_private {
unsigned supports_gmii:1;
unsigned aspm_manageable:1;
unsigned dash_enabled:1;
+ bool sfp_mode:1;
dma_addr_t counters_phys_addr;
struct rtl8169_counters *counters;
struct rtl8169_tc_offsets tc_offset;
@@ -1094,6 +1095,10 @@ static int r8168_phy_ocp_read(struct rtl8169_private *tp, u32 reg)
if (rtl_ocp_reg_failure(reg))
return 0;
+ /* Return dummy MII_PHYSID2 in SFP mode to match SFP PHY driver */
+ if (tp->sfp_mode && reg == (OCP_STD_PHY_BASE + 2 * MII_PHYSID2))
+ return 0xcbff;
+
RTL_W32(tp, GPHY_OCP, reg << 15);
return rtl_loop_wait_high(tp, &rtl_ocp_gphy_cond, 25, 10) ?
@@ -2305,6 +2310,41 @@ static void rtl8169_get_eth_ctrl_stats(struct net_device *dev,
le32_to_cpu(tp->counters->rx_unknown_opcode);
}
+static int rtl8169_set_link_ksettings(struct net_device *ndev,
+ const struct ethtool_link_ksettings *cmd)
+{
+ struct rtl8169_private *tp = netdev_priv(ndev);
+ struct phy_device *phydev = tp->phydev;
+ int duplex = cmd->base.duplex;
+ int speed = cmd->base.speed;
+
+ if (!tp->sfp_mode)
+ return phy_ethtool_ksettings_set(phydev, cmd);
+
+ if (cmd->base.autoneg != AUTONEG_DISABLE)
+ return -EINVAL;
+
+ if (!phy_check_valid(speed, duplex, phydev->supported))
+ return -EINVAL;
+
+ mutex_lock(&phydev->lock);
+
+ phydev->autoneg = AUTONEG_DISABLE;
+ phydev->speed = speed;
+ phydev->duplex = duplex;
+
+ if (phy_is_started(phydev)) {
+ phydev->state = PHY_UP;
+ phy_trigger_machine(phydev);
+ } else {
+ _phy_start_aneg(phydev);
+ }
+
+ mutex_unlock(&phydev->lock);
+
+ return 0;
+}
+
static const struct ethtool_ops rtl8169_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_MAX_FRAMES,
@@ -2324,7 +2364,7 @@ static const struct ethtool_ops rtl8169_ethtool_ops = {
.get_eee = rtl8169_get_eee,
.set_eee = rtl8169_set_eee,
.get_link_ksettings = phy_ethtool_get_link_ksettings,
- .set_link_ksettings = phy_ethtool_set_link_ksettings,
+ .set_link_ksettings = rtl8169_set_link_ksettings,
.get_ringparam = rtl8169_get_ringparam,
.get_pause_stats = rtl8169_get_pause_stats,
.get_pauseparam = rtl8169_get_pauseparam,
@@ -2436,6 +2476,22 @@ static void rtl_schedule_task(struct rtl8169_private *tp, enum rtl_flag flag)
clear_bit(flag, tp->wk.flags);
}
+static void r8127_init_sfp_10g(struct rtl8169_private *tp)
+{
+ int val;
+
+ RTL_W16(tp, 0x233a, 0x801a);
+ RTL_W16(tp, 0x233e, (RTL_R16(tp, 0x233e) & ~0x3003) | 0x1000);
+
+ r8168_phy_ocp_write(tp, 0xc40a, 0x0000);
+ r8168_phy_ocp_write(tp, 0xc466, 0x0003);
+ r8168_phy_ocp_write(tp, 0xc808, 0x0000);
+ r8168_phy_ocp_write(tp, 0xc80a, 0x0000);
+
+ val = r8168_phy_ocp_read(tp, 0xc804);
+ r8168_phy_ocp_write(tp, 0xc804, (val & ~0x000f) | 0x000c);
+}
+
static void rtl8169_init_phy(struct rtl8169_private *tp)
{
r8169_hw_phy_config(tp, tp->phydev, tp->mac_version);
@@ -2452,6 +2508,9 @@ static void rtl8169_init_phy(struct rtl8169_private *tp)
tp->pci_dev->subsystem_device == 0xe000)
phy_write_paged(tp->phydev, 0x0001, 0x10, 0xf01b);
+ if (tp->sfp_mode)
+ r8127_init_sfp_10g(tp);
+
/* We may have called phy_speed_down before */
phy_speed_up(tp->phydev);
@@ -5460,13 +5519,11 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
}
tp->aspm_manageable = !rc;
- /* Fiber mode on RTL8127AF isn't supported */
if (rtl_is_8125(tp)) {
u16 data = r8168_mac_ocp_read(tp, 0xd006);
if ((data & 0xff) == 0x07)
- return dev_err_probe(&pdev->dev, -ENODEV,
- "Fiber mode not supported\n");
+ tp->sfp_mode = true;
}
tp->dash_type = rtl_get_dash_type(tp);
diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index 67ecf3d4af2..296559dbc7f 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -1977,6 +1977,18 @@ static irqreturn_t rtl8221b_handle_interrupt(struct phy_device *phydev)
return IRQ_HANDLED;
}
+static int rtlgen_sfp_get_features(struct phy_device *phydev)
+{
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+ phydev->supported);
+ return 0;
+}
+
+static int rtlgen_sfp_config_aneg(struct phy_device *phydev)
+{
+ return 0;
+}
+
static struct phy_driver realtek_drvs[] = {
{
PHY_ID_MATCH_EXACT(0x00008201),
@@ -2212,6 +2224,20 @@ static struct phy_driver realtek_drvs[] = {
.write_page = rtl821x_write_page,
.read_mmd = rtl822x_read_mmd,
.write_mmd = rtl822x_write_mmd,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001ccbff),
+ .name = "Realtek SFP PHY Mode",
+ .flags = PHY_IS_INTERNAL,
+ .probe = rtl822x_probe,
+ .get_features = rtlgen_sfp_get_features,
+ .config_aneg = rtlgen_sfp_config_aneg,
+ .read_status = rtl822x_read_status,
+ .suspend = genphy_suspend,
+ .resume = rtlgen_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ .read_mmd = rtl822x_read_mmd,
+ .write_mmd = rtl822x_write_mmd,
}, {
PHY_ID_MATCH_EXACT(0x001ccad0),
.name = "RTL8224 2.5Gbps PHY",
--
2.52.0
Hi Heiner,
On Sun, Nov 23, 2025 at 04:58:23PM +0100, Heiner Kallweit wrote:
> This is a version with better integration with phylib, and with 10G support only.
> Maybe I simplified the PHY/Serdes initialization too much, we'll see.
> A difference to your version is that via ethtool you now can and have to set autoneg to off.
>
> I'd appreciate if you could give it a try and provide a full dmesg log and output of "ethtool <if>".
>
> Note: This patch applies on top of net-next and linux-next. However, if you apply it on top
> of some other recent kernel version, conflicts should be easy to resolve.
Thanks for the patch, ran some initial tests, I'm on Linus tree for
other reasons but applied 3dc2a17efc5f, 1479493c91fc, 28c0074fd4b7 and
the recent suspend fix, then your patch applies cleanly.
Here's ethtool output:
# ethtool eth1
Settings for eth1:
Supported ports: [ ]
Supported link modes: 10000baseT/Full
Supported pause frame use: Symmetric Receive-only
Supports auto-negotiation: No
Supported FEC modes: Not reported
Advertised link modes: 10000baseT/Full
Advertised pause frame use: Symmetric Receive-only
Advertised auto-negotiation: No
Advertised FEC modes: Not reported
Speed: 10000Mb/s
Duplex: Full
Auto-negotiation: off
master-slave status: master
Port: Twisted Pair
PHYAD: 0
Transceiver: internal
MDI-X: Unknown
Supports Wake-on: pumbg
Wake-on: d
Link detected: yes
The phy is identified correctly:
[ 1563.678133] Realtek SFP PHY Mode r8169-1-500:00: attached PHY driver (mii_bus:phy_addr=r8169-1-500:00, irq=MAC)
That said I've observed two issues with the current patch:
1. the link on the other end is flapping, I've seen this while working
on the original patch and seems to be due to the EEE settings, it is
addressed by:
@@ -2439,7 +2439,10 @@ static void rtl8125a_config_eee_mac(struct rtl8169_private *tp)
static void rtl8125b_config_eee_mac(struct rtl8169_private *tp)
{
- r8168_mac_ocp_modify(tp, 0xe040, 0, BIT(1) | BIT(0));
+ if (tp->sfp_mode)
+ r8168_mac_ocp_modify(tp, 0xe040, BIT(1) | BIT(0), 0);
+ else
+ r8168_mac_ocp_modify(tp, 0xe040, 0, BIT(1) | BIT(0));
}
static void rtl_rar_exgmac_set(struct rtl8169_private *tp, const u8 *addr)
2. the link is lost after a module reload or after an ip link down and
up, the driver logs "Link is Down" and stays there until the cable is
unplugged and re-plugged. This seems to be addressed by the code that
was in rtl8127_sds_phy_reset(), re-adding that code fixes it:
@@ -2477,6 +2480,13 @@ static void r8127_init_sfp_10g(struct rtl8169_private *tp)
{
int val;
+ RTL_W8(tp, 0x2350, RTL_R8(tp, 0x2350) & ~BIT(0));
+ udelay(1);
+
+ RTL_W16(tp, 0x233a, 0x801f);
+ RTL_W8(tp, 0x2350, RTL_R8(tp, 0x2350) | BIT(0));
+ udelay(10);
+
RTL_W16(tp, 0x233a, 0x801a);
RTL_W16(tp, 0x233e, (RTL_R16(tp, 0x233e) & ~0x3003) | 0x1000);
Guess the phy needs a reset after all.
With these two applied it seems to be working fine, tested suspend as
well.
Would you integrate these two or want to try me something different?
On 11/23/2025 7:41 PM, Fabio Baltieri wrote:
> Hi Heiner,
>
> On Sun, Nov 23, 2025 at 04:58:23PM +0100, Heiner Kallweit wrote:
>> This is a version with better integration with phylib, and with 10G support only.
>> Maybe I simplified the PHY/Serdes initialization too much, we'll see.
>> A difference to your version is that via ethtool you now can and have to set autoneg to off.
>>
>> I'd appreciate if you could give it a try and provide a full dmesg log and output of "ethtool <if>".
>>
>> Note: This patch applies on top of net-next and linux-next. However, if you apply it on top
>> of some other recent kernel version, conflicts should be easy to resolve.
>
> Thanks for the patch, ran some initial tests, I'm on Linus tree for
> other reasons but applied 3dc2a17efc5f, 1479493c91fc, 28c0074fd4b7 and
> the recent suspend fix, then your patch applies cleanly.
>
> Here's ethtool output:
>
> # ethtool eth1
> Settings for eth1:
> Supported ports: [ ]
> Supported link modes: 10000baseT/Full
> Supported pause frame use: Symmetric Receive-only
> Supports auto-negotiation: No
> Supported FEC modes: Not reported
> Advertised link modes: 10000baseT/Full
> Advertised pause frame use: Symmetric Receive-only
> Advertised auto-negotiation: No
> Advertised FEC modes: Not reported
> Speed: 10000Mb/s
> Duplex: Full
> Auto-negotiation: off
> master-slave status: master
> Port: Twisted Pair
> PHYAD: 0
> Transceiver: internal
> MDI-X: Unknown
> Supports Wake-on: pumbg
> Wake-on: d
> Link detected: yes
>
> The phy is identified correctly:
>
> [ 1563.678133] Realtek SFP PHY Mode r8169-1-500:00: attached PHY driver (mii_bus:phy_addr=r8169-1-500:00, irq=MAC)
>
> That said I've observed two issues with the current patch:
>
> 1. the link on the other end is flapping, I've seen this while working
> on the original patch and seems to be due to the EEE settings, it is
> addressed by:
>
> @@ -2439,7 +2439,10 @@ static void rtl8125a_config_eee_mac(struct rtl8169_private *tp)
>
> static void rtl8125b_config_eee_mac(struct rtl8169_private *tp)
> {
> - r8168_mac_ocp_modify(tp, 0xe040, 0, BIT(1) | BIT(0));
> + if (tp->sfp_mode)
> + r8168_mac_ocp_modify(tp, 0xe040, BIT(1) | BIT(0), 0);
> + else
> + r8168_mac_ocp_modify(tp, 0xe040, 0, BIT(1) | BIT(0));
> }
>
> static void rtl_rar_exgmac_set(struct rtl8169_private *tp, const u8 *addr)
>
>
> 2. the link is lost after a module reload or after an ip link down and
> up, the driver logs "Link is Down" and stays there until the cable is
> unplugged and re-plugged. This seems to be addressed by the code that
> was in rtl8127_sds_phy_reset(), re-adding that code fixes it:
>
> @@ -2477,6 +2480,13 @@ static void r8127_init_sfp_10g(struct rtl8169_private *tp)
> {
> int val;
>
> + RTL_W8(tp, 0x2350, RTL_R8(tp, 0x2350) & ~BIT(0));
> + udelay(1);
> +
> + RTL_W16(tp, 0x233a, 0x801f);
> + RTL_W8(tp, 0x2350, RTL_R8(tp, 0x2350) | BIT(0));
> + udelay(10);
> +
> RTL_W16(tp, 0x233a, 0x801a);
> RTL_W16(tp, 0x233e, (RTL_R16(tp, 0x233e) & ~0x3003) | 0x1000);
>
> Guess the phy needs a reset after all.
>
> With these two applied it seems to be working fine, tested suspend as
> well.
>
> Would you integrate these two or want to try me something different?
Thanks a lot for the valuable feedback!
I added the SDS PHY reset to the patch, and improved MAC EEE handling
in a second patch, incl. what you mentioned.
Patches should fully cover your use case now. Please give it a try.
From fdd7aa806184f0bc8d8feb80cb2dd2c5397ee134 Mon Sep 17 00:00:00 2001
From: Heiner Kallweit <hkallweit1@gmail.com>
Date: Sun, 23 Nov 2025 22:29:25 +0100
Subject: [PATCH 1/2] r8169: add support for 10G SFP mode on RTL8127ATF
Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
---
drivers/net/ethernet/realtek/r8169_main.c | 77 +++++++++++++++++++++--
drivers/net/phy/realtek/realtek_main.c | 26 ++++++++
2 files changed, 99 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 97dbe8f8933..cbacf1ef87a 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -731,6 +731,7 @@ struct rtl8169_private {
unsigned supports_gmii:1;
unsigned aspm_manageable:1;
unsigned dash_enabled:1;
+ bool sfp_mode:1;
dma_addr_t counters_phys_addr;
struct rtl8169_counters *counters;
struct rtl8169_tc_offsets tc_offset;
@@ -1094,6 +1095,10 @@ static int r8168_phy_ocp_read(struct rtl8169_private *tp, u32 reg)
if (rtl_ocp_reg_failure(reg))
return 0;
+ /* Return dummy MII_PHYSID2 in SFP mode to match SFP PHY driver */
+ if (tp->sfp_mode && reg == (OCP_STD_PHY_BASE + 2 * MII_PHYSID2))
+ return 0xcbff;
+
RTL_W32(tp, GPHY_OCP, reg << 15);
return rtl_loop_wait_high(tp, &rtl_ocp_gphy_cond, 25, 10) ?
@@ -2305,6 +2310,41 @@ static void rtl8169_get_eth_ctrl_stats(struct net_device *dev,
le32_to_cpu(tp->counters->rx_unknown_opcode);
}
+static int rtl8169_set_link_ksettings(struct net_device *ndev,
+ const struct ethtool_link_ksettings *cmd)
+{
+ struct rtl8169_private *tp = netdev_priv(ndev);
+ struct phy_device *phydev = tp->phydev;
+ int duplex = cmd->base.duplex;
+ int speed = cmd->base.speed;
+
+ if (!tp->sfp_mode)
+ return phy_ethtool_ksettings_set(phydev, cmd);
+
+ if (cmd->base.autoneg != AUTONEG_DISABLE)
+ return -EINVAL;
+
+ if (!phy_check_valid(speed, duplex, phydev->supported))
+ return -EINVAL;
+
+ mutex_lock(&phydev->lock);
+
+ phydev->autoneg = AUTONEG_DISABLE;
+ phydev->speed = speed;
+ phydev->duplex = duplex;
+
+ if (phy_is_started(phydev)) {
+ phydev->state = PHY_UP;
+ phy_trigger_machine(phydev);
+ } else {
+ _phy_start_aneg(phydev);
+ }
+
+ mutex_unlock(&phydev->lock);
+
+ return 0;
+}
+
static const struct ethtool_ops rtl8169_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_MAX_FRAMES,
@@ -2324,7 +2364,7 @@ static const struct ethtool_ops rtl8169_ethtool_ops = {
.get_eee = rtl8169_get_eee,
.set_eee = rtl8169_set_eee,
.get_link_ksettings = phy_ethtool_get_link_ksettings,
- .set_link_ksettings = phy_ethtool_set_link_ksettings,
+ .set_link_ksettings = rtl8169_set_link_ksettings,
.get_ringparam = rtl8169_get_ringparam,
.get_pause_stats = rtl8169_get_pause_stats,
.get_pauseparam = rtl8169_get_pauseparam,
@@ -2436,6 +2476,34 @@ static void rtl_schedule_task(struct rtl8169_private *tp, enum rtl_flag flag)
clear_bit(flag, tp->wk.flags);
}
+static void r8127_sfp_sds_phy_reset(struct rtl8169_private *tp)
+{
+ RTL_W8(tp, 0x2350, RTL_R8(tp, 0x2350) & ~BIT(0));
+ udelay(1);
+
+ RTL_W16(tp, 0x233a, 0x801f);
+ RTL_W8(tp, 0x2350, RTL_R8(tp, 0x2350) | BIT(0));
+ udelay(10);
+}
+
+static void r8127_sfp_init_10g(struct rtl8169_private *tp)
+{
+ int val;
+
+ r8127_sfp_sds_phy_reset(tp);
+
+ RTL_W16(tp, 0x233a, 0x801a);
+ RTL_W16(tp, 0x233e, (RTL_R16(tp, 0x233e) & ~0x3003) | 0x1000);
+
+ r8168_phy_ocp_write(tp, 0xc40a, 0x0000);
+ r8168_phy_ocp_write(tp, 0xc466, 0x0003);
+ r8168_phy_ocp_write(tp, 0xc808, 0x0000);
+ r8168_phy_ocp_write(tp, 0xc80a, 0x0000);
+
+ val = r8168_phy_ocp_read(tp, 0xc804);
+ r8168_phy_ocp_write(tp, 0xc804, (val & ~0x000f) | 0x000c);
+}
+
static void rtl8169_init_phy(struct rtl8169_private *tp)
{
r8169_hw_phy_config(tp, tp->phydev, tp->mac_version);
@@ -2452,6 +2520,9 @@ static void rtl8169_init_phy(struct rtl8169_private *tp)
tp->pci_dev->subsystem_device == 0xe000)
phy_write_paged(tp->phydev, 0x0001, 0x10, 0xf01b);
+ if (tp->sfp_mode)
+ r8127_sfp_init_10g(tp);
+
/* We may have called phy_speed_down before */
phy_speed_up(tp->phydev);
@@ -5460,13 +5531,11 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
}
tp->aspm_manageable = !rc;
- /* Fiber mode on RTL8127AF isn't supported */
if (rtl_is_8125(tp)) {
u16 data = r8168_mac_ocp_read(tp, 0xd006);
if ((data & 0xff) == 0x07)
- return dev_err_probe(&pdev->dev, -ENODEV,
- "Fiber mode not supported\n");
+ tp->sfp_mode = true;
}
tp->dash_type = rtl_get_dash_type(tp);
diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index 67ecf3d4af2..296559dbc7f 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -1977,6 +1977,18 @@ static irqreturn_t rtl8221b_handle_interrupt(struct phy_device *phydev)
return IRQ_HANDLED;
}
+static int rtlgen_sfp_get_features(struct phy_device *phydev)
+{
+ linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+ phydev->supported);
+ return 0;
+}
+
+static int rtlgen_sfp_config_aneg(struct phy_device *phydev)
+{
+ return 0;
+}
+
static struct phy_driver realtek_drvs[] = {
{
PHY_ID_MATCH_EXACT(0x00008201),
@@ -2212,6 +2224,20 @@ static struct phy_driver realtek_drvs[] = {
.write_page = rtl821x_write_page,
.read_mmd = rtl822x_read_mmd,
.write_mmd = rtl822x_write_mmd,
+ }, {
+ PHY_ID_MATCH_EXACT(0x001ccbff),
+ .name = "Realtek SFP PHY Mode",
+ .flags = PHY_IS_INTERNAL,
+ .probe = rtl822x_probe,
+ .get_features = rtlgen_sfp_get_features,
+ .config_aneg = rtlgen_sfp_config_aneg,
+ .read_status = rtl822x_read_status,
+ .suspend = genphy_suspend,
+ .resume = rtlgen_resume,
+ .read_page = rtl821x_read_page,
+ .write_page = rtl821x_write_page,
+ .read_mmd = rtl822x_read_mmd,
+ .write_mmd = rtl822x_write_mmd,
}, {
PHY_ID_MATCH_EXACT(0x001ccad0),
.name = "RTL8224 2.5Gbps PHY",
--
2.52.0
From 9de6ee722512946f9d090bd65070a05dc66e1214 Mon Sep 17 00:00:00 2001
From: Heiner Kallweit <hkallweit1@gmail.com>
Date: Sun, 23 Nov 2025 23:39:43 +0100
Subject: [PATCH 2/2] r8169: improve MAC EEE handling
Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
---
drivers/net/ethernet/realtek/r8169_main.c | 73 +++++++++++------------
1 file changed, 36 insertions(+), 37 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index cbacf1ef87a..33a83bf9035 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -2425,26 +2425,6 @@ void r8169_apply_firmware(struct rtl8169_private *tp)
}
}
-static void rtl8168_config_eee_mac(struct rtl8169_private *tp)
-{
- /* Adjust EEE LED frequency */
- if (tp->mac_version != RTL_GIGA_MAC_VER_38)
- RTL_W8(tp, EEE_LED, RTL_R8(tp, EEE_LED) & ~0x07);
-
- rtl_eri_set_bits(tp, 0x1b0, 0x0003);
-}
-
-static void rtl8125a_config_eee_mac(struct rtl8169_private *tp)
-{
- r8168_mac_ocp_modify(tp, 0xe040, 0, BIT(1) | BIT(0));
- r8168_mac_ocp_modify(tp, 0xeb62, 0, BIT(2) | BIT(1));
-}
-
-static void rtl8125b_config_eee_mac(struct rtl8169_private *tp)
-{
- r8168_mac_ocp_modify(tp, 0xe040, 0, BIT(1) | BIT(0));
-}
-
static void rtl_rar_exgmac_set(struct rtl8169_private *tp, const u8 *addr)
{
rtl_eri_write(tp, 0xe0, ERIAR_MASK_1111, get_unaligned_le32(addr));
@@ -3253,8 +3233,6 @@ static void rtl_hw_start_8168e_2(struct rtl8169_private *tp)
RTL_W8(tp, MCU, RTL_R8(tp, MCU) & ~NOW_IS_OOB);
- rtl8168_config_eee_mac(tp);
-
RTL_W8(tp, DLLPR, RTL_R8(tp, DLLPR) | PFM_EN);
RTL_W32(tp, MISC, RTL_R32(tp, MISC) | PWM_EN);
rtl_mod_config5(tp, Spi_en, 0);
@@ -3279,8 +3257,6 @@ static void rtl_hw_start_8168f(struct rtl8169_private *tp)
RTL_W8(tp, DLLPR, RTL_R8(tp, DLLPR) | PFM_EN);
RTL_W32(tp, MISC, RTL_R32(tp, MISC) | PWM_EN);
rtl_mod_config5(tp, Spi_en, 0);
-
- rtl8168_config_eee_mac(tp);
}
static void rtl_hw_start_8168f_1(struct rtl8169_private *tp)
@@ -3330,8 +3306,6 @@ static void rtl_hw_start_8168g(struct rtl8169_private *tp)
rtl_eri_write(tp, 0xc0, ERIAR_MASK_0011, 0x0000);
rtl_eri_write(tp, 0xb8, ERIAR_MASK_0011, 0x0000);
- rtl8168_config_eee_mac(tp);
-
rtl_w0w1_eri(tp, 0x2fc, 0x01, 0x06);
rtl_eri_clear_bits(tp, 0x1b0, BIT(12));
@@ -3472,8 +3446,6 @@ static void rtl_hw_start_8168h_1(struct rtl8169_private *tp)
rtl_eri_write(tp, 0xc0, ERIAR_MASK_0011, 0x0000);
rtl_eri_write(tp, 0xb8, ERIAR_MASK_0011, 0x0000);
- rtl8168_config_eee_mac(tp);
-
RTL_W8(tp, DLLPR, RTL_R8(tp, DLLPR) & ~PFM_EN);
RTL_W8(tp, MISC_1, RTL_R8(tp, MISC_1) & ~PFM_D3COLD_EN);
@@ -3521,8 +3493,6 @@ static void rtl_hw_start_8168ep(struct rtl8169_private *tp)
rtl_eri_write(tp, 0xc0, ERIAR_MASK_0011, 0x0000);
rtl_eri_write(tp, 0xb8, ERIAR_MASK_0011, 0x0000);
- rtl8168_config_eee_mac(tp);
-
rtl_w0w1_eri(tp, 0x2fc, 0x01, 0x06);
RTL_W8(tp, DLLPR, RTL_R8(tp, DLLPR) & ~TX_10M_PS_EN);
@@ -3578,8 +3548,6 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)
rtl_eri_write(tp, 0xc0, ERIAR_MASK_0011, 0x0000);
rtl_eri_write(tp, 0xb8, ERIAR_MASK_0011, 0x0000);
- rtl8168_config_eee_mac(tp);
-
RTL_W8(tp, DLLPR, RTL_R8(tp, DLLPR) & ~PFM_EN);
RTL_W8(tp, MISC_1, RTL_R8(tp, MISC_1) & ~PFM_D3COLD_EN);
@@ -3820,11 +3788,6 @@ static void rtl_hw_start_8125_common(struct rtl8169_private *tp)
rtl_loop_wait_low(tp, &rtl_mac_ocp_e00e_cond, 1000, 10);
- if (tp->mac_version == RTL_GIGA_MAC_VER_61)
- rtl8125a_config_eee_mac(tp);
- else
- rtl8125b_config_eee_mac(tp);
-
rtl_disable_rxdvgate(tp);
}
@@ -4827,6 +4790,41 @@ static int rtl8169_poll(struct napi_struct *napi, int budget)
return work_done;
}
+static void rtl_enable_tx_lpi(struct rtl8169_private *tp, bool enable)
+{
+ if (!rtl_supports_eee(tp))
+ return;
+
+ switch (tp->mac_version) {
+ case RTL_GIGA_MAC_VER_34 ... RTL_GIGA_MAC_VER_52:
+ /* Adjust EEE LED frequency */
+ if (tp->mac_version != RTL_GIGA_MAC_VER_38)
+ RTL_W8(tp, EEE_LED, RTL_R8(tp, EEE_LED) & ~0x07);
+ if (enable)
+ rtl_eri_set_bits(tp, 0x1b0, 0x0003);
+ else
+ rtl_eri_clear_bits(tp, 0x1b0, 0x0003);
+ break;
+ case RTL_GIGA_MAC_VER_61:
+ if (enable) {
+ r8168_mac_ocp_modify(tp, 0xe040, 0, 0x0003);
+ r8168_mac_ocp_modify(tp, 0xeb62, 0, 0x0006);
+ } else {
+ r8168_mac_ocp_modify(tp, 0xe040, 0x0003, 0);
+ r8168_mac_ocp_modify(tp, 0xeb62, 0x0006, 0);
+ }
+ break;
+ case RTL_GIGA_MAC_VER_63 ... RTL_GIGA_MAC_VER_LAST:
+ if (enable)
+ r8168_mac_ocp_modify(tp, 0xe040, 0, 0x0003);
+ else
+ r8168_mac_ocp_modify(tp, 0xe040, 0x0003, 0);
+ break;
+ default:
+ break;
+ }
+}
+
static void r8169_phylink_handler(struct net_device *ndev)
{
struct rtl8169_private *tp = netdev_priv(ndev);
@@ -4834,6 +4832,7 @@ static void r8169_phylink_handler(struct net_device *ndev)
if (netif_carrier_ok(ndev)) {
rtl_link_chg_patch(tp);
+ rtl_enable_tx_lpi(tp, tp->phydev->enable_tx_lpi);
pm_request_resume(d);
} else {
pm_runtime_idle(d);
--
2.52.0
On Sun, Nov 23, 2025 at 11:54:41PM +0100, Heiner Kallweit wrote: > Thanks a lot for the valuable feedback! > I added the SDS PHY reset to the patch, and improved MAC EEE handling > in a second patch, incl. what you mentioned. > Patches should fully cover your use case now. Please give it a try. Good stuff, applied both patches, link is stable and link detection works correctly. Thanks! Fabio
> + }, {
> + PHY_ID_MATCH_EXACT(0x001ccbff),
> + .name = "Realtek SFP PHY Mode",
> + .flags = PHY_IS_INTERNAL,
> + .probe = rtl822x_probe,
> + .get_features = rtlgen_sfp_get_features,
> + .config_aneg = rtlgen_sfp_config_aneg,
> + .read_status = rtl822x_read_status,
> + .suspend = genphy_suspend,
> + .resume = rtlgen_resume,
> + .read_page = rtl821x_read_page,
> + .write_page = rtl821x_write_page,
> + .read_mmd = rtl822x_read_mmd,
> + .write_mmd = rtl822x_write_mmd,
I didn't get a chance to test your patch, yet, but is this intended to
match RTL8127AF? Because that's not it's phy id. It's the same as
RTL_8261C and currently matches "Realtek Internal NBASE-T PHY":
# cat /proc/self/net/r8127/enp8s0/debug/eth_phy
Dump Ethernet PHY
Offset Value
------ -----
####################page 0##################
0x00: 0040 798d 001c c890 1c01 0000 0064 2001
0x08: 0000 0000 0000 0000 0000 0000 0000 2000
####################extra reg##################
0xa400: 0040 798d 001c c890 1c01 0000 0064 2001
0xa410: 0000 0000 0000
0xa434: 0a0c
0xa5d0: 0000 0000 0001 4000
0xa61a: 0400
0xa6d0: 0000 0000 0000
On 11/23/2025 5:26 PM, Michael Zimmermann wrote:
>> + }, {
>> + PHY_ID_MATCH_EXACT(0x001ccbff),
>> + .name = "Realtek SFP PHY Mode",
>> + .flags = PHY_IS_INTERNAL,
>> + .probe = rtl822x_probe,
>> + .get_features = rtlgen_sfp_get_features,
>> + .config_aneg = rtlgen_sfp_config_aneg,
>> + .read_status = rtl822x_read_status,
>> + .suspend = genphy_suspend,
>> + .resume = rtlgen_resume,
>> + .read_page = rtl821x_read_page,
>> + .write_page = rtl821x_write_page,
>> + .read_mmd = rtl822x_read_mmd,
>> + .write_mmd = rtl822x_write_mmd,
>
> I didn't get a chance to test your patch, yet, but is this intended to
> match RTL8127AF? Because that's not it's phy id. It's the same as
> RTL_8261C and currently matches "Realtek Internal NBASE-T PHY":
>
See earlier in the patch:
+ /* Return dummy MII_PHYSID2 in SFP mode to match SFP PHY driver */
+ if (tp->sfp_mode && reg == (OCP_STD_PHY_BASE + 2 * MII_PHYSID2))
+ return 0xcbff;
The PHY_ID read is intercepted.
> # cat /proc/self/net/r8127/enp8s0/debug/eth_phy
>
> Dump Ethernet PHY
>
> Offset Value
> ------ -----
>
> ####################page 0##################
>
> 0x00: 0040 798d 001c c890 1c01 0000 0064 2001
> 0x08: 0000 0000 0000 0000 0000 0000 0000 2000
> ####################extra reg##################
>
> 0xa400: 0040 798d 001c c890 1c01 0000 0064 2001
> 0xa410: 0000 0000 0000
> 0xa434: 0a0c
> 0xa5d0: 0000 0000 0001 4000
> 0xa61a: 0400
> 0xa6d0: 0000 0000 0000
© 2016 - 2025 Red Hat, Inc.