* [PATCH net-next v1 0/2] Add support for RTL8261c @ 2026-05-28 7:52 javen 2026-05-28 7:52 ` [PATCH net-next v1 1/2] net: phy: realtek: add support for RTL8261 javen 2026-05-28 7:52 ` [PATCH net-next v1 2/2] net: phy: realtek: load firmware for RTL8261C javen 0 siblings, 2 replies; 9+ messages in thread From: javen @ 2026-05-28 7:52 UTC (permalink / raw) To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, freddy_gu Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu From: Javen Xu <javen_xu@realsil.com.cn> Add support for RTL8261c and add support for loading firmware. Javen Xu (2): net: phy: realtek: add support for RTL8261 net: phy: realtek: load firmware for RTL8261C drivers/net/phy/realtek/realtek_main.c | 536 +++++++++++++++++++++++++ 1 file changed, 536 insertions(+) -- 2.43.0 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v1 1/2] net: phy: realtek: add support for RTL8261 2026-05-28 7:52 [PATCH net-next v1 0/2] Add support for RTL8261c javen @ 2026-05-28 7:52 ` javen 2026-05-28 9:37 ` Nicolai Buchwitz ` (3 more replies) 2026-05-28 7:52 ` [PATCH net-next v1 2/2] net: phy: realtek: load firmware for RTL8261C javen 1 sibling, 4 replies; 9+ messages in thread From: javen @ 2026-05-28 7:52 UTC (permalink / raw) To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, freddy_gu Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu From: Javen Xu <javen_xu@realsil.com.cn> This patch adds support for Realtek phy chip RTL8261. Its PHY id is 0x001cc898 and 0x001cc899. Signed-off-by: Javen Xu <javen_xu@realsil.com.cn> --- drivers/net/phy/realtek/realtek_main.c | 315 +++++++++++++++++++++++++ 1 file changed, 315 insertions(+) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 27268811f564..fe743fd0421b 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -22,6 +22,9 @@ #include "../phylib.h" #include "realtek.h" +#define RTL_8261C_CG 0x001cc898 +#define RTL_8261CE_CG 0x001cc899 + #define RTL8201F_IER_PAGE 0x07 #define RTL8201F_IER 0x13 #define RTL8201F_IER_LINK BIT(13) @@ -141,6 +144,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 @@ -252,6 +259,57 @@ #define RTL_8251B 0x001cc862 #define RTL_8261C 0x001cc890 +#define RTL8261C_CE_MODEL 0x00 +#define RTL8261D_MODEL 0x81 +#define RTL8261X_PHYSR_REG 0xa434 +#define RTL8261X_GBCR_REG 0xa412 +#define RTL8261X_IMR 0xa4d2 +#define RTL8261X_ISR 0xa4d4 +#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_PHYSR_LINK BIT(2) +#define RTL8261X_PHYSR_DUPLEX BIT(3) +#define RTL8261X_PHYSR_SPEED_L GENMASK(5, 4) +#define RTL8261X_PHYSR_SPEED_H GENMASK(10, 9) + +/* Concatenated 4-bit speed code values (SPD_H << 2 | SPD_L) */ +#define RTL8261X_SPEED_CODE_500M 0x3 /* H=0, L=3 */ +#define RTL8261X_SPEED_CODE_1000M 0x7 /* H=1, L=3 */ +#define RTL8261X_SPEED_CODE_2500M 0x8 /* H=2, L=0 */ +#define RTL8261X_SPEED_CODE_5000M 0x9 /* H=2, L=1 */ +#define RTL8261X_SPEED_500 500 + +#define RTL8261X_INT_MASK_DEFAULT (RTL8261X_INT_AUTONEG_DONE | \ + RTL8261X_INT_LINK_CHG) + +#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) + +#define RTL8261X_MULTIG_CTRL 0x0020 +#define RTL8261X_MASTER_SLAVE_MASK GENMASK(15, 14) + +#define RTL8261X_MS_AUTO 0x0000 +#define RTL8261X_MS_SLAVE 0x8000 +#define RTL8261X_MS_MASTER 0xC000 + +enum rtl8261x_chip_model { + RTL8261_MODEL_C_CE = 0, + RTL8261_MODEL_D, + RTL8261_MODEL_GENERIC, +}; + /* RTL8211E and RTL8211F support up to three LEDs */ #define RTL8211x_LED_COUNT 3 @@ -270,6 +328,12 @@ struct rtl821x_priv { u16 iner; }; +struct rtl8261x_priv { + enum rtl8261x_chip_model model; + u8 sub_phy_id; + bool is_generic; +}; + static int rtl821x_read_page(struct phy_device *phydev) { return __phy_read(phydev, RTL821x_PAGE_SELECT); @@ -310,6 +374,233 @@ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page, return phy_restore_page(phydev, oldpage, ret); } +static int rtl8261x_soft_reset(struct phy_device *phydev) +{ + int ret, val; + + ret = phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, MDIO_CTRL1_RESET); + if (ret < 0) + return ret; + + return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD, + MDIO_CTRL1, val, + !(val & MDIO_CTRL1_RESET), + 5000, 100000, true); +} + +static int rtl8261x_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct rtl8261x_priv *priv; + int sub_phy_id, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + 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; + priv->sub_phy_id = sub_phy_id; + priv->is_generic = false; + + switch (sub_phy_id) { + case RTL8261C_CE_MODEL: + priv->model = RTL8261_MODEL_C_CE; + phydev_info(phydev, "RTL8261C/CE detected (sub_id 0x%02x)\n", sub_phy_id); + break; + + case RTL8261D_MODEL: + priv->model = RTL8261_MODEL_D; + phydev_info(phydev, "RTL8261D detected (sub_id 0x%02x)\n", sub_phy_id); + break; + + default: + priv->model = RTL8261_MODEL_GENERIC; + priv->is_generic = true; + phydev_warn(phydev, "Unknown sub_id 0x%02x! Using GENERIC mode. Update driver for full support.\n", + sub_phy_id); + break; + } + phydev->priv = priv; + + 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_config_master_slave(struct phy_device *phydev) +{ + u16 val; + /* + * Configure bits 15:14 of MMD 7.0x0020 + * + * Bit 15 (Enable) | Bit 14 (Value) | Mode + * ----------------|----------------|------------- + * 0 | 0 | Auto (disabled) + * 1 | 0 | Force Slave + * 1 | 1 | Force Master + */ + switch (phydev->master_slave_set) { + case MASTER_SLAVE_CFG_MASTER_FORCE: + val = RTL8261X_MS_MASTER; + break; + case MASTER_SLAVE_CFG_SLAVE_FORCE: + val = RTL8261X_MS_SLAVE; + break; + case MASTER_SLAVE_CFG_UNKNOWN: + case MASTER_SLAVE_CFG_MASTER_PREFERRED: + case MASTER_SLAVE_CFG_SLAVE_PREFERRED: + default: + val = RTL8261X_MS_AUTO; + break; + } + + return phy_modify_mmd(phydev, MDIO_MMD_AN, RTL8261X_MULTIG_CTRL, + RTL8261X_MASTER_SLAVE_MASK, val); +} + +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, RTL8261X_ISR); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_IMR, + RTL8261X_INT_MASK_DEFAULT); + if (ret < 0) + return ret; + } else { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_IMR, 0); + 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, RTL8261X_ISR); + 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_read_status(struct phy_device *phydev) +{ + int ret, val, speed_code; + + ret = genphy_c45_read_status(phydev); + if (ret < 0) + return ret; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_PHYSR_REG); + if (val < 0) + return val; + + phydev->link = !!(val & RTL8261X_PHYSR_LINK); + if (!phydev->link) { + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + return 0; + } + + phydev->duplex = (val & RTL8261X_PHYSR_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF; + speed_code = (FIELD_GET(RTL8261X_PHYSR_SPEED_H, val) << 2) | + FIELD_GET(RTL8261X_PHYSR_SPEED_L, val); + switch (speed_code) { + case RTL8261X_SPEED_CODE_500M: + phydev->speed = RTL8261X_SPEED_500; + break; + case RTL8261X_SPEED_CODE_1000M: + phydev->speed = SPEED_1000; + break; + case RTL8261X_SPEED_CODE_2500M: + phydev->speed = SPEED_2500; + break; + case RTL8261X_SPEED_CODE_5000M: + phydev->speed = SPEED_5000; + break; + default: + phydev_warn(phydev, "unknown speed code 0x%x (PHYSR=0x%04x)\n", speed_code, val); + phydev->speed = SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8261x_config_aneg(struct phy_device *phydev) +{ + u16 adv_1g = 0; + int ret; + + if (phydev->autoneg == AUTONEG_DISABLE) + return genphy_c45_pma_setup_forced(phydev); + + ret = rtl8261x_config_master_slave(phydev); + if (ret < 0) + return ret; + + ret = genphy_c45_config_aneg(phydev); + if (ret < 0) + return ret; + + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + phydev->advertising)) + adv_1g = BIT(9); + + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, + RTL8261X_GBCR_REG, + BIT(9), 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 +3292,30 @@ 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_RTL8261D 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 = rtl8261x_soft_reset, + .suspend = genphy_c45_pma_suspend, + .resume = genphy_c45_pma_resume, + }, { + PHY_ID_MATCH_EXACT(RTL_8261CE_CG), + .name = "Realtek RTL8261CE 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 = rtl8261x_soft_reset, + .suspend = genphy_c45_pma_suspend, + .resume = genphy_c45_pma_resume, }, }; -- 2.43.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v1 1/2] net: phy: realtek: add support for RTL8261 2026-05-28 7:52 ` [PATCH net-next v1 1/2] net: phy: realtek: add support for RTL8261 javen @ 2026-05-28 9:37 ` Nicolai Buchwitz 2026-05-28 12:39 ` Andrew Lunn ` (2 subsequent siblings) 3 siblings, 0 replies; 9+ messages in thread From: Nicolai Buchwitz @ 2026-05-28 9:37 UTC (permalink / raw) To: javen Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, freddy_gu, netdev, linux-kernel, daniel, vladimir.oltean Hi Javen On 28.5.2026 09:52, javen wrote: > From: Javen Xu <javen_xu@realsil.com.cn> > > This patch adds support for Realtek phy chip RTL8261. Its PHY id is > 0x001cc898 and 0x001cc899. > > Signed-off-by: Javen Xu <javen_xu@realsil.com.cn> > --- > drivers/net/phy/realtek/realtek_main.c | 315 +++++++++++++++++++++++++ > 1 file changed, 315 insertions(+) > > diff --git a/drivers/net/phy/realtek/realtek_main.c > b/drivers/net/phy/realtek/realtek_main.c > index 27268811f564..fe743fd0421b 100644 > --- a/drivers/net/phy/realtek/realtek_main.c > +++ b/drivers/net/phy/realtek/realtek_main.c > @@ -22,6 +22,9 @@ > #include "../phylib.h" > #include "realtek.h" > > +#define RTL_8261C_CG 0x001cc898 > +#define RTL_8261CE_CG 0x001cc899 Imho these should be added below RTL_8261C and not at the top. > + > #define RTL8201F_IER_PAGE 0x07 > #define RTL8201F_IER 0x13 > #define RTL8201F_IER_LINK BIT(13) > @@ -141,6 +144,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 > @@ -252,6 +259,57 @@ > #define RTL_8251B 0x001cc862 > #define RTL_8261C 0x001cc890 > > +#define RTL8261C_CE_MODEL 0x00 > +#define RTL8261D_MODEL 0x81 > +#define RTL8261X_PHYSR_REG 0xa434 > +#define RTL8261X_GBCR_REG 0xa412 > +#define RTL8261X_IMR 0xa4d2 > +#define RTL8261X_ISR 0xa4d4 > +#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_PHYSR_LINK BIT(2) > +#define RTL8261X_PHYSR_DUPLEX BIT(3) > +#define RTL8261X_PHYSR_SPEED_L GENMASK(5, 4) > +#define RTL8261X_PHYSR_SPEED_H GENMASK(10, 9) > + > +/* Concatenated 4-bit speed code values (SPD_H << 2 | SPD_L) */ > +#define RTL8261X_SPEED_CODE_500M 0x3 /* H=0, L=3 */ > +#define RTL8261X_SPEED_CODE_1000M 0x7 /* H=1, L=3 */ > +#define RTL8261X_SPEED_CODE_2500M 0x8 /* H=2, L=0 */ > +#define RTL8261X_SPEED_CODE_5000M 0x9 /* H=2, L=1 */ > +#define RTL8261X_SPEED_500 500 500 MBit/s isnt a standard rate which also isnt represented in ethtool. Is this just for documentation or intended to be used? If yes, how? > [...] > + > +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); Feature bits for 2.5 and 5 GBit/s are set unconditionally - even for the generic variant. Do all variants have these capabilities? If not better add check for at least the generic one. > + > + return 0; > +} > + > +static int rtl8261x_config_master_slave(struct phy_device *phydev) > +{ > + u16 val; > + /* > + * Configure bits 15:14 of MMD 7.0x0020 > + * > + * Bit 15 (Enable) | Bit 14 (Value) | Mode > + * ----------------|----------------|------------- > + * 0 | 0 | Auto (disabled) > + * 1 | 0 | Force Slave > + * 1 | 1 | Force Master > + */ > + switch (phydev->master_slave_set) { > + case MASTER_SLAVE_CFG_MASTER_FORCE: > + val = RTL8261X_MS_MASTER; > + break; > + case MASTER_SLAVE_CFG_SLAVE_FORCE: > + val = RTL8261X_MS_SLAVE; > + break; > + case MASTER_SLAVE_CFG_UNKNOWN: > + case MASTER_SLAVE_CFG_MASTER_PREFERRED: > + case MASTER_SLAVE_CFG_SLAVE_PREFERRED: > + default: > + val = RTL8261X_MS_AUTO; > + break; > + } > + > + return phy_modify_mmd(phydev, MDIO_MMD_AN, RTL8261X_MULTIG_CTRL, > + RTL8261X_MASTER_SLAVE_MASK, val); > +} RTL8261X_MULTIG_CTRL is a IEEE-defined register, so it can use MDIO_AN_10GBT_CTRL instead. Also the define for RTL8261X_MULTIG_CTRL can be dropped. While at it I would suggest to add MDIO_AN_10GBT_CTRL_MS_ENABLE (bit 15) and MDIO_AN_10GBT_CTRL_MS_VALUE (bit 14) to include/uapi/linux/mdio.h and also make use of it. RTL8261X_MS_AUTO would become val = MDIO_AN_10GBT_CTRL_MS_ENABLE | MDIO_AN_10GBT_CTRL_MS_VALUE; > [...] > + > +static int rtl8261x_read_status(struct phy_device *phydev) > +{ > + int ret, val, speed_code; > + > + ret = genphy_c45_read_status(phydev); > + if (ret < 0) > + return ret; > + > + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_PHYSR_REG); > + if (val < 0) > + return val; > + > + phydev->link = !!(val & RTL8261X_PHYSR_LINK); > + if (!phydev->link) { > + phydev->speed = SPEED_UNKNOWN; > + phydev->duplex = DUPLEX_UNKNOWN; > + return 0; > + } Why does it need to overreide the values from genphy_c45_read_status() ? > + > + phydev->duplex = (val & RTL8261X_PHYSR_DUPLEX) ? DUPLEX_FULL : > DUPLEX_HALF; > + speed_code = (FIELD_GET(RTL8261X_PHYSR_SPEED_H, val) << 2) | > + FIELD_GET(RTL8261X_PHYSR_SPEED_L, val); > + switch (speed_code) { > + case RTL8261X_SPEED_CODE_500M: > + phydev->speed = RTL8261X_SPEED_500; See question above (defines). > + break; > + case RTL8261X_SPEED_CODE_1000M: > + phydev->speed = SPEED_1000; > + break; > + case RTL8261X_SPEED_CODE_2500M: > + phydev->speed = SPEED_2500; > + break; > + case RTL8261X_SPEED_CODE_5000M: > + phydev->speed = SPEED_5000; > + break; > + default: > + phydev_warn(phydev, "unknown speed code 0x%x (PHYSR=0x%04x)\n", > speed_code, val); > + phydev->speed = SPEED_UNKNOWN; > + break; > + } > + > + return 0; > +} > + > +static int rtl8261x_config_aneg(struct phy_device *phydev) > +{ > + u16 adv_1g = 0; > + int ret; > + > + if (phydev->autoneg == AUTONEG_DISABLE) > + return genphy_c45_pma_setup_forced(phydev); > + > + ret = rtl8261x_config_master_slave(phydev); > + if (ret < 0) > + return ret; > + > + ret = genphy_c45_config_aneg(phydev); > + if (ret < 0) > + return ret; > + > + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, > + phydev->advertising)) > + adv_1g = BIT(9); > + > + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, > + RTL8261X_GBCR_REG, > + BIT(9), adv_1g); > + if (ret < 0) > + return ret; > + > + if (ret > 0) > + return genphy_c45_restart_aneg(phydev); This does not take rtl8261x_config_master_slave() into account. So maybe change rtl8261x_config_master_slave return phy_modify_mmd_changed ... and keep the result in a bool and also trigger auto nego restart if needed. > + > + return 0; > +} > + > static int rtl821x_probe(struct phy_device *phydev) > { > struct device *dev = &phydev->mdio.dev; > @@ -3001,6 +3292,30 @@ 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_RTL8261D 10Gbps PHY", > + .probe = rtl8261x_probe, RTL8261C/RTL8261D? Or just the c variant as the phy id suggests? > + .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 = rtl8261x_soft_reset, > + .suspend = genphy_c45_pma_suspend, > + .resume = genphy_c45_pma_resume, > + }, { > + PHY_ID_MATCH_EXACT(RTL_8261CE_CG), > + .name = "Realtek RTL8261CE 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 = rtl8261x_soft_reset, > + .suspend = genphy_c45_pma_suspend, > + .resume = genphy_c45_pma_resume, > }, > }; Please also make sure for both patches that checkpatch has no further complaints. Thanks. Nicolai ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v1 1/2] net: phy: realtek: add support for RTL8261 2026-05-28 7:52 ` [PATCH net-next v1 1/2] net: phy: realtek: add support for RTL8261 javen 2026-05-28 9:37 ` Nicolai Buchwitz @ 2026-05-28 12:39 ` Andrew Lunn 2026-05-28 12:42 ` Andrew Lunn 2026-05-28 16:56 ` Aleksander Jan Bajkowski 3 siblings, 0 replies; 9+ messages in thread From: Andrew Lunn @ 2026-05-28 12:39 UTC (permalink / raw) To: javen Cc: hkallweit1, linux, davem, edumazet, kuba, pabeni, freddy_gu, netdev, linux-kernel, daniel, vladimir.oltean > +static int rtl8261x_soft_reset(struct phy_device *phydev) > +{ > + int ret, val; > + > + ret = phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, MDIO_CTRL1_RESET); > + if (ret < 0) > + return ret; > + > + return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD, > + MDIO_CTRL1, val, > + !(val & MDIO_CTRL1_RESET), > + 5000, 100000, true); We have a few copies of this function in various PHY drivers. Please add a genphy_c45_soft_reset() helper. > +} > + > +static int rtl8261x_probe(struct phy_device *phydev) > +{ > + struct device *dev = &phydev->mdio.dev; > + struct rtl8261x_priv *priv; > + int sub_phy_id, ret; > + > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_ADDR_REG, > + RTL_8261X_SUB_PHY_ID_ADDR); > + if (ret < 0) > + return ret; blank line > + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_DATA_REG); > + if (ret < 0) > + return ret; > + > + sub_phy_id = (ret >> 8) & 0xff; > + priv->sub_phy_id = sub_phy_id; > + priv->is_generic = false; > + > + switch (sub_phy_id) { > + case RTL8261C_CE_MODEL: > + priv->model = RTL8261_MODEL_C_CE; > + phydev_info(phydev, "RTL8261C/CE detected (sub_id 0x%02x)\n", sub_phy_id); > + break; > + > + case RTL8261D_MODEL: > + priv->model = RTL8261_MODEL_D; > + phydev_info(phydev, "RTL8261D detected (sub_id 0x%02x)\n", sub_phy_id); > + break; > + > + default: > + priv->model = RTL8261_MODEL_GENERIC; > + priv->is_generic = true; > + phydev_warn(phydev, "Unknown sub_id 0x%02x! Using GENERIC mode. Update driver for full support.\n", > + sub_phy_id); > + break; This will get ignored. It is better to return -ENODEV so the probe fails. Andrew ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v1 1/2] net: phy: realtek: add support for RTL8261 2026-05-28 7:52 ` [PATCH net-next v1 1/2] net: phy: realtek: add support for RTL8261 javen 2026-05-28 9:37 ` Nicolai Buchwitz 2026-05-28 12:39 ` Andrew Lunn @ 2026-05-28 12:42 ` Andrew Lunn 2026-05-28 16:56 ` Aleksander Jan Bajkowski 3 siblings, 0 replies; 9+ messages in thread From: Andrew Lunn @ 2026-05-28 12:42 UTC (permalink / raw) To: javen Cc: hkallweit1, linux, davem, edumazet, kuba, pabeni, freddy_gu, netdev, linux-kernel, daniel, vladimir.oltean > +struct rtl8261x_priv { > + enum rtl8261x_chip_model model; > + u8 sub_phy_id; > + bool is_generic; These don't appear to be used anywhere, just set. Are they really needed? Andrew ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v1 1/2] net: phy: realtek: add support for RTL8261 2026-05-28 7:52 ` [PATCH net-next v1 1/2] net: phy: realtek: add support for RTL8261 javen ` (2 preceding siblings ...) 2026-05-28 12:42 ` Andrew Lunn @ 2026-05-28 16:56 ` Aleksander Jan Bajkowski 3 siblings, 0 replies; 9+ messages in thread From: Aleksander Jan Bajkowski @ 2026-05-28 16:56 UTC (permalink / raw) To: javen, andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, freddy_gu Cc: netdev, linux-kernel, daniel, vladimir.oltean Hi Javen, On 28/05/2026 09:52, javen wrote: > From: Javen Xu <javen_xu@realsil.com.cn> > > This patch adds support for Realtek phy chip RTL8261. Its PHY id is > 0x001cc898 and 0x001cc899. > > Signed-off-by: Javen Xu <javen_xu@realsil.com.cn> > --- > drivers/net/phy/realtek/realtek_main.c | 315 +++++++++++++++++++++++++ > 1 file changed, 315 insertions(+) > > diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c > index 27268811f564..fe743fd0421b 100644 > --- a/drivers/net/phy/realtek/realtek_main.c > +++ b/drivers/net/phy/realtek/realtek_main.c > +static int rtl8261x_config_aneg(struct phy_device *phydev) > +{ > + u16 adv_1g = 0; > + int ret; > + > + if (phydev->autoneg == AUTONEG_DISABLE) > + return genphy_c45_pma_setup_forced(phydev); > + > + ret = rtl8261x_config_master_slave(phydev); > + if (ret < 0) > + return ret; > + > + ret = genphy_c45_config_aneg(phydev); > + if (ret < 0) > + return ret; > + > + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, > + phydev->advertising)) > + adv_1g = BIT(9); adv_1g = ADVERTISE_1000FULL; > + > + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, > + RTL8261X_GBCR_REG, > + BIT(9), adv_1g); This is the C22 register mapped in VEND2 page. You can replace it with: ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, RTL822X_VND2_C22_REG(MII_CTRL1000 ), ADVERTISE_1000FULL, adv_1g); Best regards, Aleksander ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v1 2/2] net: phy: realtek: load firmware for RTL8261C 2026-05-28 7:52 [PATCH net-next v1 0/2] Add support for RTL8261c javen 2026-05-28 7:52 ` [PATCH net-next v1 1/2] net: phy: realtek: add support for RTL8261 javen @ 2026-05-28 7:52 ` javen 2026-05-28 9:08 ` Nicolai Buchwitz 2026-05-28 12:20 ` Daniel Golle 1 sibling, 2 replies; 9+ messages in thread From: javen @ 2026-05-28 7:52 UTC (permalink / raw) To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, freddy_gu Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu From: Javen Xu <javen_xu@realsil.com.cn> This patch adds support for loading firmware. Download some parameters for RTL8261C. Signed-off-by: Javen Xu <javen_xu@realsil.com.cn> --- drivers/net/phy/realtek/realtek_main.c | 221 +++++++++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index fe743fd0421b..d20cdc68cc62 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -18,12 +18,51 @@ #include <linux/clk.h> #include <linux/string_choices.h> #include <net/phy/realtek_phy.h> +#include <linux/firmware.h> +#include <linux/crc32.h> #include "../phylib.h" #include "realtek.h" #define RTL_8261C_CG 0x001cc898 #define RTL_8261CE_CG 0x001cc899 +#define FW_MAIN_MAGIC 0x52544C38 +#define FW_SUB_MAGIC_8261C 0x32363143 +#define FW_SUB_MAGIC_8261D 0x32363144 +#define RTL8261X_POLL_TIMEOUT_MS 100 + +#define RTL8261C_CE_FW_NAME "rtl_nic/rtl8261c.bin" +MODULE_FIRMWARE(RTL8261C_CE_FW_NAME); + +enum rtl8261x_fw_op { + OP_WRITE = 0x00, /* Write */ + OP_POLL = 0x02, /* Polling */ +}; + +struct rtl8261x_fw_header { + __le32 main_magic; /* Main magic number 0x52544C38 ("RTL8") */ + __le32 sub_magic; /* Sub magic number */ + __le16 version_major; /* Major version */ + __le16 version_minor; /* Minor version */ + __le16 num_entries; /* Number of entries */ + __le16 reserved; /* Reserved */ + __le32 crc32; /* CRC32 checksum */ +} __packed; + +struct rtl8261x_fw_entry { + __u8 type; /* Operation type (OP_*) */ + __u8 dev; /* MMD device */ + __le16 addr; /* Register address */ + __u8 msb; /* MSB bit position */ + __u8 lsb; /* LSB bit position */ + __le16 value; /* Value to write/compare */ + __le16 timeout_ms; /* Poll timeout in milliseconds */ + __u8 poll_set; /* Poll for set (1) or clear (0) */ + __u8 reserved; /* Reserved */ +} __packed; + +#define FW_HEADER_SIZE sizeof(struct rtl8261x_fw_header) +#define FW_ENTRY_SIZE sizeof(struct rtl8261x_fw_entry) #define RTL8201F_IER_PAGE 0x07 #define RTL8201F_IER 0x13 @@ -332,6 +371,8 @@ struct rtl8261x_priv { enum rtl8261x_chip_model model; u8 sub_phy_id; bool is_generic; + const char *fw_name; + bool fw_loaded; }; static int rtl821x_read_page(struct phy_device *phydev) @@ -413,16 +454,19 @@ static int rtl8261x_probe(struct phy_device *phydev) switch (sub_phy_id) { case RTL8261C_CE_MODEL: priv->model = RTL8261_MODEL_C_CE; + priv->fw_name = RTL8261C_CE_FW_NAME; phydev_info(phydev, "RTL8261C/CE detected (sub_id 0x%02x)\n", sub_phy_id); break; case RTL8261D_MODEL: priv->model = RTL8261_MODEL_D; + priv->fw_name = NULL; phydev_info(phydev, "RTL8261D detected (sub_id 0x%02x)\n", sub_phy_id); break; default: priv->model = RTL8261_MODEL_GENERIC; + priv->fw_name = NULL; priv->is_generic = true; phydev_warn(phydev, "Unknown sub_id 0x%02x! Using GENERIC mode. Update driver for full support.\n", sub_phy_id); @@ -452,6 +496,165 @@ static int rtl8261x_get_features(struct phy_device *phydev) return 0; } +static int rtl8261x_verify_firmware(struct phy_device *phydev, const struct firmware *fw) +{ + const struct rtl8261x_fw_header *hdr; + u32 calc_crc, file_crc; + size_t data_len; + u16 num_entries; + u32 main_magic, sub_magic; + + if (fw->size < FW_HEADER_SIZE) { + phydev_err(phydev, "Firmware too small: %zu bytes\n", fw->size); + return -EINVAL; + } + + hdr = (const struct rtl8261x_fw_header *)fw->data; + + main_magic = le32_to_cpu(hdr->main_magic); + if (main_magic != FW_MAIN_MAGIC) { + phydev_err(phydev, "Invalid firmware magic: 0x%08x\n", main_magic); + return -EINVAL; + } + + sub_magic = le32_to_cpu(hdr->sub_magic); + if (sub_magic != FW_SUB_MAGIC_8261C && sub_magic != FW_SUB_MAGIC_8261D) { + phydev_err(phydev, "Invalid sub magic: 0x%08x\n", sub_magic); + return -EINVAL; + } + + num_entries = le16_to_cpu(hdr->num_entries); + data_len = num_entries * FW_ENTRY_SIZE; + + if (fw->size != sizeof(*hdr) + data_len) { + phydev_err(phydev, "Firmware size mismatch\n"); + return -EINVAL; + } + + calc_crc = crc32(~0, fw->data + FW_HEADER_SIZE, data_len) ^ ~0; + file_crc = le32_to_cpu(hdr->crc32); + + if (calc_crc != file_crc) { + phydev_err(phydev, "CRC32 mismatch: calculated=0x%08x file=0x%08x\n", + calc_crc, file_crc); + return -EINVAL; + } + + return 0; +} + +static int rtl_phy_write_mmd_bits(struct phy_device *phydev, int devnum, + u16 reg, u8 msb, u8 lsb, u16 val) +{ + int ret; + u32 reg_val; + + if (msb > 15 || lsb > msb) + return -EINVAL; + + ret = phy_read_mmd(phydev, devnum, reg); + if (ret < 0) + return ret; + reg_val = ret; + + reg_val &= ~GENMASK(msb, lsb); + reg_val |= (val << lsb) & GENMASK(msb, lsb); + + return phy_write_mmd(phydev, devnum, reg, reg_val); +} + +static int rtl8261x_fw_execute_entry(struct phy_device *phydev, + const struct rtl8261x_fw_entry *entry) +{ + u16 addr, value, timeout_ms; + u8 dev, msb, lsb, poll_set; + u32 bits, expect_val; + int ret = 0; + int val; + + dev = entry->dev; + addr = le16_to_cpu(entry->addr); + msb = entry->msb; + lsb = entry->lsb; + value = le16_to_cpu(entry->value); + timeout_ms = le16_to_cpu(entry->timeout_ms); + poll_set = entry->poll_set; + + if (timeout_ms == 0) + timeout_ms = RTL8261X_POLL_TIMEOUT_MS; + + switch (entry->type) { + case OP_WRITE: + ret = rtl_phy_write_mmd_bits(phydev, dev, addr, msb, lsb, value); + if (ret) { + phydev_err(phydev, "WRITE failed: dev=%d addr=0x%04x\n", dev, addr); + return ret; + } + break; + + case OP_POLL: { + bits = GENMASK(msb, lsb); + expect_val = (value << lsb) & bits; + + if (poll_set) + ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val, + (val & bits) == expect_val, + 1000, timeout_ms * 1000, false); + else + ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val, + (val & bits) != expect_val, + 1000, timeout_ms * 1000, false); + if (ret) + phydev_err(phydev, "POLL timeout: dev=%d addr=0x%04x\n", + dev, addr); + break; + } + default: + phydev_err(phydev, "Unknown firmware operation: %d\n", entry->type); + ret = -EINVAL; + break; + } + + return ret; +} + +static int rtl8261x_fw_load(struct phy_device *phydev) +{ + struct rtl8261x_priv *priv = phydev->priv; + const struct rtl8261x_fw_entry *entry; + const struct rtl8261x_fw_header *hdr; + const struct firmware *fw; + int ret, i; + + if (!priv->fw_name) + return 0; + + ret = request_firmware(&fw, priv->fw_name, &phydev->mdio.dev); + if (ret) + return ret; + + ret = rtl8261x_verify_firmware(phydev, fw); + if (ret) + goto release_fw; + + hdr = (const struct rtl8261x_fw_header *)fw->data; + + entry = (const struct rtl8261x_fw_entry *)(fw->data + FW_HEADER_SIZE); + for (i = 0; i < le16_to_cpu(hdr->num_entries); i++, entry++) { + ret = rtl8261x_fw_execute_entry(phydev, entry); + if (ret) { + phydev_err(phydev, "Entry %d failed: %d\n", i, ret); + goto release_fw; + } + } + + priv->fw_loaded = true; + +release_fw: + release_firmware(fw); + return ret; +} + static int rtl8261x_config_master_slave(struct phy_device *phydev) { u16 val; @@ -601,6 +804,22 @@ static int rtl8261x_config_aneg(struct phy_device *phydev) return 0; } +static int rtl8261x_config_init(struct phy_device *phydev) +{ + struct rtl8261x_priv *priv = phydev->priv; + int ret = 0; + + if (!priv->is_generic && !priv->fw_loaded) { + ret = rtl8261x_fw_load(phydev); + if (ret) { + phydev_err(phydev, "Firmware loading failed: %d\n", ret); + return ret; + } + } + + return ret; +} + static int rtl821x_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; @@ -3296,6 +3515,7 @@ static struct phy_driver realtek_drvs[] = { PHY_ID_MATCH_EXACT(RTL_8261C_CG), .name = "Realtek RTL8261C_RTL8261D 10Gbps PHY", .probe = rtl8261x_probe, + .config_init = rtl8261x_config_init, .get_features = rtl8261x_get_features, .config_aneg = rtl8261x_config_aneg, .read_status = rtl8261x_read_status, @@ -3308,6 +3528,7 @@ static struct phy_driver realtek_drvs[] = { PHY_ID_MATCH_EXACT(RTL_8261CE_CG), .name = "Realtek RTL8261CE 10Gbps PHY", .probe = rtl8261x_probe, + .config_init = rtl8261x_config_init, .get_features = rtl8261x_get_features, .config_aneg = rtl8261x_config_aneg, .read_status = rtl8261x_read_status, -- 2.43.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v1 2/2] net: phy: realtek: load firmware for RTL8261C 2026-05-28 7:52 ` [PATCH net-next v1 2/2] net: phy: realtek: load firmware for RTL8261C javen @ 2026-05-28 9:08 ` Nicolai Buchwitz 2026-05-28 12:20 ` Daniel Golle 1 sibling, 0 replies; 9+ messages in thread From: Nicolai Buchwitz @ 2026-05-28 9:08 UTC (permalink / raw) To: javen Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, freddy_gu, netdev, linux-kernel, daniel, vladimir.oltean Hi Javen On 28.5.2026 09:52, javen wrote: > From: Javen Xu <javen_xu@realsil.com.cn> > > This patch adds support for loading firmware. Download some parameters > for RTL8261C. Maybe you can add a bit more context to the commit message? From what I can read in the code this also add other variant etc. > > Signed-off-by: Javen Xu <javen_xu@realsil.com.cn> > --- > drivers/net/phy/realtek/realtek_main.c | 221 +++++++++++++++++++++++++ > 1 file changed, 221 insertions(+) > > diff --git a/drivers/net/phy/realtek/realtek_main.c > b/drivers/net/phy/realtek/realtek_main.c > index fe743fd0421b..d20cdc68cc62 100644 > --- a/drivers/net/phy/realtek/realtek_main.c > +++ b/drivers/net/phy/realtek/realtek_main.c > @@ -18,12 +18,51 @@ > #include <linux/clk.h> > #include <linux/string_choices.h> > #include <net/phy/realtek_phy.h> > +#include <linux/firmware.h> > +#include <linux/crc32.h> Move before net/ include > > #include "../phylib.h" > #include "realtek.h" > > #define RTL_8261C_CG 0x001cc898 > #define RTL_8261CE_CG 0x001cc899 > +#define FW_MAIN_MAGIC 0x52544C38 > +#define FW_SUB_MAGIC_8261C 0x32363143 > +#define FW_SUB_MAGIC_8261D 0x32363144 > +#define RTL8261X_POLL_TIMEOUT_MS 100 > + > +#define RTL8261C_CE_FW_NAME "rtl_nic/rtl8261c.bin" > +MODULE_FIRMWARE(RTL8261C_CE_FW_NAME); > + > +enum rtl8261x_fw_op { > + OP_WRITE = 0x00, /* Write */ > + OP_POLL = 0x02, /* Polling */ > +}; > + > +struct rtl8261x_fw_header { > + __le32 main_magic; /* Main magic number 0x52544C38 ("RTL8") */ > + __le32 sub_magic; /* Sub magic number */ > + __le16 version_major; /* Major version */ > + __le16 version_minor; /* Minor version */ > + __le16 num_entries; /* Number of entries */ > + __le16 reserved; /* Reserved */ > + __le32 crc32; /* CRC32 checksum */ > +} __packed; > + > +struct rtl8261x_fw_entry { > + __u8 type; /* Operation type (OP_*) */ > + __u8 dev; /* MMD device */ > + __le16 addr; /* Register address */ > + __u8 msb; /* MSB bit position */ > + __u8 lsb; /* LSB bit position */ > + __le16 value; /* Value to write/compare */ > + __le16 timeout_ms; /* Poll timeout in milliseconds */ > + __u8 poll_set; /* Poll for set (1) or clear (0) */ > + __u8 reserved; /* Reserved */ > +} __packed; > + > +#define FW_HEADER_SIZE sizeof(struct rtl8261x_fw_header) > +#define FW_ENTRY_SIZE sizeof(struct rtl8261x_fw_entry) > > #define RTL8201F_IER_PAGE 0x07 > #define RTL8201F_IER 0x13 > @@ -332,6 +371,8 @@ struct rtl8261x_priv { > enum rtl8261x_chip_model model; > u8 sub_phy_id; > bool is_generic; > + const char *fw_name; > + bool fw_loaded; > }; > > static int rtl821x_read_page(struct phy_device *phydev) > @@ -413,16 +454,19 @@ static int rtl8261x_probe(struct phy_device > *phydev) > switch (sub_phy_id) { > case RTL8261C_CE_MODEL: > priv->model = RTL8261_MODEL_C_CE; > + priv->fw_name = RTL8261C_CE_FW_NAME; > phydev_info(phydev, "RTL8261C/CE detected (sub_id 0x%02x)\n", > sub_phy_id); > break; > > case RTL8261D_MODEL: > priv->model = RTL8261_MODEL_D; > + priv->fw_name = NULL; priv->fw_name is already initialized with NULL by devm_kzalloc > phydev_info(phydev, "RTL8261D detected (sub_id 0x%02x)\n", > sub_phy_id); > break; > > default: > priv->model = RTL8261_MODEL_GENERIC; > + priv->fw_name = NULL; priv->fw_name is already initialized with NULL by devm_kzalloc > priv->is_generic = true; > phydev_warn(phydev, "Unknown sub_id 0x%02x! Using GENERIC mode. > Update driver for full support.\n", > sub_phy_id); > @@ -452,6 +496,165 @@ static int rtl8261x_get_features(struct > phy_device *phydev) > return 0; > } > > +static int rtl8261x_verify_firmware(struct phy_device *phydev, const > struct firmware *fw) > +{ > + const struct rtl8261x_fw_header *hdr; > + u32 calc_crc, file_crc; > + size_t data_len; > + u16 num_entries; > + u32 main_magic, sub_magic; Please re-order to match reverse x-mas > + > + if (fw->size < FW_HEADER_SIZE) { > + phydev_err(phydev, "Firmware too small: %zu bytes\n", fw->size); > + return -EINVAL; > + } > + > + hdr = (const struct rtl8261x_fw_header *)fw->data; > + > + main_magic = le32_to_cpu(hdr->main_magic); > + if (main_magic != FW_MAIN_MAGIC) { > + phydev_err(phydev, "Invalid firmware magic: 0x%08x\n", main_magic); > + return -EINVAL; > + } > + > + sub_magic = le32_to_cpu(hdr->sub_magic); > + if (sub_magic != FW_SUB_MAGIC_8261C && sub_magic != > FW_SUB_MAGIC_8261D) { > + phydev_err(phydev, "Invalid sub magic: 0x%08x\n", sub_magic); > + return -EINVAL; > + } It checks for FW_SUB_MAGIC_8261D, but priv->fw_name is always NULL and no firmwaer? Is this a prepartion for a follow-up or am I missing something? > + > + num_entries = le16_to_cpu(hdr->num_entries); > + data_len = num_entries * FW_ENTRY_SIZE; > + > + if (fw->size != sizeof(*hdr) + data_len) { > + phydev_err(phydev, "Firmware size mismatch\n"); > + return -EINVAL; > + } > + > + calc_crc = crc32(~0, fw->data + FW_HEADER_SIZE, data_len) ^ ~0; > + file_crc = le32_to_cpu(hdr->crc32); > + > + if (calc_crc != file_crc) { > + phydev_err(phydev, "CRC32 mismatch: calculated=0x%08x > file=0x%08x\n", > + calc_crc, file_crc); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int rtl_phy_write_mmd_bits(struct phy_device *phydev, int > devnum, > + u16 reg, u8 msb, u8 lsb, u16 val) > +{ > + int ret; > + u32 reg_val; Please re-order to match reverse x-mas > + > + if (msb > 15 || lsb > msb) > + return -EINVAL; > + > + ret = phy_read_mmd(phydev, devnum, reg); > + if (ret < 0) > + return ret; > + reg_val = ret; > + > + reg_val &= ~GENMASK(msb, lsb); > + reg_val |= (val << lsb) & GENMASK(msb, lsb); > + > + return phy_write_mmd(phydev, devnum, reg, reg_val); > +} > + > +static int rtl8261x_fw_execute_entry(struct phy_device *phydev, > + const struct rtl8261x_fw_entry *entry) > +{ > + u16 addr, value, timeout_ms; > + u8 dev, msb, lsb, poll_set; > + u32 bits, expect_val; > + int ret = 0; > + int val; > + > + dev = entry->dev; > + addr = le16_to_cpu(entry->addr); > + msb = entry->msb; > + lsb = entry->lsb; > + value = le16_to_cpu(entry->value); > + timeout_ms = le16_to_cpu(entry->timeout_ms); > + poll_set = entry->poll_set; > + > + if (timeout_ms == 0) > + timeout_ms = RTL8261X_POLL_TIMEOUT_MS; > + > + switch (entry->type) { > + case OP_WRITE: > + ret = rtl_phy_write_mmd_bits(phydev, dev, addr, msb, lsb, value); > + if (ret) { > + phydev_err(phydev, "WRITE failed: dev=%d addr=0x%04x\n", dev, > addr); > + return ret; > + } > + break; > + > + case OP_POLL: { > + bits = GENMASK(msb, lsb); > + expect_val = (value << lsb) & bits; > + > + if (poll_set) > + ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val, > + (val & bits) == expect_val, > + 1000, timeout_ms * 1000, false); > + else > + ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val, > + (val & bits) != expect_val, > + 1000, timeout_ms * 1000, false); > + if (ret) > + phydev_err(phydev, "POLL timeout: dev=%d addr=0x%04x\n", > + dev, addr); > + break; > + } > + default: > + phydev_err(phydev, "Unknown firmware operation: %d\n", entry->type); > + ret = -EINVAL; > + break; > + } > + > + return ret; > +} > + > +static int rtl8261x_fw_load(struct phy_device *phydev) > +{ > + struct rtl8261x_priv *priv = phydev->priv; > + const struct rtl8261x_fw_entry *entry; > + const struct rtl8261x_fw_header *hdr; > + const struct firmware *fw; > + int ret, i; > + > + if (!priv->fw_name) > + return 0; > + > + ret = request_firmware(&fw, priv->fw_name, &phydev->mdio.dev); > + if (ret) > + return ret; > + > + ret = rtl8261x_verify_firmware(phydev, fw); > + if (ret) > + goto release_fw; > + > + hdr = (const struct rtl8261x_fw_header *)fw->data; > + > + entry = (const struct rtl8261x_fw_entry *)(fw->data + > FW_HEADER_SIZE); > + for (i = 0; i < le16_to_cpu(hdr->num_entries); i++, entry++) { > + ret = rtl8261x_fw_execute_entry(phydev, entry); > + if (ret) { > + phydev_err(phydev, "Entry %d failed: %d\n", i, ret); > + goto release_fw; > + } > + } > + > + priv->fw_loaded = true; > + > +release_fw: > + release_firmware(fw); > + return ret; > +} > + > static int rtl8261x_config_master_slave(struct phy_device *phydev) > { > u16 val; > @@ -601,6 +804,22 @@ static int rtl8261x_config_aneg(struct phy_device > *phydev) > return 0; > } > > +static int rtl8261x_config_init(struct phy_device *phydev) > +{ > + struct rtl8261x_priv *priv = phydev->priv; > + int ret = 0; > + > + if (!priv->is_generic && !priv->fw_loaded) { > + ret = rtl8261x_fw_load(phydev); > + if (ret) { > + phydev_err(phydev, "Firmware loading failed: %d\n", ret); > + return ret; > + } > + } > + > + return ret; > +} > + > static int rtl821x_probe(struct phy_device *phydev) > { > struct device *dev = &phydev->mdio.dev; > @@ -3296,6 +3515,7 @@ static struct phy_driver realtek_drvs[] = { > PHY_ID_MATCH_EXACT(RTL_8261C_CG), > .name = "Realtek RTL8261C_RTL8261D 10Gbps PHY", > .probe = rtl8261x_probe, > + .config_init = rtl8261x_config_init, Use spaces before = to keep alignment of the rest. > .get_features = rtl8261x_get_features, > .config_aneg = rtl8261x_config_aneg, > .read_status = rtl8261x_read_status, > @@ -3308,6 +3528,7 @@ static struct phy_driver realtek_drvs[] = { > PHY_ID_MATCH_EXACT(RTL_8261CE_CG), > .name = "Realtek RTL8261CE 10Gbps PHY", > .probe = rtl8261x_probe, > + .config_init = rtl8261x_config_init, Use spaces before = to keep alignment of the rest. > .get_features = rtl8261x_get_features, > .config_aneg = rtl8261x_config_aneg, > .read_status = rtl8261x_read_status, Nicolai ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v1 2/2] net: phy: realtek: load firmware for RTL8261C 2026-05-28 7:52 ` [PATCH net-next v1 2/2] net: phy: realtek: load firmware for RTL8261C javen 2026-05-28 9:08 ` Nicolai Buchwitz @ 2026-05-28 12:20 ` Daniel Golle 1 sibling, 0 replies; 9+ messages in thread From: Daniel Golle @ 2026-05-28 12:20 UTC (permalink / raw) To: javen Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, freddy_gu, netdev, linux-kernel, vladimir.oltean, Balázs Triszka On Thu, May 28, 2026 at 03:52:26PM +0800, javen wrote: > From: Javen Xu <javen_xu@realsil.com.cn> > > This patch adds support for loading firmware. Download some parameters > for RTL8261C. I'd like to bring to your attention that the OpenWrt community has impemented a more complete support for initial register patching using a extremely similar firmware format. https://github.com/openwrt/openwrt/blob/main/target/linux/generic/pending-6.18/742-net-phy-realtek-add-5G-and-10G-PHY-support.patch We used 'RTK_PATCH_OP_*' while this series uses only 'OP_*' as macro names defining the initval patching operations. As the vendor downstream driver had the operations hard-coded in tables our community came up with a tool to generate the patching blobs from the GPL-licensed vendor driver: https://github.com/balika011/realtek_phy_firmware Given the similarity I wonder if this code has been taken from Balázs Triszka work (which clearly predates it) without giving him the due credit. ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-05-28 16:56 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-05-28 7:52 [PATCH net-next v1 0/2] Add support for RTL8261c javen 2026-05-28 7:52 ` [PATCH net-next v1 1/2] net: phy: realtek: add support for RTL8261 javen 2026-05-28 9:37 ` Nicolai Buchwitz 2026-05-28 12:39 ` Andrew Lunn 2026-05-28 12:42 ` Andrew Lunn 2026-05-28 16:56 ` Aleksander Jan Bajkowski 2026-05-28 7:52 ` [PATCH net-next v1 2/2] net: phy: realtek: load firmware for RTL8261C javen 2026-05-28 9:08 ` Nicolai Buchwitz 2026-05-28 12:20 ` Daniel Golle
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox