From: Javen Xu 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 Signed-off-by: Javen Xu --- Changes in v2: - no changes, new file Changes in v3: - re-order function according to the order in phy-c45.c - add kernel-doc about return value - add MASTER_SLAVE_CFG_MASTER_PREFERRED, MASTER_SLAVE_CFG_SLAVE_PREFERRED, MASTER_SLAVE_CFG_UNKNOWN, MASTER_SLAVE_CFG_UNSUPPORTED, MASTER_SLAVE_CFG_SLAVE_PREFERRED cfg Changes in v4: - no changes Changes in v5: - remove genphy_c45_pma_setup_forced() for this is already done when calling genphy_c45_config_aneg() Changes in v6: - when PHY_INTERRUPT_DISABLE, clear IMR and ISR - if AUTONEG_DISABLE, nothing need to do in rtl8261x_config_aneg - add rtl8261x_read_status, support 1G speed Changes in v7: - remove RTL8261X_IMR and RTL8261X_ISR, duplicated definition - modify commit message - continue with default behavior when meet unknown sub_phy_id - change the internal order of rtl8261x_read_status - expand RTL8261X_INT_MASK_DEFAULT - add the handle for ADVERTISE_1000HALF in rtl8261x_config_aneg Changes in v8: - no changes --- drivers/net/phy/realtek/realtek_main.c | 186 +++++++++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 27268811f564..ef3700894ebf 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -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,150 @@ 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; + + if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) { + 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; @@ -3001,6 +3175,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_soft_reset, + .suspend = genphy_c45_pma_suspend, + .resume = genphy_c45_pma_resume, }, }; -- 2.43.0