From: Javen Xu Add a generic Clause 45 software reset helper. The helper sets the reset bit in the PMA/PMD control register and waits until the bit is cleared by hardware. Signed-off-by: Javen Xu --- Changes in v2: - no changes, new file --- drivers/net/phy/phy-c45.c | 22 ++++++++++++++++++++++ include/linux/phy.h | 1 + 2 files changed, 23 insertions(+) diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index 126951741428..216a2cb9e3f0 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -330,6 +330,28 @@ int genphy_c45_an_disable_aneg(struct phy_device *phydev) } EXPORT_SYMBOL_GPL(genphy_c45_an_disable_aneg); +/** + * genphy_c45_soft_reset - software reset the PHY via Clause 45 PMA/PMD control register + * @phydev: target phy_device struct + * + * Return: 0 on success, negative errno on failure. + */ +int genphy_c45_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); +} +EXPORT_SYMBOL_GPL(genphy_c45_soft_reset); + /** * genphy_c45_restart_aneg - Enable and restart auto-negotiation * @phydev: target phy_device struct diff --git a/include/linux/phy.h b/include/linux/phy.h index 199a7aaa341b..25a66320df56 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -2309,6 +2309,7 @@ int genphy_c37_read_status(struct phy_device *phydev, bool *changed); /* Clause 45 PHY */ int genphy_c45_restart_aneg(struct phy_device *phydev); int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart); +int genphy_c45_soft_reset(struct phy_device *phydev); int genphy_c45_aneg_done(struct phy_device *phydev); int genphy_c45_read_link(struct phy_device *phydev); int genphy_c45_read_lpa(struct phy_device *phydev); -- 2.43.0 From: Javen Xu Add a generic helper to configure forced master/slave mode for Clause 45 PHYs using the 10GBASE-T AN control register. Signed-off-by: Javen Xu --- Changes in v2: - no changes, new file --- drivers/net/phy/phy-c45.c | 32 ++++++++++++++++++++++++++++++++ include/linux/phy.h | 1 + include/uapi/linux/mdio.h | 2 ++ 3 files changed, 35 insertions(+) diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index 216a2cb9e3f0..bbab5586c348 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -352,6 +352,38 @@ int genphy_c45_soft_reset(struct phy_device *phydev) } EXPORT_SYMBOL_GPL(genphy_c45_soft_reset); +/** + * genphy_c45_config_master_slave - Configure Master/Slave setting for C45 PHYs + * @phydev: target phy_device struct + * + * Description: Configures the Master/Slave manual setting in the + * 10GBASE-T control register (MMD 7, Register 0x0020) according to + * IEEE 802.3 standards. + */ +int genphy_c45_config_master_slave(struct phy_device *phydev) +{ + u16 val = 0; + + switch (phydev->master_slave_set) { + case MASTER_SLAVE_CFG_MASTER_FORCE: + val = MDIO_AN_10GBT_CTRL_MS_ENABLE | MDIO_AN_10GBT_CTRL_MS_VALUE; + break; + case MASTER_SLAVE_CFG_SLAVE_FORCE: + val = MDIO_AN_10GBT_CTRL_MS_ENABLE; + break; + case MASTER_SLAVE_CFG_UNKNOWN: + case MASTER_SLAVE_CFG_MASTER_PREFERRED: + case MASTER_SLAVE_CFG_SLAVE_PREFERRED: + default: + break; + } + + return phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL, + MDIO_AN_10GBT_CTRL_MS_ENABLE | + MDIO_AN_10GBT_CTRL_MS_VALUE, val); +} +EXPORT_SYMBOL_GPL(genphy_c45_config_master_slave); + /** * genphy_c45_restart_aneg - Enable and restart auto-negotiation * @phydev: target phy_device struct diff --git a/include/linux/phy.h b/include/linux/phy.h index 25a66320df56..d9c8c86e2d81 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -2310,6 +2310,7 @@ int genphy_c37_read_status(struct phy_device *phydev, bool *changed); int genphy_c45_restart_aneg(struct phy_device *phydev); int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart); int genphy_c45_soft_reset(struct phy_device *phydev); +int genphy_c45_config_master_slave(struct phy_device *phydev); int genphy_c45_aneg_done(struct phy_device *phydev); int genphy_c45_read_link(struct phy_device *phydev); int genphy_c45_read_lpa(struct phy_device *phydev); diff --git a/include/uapi/linux/mdio.h b/include/uapi/linux/mdio.h index b2541c948fc1..2f36005b3133 100644 --- a/include/uapi/linux/mdio.h +++ b/include/uapi/linux/mdio.h @@ -332,6 +332,8 @@ #define MDIO_AN_10GBT_CTRL_ADV2_5G 0x0080 /* Advertise 2.5GBASE-T */ #define MDIO_AN_10GBT_CTRL_ADV5G 0x0100 /* Advertise 5GBASE-T */ #define MDIO_AN_10GBT_CTRL_ADV10G 0x1000 /* Advertise 10GBASE-T */ +#define MDIO_AN_10GBT_CTRL_MS_ENABLE 0x8000 /* Master/slave manual config enable */ +#define MDIO_AN_10GBT_CTRL_MS_VALUE 0x4000 /* Master/slave config value (1=Master) */ /* AN 10GBASE-T status register. */ #define MDIO_AN_10GBT_STAT_LP2_5G 0x0020 /* LP is 2.5GBT capable */ -- 2.43.0 From: Javen Xu This patch adds support for Realtek phy chip RTL8261C_CG. Its PHY ID is 0x001cc898. Signed-off-by: Javen Xu --- Changes in v2: - remove RTL8261D, only support RTL8261C_CG - remove speed 500, rarely used - add helper function, genphy_c45_soft_reset() and genphy_c45_config_master_slave() --- drivers/net/phy/realtek/realtek_main.c | 178 +++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 27268811f564..86788feaae92 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -141,6 +141,10 @@ #define RTL8211F_PHYSICAL_ADDR_WORD1 17 #define RTL8211F_PHYSICAL_ADDR_WORD2 18 +#define RTL8261X_EXT_ADDR_REG 0xa436 +#define RTL8261X_EXT_DATA_REG 0xa438 +#define RTL_8261X_SUB_PHY_ID_ADDR 0x801d + #define RTL822X_VND1_SERDES_OPTION 0x697a #define RTL822X_VND1_SERDES_OPTION_MODE_MASK GENMASK(5, 0) #define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII 0 @@ -251,6 +255,33 @@ #define RTL_8221B_VM_CG 0x001cc84a #define RTL_8251B 0x001cc862 #define RTL_8261C 0x001cc890 +#define RTL_8261C_CG 0x001cc898 + +#define RTL8261C_CE_MODEL 0x00 +#define RTL8261X_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_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) + /* RTL8211E and RTL8211F support up to three LEDs */ #define RTL8211x_LED_COUNT 3 @@ -270,6 +301,10 @@ struct rtl821x_priv { u16 iner; }; +struct rtl8261x_priv { + u8 sub_phy_id; +}; + static int rtl821x_read_page(struct phy_device *phydev) { return __phy_read(phydev, RTL821x_PAGE_SELECT); @@ -310,6 +345,137 @@ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page, return phy_restore_page(phydev, oldpage, ret); } +static int rtl8261x_probe(struct phy_device *phydev) +{ + 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; + + switch (sub_phy_id) { + case RTL8261C_CE_MODEL: + phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id); + break; + + default: + phydev_err(phydev, "Unknown sub_id 0x%02x\n", sub_phy_id); + return -ENODEV; + } + 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_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_config_aneg(struct phy_device *phydev) +{ + bool changed = false; + u16 adv_1g = 0; + int ret; + + if (phydev->autoneg == AUTONEG_DISABLE) + return genphy_c45_pma_setup_forced(phydev); + + ret = genphy_c45_config_master_slave(phydev); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + 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 = ADVERTISE_1000FULL; + + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, + RTL8261X_GBCR_REG, + ADVERTISE_1000FULL, adv_1g); + if (ret < 0) + return ret; + if (ret > 0 || changed) + return genphy_c45_restart_aneg(phydev); + + return 0; +} + static int rtl821x_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; @@ -3001,6 +3167,18 @@ static struct phy_driver realtek_drvs[] = { .resume = genphy_resume, .read_mmd = genphy_read_mmd_unsupported, .write_mmd = genphy_write_mmd_unsupported, + }, { + PHY_ID_MATCH_EXACT(RTL_8261C_CG), + .name = "Realtek RTL8261C 10Gbps PHY", + .probe = rtl8261x_probe, + .get_features = rtl8261x_get_features, + .config_aneg = rtl8261x_config_aneg, + .read_status = genphy_c45_read_status, + .config_intr = rtl8261x_config_intr, + .handle_interrupt = rtl8261x_handle_interrupt, + .soft_reset = genphy_c45_soft_reset, + .suspend = genphy_c45_pma_suspend, + .resume = genphy_c45_pma_resume, }, }; -- 2.43.0 From: Javen Xu This patch adds support for loading firmware. Download some parameters for RTL8261C_CG. Signed-off-by: Javen Xu --- Changes in v2: - remove __pack, struct rtl8261x_fw_header and rtl8261x_fw_entry will not pad - reverse xmas tree for some definition - add explanation on rtl_phy_write_mmd_bits() --- drivers/net/phy/realtek/realtek_main.c | 230 +++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 86788feaae92..612ee7012e68 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -8,7 +8,9 @@ * Copyright (c) 2004 Freescale Semiconductor, Inc. */ #include +#include #include +#include #include #include #include @@ -282,6 +284,42 @@ RTL8261X_INT_ALDPS_CHG | \ RTL8261X_INT_JABBER) +#define FW_MAIN_MAGIC 0x52544C38 +#define FW_SUB_MAGIC_8261C 0x32363143 +#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 */ +}; + +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 */ +}; + +#define FW_HEADER_SIZE sizeof(struct rtl8261x_fw_header) +#define FW_ENTRY_SIZE sizeof(struct rtl8261x_fw_entry) /* RTL8211E and RTL8211F support up to three LEDs */ #define RTL8211x_LED_COUNT 3 @@ -303,6 +341,8 @@ struct rtl821x_priv { struct rtl8261x_priv { u8 sub_phy_id; + const char *fw_name; + bool fw_loaded; }; static int rtl821x_read_page(struct phy_device *phydev) @@ -369,6 +409,7 @@ static int rtl8261x_probe(struct phy_device *phydev) switch (sub_phy_id) { case RTL8261C_CE_MODEL: + priv->fw_name = RTL8261C_CE_FW_NAME; phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id); break; @@ -400,6 +441,178 @@ 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 main_magic, sub_magic; + u32 calc_crc, file_crc; + size_t data_len; + u16 num_entries; + + 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) { + 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; +} + +/** + * rtl_phy_write_mmd_bits - Write a bitfield in an MMD register + * @phydev: PHY device structure + * @devnum: MMD device number + * @reg: MMD register address + * @msb: Most significant bit of the field (inclusive) + * @lsb: Least significant bit of the field (inclusive) + * @val: Value to write into the field (right-aligned) + * + * Return: 0 on success, negative error code on failure. + */ +static int rtl_phy_write_mmd_bits(struct phy_device *phydev, int devnum, + u16 reg, u8 msb, u8 lsb, u16 val) +{ + u32 reg_val; + int ret; + + 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) { + phydev_err(phydev, "Failed to load firmware %s: %d\n", priv->fw_name, 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_intr(struct phy_device *phydev) { int ret; @@ -476,6 +689,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->fw_name && !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; @@ -3171,6 +3400,7 @@ static struct phy_driver realtek_drvs[] = { PHY_ID_MATCH_EXACT(RTL_8261C_CG), .name = "Realtek RTL8261C 10Gbps PHY", .probe = rtl8261x_probe, + .config_init = rtl8261x_config_init, .get_features = rtl8261x_get_features, .config_aneg = rtl8261x_config_aneg, .read_status = genphy_c45_read_status, -- 2.43.0