From nobody Mon Jun 8 05:26:10 2026 Received: from mail-qt1-f182.google.com (mail-qt1-f182.google.com [209.85.160.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EABE439936D for ; Tue, 2 Jun 2026 23:55:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780444532; cv=none; b=XwVK79nKyfRuUGeHbeXKyp+5vyeBgVNFOQnmLMwBsfMdmCLEnAC6tuunL8XgXAKSDK4/KJRn2Ij1fyXc2afo6BZbdHPsdHO4FInbUNrzAKVecDWXghlKsalEhMtyJNKgguRn2fOskstL4kjFNZR1dmShTzMm09doEOiuHijh5sw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780444532; c=relaxed/simple; bh=wpBrehQFmNvRP0r6KS8akDFh40prvkx7Ba1gqXcCN9o=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=ip/8UPYy7/sn8lSKToOQXVWfH8Bt5ISLfaXWsainrpUDdbPJjfeTQ/yA/6DKtOuU0KaFye7xnhqTMoN3Sj36/EeoCVL8+vrQ3uLQ6kCAj2eBJutS2xFcczs/TaPHfNvBn/LrH1lE/tR/NYyUrJQDfrkrZRG5UaH/gShoUzqB9ZM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=hotmail.com; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.160.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=hotmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-qt1-f182.google.com with SMTP id d75a77b69052e-51776b4de37so3945681cf.1 for ; Tue, 02 Jun 2026 16:55:30 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780444530; x=1781049330; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=zzZ6s0AznRAk34C0Bcc3By/ncVdYtLX2scOr3Lki/eU=; b=eVlWztQyrrY4+Mnv/b1iVyEH36GnoWkS/L4s+ZyUSNjuMyyWqHQvGR+5fpnGlLocko 04gcF4WgTPglwYMVlh4sMzYhGYnA0r4ukpscXnG39GBuc3wMMXm/O44hgDmRuH3TYN7K O3utOk0pyt3mg+H9xcQssIEf3Qb173B2prDVpVND0aX5ZFa/g+B4XVPdaR6Bm1CQYfbM PHwP1hgsUx8J/4SBnmUZ9mbI7hFfpeuBR+VZGk8AkSFrgUTXT1LRk0dKJ76iou54VFgx 3pENq6Tt3FJm1SOnhAr1ECShi8Yt+ICgy17ExPPU+/g+Ks9ZsFRd7JftTgWz1tASUPz8 Qdow== X-Forwarded-Encrypted: i=1; AFNElJ/dLGNo+mMry7wTuiuXizWRYT/uhg+OP3lJ9dv6+cncHVD5+tc/GuOQAEodvdiEjY7SE0LE0Ld9nhWpC80=@vger.kernel.org X-Gm-Message-State: AOJu0Yxm4GV+mybw2+TAk5Gs3BfBC6shUZExuWpwlF7rbbhfPcsKzhIP DPp0zPHIOOeS0uaVqm0xu+EzfPC4nYmVLj36xEuetIJcPsXMhjPMAYnYs5VkXpZR X-Gm-Gg: Acq92OH+KBGZ+cDoNl9Gg6u6RIyin6fIsLaTnlpAK/5QNZ9j1lfx6Se2mpG6xKQmBZh /Q320fmXwiHoWBtqjY+UnnrblFIu8KZTVqxjDgVXAcvq26DlEwkPR/GODdQzON0bJd15GUrssNF eTPTfEAoeKCzDpJmaurjQ17I1EutUz24tDBnHA/OGahbr/S4jmEH+kLhWtPG6Ofidr0wujVBg7l s8ZO6684ktd2+sI/rWeCSSJ1bVzJqOA0bmv86V798bSk+uan4tbrZRycZOTM1RXXQWBVYNbZ8vA 0vQt1S8+CFhfKCxzCwzUbEzw5uuSWYo6lmEic9Hm0X7t4CeQFzzG/7RObMdJ5Jj7W61E5L/BExK S+PzeEe/DLy4wC4FYpgdK/HaXJhU1JBpBDuX+x9CD/mQctffrn+QYt670BvY/IOjJ3MzTb40bZU rmxJLt13TeURe6LsXjlf/YmCivNIMLvFsqrt6ylYUUNjXZ5uOPN27qpLw0cKDSyUeiz64A9zff1 5IvNOCZ7y6abi2RLOJwJEhASB4c7U+0 X-Received: by 2002:a05:622a:346:b0:50d:d1ea:65dd with SMTP id d75a77b69052e-517785cd83cmr21015931cf.14.1780444529845; Tue, 02 Jun 2026 16:55:29 -0700 (PDT) Received: from lxc-docker-tertiary.local (pool-108-45-26-115.washdc.fios.verizon.net. [108.45.26.115]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8ceccdb5e92sm5810016d6.11.2026.06.02.16.55.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 02 Jun 2026 16:55:29 -0700 (PDT) From: Greg Patrick To: Russell King , Andrew Lunn , Heiner Kallweit Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni 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 Message-ID: <20260602235528.2795028-1-gregspatrick@hotmail.com> X-Mailer: git-send-email 2.53.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- 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 befo= re 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 likewi= se 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[] =3D { #define T_PROBE_RETRY_SLOW msecs_to_jiffies(5000) #define R_PROBE_RETRY_SLOW 12 =20 +/* 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 { =20 bool need_poll; =20 + /* 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; } =20 -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); } =20 +/* 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 pre= sence + * 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)) =3D=3D sizeof(i= d); +} + +/* get_state variant for boards without a MOD_DEF0 GPIO. Instead of assumi= ng + * 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 asse= rts + * 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 =3D sfp_gpio_get_state(sfp); + + if (time_after_eq(jiffies, sfp->i2c_present_next)) { + if (sfp_module_present_i2c(sfp)) { + sfp->i2c_present =3D true; + sfp->i2c_present_nak =3D 0; + } else if (sfp->i2c_present && ++sfp->i2c_present_nak >=3D 2) { + sfp->i2c_present =3D false; + sfp->i2c_present_nak =3D 0; + } + sfp->i2c_present_next =3D jiffies + T_PROBE_PRESENT; + } + + if (sfp->i2c_present) + state |=3D 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 =3D sfp_gpio_get_state; sfp->set_state =3D sfp_gpio_set_state; =20 - /* Modules that have no detect signal are always present */ - if (!(sfp->gpio[GPIO_MODDEF0])) - sfp->get_state =3D 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 =3D sfp_i2c_get_state; + sfp->i2c_block_size =3D sfp->i2c_max_block_size; + sfp->i2c_present_next =3D jiffies; + sfp->need_poll =3D true; + } =20 device_property_read_u32(&pdev->dev, "maximum-power-milliwatt", &sfp->max_power_mW); --=20 2.53.0