From: Bjørn Mork Date: Thu, 6 Feb 2025 17:30:06 +0000 (+0100) Subject: realtek: rtl93xx: mdio-smbus support for clause 45 and Rollball SFPs X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=1fc19bc06edc62a748327d210eea030d36541143;p=openwrt%2Fopenwrt.git realtek: rtl93xx: mdio-smbus support for clause 45 and Rollball SFPs These features have been added to the mdio-i2c driver and are now used by the sfp driver. The support is required for some newer SFPs. Signed-off-by: Bjørn Mork Link: https://github.com/openwrt/openwrt/pull/17950 Signed-off-by: Sander Vanheule --- diff --git a/target/linux/realtek/patches-6.6/712-net-phy-add-an-MDIO-SMBus-library.patch b/target/linux/realtek/patches-6.6/712-net-phy-add-an-MDIO-SMBus-library.patch index 03c62b505d..229e2612ea 100644 --- a/target/linux/realtek/patches-6.6/712-net-phy-add-an-MDIO-SMBus-library.patch +++ b/target/linux/realtek/patches-6.6/712-net-phy-add-an-MDIO-SMBus-library.patch @@ -45,49 +45,308 @@ Signed-off-by: Antoine Tenart obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o --- /dev/null +++ b/drivers/net/mdio/mdio-smbus.c -@@ -0,0 +1,62 @@ +@@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MDIO SMBus bridge + * + * Copyright (C) 2020 Antoine Tenart ++ * Copyright (C) 2025 Bjørn Mork + * + * Network PHYs can appear on SMBus when they are part of SFP modules. + */ +#include +#include +#include ++#include + -+static int smbus_mii_read(struct mii_bus *mii, int phy_id, int reg) ++static int smbus_mii_read_c45(struct mii_bus *mii, int phy_id, int devad, int reg) +{ ++ u16 bus_addr = i2c_mii_phy_addr(phy_id); + struct i2c_adapter *i2c = mii->priv; + union i2c_smbus_data data; ++ size_t addrlen; ++ u8 buf[5], *p; ++ int i, ret; ++ ++ if (!i2c_mii_valid_phy_id(phy_id)) ++ return 0xffff; ++ ++ p = buf; ++ if (devad >= 0) { ++ *p++ = 0x20 | devad; ++ *p++ = reg >> 8; ++ } ++ *p++ = reg; ++ addrlen = p - buf; ++ ++ i2c_lock_bus(i2c, I2C_LOCK_SEGMENT); ++ if (addrlen > 1) { ++ for (i = 1; i < addrlen; i++) { ++ data.byte = buf[i]; ++ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, buf[0], I2C_SMBUS_BYTE_DATA, &data); ++ if (ret < 0) ++ goto unlock; ++ } ++ } ++ ++ for (i = addrlen; i < addrlen + 2; i++) { ++ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, buf[0], I2C_SMBUS_BYTE_DATA, &data); ++ if (ret < 0) ++ goto unlock; ++ buf[i] = data.byte; ++ } ++ ++unlock: ++ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT); ++ if (ret < 0) ++ return 0xffff; ++ return buf[addrlen] << 8 | buf[addrlen + 1]; ++} ++ ++static int smbus_mii_write_c45(struct mii_bus *mii, int phy_id, int devad, int reg, u16 val) ++{ ++ u16 bus_addr = i2c_mii_phy_addr(phy_id); ++ struct i2c_adapter *i2c = mii->priv; ++ union i2c_smbus_data data; ++ size_t buflen; ++ u8 buf[5], *p; ++ int i, ret; ++ ++ if (!i2c_mii_valid_phy_id(phy_id)) ++ return 0; ++ ++ p = buf; ++ if (devad >= 0) { ++ *p++ = devad; ++ *p++ = reg >> 8; ++ } ++ *p++ = reg; ++ *p++ = val >> 8; ++ *p++ = val; ++ buflen = p - buf; ++ ++ i2c_lock_bus(i2c, I2C_LOCK_SEGMENT); ++ for (i = 1; i < buflen; i++) { ++ data.byte = buf[i]; ++ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, buf[0], I2C_SMBUS_BYTE_DATA, &data); ++ if (ret < 0) ++ goto unlock; ++ } ++unlock: ++ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT); ++ return ret < 0 ? ret : 0; ++} ++ ++static int smbus_mii_read_c22(struct mii_bus *bus, int phy_id, int reg) ++{ ++ return smbus_mii_read_c45(bus, phy_id, -1, reg); ++} ++ ++static int smbus_mii_write_c22(struct mii_bus *bus, int phy_id, int reg, u16 val) ++{ ++ return smbus_mii_write_c45(bus, phy_id, -1, reg, val); ++} ++ ++/* From mdio-i2c.c: ++ * ++ * RollBall SFPs do not access internal PHY via I2C address 0x56, but ++ * instead via address 0x51, when SFP page is set to 0x03 and password to ++ * 0xffffffff. ++ * ++ * address size contents description ++ * ------- ---- -------- ----------- ++ * 0x80 1 CMD 0x01/0x02/0x04 for write/read/done ++ * 0x81 1 DEV Clause 45 device ++ * 0x82 2 REG Clause 45 register ++ * 0x84 2 VAL Register value ++ */ ++#define ROLLBALL_PHY_I2C_ADDR 0x51 ++ ++#define ROLLBALL_PASSWORD (SFP_VSL + 3) ++ ++#define ROLLBALL_CMD_ADDR 0x80 ++#define ROLLBALL_DATA_ADDR 0x81 ++ ++#define ROLLBALL_CMD_WRITE 0x01 ++#define ROLLBALL_CMD_READ 0x02 ++#define ROLLBALL_CMD_DONE 0x04 ++ ++#define SFP_PAGE_ROLLBALL_MDIO 3 ++ ++static int smbus_set_sfp_page_lock(struct i2c_adapter *i2c, int bus_addr, u8 page) ++{ ++ union i2c_smbus_data data; ++ u8 oldpage; + int ret; + -+ ret = i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0, I2C_SMBUS_READ, -+ reg, I2C_SMBUS_BYTE_DATA, &data); ++ i2c_lock_bus(i2c, I2C_LOCK_SEGMENT); ++ ++ /* read current page */ ++ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, SFP_PAGE, I2C_SMBUS_BYTE_DATA, &data); + if (ret < 0) -+ return 0xff; ++ goto unlock; ++ ++ oldpage = data.byte; ++ data.byte = page; ++ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, SFP_PAGE, I2C_SMBUS_BYTE_DATA, &data); ++ if (ret == 0) ++ return oldpage; + -+ return data.byte; ++unlock: ++ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT); ++ ++ return ret; +} + -+static int smbus_mii_write(struct mii_bus *mii, int phy_id, int reg, u16 val) ++static int __smbus_set_sfp_page_unlock(struct i2c_adapter *i2c, int bus_addr, u8 page) +{ -+ struct i2c_adapter *i2c = mii->priv; + union i2c_smbus_data data; + int ret; + -+ data.byte = val; ++ data.byte = page; ++ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, SFP_PAGE, I2C_SMBUS_BYTE_DATA, &data); ++ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT); + -+ ret = i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0, I2C_SMBUS_WRITE, -+ reg, I2C_SMBUS_BYTE_DATA, &data); -+ return ret < 0 ? ret : 0; ++ return ret; ++} ++ ++/* Wait for the ROLLBALL_CMD_ADDR register to read ROLLBALL_CMD_DONE, ++ * indicating that the previous command has completed. ++ * ++ * Quoting from the mdio-i2c.c implementation: ++ * ++ * By experiment it takes up to 70 ms to access a register for these ++ * SFPs. Sleep 20ms between iterations and try 10 times. ++ */ ++static int __smbus_rollball_mii_poll(struct i2c_adapter *i2c , int bus_addr) ++{ ++ union i2c_smbus_data data; ++ int i, ret; ++ ++ i = 10; ++ do { ++ msleep(20); ++ ++ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, ROLLBALL_CMD_ADDR, I2C_SMBUS_BYTE_DATA, &data); ++ if (ret < 0) ++ return ret; ++ ++ if (data.byte == ROLLBALL_CMD_DONE) ++ return 0; ++ } while (i-- > 0); ++ dev_dbg(&i2c->dev, "poll timed out\n"); ++ return -ETIMEDOUT; +} + -+struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c) ++static int smbus_mii_read_rollball(struct mii_bus *bus, int phy_id, int devad, int reg) ++{ ++ struct i2c_adapter *i2c = bus->priv; ++ union i2c_smbus_data data; ++ int i, bus_addr, old, ret; ++ u8 buf[6]; ++ ++ bus_addr = i2c_mii_phy_addr(phy_id); ++ if (bus_addr != ROLLBALL_PHY_I2C_ADDR) ++ return 0xffff; ++ ++ old = smbus_set_sfp_page_lock(i2c, bus_addr, SFP_PAGE_ROLLBALL_MDIO); ++ if (old < 0) ++ return 0xffff; ++ ++ /* set address */ ++ buf[0] = ROLLBALL_CMD_READ; ++ buf[1] = devad; ++ buf[2] = reg >> 8; ++ buf[3] = reg & 0xff; ++ ++ /* send address */ ++ for (i = 0; i < 4; i++) { ++ data.byte = buf[i]; ++ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, ROLLBALL_CMD_ADDR + i, I2C_SMBUS_BYTE_DATA, &data); ++ if (ret < 0) ++ goto unlock; ++ } ++ ++ /* wait for command to complete */ ++ ret = __smbus_rollball_mii_poll(i2c, bus_addr); ++ if (ret) ++ goto unlock; ++ ++ /* read result */ ++ for (i = 4; i < 6; i++) { ++ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, ROLLBALL_CMD_ADDR + i, I2C_SMBUS_BYTE_DATA, &data); ++ if (ret < 0) ++ goto unlock; ++ buf[i] = data.byte; ++ } ++ ++unlock: ++ __smbus_set_sfp_page_unlock(i2c, bus_addr, old); ++ if (ret < 0) ++ return 0xffff; ++ return buf[4] << 8 | buf[5]; ++} ++ ++static int smbus_mii_write_rollball(struct mii_bus *bus, int phy_id, int devad, int reg, u16 val) ++{ ++ struct i2c_adapter *i2c = bus->priv; ++ union i2c_smbus_data data; ++ int i, bus_addr, old, ret; ++ u8 buf[6]; ++ ++ bus_addr = i2c_mii_phy_addr(phy_id); ++ if (bus_addr != ROLLBALL_PHY_I2C_ADDR) ++ return 0; ++ ++ old = smbus_set_sfp_page_lock(i2c, bus_addr, SFP_PAGE_ROLLBALL_MDIO); ++ if (old < 0) ++ return old; ++ ++ /* set address */ ++ buf[0] = ROLLBALL_CMD_WRITE; ++ buf[1] = devad; ++ buf[2] = reg >> 8; ++ buf[3] = reg & 0xff; ++ buf[4] = val >> 8; ++ buf[5] = val & 0xff; ++ ++ /* send address and value */ ++ for (i = 0; i < 6; i++) { ++ data.byte = buf[i]; ++ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, ROLLBALL_CMD_ADDR + i, I2C_SMBUS_BYTE_DATA, &data); ++ if (ret < 0) ++ goto unlock; ++ } ++ ++ /* wait for command to complete */ ++ ret = __smbus_rollball_mii_poll(i2c, bus_addr); ++ ++unlock: ++ __smbus_set_sfp_page_unlock(i2c, bus_addr, old); ++ return ret; ++} ++ ++/* write "password" - four 0xff bytes - to the ROLLBALL_PASSWORD register */ ++static int smbus_mii_init_rollball(struct i2c_adapter *i2c) ++{ ++ union i2c_smbus_data data; ++ int i, ret; ++ ++ data.byte = 0xff; ++ for (i = 0; i < 4; i++) { ++ ret = i2c_smbus_xfer(i2c, ROLLBALL_PHY_I2C_ADDR, 0, I2C_SMBUS_WRITE, ROLLBALL_PASSWORD + i, I2C_SMBUS_BYTE_DATA, &data); ++ if (ret < 0) ++ return ret; ++ } ++ return 0; ++} ++ ++struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c, ++ enum mdio_i2c_proto protocol) +{ + struct mii_bus *mii; ++ int ret; + + if (!i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA)) + return ERR_PTR(-EINVAL); @@ -98,10 +357,30 @@ Signed-off-by: Antoine Tenart + + snprintf(mii->id, MII_BUS_ID_SIZE, "smbus:%s", dev_name(parent)); + mii->parent = parent; -+ mii->read = smbus_mii_read; -+ mii->write = smbus_mii_write; + mii->priv = i2c; + ++ switch (protocol) { ++ case MDIO_I2C_ROLLBALL: ++ ret = smbus_mii_init_rollball(i2c); ++ if (ret < 0) { ++ dev_err(parent, ++ "Cannot initialize RollBall MDIO protocol on SMBus: %d\n", ++ ret); ++ mdiobus_free(mii); ++ return ERR_PTR(ret); ++ } ++ ++ mii->read_c45 = smbus_mii_read_rollball; ++ mii->write_c45 = smbus_mii_write_rollball; ++ break; ++ default: ++ mii->read = smbus_mii_read_c22; ++ mii->write = smbus_mii_write_c22; ++ mii->read_c45 = smbus_mii_read_c45; ++ mii->write_c45 = smbus_mii_write_c45; ++ break; ++ } ++ + return mii; +} + @@ -120,11 +399,12 @@ Signed-off-by: Antoine Tenart --- a/include/linux/mdio/mdio-i2c.h +++ b/include/linux/mdio/mdio-i2c.h -@@ -20,5 +20,8 @@ enum mdio_i2c_proto { +@@ -20,5 +20,9 @@ enum mdio_i2c_proto { struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c, enum mdio_i2c_proto protocol); -+struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c); ++struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c, ++ enum mdio_i2c_proto protocol); +bool i2c_mii_valid_phy_id(int phy_id); +unsigned int i2c_mii_phy_addr(int phy_id); diff --git a/target/linux/realtek/patches-6.6/714-net-phy-sfp-add-support-for-SMBus.patch b/target/linux/realtek/patches-6.6/714-net-phy-sfp-add-support-for-SMBus.patch index cb9a1da7e6..96c1088442 100644 --- a/target/linux/realtek/patches-6.6/714-net-phy-sfp-add-support-for-SMBus.patch +++ b/target/linux/realtek/patches-6.6/714-net-phy-sfp-add-support-for-SMBus.patch @@ -86,7 +86,7 @@ Signed-off-by: Antoine Tenart + struct mii_bus *sm_mii; + int ret; + -+ sm_mii = mdio_smbus_alloc(sfp->dev, sfp->i2c); ++ sm_mii = mdio_smbus_alloc(sfp->dev, sfp->i2c, sfp->mdio_protocol); + if (IS_ERR(sm_mii)) + return PTR_ERR(sm_mii); +