generic: 6.18: backport SFP I2C presence detection without MOD_DEF0 GPIO
Backport upstream net-next commit 8ac44d24c3a1. An SFP cage whose MOD_DEF0 signal is not wired to a readable GPIO currently uses sff_gpio_get_state(), which always reports the module present: an empty cage gets stuck in MOD_ERROR, hot-insertion is never detected and empty cages spam -EIO at boot. Derive presence from a throttled single-byte I2C read of the module EEPROM instead, so hot-plug works and the boot spam stops. A soldered-down "sff,sff" module stays always-present. This helps RTL93xx SFP boards that route no cage presence signal to a GPIO. Remaining sfp.c patches refreshed. Link: https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit/?id=8ac44d24c3a1 Signed-off-by: Carlo Szelinsky <github@szelinsky.de> Link: https://github.com/openwrt/openwrt/pull/23836 Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
This commit is contained in:
+156
@@ -0,0 +1,156 @@
|
||||
From 8ac44d24c3a148c4177bd3ad790c377279f4674f Mon Sep 17 00:00:00 2001
|
||||
From: Greg Patrick <gregspatrick@hotmail.com>
|
||||
Date: Thu, 11 Jun 2026 17:53:41 +0000
|
||||
Subject: [PATCH] net: phy: sfp: detect presence via I2C when no MOD_DEF0 GPIO
|
||||
|
||||
An SFP cage (compatible "sff,sfp") whose MOD_DEF0 signal is not wired to a
|
||||
GPIO currently falls back to sff_gpio_get_state(), which unconditionally
|
||||
reports the module as present. An empty cage therefore fails its probe and
|
||||
is parked in SFP_MOD_ERROR forever; because SFP_F_PRESENT never deasserts
|
||||
there is no REMOVE event to recover the state machine, so a module inserted
|
||||
after boot is never detected, and empty cages spam -EIO at boot.
|
||||
|
||||
This affects boards that route none of the cage presence signal to a
|
||||
software-readable input. On the NicGiga S100-0800S-M (RTL9303, 8x SFP+) the
|
||||
cage I2C bus is the switch's SMBus master; TX_DISABLE is driven via a
|
||||
PCA9534 I/O expander, but no MOD_ABS/MOD_DEF0 line reaches a readable GPIO
|
||||
(the RTL9303 gpio0 lines read stuck-low, the single PCA9534 is fully
|
||||
consumed by TX_DISABLE, and there is no RTL8231). The Horaco ZX-SW82TS-L2P
|
||||
(RTL9302D, 2x SFP+) is independently affected in the same way.
|
||||
|
||||
For such an SFP cage, derive presence from a throttled single-byte I2C read
|
||||
of the module EEPROM instead: a successful read asserts SFP_F_PRESENT,
|
||||
R_PROBE_ABSENT consecutive failures clear it (to ride out a transient error
|
||||
on a live module). The existing poll then emits SFP_E_INSERT / SFP_E_REMOVE
|
||||
normally, giving working hot-plug and silencing the boot-time -EIO spam on
|
||||
empty cages. Presence is re-probed every T_PROBE_PRESENT, so insertion is
|
||||
detected within that interval and removal within
|
||||
T_PROBE_PRESENT * R_PROBE_ABSENT.
|
||||
|
||||
A soldered-down module (compatible "sff,sff") has no presence signal and is
|
||||
genuinely always present, so it continues to use sff_gpio_get_state(); the
|
||||
new path is gated on the cage type advertising SFP_F_PRESENT.
|
||||
|
||||
Signed-off-by: Greg Patrick <gregspatrick@hotmail.com>
|
||||
Tested-by: Manuel Stocker <mensi@mensi.ch>
|
||||
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
|
||||
Tested-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
|
||||
Link: https://patch.msgid.link/20260611175341.2223184-1-gregspatrick@hotmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/phy/sfp.c | 83 +++++++++++++++++++++++++++++++++++++++++--
|
||||
1 file changed, 80 insertions(+), 3 deletions(-)
|
||||
|
||||
--- a/drivers/net/phy/sfp.c
|
||||
+++ b/drivers/net/phy/sfp.c
|
||||
@@ -206,6 +206,16 @@ static const enum gpiod_flags gpio_flags
|
||||
#define T_PROBE_RETRY_SLOW msecs_to_jiffies(5000)
|
||||
#define R_PROBE_RETRY_SLOW 12
|
||||
|
||||
+/* Polling interval and consecutive-failure threshold for the I2C presence
|
||||
+ * probe used on boards without a MOD_DEF0 GPIO (see sfp_i2c_get_state()).
|
||||
+ * A single successful read asserts presence immediately; R_PROBE_ABSENT
|
||||
+ * consecutive failures are required to declare a live module removed, to ride
|
||||
+ * out a transient I2C error. Insertion is thus detected within
|
||||
+ * T_PROBE_PRESENT and removal within T_PROBE_PRESENT * R_PROBE_ABSENT.
|
||||
+ */
|
||||
+#define T_PROBE_PRESENT msecs_to_jiffies(500)
|
||||
+#define R_PROBE_ABSENT 3
|
||||
+
|
||||
/* SFP modules appear to always have their PHY configured for bus address
|
||||
* 0x56 (which with mdio-i2c, translates to a PHY address of 22).
|
||||
* RollBall SFPs access phy via SFP Enhanced Digital Diagnostic Interface
|
||||
@@ -249,6 +259,13 @@ struct sfp {
|
||||
|
||||
bool need_poll;
|
||||
|
||||
+ /* I2C-probed presence, for boards without a MOD_DEF0 GPIO.
|
||||
+ * Access rules: st_mutex held (updated from the poll/state machine).
|
||||
+ */
|
||||
+ bool i2c_present;
|
||||
+ u8 i2c_present_nak;
|
||||
+ unsigned long i2c_present_next;
|
||||
+
|
||||
/* Access rules:
|
||||
* state_hw_drive: st_mutex held
|
||||
* state_hw_mask: st_mutex held
|
||||
@@ -860,6 +877,45 @@ static int sfp_read(struct sfp *sfp, boo
|
||||
return sfp->read(sfp, a2, addr, buf, len);
|
||||
}
|
||||
|
||||
+/* Probe whether a module is physically present by attempting a single-byte
|
||||
+ * I2C read of the EEPROM identifier (an empty cage NAKs). Used as the presence
|
||||
+ * source on boards that do not wire MOD_DEF0 to a GPIO.
|
||||
+ */
|
||||
+static bool sfp_module_present_i2c(struct sfp *sfp)
|
||||
+{
|
||||
+ u8 id;
|
||||
+
|
||||
+ return sfp_read(sfp, false, SFP_PHYS_ID, &id, sizeof(id)) == sizeof(id);
|
||||
+}
|
||||
+
|
||||
+/* get_state variant for boards without a MOD_DEF0 GPIO. Instead of assuming
|
||||
+ * the module is always present, derive SFP_F_PRESENT from a throttled I2C
|
||||
+ * probe so that hot-insertion and removal are detected. A single ACK asserts
|
||||
+ * presence; R_PROBE_ABSENT consecutive failures clear it, to ride out a
|
||||
+ * transient I2C error on a live module.
|
||||
+ */
|
||||
+static unsigned int sfp_i2c_get_state(struct sfp *sfp)
|
||||
+{
|
||||
+ unsigned int state = sfp_gpio_get_state(sfp);
|
||||
+
|
||||
+ if (time_after_eq(jiffies, sfp->i2c_present_next)) {
|
||||
+ if (sfp_module_present_i2c(sfp)) {
|
||||
+ sfp->i2c_present = true;
|
||||
+ sfp->i2c_present_nak = 0;
|
||||
+ } else if (sfp->i2c_present &&
|
||||
+ ++sfp->i2c_present_nak >= R_PROBE_ABSENT) {
|
||||
+ sfp->i2c_present = false;
|
||||
+ sfp->i2c_present_nak = 0;
|
||||
+ }
|
||||
+ sfp->i2c_present_next = jiffies + T_PROBE_PRESENT;
|
||||
+ }
|
||||
+
|
||||
+ if (sfp->i2c_present)
|
||||
+ state |= SFP_F_PRESENT;
|
||||
+
|
||||
+ return state;
|
||||
+}
|
||||
+
|
||||
static int sfp_write(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len)
|
||||
{
|
||||
return sfp->write(sfp, a2, addr, buf, len);
|
||||
@@ -3158,9 +3214,30 @@ static int sfp_probe(struct platform_dev
|
||||
sfp->get_state = sfp_gpio_get_state;
|
||||
sfp->set_state = sfp_gpio_set_state;
|
||||
|
||||
- /* Modules that have no detect signal are always present */
|
||||
- if (!(sfp->gpio[GPIO_MODDEF0]))
|
||||
- sfp->get_state = sff_gpio_get_state;
|
||||
+ /* An SFP cage with no MOD_DEF0 GPIO has no hardware presence signal.
|
||||
+ * Assuming the module is always present traps an empty cage in
|
||||
+ * MOD_ERROR and never detects hot-insertion, so derive presence from a
|
||||
+ * throttled I2C probe and poll for changes instead. sfp_i2c_configure()
|
||||
+ * has already set i2c_max_block_size; seed i2c_block_size so the
|
||||
+ * presence read does not issue a zero-length transfer before the first
|
||||
+ * EEPROM read. Seed i2c_present_next to jiffies so the first probe
|
||||
+ * happens immediately (a zero value would be in the past relative to
|
||||
+ * the negative INITIAL_JIFFIES at boot and delay detection).
|
||||
+ *
|
||||
+ * A soldered-down module (sff,sff) has no presence signal and is
|
||||
+ * genuinely always present, so it keeps the always-present behaviour;
|
||||
+ * the I2C probe is gated on the cage type advertising SFP_F_PRESENT.
|
||||
+ */
|
||||
+ if (!sfp->gpio[GPIO_MODDEF0]) {
|
||||
+ if (sff->gpios & SFP_F_PRESENT) {
|
||||
+ sfp->get_state = sfp_i2c_get_state;
|
||||
+ sfp->i2c_block_size = sfp->i2c_max_block_size;
|
||||
+ sfp->i2c_present_next = jiffies;
|
||||
+ sfp->need_poll = true;
|
||||
+ } else {
|
||||
+ sfp->get_state = sff_gpio_get_state;
|
||||
+ }
|
||||
+ }
|
||||
|
||||
device_property_read_u32(&pdev->dev, "maximum-power-milliwatt",
|
||||
&sfp->max_power_mW);
|
||||
+1
-1
@@ -16,7 +16,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||
|
||||
--- a/drivers/net/phy/sfp.c
|
||||
+++ b/drivers/net/phy/sfp.c
|
||||
@@ -585,6 +585,7 @@ static const struct sfp_quirk sfp_quirks
|
||||
@@ -602,6 +602,7 @@ static const struct sfp_quirk sfp_quirks
|
||||
SFP_QUIRK_S("OEM", "SFP-2.5G-BX10-U", sfp_quirk_2500basex),
|
||||
SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc),
|
||||
SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc),
|
||||
|
||||
@@ -14,7 +14,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||
|
||||
--- a/drivers/net/phy/sfp.c
|
||||
+++ b/drivers/net/phy/sfp.c
|
||||
@@ -591,6 +591,7 @@ static const struct sfp_quirk sfp_quirks
|
||||
@@ -608,6 +608,7 @@ static const struct sfp_quirk sfp_quirks
|
||||
SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball),
|
||||
|
||||
SFP_QUIRK_S("ZOERAX", "SFP-2.5G-T", sfp_quirk_oem_2_5g),
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
|
||||
--- a/drivers/net/phy/sfp.c
|
||||
+++ b/drivers/net/phy/sfp.c
|
||||
@@ -824,6 +824,7 @@ static int sfp_i2c_configure(struct sfp
|
||||
@@ -841,6 +841,7 @@ static int sfp_i2c_configure(struct sfp
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
|
||||
--- a/drivers/net/phy/sfp.c
|
||||
+++ b/drivers/net/phy/sfp.c
|
||||
@@ -809,21 +809,29 @@ static int sfp_smbus_byte_write(struct s
|
||||
@@ -826,21 +826,29 @@ static int sfp_smbus_byte_write(struct s
|
||||
|
||||
static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
|
||||
{
|
||||
|
||||
@@ -75,7 +75,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "sfp.h"
|
||||
@@ -758,50 +759,113 @@ static int sfp_i2c_write(struct sfp *sfp
|
||||
@@ -775,50 +776,113 @@ static int sfp_i2c_write(struct sfp *sfp
|
||||
return ret == ARRAY_SIZE(msgs) ? len : 0;
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
}
|
||||
|
||||
return data - (u8 *)buf;
|
||||
@@ -817,10 +881,29 @@ static int sfp_i2c_configure(struct sfp
|
||||
@@ -834,10 +898,29 @@ static int sfp_i2c_configure(struct sfp
|
||||
sfp->read = sfp_i2c_read;
|
||||
sfp->write = sfp_i2c_write;
|
||||
max_block_size = SFP_EEPROM_BLOCK_SIZE;
|
||||
|
||||
@@ -10,7 +10,7 @@ Signed-off-by: Antoine Tenart <antoine.tenart@bootlin.com>
|
||||
|
||||
--- a/drivers/net/phy/sfp.c
|
||||
+++ b/drivers/net/phy/sfp.c
|
||||
@@ -942,6 +942,29 @@ static int sfp_i2c_mdiobus_create(struct
|
||||
@@ -959,6 +959,29 @@ static int sfp_i2c_mdiobus_create(struct
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ Signed-off-by: Antoine Tenart <antoine.tenart@bootlin.com>
|
||||
static void sfp_i2c_mdiobus_destroy(struct sfp *sfp)
|
||||
{
|
||||
mdiobus_unregister(sfp->i2c_mii);
|
||||
@@ -2116,9 +2139,15 @@ static void sfp_sm_fault(struct sfp *sfp
|
||||
@@ -2172,9 +2195,15 @@ static void sfp_sm_fault(struct sfp *sfp
|
||||
|
||||
static int sfp_sm_add_mdio_bus(struct sfp *sfp)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user