[PATCH net-next v3 4/7] r8169: add support for RTL8116af

From: javen

Date: Mon Jun 29 2026 - 02:12:57 EST


From: Javen Xu <javen_xu@xxxxxxxxxxxxxx>

RTL8116af is sfp mode. Phylink uses pcs to get the link status from its
serdes reg, instead of standard phy reg. Speed and duplex are hardcoded
to 1000Mbps Full-Duplex. Also, RTL8116af doesn't have internal phy, so
we add some checks to ensure that tp->phydev is not empty when we need it.
In rtl_hw_start_8117(), the MAC calibration for register 0xd412 relies
on reading the internal PHY register 0x0c42. Since RTL8116af does not
have an internal PHY, this calibration step is intentionally bypassed.

Signed-off-by: Javen Xu <javen_xu@xxxxxxxxxxxxxx>
---
Changes in v2:
- replace some magic numbers with macro

Changes in v3:
- change commit message
- add lock when we do rtl8169_sds_read
- use phylink_mii_c22_pcs_decode_state to get status
- add phylink_mac_change for RTL8116af for it doesn't have phy
---
drivers/net/ethernet/realtek/r8169_main.c | 184 ++++++++++++++++++----
1 file changed, 150 insertions(+), 34 deletions(-)

diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 1d4a136519ab..009095e9c706 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -99,6 +99,18 @@
#define JUMBO_9K (9 * SZ_1K - VLAN_ETH_HLEN - ETH_FCS_LEN)
#define JUMBO_16K (SZ_16K - VLAN_ETH_HLEN - ETH_FCS_LEN)

+#define OCP_SDS_ADDR_REG 0xEB10
+#define OCP_SDS_CMD_REG 0xEB0E
+#define OCP_SDS_DATA_REG 0xEB14
+#define SDS_CMD_READ 0x0001
+#define RTL_SDS_C22_BASE 0x40
+#define RTL_PKG_DETECT 0xdc00
+#define RTL_PKG_DETECT_MASK 0x0078
+#define RTL_PKG_DETECT_8116AF 0x0030
+#define RTL_INT_HW_ID 0xd006
+#define RTL_INT_HW_ID_MASK 0x00ff
+#define RTL_INT_HW_ID_8116AF 0x0000
+
static const struct rtl_chip_info {
u32 mask;
u32 val;
@@ -731,6 +743,12 @@ enum rtl_dash_type {
RTL_DASH_25_BP,
};

+enum rtl_sfp_mode {
+ RTL_SFP_NONE,
+ RTL_SFP_8168_AF,
+ RTL_SFP_8127_ATF,
+};
+
struct rtl8169_private {
void __iomem *mmio_addr; /* memory map physical address */
struct pci_dev *pci_dev;
@@ -739,6 +757,7 @@ struct rtl8169_private {
struct napi_struct napi;
enum mac_version mac_version;
enum rtl_dash_type dash_type;
+ enum rtl_sfp_mode sfp_mode;
u32 cur_rx; /* Index into the Rx descriptor buffer of next Rx pkt. */
u32 cur_tx; /* Index into the Tx descriptor buffer of next Rx pkt. */
u32 dirty_tx;
@@ -766,7 +785,6 @@ 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;
@@ -780,6 +798,7 @@ struct rtl8169_private {
u32 ocp_base;
struct phylink *phylink;
struct phylink_config phylink_config;
+ struct phylink_pcs pcs;
struct irq_domain *phy_irq_domain;
struct ethtool_pauseparam saved_pause;
bool jumbo_pause_saved;
@@ -1138,7 +1157,7 @@ static int r8168_phy_ocp_read(struct rtl8169_private *tp, u32 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))
+ if (tp->sfp_mode == RTL_SFP_8127_ATF && reg == (OCP_STD_PHY_BASE + 2 * MII_PHYSID2))
return PHY_ID_RTL_DUMMY_SFP & 0xffff;

RTL_W32(tp, GPHY_OCP, reg << 15);
@@ -1292,6 +1311,15 @@ static void mac_mcu_write(struct rtl8169_private *tp, int reg, int value)
r8168_mac_ocp_write(tp, tp->ocp_base + reg, value);
}

+static bool rtl_is_8116af(struct rtl8169_private *tp)
+{
+ return tp->mac_version == RTL_GIGA_MAC_VER_52 &&
+ (r8168_mac_ocp_read(tp, RTL_PKG_DETECT) & RTL_PKG_DETECT_MASK) ==
+ RTL_PKG_DETECT_8116AF &&
+ (r8168_mac_ocp_read(tp, RTL_INT_HW_ID) & RTL_INT_HW_ID_MASK) ==
+ RTL_INT_HW_ID_8116AF;
+}
+
static int mac_mcu_read(struct rtl8169_private *tp, int reg)
{
return r8168_mac_ocp_read(tp, tp->ocp_base + reg);
@@ -1587,6 +1615,20 @@ static bool rtl_dash_is_enabled(struct rtl8169_private *tp)
}
}

+static enum rtl_sfp_mode rtl_get_sfp_mode(struct rtl8169_private *tp)
+{
+ if (rtl_is_8125(tp)) {
+ u16 data = r8168_mac_ocp_read(tp, RTL_INT_HW_ID);
+
+ if ((data & 0xff) == 0x07)
+ return RTL_SFP_8127_ATF;
+ } else if (rtl_is_8116af(tp)) {
+ return RTL_SFP_8168_AF;
+ }
+
+ return RTL_SFP_NONE;
+}
+
static enum rtl_dash_type rtl_get_dash_type(struct rtl8169_private *tp)
{
switch (tp->mac_version) {
@@ -2402,8 +2444,8 @@ static int rtl8169_set_link_ksettings(struct net_device *ndev,
int duplex = cmd->base.duplex;
int speed = cmd->base.speed;

- if (!tp->sfp_mode)
- return phy_ethtool_ksettings_set(phydev, cmd);
+ if (tp->sfp_mode != RTL_SFP_8127_ATF)
+ return phylink_ethtool_ksettings_set(tp->phylink, cmd);

if (cmd->base.autoneg != AUTONEG_DISABLE)
return -EINVAL;
@@ -2515,9 +2557,10 @@ void r8169_apply_firmware(struct rtl8169_private *tp)
tp->ocp_base = OCP_STD_PHY_BASE;

/* PHY soft reset may still be in progress */
- phy_read_poll_timeout(tp->phydev, MII_BMCR, val,
- !(val & BMCR_RESET),
- 50000, 600000, true);
+ if (tp->phydev)
+ phy_read_poll_timeout(tp->phydev, MII_BMCR, val,
+ !(val & BMCR_RESET),
+ 50000, 600000, true);
}
}

@@ -2554,6 +2597,8 @@ static void rtl_schedule_task(struct rtl8169_private *tp, enum rtl_flag flag)

static void rtl8169_init_phy(struct rtl8169_private *tp)
{
+ phy_init_hw(tp->phydev);
+ phy_resume(tp->phydev);
r8169_hw_phy_config(tp, tp->phydev, tp->mac_version);

if (tp->mac_version <= RTL_GIGA_MAC_VER_06) {
@@ -2568,7 +2613,7 @@ 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)
+ if (tp->sfp_mode == RTL_SFP_8127_ATF)
rtl_sfp_init(tp);

/* We may have called phy_speed_down before */
@@ -3755,12 +3800,14 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)

rtl_pcie_state_l2l3_disable(tp);

- rg_saw_cnt = phy_read_paged(tp->phydev, 0x0c42, 0x13) & 0x3fff;
- if (rg_saw_cnt > 0) {
- u16 sw_cnt_1ms_ini;
+ if (tp->phydev) {
+ rg_saw_cnt = phy_read_paged(tp->phydev, 0x0c42, 0x13) & 0x3fff;
+ if (rg_saw_cnt > 0) {
+ u16 sw_cnt_1ms_ini;

- sw_cnt_1ms_ini = (16000000 / rg_saw_cnt) & 0x0fff;
- r8168_mac_ocp_modify(tp, 0xd412, 0x0fff, sw_cnt_1ms_ini);
+ sw_cnt_1ms_ini = (16000000 / rg_saw_cnt) & 0x0fff;
+ r8168_mac_ocp_modify(tp, 0xd412, 0x0fff, sw_cnt_1ms_ini);
+ }
}

r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000);
@@ -4937,8 +4984,13 @@ static irqreturn_t rtl8169_interrupt(int irq, void *dev_instance)
goto out;
}

- if (status & LinkChg)
- generic_handle_domain_irq(tp->phy_irq_domain, 0);
+ if (status & LinkChg) {
+ if (tp->phy_irq_domain)
+ generic_handle_domain_irq(tp->phy_irq_domain, 0);
+ else if (tp->sfp_mode == RTL_SFP_8168_AF)
+ phylink_mac_change(tp->phylink,
+ !!(RTL_R8(tp, PHYstatus) & LinkStatus));
+ }

rtl_irq_disable(tp);
napi_schedule(&tp->napi);
@@ -5050,7 +5102,7 @@ static void rtl8169_down(struct rtl8169_private *tp)
phylink_stop(tp->phylink);

/* Reset SerDes PHY to bring down fiber link */
- if (tp->sfp_mode)
+ if (tp->sfp_mode == RTL_SFP_8127_ATF)
rtl_sfp_reset(tp);

rtl8169_update_counters(tp);
@@ -5072,9 +5124,9 @@ static void rtl8169_up(struct rtl8169_private *tp)
rtl8168_driver_start(tp);

pci_set_master(tp->pci_dev);
- phy_init_hw(tp->phydev);
- phy_resume(tp->phydev);
- rtl8169_init_phy(tp);
+ if (tp->phydev)
+ rtl8169_init_phy(tp);
+
napi_enable(&tp->napi);
enable_work(&tp->wk.work);
rtl_reset_work(tp);
@@ -5152,9 +5204,11 @@ static int rtl_open(struct net_device *dev)
if (retval < 0)
goto err_release_fw_2;

- retval = r8169_phy_connect(tp);
- if (retval)
- goto err_free_irq;
+ if (tp->phydev) {
+ retval = r8169_phy_connect(tp);
+ if (retval)
+ goto err_free_irq;
+ }

rtl8169_up(tp);
rtl8169_init_counter_offsets(tp);
@@ -5701,6 +5755,10 @@ static void rtl_mac_link_up(struct phylink_config *config, struct phy_device *ph
static struct phylink_pcs *rtl_mac_select_pcs(struct phylink_config *config,
phy_interface_t interface)
{
+ struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
+
+ if (interface == PHY_INTERFACE_MODE_1000BASEX || interface == PHY_INTERFACE_MODE_SGMII)
+ return &tp->pcs;
return NULL;
}

@@ -5709,6 +5767,51 @@ static void rtl_mac_config(struct phylink_config *config, unsigned int mode,
{
}

+static u16 rtl8169_sds_read(struct rtl8169_private *tp, u16 sds_reg)
+{
+ unsigned long flags;
+ u16 val = 0;
+
+ raw_spin_lock_irqsave(&tp->mac_ocp_lock, flags);
+ __r8168_mac_ocp_write(tp, OCP_SDS_ADDR_REG, sds_reg);
+ __r8168_mac_ocp_write(tp, OCP_SDS_CMD_REG, SDS_CMD_READ);
+ val = __r8168_mac_ocp_read(tp, OCP_SDS_DATA_REG);
+ raw_spin_unlock_irqrestore(&tp->mac_ocp_lock, flags);
+
+ return val;
+}
+
+static void rtl8169_pcs_get_state(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ struct phylink_link_state *state)
+{
+ struct rtl8169_private *tp = container_of(pcs, struct rtl8169_private, pcs);
+ u16 bmsr, lpa;
+
+ bmsr = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_BMSR);
+ lpa = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_LPA);
+
+ phylink_mii_c22_pcs_decode_state(state, neg_mode, bmsr, lpa);
+}
+
+static int rtl8169_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ return 0;
+}
+
+static int rtl8169_pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
+ const struct phylink_link_state *state)
+{
+ return 0;
+}
+
+static void rtl8169_pcs_an_restart(struct phylink_pcs *pcs)
+{
+}
+
static void rtl_mac_disable_tx_lpi(struct phylink_config *config)
{
struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
@@ -5759,6 +5862,13 @@ static unsigned long rtl8169_get_lpi_caps(struct rtl8169_private *tp)
return caps;
}

+static const struct phylink_pcs_ops r8169_pcs_ops = {
+ .pcs_validate = rtl8169_pcs_validate,
+ .pcs_get_state = rtl8169_pcs_get_state,
+ .pcs_config = rtl8169_pcs_config,
+ .pcs_an_restart = rtl8169_pcs_an_restart,
+};
+
static int rtl_init_phylink(struct rtl8169_private *tp)
{
struct phylink *pl;
@@ -5770,10 +5880,18 @@ static int rtl_init_phylink(struct rtl8169_private *tp)
tp->phylink_config.lpi_capabilities = rtl8169_get_lpi_caps(tp);
tp->phylink_config.mac_capabilities |= MAC_ASYM_PAUSE | MAC_SYM_PAUSE;

- if (tp->sfp_mode) {
+ switch (tp->sfp_mode) {
+ case RTL_SFP_8168_AF:
+ tp->pcs.ops = &r8169_pcs_ops;
+ tp->phylink_config.default_an_inband = true;
+ phy_mode = PHY_INTERFACE_MODE_1000BASEX;
+ tp->phylink_config.mac_capabilities |= MAC_1000FD;
+ break;
+ case RTL_SFP_8127_ATF:
phy_mode = PHY_INTERFACE_MODE_INTERNAL;
tp->phylink_config.mac_capabilities |= MAC_10000FD;
- } else {
+ break;
+ default:
phy_mode = PHY_INTERFACE_MODE_INTERNAL;
tp->phylink_config.mac_capabilities |= MAC_10 | MAC_100;

@@ -5794,6 +5912,7 @@ static int rtl_init_phylink(struct rtl8169_private *tp)
PHY_INTERFACE_MODE_MII;
else
phy_mode = PHY_INTERFACE_MODE_INTERNAL;
+ break;
}

__set_bit(phy_mode, tp->phylink_config.supported_interfaces);
@@ -5888,12 +6007,7 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
}
tp->aspm_manageable = !rc;

- if (rtl_is_8125(tp)) {
- u16 data = r8168_mac_ocp_read(tp, 0xd006);
-
- if ((data & 0xff) == 0x07)
- tp->sfp_mode = true;
- }
+ tp->sfp_mode = rtl_get_sfp_mode(tp);

tp->dash_type = rtl_get_dash_type(tp);
tp->dash_enabled = rtl_dash_is_enabled(tp);
@@ -6001,10 +6115,12 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
if (rc)
return rc;

- rc = r8169_mdio_register(tp);
- if (rc) {
- phylink_destroy(tp->phylink);
- return rc;
+ if (tp->sfp_mode != RTL_SFP_8168_AF) {
+ rc = r8169_mdio_register(tp);
+ if (rc) {
+ phylink_destroy(tp->phylink);
+ return rc;
+ }
}

rc = register_netdev(dev);
--
2.43.0