From: Greg Patrick <gregspatrick@hotmail.com>
To: Russell King <linux@armlinux.org.uk>,
Andrew Lunn <andrew@lunn.ch>,
Heiner Kallweit <hkallweit1@gmail.com>
Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
"David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>
Subject: [PATCH net-next v1] net: phy: sfp: detect presence via I2C when no MOD_DEF0 GPIO
Date: Tue, 2 Jun 2026 23:55:28 +0000 [thread overview]
Message-ID: <20260602235528.2795028-1-gregspatrick@hotmail.com> (raw)
Boards that do not wire the SFP cage MOD_DEF0 signal to a GPIO (for
example the NicGiga S100-0800S-M, where the I/O expander is fully
consumed by TX_DISABLE) currently use 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.
Derive presence from a throttled single-byte I2C read of the module
EEPROM instead: an ACK asserts SFP_F_PRESENT, two 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 avoiding the boot-time -EIO spam on empty cages.
Signed-off-by: Greg Patrick <gregspatrick@hotmail.com>
---
Posting for review per a downstream discussion (OpenWrt PR #23579) on adding
the NicGiga S100-0800S-M, an RTL9303 8x SFP+ switch that wires none of the
cage sideband signals to a software-visible GPIO. I'd value your steer before
this is carried downstream.
Open design question: this changes behaviour for *all* boards without a
MOD_DEF0 GPIO, from "always present" (sff_gpio_get_state) to I2C-probed
presence. That fixes hot-plug and the empty-cage -EIO spam on boards that
simply lack a wired MOD_ABS, but it also affects boards that intentionally
carry a permanently-fitted module and relied on the always-present path.
Would you prefer this gated behind a DT opt-in property rather than changing
the default? Happy to respin either way. The 2s re-probe interval is likewise
a first guess.
Tested on a NicGiga S100-0800S-M (RTL9303, 8x SFP+, no MOD_DEF0 GPIO):
inserting a module into a cage that was empty at boot now links within ~2s,
removal is detected, and the boot-time -EIO burst on empty cages is gone. No
behaviour change observed on cages populated at boot.
drivers/net/phy/sfp.c | 74 ++++++++++++++++++++++++++++++++++++++-----
1 file changed, 66 insertions(+), 8 deletions(-)
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index 376c705a9..fd99638e9 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -206,6 +206,11 @@ static const enum gpiod_flags gpio_flags[] = {
#define T_PROBE_RETRY_SLOW msecs_to_jiffies(5000)
#define R_PROBE_RETRY_SLOW 12
+/* Interval at which sfp_i2c_get_state() re-probes the I2C bus for module
+ * presence on boards without a MOD_DEF0 GPIO (see sfp_i2c_get_state()).
+ */
+#define T_PROBE_PRESENT msecs_to_jiffies(2000)
+
/* 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 +254,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
@@ -651,11 +663,6 @@ static unsigned int sfp_gpio_get_state(struct sfp *sfp)
return state;
}
-static unsigned int sff_gpio_get_state(struct sfp *sfp)
-{
- return sfp_gpio_get_state(sfp) | SFP_F_PRESENT;
-}
-
static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state)
{
unsigned int drive;
@@ -863,6 +870,44 @@ static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len)
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; two 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 >= 2) {
+ 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);
@@ -3168,9 +3213,22 @@ static int sfp_probe(struct platform_device *pdev)
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;
+ /* Boards with no MOD_DEF0 GPIO have no hardware presence signal. Rather
+ * than assume the module is always present (which traps an empty cage
+ * in MOD_ERROR and never detects hot-insertion), derive presence from a
+ * throttled I2C probe and poll for changes. 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 probe.
+ * 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).
+ */
+ if (!(sfp->gpio[GPIO_MODDEF0])) {
+ 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;
+ }
device_property_read_u32(&pdev->dev, "maximum-power-milliwatt",
&sfp->max_power_mW);
--
2.53.0
next reply other threads:[~2026-06-02 23:55 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-02 23:55 Greg Patrick [this message]
2026-06-03 0:06 ` Andrew Lunn
2026-06-03 14:37 ` Greg Patrick
2026-06-03 15:12 ` Andrew Lunn
2026-06-03 0:15 ` Andrew Lunn
2026-06-03 0:16 ` Andrew Lunn
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260602235528.2795028-1-gregspatrick@hotmail.com \
--to=gregspatrick@hotmail.com \
--cc=andrew@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=hkallweit1@gmail.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®