Netdev List
 help / color / mirror / Atom feed
* [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