[openwrt/openwrt] realtek: rtl93xx: mdio-smbus support for clause 45 and Rollball SFPs

LEDE Commits lede-commits at lists.infradead.org
Tue Feb 25 11:57:53 PST 2025


svanheule pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/1fc19bc06edc62a748327d210eea030d36541143

commit 1fc19bc06edc62a748327d210eea030d36541143
Author: Bjørn Mork <bjorn at mork.no>
AuthorDate: Thu Feb 6 18:30:06 2025 +0100

    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 <bjorn at mork.no>
    Link: https://github.com/openwrt/openwrt/pull/17950
    Signed-off-by: Sander Vanheule <sander at svanheule.net>
---
 .../712-net-phy-add-an-MDIO-SMBus-library.patch    | 314 +++++++++++++++++++--
 .../714-net-phy-sfp-add-support-for-SMBus.patch    |   2 +-
 2 files changed, 298 insertions(+), 18 deletions(-)

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 <antoine.tenart at bootlin.com>
  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 <bjorn at mork.no>
 + *
 + * Network PHYs can appear on SMBus when they are part of SFP modules.
 + */
 +#include <linux/i2c.h>
 +#include <linux/phy.h>
 +#include <linux/mdio/mdio-i2c.h>
++#include <linux/sfp.h>
 +
-+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 <antoine.tenart at bootlin.com>
 +
 +	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 <antoine.tenart at bootlin.com>
  
 --- 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 <antoine.tenart at bootlin.com>
 +	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);
 +




More information about the lede-commits mailing list