* [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; 8+ 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] 8+ 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
` (2 more replies)
2026-05-28 7:52 ` [PATCH net-next v1 2/2] net: phy: realtek: load firmware for RTL8261C javen
1 sibling, 3 replies; 8+ 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] 8+ 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; 8+ 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] 8+ 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; 8+ 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] 8+ 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
2 siblings, 0 replies; 8+ 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] 8+ 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; 8+ 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] 8+ 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
2 siblings, 0 replies; 8+ 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] 8+ 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
2 siblings, 0 replies; 8+ 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] 8+ messages in thread
end of thread, other threads:[~2026-05-28 12:42 UTC | newest]
Thread overview: 8+ 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 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