realtek: Add support for SFP EEPROM-access over SMBus
authorBirger Koblitz <git@birger-koblitz.de>
Sun, 16 Jan 2022 10:18:38 +0000 (11:18 +0100)
committerDaniel Golle <daniel@makrotopia.org>
Thu, 17 Feb 2022 15:21:46 +0000 (15:21 +0000)
The EEPROMs on SFP modules are compatible both to I2C as well
as SMBus. However, the kernel so far only supports I2C
access. We add SMBus access routines, because the I2C driver
for the RTL9300 HW only supports that protocol. At the same
time we disable I2C access to PHYs on SFP modules as otherwise
detection of any SFP module would fail. This is not in any
way problematic at this point in time since the RTL93XX
platform so far does not support PHYs on SFP modules.

The patches are copied and rebased version of:
https://bootlin.com/blog/sfp-modules-on-a-board-running-linux/

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Signed-off-by: Birger Koblitz <git@birger-koblitz.de>
target/linux/realtek/config-5.10
target/linux/realtek/patches-5.10/710-net-phy-sfp-re-probe-modules-on-DEV_UP-event.patch [new file with mode: 0644]
target/linux/realtek/patches-5.10/711-net-phy-add-an-MDIO-SMBus-library.patch [new file with mode: 0644]
target/linux/realtek/patches-5.10/712-net-phy-sfp-add-support-for-SMBus.patch [new file with mode: 0644]

index 3f76f6cfaa16f8f5f34842e06cf855e88077a22a..07907d52b268ebc0e6199c1a2662a4f239d15db9 100644 (file)
@@ -87,6 +87,7 @@ CONFIG_I2C_GPIO=y
 CONFIG_I2C_MUX=y
 CONFIG_I2C_MUX_RTL9300=y
 CONFIG_I2C_RTL9300=y
+CONFIG_I2C_SMBUS=y
 CONFIG_INITRAMFS_SOURCE=""
 CONFIG_IRQCHIP=y
 CONFIG_IRQ_DOMAIN=y
diff --git a/target/linux/realtek/patches-5.10/710-net-phy-sfp-re-probe-modules-on-DEV_UP-event.patch b/target/linux/realtek/patches-5.10/710-net-phy-sfp-re-probe-modules-on-DEV_UP-event.patch
new file mode 100644 (file)
index 0000000..591cdbf
--- /dev/null
@@ -0,0 +1,26 @@
+From a381ac0aa281fdb0b41a39d8a2bc08fd88f6db92 Mon Sep 17 00:00:00 2001
+From: Antoine Tenart <antoine.tenart@bootlin.com>
+Date: Tue, 25 Feb 2020 16:32:37 +0100
+Subject: [PATCH 1/3] net: phy: sfp: re-probe modules on DEV_UP event
+
+Signed-off-by: Antoine Tenart <antoine.tenart@bootlin.com>
+---
+ drivers/net/phy/sfp.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -1959,6 +1959,13 @@ static void sfp_sm_module(struct sfp *sf
+               return;
+       }
++      /* Re-probe the SFP modules when an interface is brought up, as the MAC
++       * do not report its link status (This means Phylink wouldn't be
++       * triggered if the PHY had a link before a MAC is brought up).
++       */
++      if (event == SFP_E_DEV_UP && sfp->sm_mod_state == SFP_MOD_PRESENT)
++              sfp_sm_mod_next(sfp, SFP_MOD_PROBE, T_SERIAL);
++
+       switch (sfp->sm_mod_state) {
+       default:
+               if (event == SFP_E_INSERT) {
diff --git a/target/linux/realtek/patches-5.10/711-net-phy-add-an-MDIO-SMBus-library.patch b/target/linux/realtek/patches-5.10/711-net-phy-add-an-MDIO-SMBus-library.patch
new file mode 100644 (file)
index 0000000..5e1053c
--- /dev/null
@@ -0,0 +1,168 @@
+From d585c55b9f70cf9e8c66820d7efe7130c683f19e Mon Sep 17 00:00:00 2001
+From: Antoine Tenart <antoine.tenart@bootlin.com>
+Date: Fri, 21 Feb 2020 11:51:27 +0100
+Subject: [PATCH 2/3] net: phy: add an MDIO SMBus library
+
+Signed-off-by: Antoine Tenart <antoine.tenart@bootlin.com>
+---
+ drivers/net/mdio/Kconfig      | 11 +++++++
+ drivers/net/mdio/Makefile     |  1 +
+ drivers/net/mdio/mdio-smbus.c | 62 +++++++++++++++++++++++++++++++++++
+ drivers/net/phy/Kconfig       |  1 +
+ include/linux/mdio/mdio-i2c.h | 16 +++++++++
+ 5 files changed, 91 insertions(+)
+ create mode 100644 drivers/net/mdio/mdio-smbus.c
+
+--- a/drivers/net/mdio/Kconfig
++++ b/drivers/net/mdio/Kconfig
+@@ -40,6 +40,17 @@ config MDIO_SUN4I
+         interface units of the Allwinner SoC that have an EMAC (A10,
+         A12, A10s, etc.)
++config MDIO_SMBUS
++      tristate
++      depends on I2C_SMBUS
++      help
++        Support SMBus based PHYs. This provides a MDIO bus bridged
++        to SMBus to allow PHYs connected in SMBus mode to be accessed
++        using the existing infrastructure.
++
++        This is library mode.
++
++
+ config MDIO_XGENE
+       tristate "APM X-Gene SoC MDIO bus controller"
+       depends on ARCH_XGENE || COMPILE_TEST
+--- a/drivers/net/mdio/Makefile
++++ b/drivers/net/mdio/Makefile
+@@ -17,6 +17,7 @@ obj-$(CONFIG_MDIO_MOXART)            += mdio-moxar
+ obj-$(CONFIG_MDIO_MSCC_MIIM)          += mdio-mscc-miim.o
+ obj-$(CONFIG_MDIO_MVUSB)              += mdio-mvusb.o
+ obj-$(CONFIG_MDIO_OCTEON)             += mdio-octeon.o
++obj-$(CONFIG_MDIO_SMBUS)              += mdio-smbus.o
+ obj-$(CONFIG_MDIO_SUN4I)              += mdio-sun4i.o
+ obj-$(CONFIG_MDIO_THUNDER)            += mdio-thunder.o
+ obj-$(CONFIG_MDIO_XGENE)              += mdio-xgene.o
+--- /dev/null
++++ b/drivers/net/mdio/mdio-smbus.c
+@@ -0,0 +1,62 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * MDIO SMBus bridge
++ *
++ * Copyright (C) 2020 Antoine Tenart
++ *
++ * 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>
++
++static int smbus_mii_read(struct mii_bus *mii, int phy_id, int reg)
++{
++      struct i2c_adapter *i2c = mii->priv;
++      union i2c_smbus_data data;
++      int ret;
++
++      ret = i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0, I2C_SMBUS_READ,
++                           reg, I2C_SMBUS_BYTE_DATA, &data);
++      if (ret < 0)
++              return 0xff;
++
++      return data.byte;
++}
++
++static int smbus_mii_write(struct mii_bus *mii, int phy_id, int reg, u16 val)
++{
++      struct i2c_adapter *i2c = mii->priv;
++      union i2c_smbus_data data;
++      int ret;
++
++      data.byte = val;
++
++      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;
++}
++
++struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c)
++{
++      struct mii_bus *mii;
++
++      if (!i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA))
++              return ERR_PTR(-EINVAL);
++
++      mii = mdiobus_alloc();
++      if (!mii)
++              return ERR_PTR(-ENOMEM);
++
++      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;
++
++      return mii;
++}
++
++MODULE_AUTHOR("Antoine Tenart");
++MODULE_DESCRIPTION("MDIO SMBus bridge library");
++MODULE_LICENSE("GPL");
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -60,6 +60,7 @@ config SFP
+       depends on I2C && PHYLINK
+       depends on HWMON || HWMON=n
+       select MDIO_I2C
++      select MDIO_SMBUS
+ comment "Switch configuration API + drivers"
+--- a/include/linux/mdio/mdio-i2c.h
++++ b/include/linux/mdio/mdio-i2c.h
+@@ -12,5 +12,21 @@ struct i2c_adapter;
+ struct mii_bus;
+ struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c);
++struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c);
++
++/*
++ * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is
++ * specified to be present in SFP modules.  These correspond with PHY
++ * addresses 16 and 17.  Disallow access to these "phy" addresses.
++ */
++static bool i2c_mii_valid_phy_id(int phy_id)
++{
++      return phy_id != 0x10 && phy_id != 0x11;
++}
++
++static unsigned int i2c_mii_phy_addr(int phy_id)
++{
++      return phy_id + 0x40;
++}
+ #endif
+--- a/drivers/net/mdio/mdio-i2c.c
++++ b/drivers/net/mdio/mdio-i2c.c
+@@ -13,21 +13,6 @@
+ #include <linux/mdio/mdio-i2c.h>
+ #include <linux/phy.h>
+-/*
+- * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is
+- * specified to be present in SFP modules.  These correspond with PHY
+- * addresses 16 and 17.  Disallow access to these "phy" addresses.
+- */
+-static bool i2c_mii_valid_phy_id(int phy_id)
+-{
+-      return phy_id != 0x10 && phy_id != 0x11;
+-}
+-
+-static unsigned int i2c_mii_phy_addr(int phy_id)
+-{
+-      return phy_id + 0x40;
+-}
+-
+ static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg)
+ {
+       struct i2c_adapter *i2c = bus->priv;
diff --git a/target/linux/realtek/patches-5.10/712-net-phy-sfp-add-support-for-SMBus.patch b/target/linux/realtek/patches-5.10/712-net-phy-sfp-add-support-for-SMBus.patch
new file mode 100644 (file)
index 0000000..8eef188
--- /dev/null
@@ -0,0 +1,99 @@
+From 3cb0bde365d913c484d20224367a54a0eac780a7 Mon Sep 17 00:00:00 2001
+From: Antoine Tenart <antoine.tenart@bootlin.com>
+Date: Fri, 21 Feb 2020 11:55:29 +0100
+Subject: [PATCH 3/3] net: phy: sfp: add support for SMBus
+
+Signed-off-by: Antoine Tenart <antoine.tenart@bootlin.com>
+---
+ drivers/net/phy/sfp.c | 68 ++++++++++++++++++++++++++++++++++---------
+ 1 file changed, 54 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -412,32 +412,72 @@ static int sfp_i2c_write(struct sfp *sfp
+       return ret == ARRAY_SIZE(msgs) ? len : 0;
+ }
++static int sfp_smbus_read(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
++                        size_t len)
++{
++      u8 bus_addr = a2 ? 0x51 : 0x50, *val = buf;
++
++      bus_addr -= 0x40;
++
++      while (len > 0) {
++              *val = sfp->i2c_mii->read(sfp->i2c_mii, bus_addr, dev_addr);
++
++              val++;
++              dev_addr++;
++              len--;
++      }
++
++      return val - (u8 *)buf;
++}
++
++static int sfp_smbus_write(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
++                         size_t len)
++{
++      u8 bus_addr = a2 ? 0x51 : 0x50;
++      u16 val;
++
++      memcpy(&val, buf, len);
++
++      return sfp->i2c_mii->write(sfp->i2c_mii, bus_addr, dev_addr, val);
++}
++
+ static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
+ {
+-      struct mii_bus *i2c_mii;
++      struct mii_bus *mii;
+       int ret;
+-      if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
+-              return -EINVAL;
+-
+       sfp->i2c = i2c;
+-      sfp->read = sfp_i2c_read;
+-      sfp->write = sfp_i2c_write;
+-      i2c_mii = mdio_i2c_alloc(sfp->dev, i2c);
+-      if (IS_ERR(i2c_mii))
+-              return PTR_ERR(i2c_mii);
++      if (i2c_check_functionality(i2c, I2C_FUNC_I2C)) {
++              sfp->read = sfp_i2c_read;
++              sfp->write = sfp_i2c_write;
++
++              mii = mdio_i2c_alloc(sfp->dev, i2c);
++              if (IS_ERR(mii))
++                      return PTR_ERR(mii);
++
++              mii->name = "SFP I2C Bus";
++      } else if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA)) {
++              sfp->read = sfp_smbus_read;
++              sfp->write = sfp_smbus_write;
++
++              mii = mdio_smbus_alloc(sfp->dev, i2c);
++              if (IS_ERR(mii))
++                      return PTR_ERR(mii);
+-      i2c_mii->name = "SFP I2C Bus";
+-      i2c_mii->phy_mask = ~0;
++              mii->name = "SFP SMBus";
++      } else {
++              return -EINVAL;
++      }
+-      ret = mdiobus_register(i2c_mii);
++      mii->phy_mask = ~0;
++      ret = mdiobus_register(mii);
+       if (ret < 0) {
+-              mdiobus_free(i2c_mii);
++              mdiobus_free(mii);
+               return ret;
+       }
+-      sfp->i2c_mii = i2c_mii;
++      sfp->i2c_mii = mii;
+       return 0;
+ }