net: phy: realtek: add support for RTL8261C_CG

This patch adds support for Realtek phy chip RTL8261C_CG. Its PHY ID is
0x001cc898.
This patch introduces a distinct family of handlers (probe, get_features,
config_aneg, read_status, config_intr, handle_interrupt).

Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
Link: https://patch.msgid.link/20260728073106.1515-4-javen_xu@realsil.com.cn
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Javen Xu 2026-07-28 15:31:04 +08:00 committed by Jakub Kicinski
parent d7722c0308
commit b772b5ae45

View File

@ -141,6 +141,10 @@
#define RTL8211F_PHYSICAL_ADDR_WORD1 17
#define RTL8211F_PHYSICAL_ADDR_WORD2 18
#define RTL8261X_EXT_ADDR_REG 0xa436
#define RTL8261X_EXT_DATA_REG 0xa438
#define RTL_8261X_SUB_PHY_ID_ADDR 0x801d
#define RTL822X_VND1_SERDES_OPTION 0x697a
#define RTL822X_VND1_SERDES_OPTION_MODE_MASK GENMASK(5, 0)
#define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII 0
@ -251,6 +255,32 @@
#define RTL_8221B_VM_CG 0x001cc84a
#define RTL_8251B 0x001cc862
#define RTL_8261C 0x001cc890
#define RTL_8261C_CG 0x001cc898
#define RTL8261C_CE_MODEL 0x00
#define RTL8261X_INT_AUTONEG_ERROR BIT(0)
#define RTL8261X_INT_PAGE_RECV BIT(2)
#define RTL8261X_INT_AUTONEG_DONE BIT(3)
#define RTL8261X_INT_LINK_CHG BIT(4)
#define RTL8261X_INT_PHY_REG_ACCESS BIT(5)
#define RTL8261X_INT_PME BIT(7)
#define RTL8261X_INT_ALDPS_CHG BIT(9)
#define RTL8261X_INT_JABBER BIT(10)
#define RTL8261X_INT_MASK_DEFAULT (RTL8261X_INT_AUTONEG_DONE | \
RTL8261X_INT_LINK_CHG | \
RTL8261X_INT_AUTONEG_ERROR | \
RTL8261X_INT_JABBER)
#define RTL8261X_INT_MASK_ALL (RTL8261X_INT_AUTONEG_ERROR | \
RTL8261X_INT_PAGE_RECV | \
RTL8261X_INT_AUTONEG_DONE | \
RTL8261X_INT_LINK_CHG | \
RTL8261X_INT_PHY_REG_ACCESS | \
RTL8261X_INT_PME | \
RTL8261X_INT_ALDPS_CHG | \
RTL8261X_INT_JABBER)
/* RTL8211E and RTL8211F support up to three LEDs */
#define RTL8211x_LED_COUNT 3
@ -310,6 +340,156 @@ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page,
return phy_restore_page(phydev, oldpage, ret);
}
static int rtl8261x_probe(struct phy_device *phydev)
{
int sub_phy_id, ret;
ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_ADDR_REG,
RTL_8261X_SUB_PHY_ID_ADDR);
if (ret < 0)
return ret;
ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_DATA_REG);
if (ret < 0)
return ret;
sub_phy_id = (ret >> 8) & 0xff;
switch (sub_phy_id) {
case RTL8261C_CE_MODEL:
phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id);
break;
default:
phydev_warn(phydev, "Unknown sub_id 0x%02x, default behavior\n", sub_phy_id);
return -ENODEV;
}
return 0;
}
static int rtl8261x_get_features(struct phy_device *phydev)
{
int ret;
ret = genphy_c45_pma_read_abilities(phydev);
if (ret)
return ret;
/*
* Supplement Multi-Gig speeds that may not be automatically detected
* RTL8261X supports 2.5G/5G in addition to standard 10G
*/
linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
phydev->supported);
linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
phydev->supported);
return 0;
}
static int rtl8261x_read_status(struct phy_device *phydev)
{
int ret, val = 0;
if (phydev->autoneg == AUTONEG_ENABLE) {
ret = genphy_c45_aneg_done(phydev);
if (ret < 0)
return ret;
if (ret) {
val = phy_read_mmd(phydev, MDIO_MMD_VEND2,
RTL822X_VND2_C22_REG(MII_STAT1000));
if (val < 0)
return val;
}
}
mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val);
ret = genphy_c45_read_status(phydev);
if (ret < 0)
return ret;
return 0;
}
static int rtl8261x_config_intr(struct phy_device *phydev)
{
int ret;
if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
if (ret < 0)
return ret;
ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER,
RTL8261X_INT_MASK_DEFAULT);
if (ret < 0)
return ret;
} else {
ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER, 0);
if (ret < 0)
return ret;
ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
if (ret < 0)
return ret;
}
return 0;
}
static irqreturn_t rtl8261x_handle_interrupt(struct phy_device *phydev)
{
int irq_status;
irq_status = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
if (irq_status < 0) {
phy_error(phydev);
return IRQ_NONE;
}
if (!(irq_status & RTL8261X_INT_MASK_ALL))
return IRQ_NONE;
if (irq_status & (RTL8261X_INT_LINK_CHG | RTL8261X_INT_AUTONEG_DONE |
RTL8261X_INT_AUTONEG_ERROR | RTL8261X_INT_JABBER))
phy_trigger_machine(phydev);
return IRQ_HANDLED;
}
static int rtl8261x_config_aneg(struct phy_device *phydev)
{
u16 adv_1g = 0;
int ret;
ret = genphy_c45_config_aneg(phydev);
if (ret < 0)
return ret;
if (phydev->autoneg == AUTONEG_DISABLE)
return 0;
if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
phydev->advertising))
adv_1g = ADVERTISE_1000FULL;
if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
phydev->advertising))
adv_1g |= ADVERTISE_1000HALF;
ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2,
RTL822X_VND2_C22_REG(MII_CTRL1000),
ADVERTISE_1000FULL | ADVERTISE_1000HALF,
adv_1g);
if (ret < 0)
return ret;
if (ret > 0)
return genphy_c45_restart_aneg(phydev);
return 0;
}
static int rtl821x_probe(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
@ -3002,6 +3182,18 @@ static struct phy_driver realtek_drvs[] = {
.resume = genphy_resume,
.read_mmd = genphy_read_mmd_unsupported,
.write_mmd = genphy_write_mmd_unsupported,
}, {
PHY_ID_MATCH_EXACT(RTL_8261C_CG),
.name = "Realtek RTL8261C 10Gbps PHY",
.probe = rtl8261x_probe,
.get_features = rtl8261x_get_features,
.config_aneg = rtl8261x_config_aneg,
.read_status = rtl8261x_read_status,
.config_intr = rtl8261x_config_intr,
.handle_interrupt = rtl8261x_handle_interrupt,
.soft_reset = genphy_c45_pma_soft_reset,
.suspend = genphy_c45_pma_suspend,
.resume = genphy_c45_pma_resume,
},
};