Skip to content

Commit

Permalink
Merge branch 'net-phy-sfp-add-single-byte-smbus-sfp-access'
Browse files Browse the repository at this point in the history
Maxime Chevallier says:

====================
net: phy: sfp: Add single-byte SMBus SFP access

This is V4 for the single-byte SMBus support for SFP cages as well as
embedded PHYs accessed over mdio-i2c.

v3: https://lore.kernel.org/20250314162319.516163-1-maxime.chevallier@bootlin.com
v2: https://lore.kernel.org/20250225112043.419189-1-maxime.chevallier@bootlin.com
v1: https://lore.kernel.org/20250223172848.1098621-1-maxime.chevallier@bootlin.com
====================

Link: https://patch.msgid.link/20250322075745.120831-1-maxime.chevallier@bootlin.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
  • Loading branch information
Jakub Kicinski committed Mar 25, 2025
2 parents 586b7b3 + d4bd3ac commit aa3651c
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 9 deletions.
79 changes: 78 additions & 1 deletion drivers/net/mdio/mdio-i2c.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,62 @@ static int i2c_mii_write_default_c22(struct mii_bus *bus, int phy_id, int reg,
return i2c_mii_write_default_c45(bus, phy_id, -1, reg, val);
}

static int smbus_byte_mii_read_default_c22(struct mii_bus *bus, int phy_id,
int reg)
{
struct i2c_adapter *i2c = bus->priv;
union i2c_smbus_data smbus_data;
int val = 0, ret;

if (!i2c_mii_valid_phy_id(phy_id))
return 0;

ret = i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0,
I2C_SMBUS_READ, reg,
I2C_SMBUS_BYTE_DATA, &smbus_data);
if (ret < 0)
return ret;

val = (smbus_data.byte & 0xff) << 8;

ret = i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0,
I2C_SMBUS_READ, reg,
I2C_SMBUS_BYTE_DATA, &smbus_data);
if (ret < 0)
return ret;

val |= smbus_data.byte & 0xff;

return val;
}

static int smbus_byte_mii_write_default_c22(struct mii_bus *bus, int phy_id,
int reg, u16 val)
{
struct i2c_adapter *i2c = bus->priv;
union i2c_smbus_data smbus_data;
int ret;

if (!i2c_mii_valid_phy_id(phy_id))
return 0;

smbus_data.byte = (val & 0xff00) >> 8;

ret = i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0,
I2C_SMBUS_WRITE, reg,
I2C_SMBUS_BYTE_DATA, &smbus_data);
if (ret < 0)
return ret;

smbus_data.byte = val & 0xff;

ret = i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0,
I2C_SMBUS_WRITE, reg,
I2C_SMBUS_BYTE_DATA, &smbus_data);

return ret < 0 ? ret : 0;
}

/* 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.
Expand Down Expand Up @@ -378,13 +434,26 @@ static int i2c_mii_init_rollball(struct i2c_adapter *i2c)
return 0;
}

static bool mdio_i2c_check_functionality(struct i2c_adapter *i2c,
enum mdio_i2c_proto protocol)
{
if (i2c_check_functionality(i2c, I2C_FUNC_I2C))
return true;

if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA) &&
protocol == MDIO_I2C_MARVELL_C22)
return true;

return false;
}

struct mii_bus *mdio_i2c_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_I2C))
if (!mdio_i2c_check_functionality(i2c, protocol))
return ERR_PTR(-EINVAL);

mii = mdiobus_alloc();
Expand All @@ -395,6 +464,14 @@ struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
mii->parent = parent;
mii->priv = i2c;

/* Only use SMBus if we have no other choice */
if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA) &&
!i2c_check_functionality(i2c, I2C_FUNC_I2C)) {
mii->read = smbus_byte_mii_read_default_c22;
mii->write = smbus_byte_mii_write_default_c22;
return mii;
}

switch (protocol) {
case MDIO_I2C_ROLLBALL:
ret = i2c_mii_init_rollball(i2c);
Expand Down
82 changes: 74 additions & 8 deletions drivers/net/phy/sfp.c
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ struct sfp {
enum mdio_i2c_proto mdio_protocol;
struct phy_device *mod_phy;
const struct sff_data *type;
size_t i2c_max_block_size;
size_t i2c_block_size;
u32 max_power_mW;

Expand Down Expand Up @@ -691,14 +692,71 @@ static int sfp_i2c_write(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
return ret == ARRAY_SIZE(msgs) ? len : 0;
}

static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
static int sfp_smbus_byte_read(struct sfp *sfp, bool a2, u8 dev_addr,
void *buf, size_t len)
{
if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
return -EINVAL;
union i2c_smbus_data smbus_data;
u8 bus_addr = a2 ? 0x51 : 0x50;
u8 *data = buf;
int ret;

while (len) {
ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
I2C_SMBUS_READ, dev_addr,
I2C_SMBUS_BYTE_DATA, &smbus_data);
if (ret < 0)
return ret;

*data = smbus_data.byte;

len--;
data++;
dev_addr++;
}

return data - (u8 *)buf;
}

static int sfp_smbus_byte_write(struct sfp *sfp, bool a2, u8 dev_addr,
void *buf, size_t len)
{
union i2c_smbus_data smbus_data;
u8 bus_addr = a2 ? 0x51 : 0x50;
u8 *data = buf;
int ret;

while (len) {
smbus_data.byte = *data;
ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
I2C_SMBUS_WRITE, dev_addr,
I2C_SMBUS_BYTE_DATA, &smbus_data);
if (ret)
return ret;

len--;
data++;
dev_addr++;
}

return 0;
}

static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
{
sfp->i2c = i2c;
sfp->read = sfp_i2c_read;
sfp->write = sfp_i2c_write;

if (i2c_check_functionality(i2c, I2C_FUNC_I2C)) {
sfp->read = sfp_i2c_read;
sfp->write = sfp_i2c_write;
sfp->i2c_max_block_size = SFP_EEPROM_BLOCK_SIZE;
} else if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA)) {
sfp->read = sfp_smbus_byte_read;
sfp->write = sfp_smbus_byte_write;
sfp->i2c_max_block_size = 1;
} else {
sfp->i2c = NULL;
return -EINVAL;
}

return 0;
}
Expand Down Expand Up @@ -1594,7 +1652,7 @@ static void sfp_hwmon_probe(struct work_struct *work)
*/
if (sfp->i2c_block_size < 2) {
dev_info(sfp->dev,
"skipping hwmon device registration due to broken EEPROM\n");
"skipping hwmon device registration\n");
dev_info(sfp->dev,
"diagnostic EEPROM area cannot be read atomically to guarantee data coherency\n");
return;
Expand Down Expand Up @@ -2201,7 +2259,7 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
u8 check;
int ret;

sfp->i2c_block_size = SFP_EEPROM_BLOCK_SIZE;
sfp->i2c_block_size = sfp->i2c_max_block_size;

ret = sfp_read(sfp, false, 0, &id.base, sizeof(id.base));
if (ret < 0) {
Expand Down Expand Up @@ -2941,7 +2999,6 @@ static struct sfp *sfp_alloc(struct device *dev)
return ERR_PTR(-ENOMEM);

sfp->dev = dev;
sfp->i2c_block_size = SFP_EEPROM_BLOCK_SIZE;

mutex_init(&sfp->sm_mutex);
mutex_init(&sfp->st_mutex);
Expand Down Expand Up @@ -3115,6 +3172,15 @@ static int sfp_probe(struct platform_device *pdev)
if (!sfp->sfp_bus)
return -ENOMEM;

if (sfp->i2c_max_block_size < 2)
dev_warn(sfp->dev,
"Please note:\n"
"This SFP cage is accessed via an SMBus only capable of single byte\n"
"transactions. Some features are disabled, other may be unreliable or\n"
"sporadically fail. Use with caution. There is nothing that the kernel\n"
"or community can do to fix it, the kernel will try best efforts. Please\n"
"verify any problems on hardware that supports multi-byte I2C transactions.\n");

sfp_debugfs_init(sfp);

return 0;
Expand Down

0 comments on commit aa3651c

Please sign in to comment.