Netdev List
 help / color / mirror / Atom feed
* [PATCH v2 0/2] net: phy: add PHY-autonomous EEE provider support
@ 2026-08-03 21:54 James Hilliard
  2026-08-03 21:54 ` [PATCH v2 1/2] net: phy: support PHY-autonomous EEE as an LPI provider James Hilliard
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: James Hilliard @ 2026-08-03 21:54 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel, James Hilliard

Some PHYs implement a vendor-specific autonomous EEE mode. Once EEE is
negotiated, the PHY can generate transmit Low Power Idle (LPI) without
MAC assistance. Linux must select exactly one LPI provider: enabling both
the MAC and PHY paths conflicts, while enabling neither makes a successful
ethtool request ineffective.

Earlier autonomous-EEE design discussions established that:

  - provider control belongs in phylib first, so non-phylink drivers can
    use it too;
  - PHY drivers should receive a narrow autonomous-EEE operation rather
    than the complete ethtool structure;
  - phylib must remember whether the MAC or PHY provides Tx LPI, because
    enable_tx_lpi must ask the MAC only when the MAC was selected; and
  - userspace should continue to see one EEE interface. Provider choice is
    an implementation detail, not a new ethtool mode.

Patch 1 implements that phylib model. It adds explicit legacy, MAC and
PHY provider states, a set_autonomous_eee() driver operation taking the
enable state and idle timer, and phylib calls which select either provider.
It routes ethtool Tx LPI changes to the selected PHY with rollback, keeps
enable_tx_lpi clear for the PHY provider, and restores the selected mode
after a PHY reset.

Patch 2 makes phylink select the MAC when its LPI implementation is
usable for the attached interface, otherwise fall back to a configurable
autonomous PHY. A PHY which changes interface with speed uses the MAC only
when all its possible interfaces support MAC LPI, keeping the provider
stable across link transitions. EEE remains unavailable when neither path
can provide it.

Previous discussions and review:

  https://lore.kernel.org/netdev/d86c53213a6328b701b8aabbde5d1c83@tipi-net.de/
  https://lore.kernel.org/netdev/20260406-devel-autonomous-eee-v1-0-b335e7143711@tipi-net.de/
  https://lore.kernel.org/netdev/ad954550-9dec-4e0d-8636-0b6cb379421e@lunn.ch/

Changes v1 -> v2:
  - split the generic EEE work into this standalone series
  - replace callback-presence inference with explicit provider state
  - replace the full eee_config callback argument with enable and timer
  - add the phylib provider API before integrating it with phylink
  - handle interface-specific MAC support without changing providers when a
    PHY switches interface with link speed

Tested with x86_64 and arm64 defconfig builds of phy.o, phy_device.o
and phylink.o. Runtime-tested on an Allwinner H616 system with an
X-Powers AC300 PHY using the autonomous PHY provider path. Testing
covered EEE enable and disable, Tx LPI timer validation and rollback,
restoration across a control-provider power cycle, and link traffic
with EEE active. The generated patches pass checkpatch.pl --strict.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
James Hilliard (2):
      net: phy: support PHY-autonomous EEE as an LPI provider
      net: phylink: use PHY-autonomous EEE when MAC LPI is unavailable

 drivers/net/phy/phy.c        |  48 ++++++++++++++++--
 drivers/net/phy/phy_device.c | 118 +++++++++++++++++++++++++++++++++++++------
 drivers/net/phy/phylink.c    |  89 ++++++++++++++++++++++++++++----
 include/linux/phy.h          |  41 +++++++++++++--
 4 files changed, 262 insertions(+), 34 deletions(-)
---
base-commit: 848acc8ffe1b7cd5f1bf427b93069becfebc2c9d
change-id: 20260803-phy-autonomous-eee-v1-38ed922f20c9

Best regards,
--  
James Hilliard <james.hilliard1@gmail.com>


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v2 1/2] net: phy: support PHY-autonomous EEE as an LPI provider
  2026-08-03 21:54 [PATCH v2 0/2] net: phy: add PHY-autonomous EEE provider support James Hilliard
@ 2026-08-03 21:54 ` James Hilliard
  2026-08-03 21:54 ` [PATCH v2 2/2] net: phylink: use PHY-autonomous EEE when MAC LPI is unavailable James Hilliard
  2026-08-03 22:08 ` [PATCH v2 0/2] net: phy: add PHY-autonomous EEE provider support Andrew Lunn
  2 siblings, 0 replies; 4+ messages in thread
From: James Hilliard @ 2026-08-03 21:54 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel, James Hilliard

Some PHYs can generate transmit Low Power Idle (LPI) autonomously after
EEE is negotiated. Phylib can currently disable this mode when a MAC
takes control, but cannot select the PHY as the LPI provider or apply
ethtool Tx LPI settings to it.

Callback presence alone is not enough to infer ownership. Phylib must
remember whether the MAC or PHY was selected so that enable_tx_lpi only
asks the MAC to generate LPI in the former case.

Add explicit MAC, PHY and legacy provider states. The legacy state
preserves the behaviour of drivers which have not selected a provider.
Add set_autonomous_eee(), a narrow PHY-driver operation taking only the
enable state and idle timer rather than the complete ethtool structure.

Expose phylib calls to select either provider, allowing non-phylink
drivers to use the same policy, and make phy_support_eee() select the MAC
provider as existing callers expect. When the PHY is selected, apply Tx
LPI changes through the new operation, roll them back if the EEE update
fails, and keep enable_tx_lpi clear so the MAC is not also enabled.

Restore the selected provider after PHY reset. Drivers without the new
operation retain the legacy behaviour, while drivers implementing only
disable_autonomous_eee() continue to support the MAC handoff.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v1 -> v2:
  - model legacy, MAC and PHY LPI providers explicitly instead of inferring
    ownership from callback presence
  - replace the callback taking eee_config with set_autonomous_eee() taking
    only the enable state and timer
  - add phylib provider-selection helpers for phylink and non-phylink users
  - route Tx LPI updates, rollback and reset restoration only to the selected
    provider
  - keep enable_tx_lpi clear when the PHY supplies LPI
---
 drivers/net/phy/phy.c        |  48 ++++++++++++++++--
 drivers/net/phy/phy_device.c | 118 +++++++++++++++++++++++++++++++++++++------
 include/linux/phy.h          |  41 +++++++++++++--
 3 files changed, 184 insertions(+), 23 deletions(-)

diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index fce9bc7be330..86d63d380ab2 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -1024,8 +1024,10 @@ static int phy_check_link_status(struct phy_device *phydev)
 		phydev->state = PHY_RUNNING;
 		err = genphy_c45_eee_is_active(phydev, NULL);
 		phydev->eee_active = err > 0;
-		phydev->enable_tx_lpi = phydev->eee_cfg.tx_lpi_enabled &&
-					phydev->eee_active;
+		phydev->enable_tx_lpi =
+			phydev->eee_lpi_provider !=
+			PHY_EEE_LPI_PROVIDER_PHY &&
+			phydev->eee_cfg.tx_lpi_enabled && phydev->eee_active;
 
 		phy_link_up(phydev);
 	} else if (!phydev->link && phydev->state != PHY_NOLINK) {
@@ -1970,7 +1972,8 @@ static void phy_ethtool_set_eee_noneg(struct phy_device *phydev,
 {
 	bool enable_tx_lpi;
 
-	if (!phydev->link)
+	if (!phydev->link ||
+	    phydev->eee_lpi_provider == PHY_EEE_LPI_PROVIDER_PHY)
 		return;
 
 	enable_tx_lpi = phydev->eee_cfg.tx_lpi_enabled && phydev->eee_active;
@@ -1996,6 +1999,8 @@ static void phy_ethtool_set_eee_noneg(struct phy_device *phydev,
 int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_keee *data)
 {
 	struct eee_config old_cfg;
+	bool tx_lpi_cfg_attempted = false;
+	bool tx_lpi_cfg_changed;
 	int ret;
 
 	if (!phydev->drv)
@@ -2005,16 +2010,49 @@ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_keee *data)
 
 	old_cfg = phydev->eee_cfg;
 	eee_to_eeecfg(&phydev->eee_cfg, data);
+	tx_lpi_cfg_changed = phydev->eee_cfg.tx_lpi_enabled !=
+			     old_cfg.tx_lpi_enabled ||
+			     phydev->eee_cfg.tx_lpi_timer !=
+			     old_cfg.tx_lpi_timer;
+
+	if (tx_lpi_cfg_changed &&
+	    phydev->eee_lpi_provider == PHY_EEE_LPI_PROVIDER_PHY) {
+		ret = phydev->drv->set_autonomous_eee(phydev,
+				phydev->eee_cfg.tx_lpi_enabled,
+				phydev->eee_cfg.tx_lpi_timer);
+		tx_lpi_cfg_attempted = true;
+		if (ret)
+			goto restore_tx_lpi;
+	}
 
 	ret = genphy_c45_ethtool_set_eee(phydev, data);
 	if (ret == 0)
 		phy_ethtool_set_eee_noneg(phydev, &old_cfg);
 	else if (ret < 0)
-		phydev->eee_cfg = old_cfg;
+		goto restore_tx_lpi;
 
 	mutex_unlock(&phydev->lock);
 
-	return ret < 0 ? ret : 0;
+	return 0;
+
+restore_tx_lpi:
+	if (tx_lpi_cfg_attempted) {
+		int rollback_ret;
+
+		rollback_ret = phydev->drv->set_autonomous_eee(phydev,
+				old_cfg.tx_lpi_enabled,
+				old_cfg.tx_lpi_timer);
+		if (rollback_ret)
+			phydev_warn(phydev,
+				    "Failed to restore autonomous Tx LPI: %pe\n",
+				    ERR_PTR(rollback_ret));
+	}
+
+	phydev->eee_cfg = old_cfg;
+
+	mutex_unlock(&phydev->lock);
+
+	return ret;
 }
 EXPORT_SYMBOL(phy_ethtool_set_eee);
 
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 0615228459ef..f07df601267a 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1338,6 +1338,96 @@ static int phy_poll_reset(struct phy_device *phydev)
 	return 0;
 }
 
+bool phy_has_autonomous_eee(struct phy_device *phydev)
+{
+	return phydev->drv && phydev->drv->set_autonomous_eee;
+}
+EXPORT_SYMBOL_GPL(phy_has_autonomous_eee);
+
+static int phy_configure_autonomous_eee(struct phy_device *phydev,
+					bool enable)
+{
+	if (phydev->drv->set_autonomous_eee)
+		return phydev->drv->set_autonomous_eee(phydev, enable,
+					phydev->eee_cfg.tx_lpi_timer);
+
+	if (!enable && phydev->drv->disable_autonomous_eee)
+		return phydev->drv->disable_autonomous_eee(phydev);
+
+	return enable ? -EOPNOTSUPP : 0;
+}
+
+static int phy_set_eee_lpi_provider(struct phy_device *phydev,
+				    enum phy_eee_lpi_provider provider)
+{
+	bool autonomous;
+	int ret;
+
+	if (!phydev->drv)
+		return -EIO;
+
+	switch (provider) {
+	case PHY_EEE_LPI_PROVIDER_MAC:
+		autonomous = false;
+		break;
+	case PHY_EEE_LPI_PROVIDER_PHY:
+		if (!phy_has_autonomous_eee(phydev))
+			return -EOPNOTSUPP;
+		autonomous = phydev->eee_cfg.tx_lpi_enabled;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (phydev->eee_lpi_provider == provider)
+		return 0;
+
+	ret = phy_configure_autonomous_eee(phydev, autonomous);
+	if (ret)
+		return ret;
+
+	phydev->eee_lpi_provider = provider;
+	phydev->enable_tx_lpi = provider != PHY_EEE_LPI_PROVIDER_PHY &&
+				phydev->eee_cfg.tx_lpi_enabled &&
+				phydev->eee_active;
+
+	return 0;
+}
+
+/**
+ * phy_disable_autonomous_eee - hand EEE Tx LPI control to the MAC
+ * @phydev: PHY device to configure
+ *
+ * Disable PHY-autonomous EEE without changing EEE advertisement. The current
+ * Tx LPI policy remains in &phy_device.eee_cfg and phylib reports its resolved
+ * state to the MAC through &phy_device.enable_tx_lpi.
+ *
+ * Return: 0 on success or a negative error code.
+ */
+int phy_disable_autonomous_eee(struct phy_device *phydev)
+{
+	return phy_set_eee_lpi_provider(phydev,
+					PHY_EEE_LPI_PROVIDER_MAC);
+}
+EXPORT_SYMBOL_GPL(phy_disable_autonomous_eee);
+
+/**
+ * phy_support_autonomous_eee - hand EEE Tx LPI control to the PHY
+ * @phydev: PHY device to configure
+ *
+ * Apply the current ethtool Tx LPI policy through the PHY driver's autonomous
+ * EEE operation. Phylib continues to resolve EEE negotiation, but no longer
+ * asks the MAC to generate Tx LPI.
+ *
+ * Return: 0 on success or a negative error code.
+ */
+int phy_support_autonomous_eee(struct phy_device *phydev)
+{
+	return phy_set_eee_lpi_provider(phydev,
+					PHY_EEE_LPI_PROVIDER_PHY);
+}
+EXPORT_SYMBOL_GPL(phy_support_autonomous_eee);
+
 int phy_init_hw(struct phy_device *phydev)
 {
 	int ret = 0;
@@ -1375,10 +1465,13 @@ int phy_init_hw(struct phy_device *phydev)
 			return ret;
 	}
 
-	/* Re-apply autonomous EEE disable after soft reset */
-	if (phydev->autonomous_eee_disabled &&
-	    phydev->drv->disable_autonomous_eee) {
-		ret = phydev->drv->disable_autonomous_eee(phydev);
+	/* Restore the selected LPI provider after a soft reset. */
+	if (phydev->eee_lpi_provider != PHY_EEE_LPI_PROVIDER_LEGACY) {
+		bool enable = phydev->eee_lpi_provider ==
+			      PHY_EEE_LPI_PROVIDER_PHY &&
+			      phydev->eee_cfg.tx_lpi_enabled;
+
+		ret = phy_configure_autonomous_eee(phydev, enable);
 		if (ret)
 			return ret;
 	}
@@ -2895,9 +2988,7 @@ EXPORT_SYMBOL_GPL(phy_advertise_eee_all);
  * This function configures the initial policy for Energy Efficient Ethernet
  * (EEE) on the specified PHY device, influencing that EEE capabilities are
  * advertised before the link is established. It should be called during PHY
- * registration by the MAC driver and/or the PHY driver (for SmartEEE PHYs)
- * if MAC supports LPI or PHY is capable to compensate missing LPI functionality
- * of the MAC.
+ * registration by a MAC driver which supports LPI generation.
  *
  * The function sets default EEE policy parameters, including preparing the PHY
  * to advertise EEE capabilities based on hardware support.
@@ -2915,18 +3006,15 @@ void phy_support_eee(struct phy_device *phydev)
 	phydev->eee_cfg.tx_lpi_enabled = true;
 	phydev->eee_cfg.eee_enabled = true;
 
-	/* If the PHY supports autonomous EEE, disable it so the MAC can
-	 * manage LPI signaling instead. The flag is stored so it can be
-	 * re-applied after a PHY soft reset (e.g. suspend/resume).
-	 */
-	if (phydev->drv && phydev->drv->disable_autonomous_eee) {
-		int ret = phydev->drv->disable_autonomous_eee(phydev);
+	/* Select the MAC as the LPI provider and disable autonomous EEE. */
+	if (phydev->drv) {
+		int ret;
+
+		ret = phy_disable_autonomous_eee(phydev);
 
 		if (ret)
 			phydev_warn(phydev, "Failed to disable autonomous EEE: %pe\n",
 				    ERR_PTR(ret));
-		else
-			phydev->autonomous_eee_disabled = true;
 	}
 }
 EXPORT_SYMBOL(phy_support_eee);
diff --git a/include/linux/phy.h b/include/linux/phy.h
index fc680901275b..76fb5af5a55b 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -555,6 +555,22 @@ struct phy_oatc14_sqi_capability {
 	u8 sqiplus_bits;
 };
 
+/**
+ * enum phy_eee_lpi_provider - provider of EEE Tx LPI signalling
+ * @PHY_EEE_LPI_PROVIDER_LEGACY: No provider has been selected explicitly
+ * @PHY_EEE_LPI_PROVIDER_MAC: The MAC generates Tx LPI
+ * @PHY_EEE_LPI_PROVIDER_PHY: The PHY generates Tx LPI autonomously
+ *
+ * The legacy state preserves the behaviour of drivers which predate explicit
+ * provider selection. In that state, phylib continues to report the resolved
+ * Tx LPI state to the MAC through &phy_device.enable_tx_lpi.
+ */
+enum phy_eee_lpi_provider {
+	PHY_EEE_LPI_PROVIDER_LEGACY,
+	PHY_EEE_LPI_PROVIDER_MAC,
+	PHY_EEE_LPI_PROVIDER_PHY,
+};
+
 /**
  * struct phy_device - An instance of a PHY
  *
@@ -612,8 +628,7 @@ struct phy_oatc14_sqi_capability {
  * @advertising_eee: Currently advertised EEE linkmodes
  * @enable_tx_lpi: When True, MAC should transmit LPI to PHY
  * @eee_active: phylib private state, indicating that EEE has been negotiated
- * @autonomous_eee_disabled: Set when autonomous EEE has been disabled,
- *	used to re-apply after PHY soft reset
+ * @eee_lpi_provider: Provider selected to generate EEE Tx LPI
  * @eee_cfg: User configuration of EEE
  * @lp_advertising: Current link partner advertised linkmodes
  * @host_interfaces: PHY interface modes supported by host
@@ -741,7 +756,7 @@ struct phy_device {
 	__ETHTOOL_DECLARE_LINK_MODE_MASK(eee_disabled_modes);
 	bool enable_tx_lpi;
 	bool eee_active;
-	bool autonomous_eee_disabled;
+	enum phy_eee_lpi_provider eee_lpi_provider;
 	struct eee_config eee_cfg;
 
 	/* Host supported PHY interface types. Should be ignored if empty. */
@@ -1373,6 +1388,23 @@ struct phy_driver {
 	 */
 	int (*disable_autonomous_eee)(struct phy_device *dev);
 
+	/**
+	 * @set_autonomous_eee: Configure PHY-autonomous EEE
+	 * @dev: PHY device to configure
+	 * @enable: Whether the PHY should generate Tx LPI autonomously
+	 * @tx_lpi_timer: Time in microseconds before entering LPI
+	 *
+	 * The presence of this callback advertises that the driver supports
+	 * using the PHY as the EEE Tx LPI provider. Phylib calls it for ethtool
+	 * Tx LPI configuration only while the PHY is the selected provider. A
+	 * request with @enable false must be accepted regardless of
+	 * @tx_lpi_timer, since the timer has no meaning while Tx LPI is disabled.
+	 *
+	 * Return: 0 on success, negative errno on failure.
+	 */
+	int (*set_autonomous_eee)(struct phy_device *dev, bool enable,
+				  u32 tx_lpi_timer);
+
 	/* Get and Set PHY tunables */
 	/** @get_tunable: Return the value of a tunable */
 	int (*get_tunable)(struct phy_device *dev,
@@ -2392,6 +2424,9 @@ void phy_advertise_eee_all(struct phy_device *phydev);
 void phy_support_sym_pause(struct phy_device *phydev);
 void phy_support_asym_pause(struct phy_device *phydev);
 void phy_support_eee(struct phy_device *phydev);
+bool phy_has_autonomous_eee(struct phy_device *phydev);
+int phy_disable_autonomous_eee(struct phy_device *phydev);
+int phy_support_autonomous_eee(struct phy_device *phydev);
 void phy_disable_eee(struct phy_device *phydev);
 void phy_set_sym_pause(struct phy_device *phydev, bool rx, bool tx,
 		       bool autoneg);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH v2 2/2] net: phylink: use PHY-autonomous EEE when MAC LPI is unavailable
  2026-08-03 21:54 [PATCH v2 0/2] net: phy: add PHY-autonomous EEE provider support James Hilliard
  2026-08-03 21:54 ` [PATCH v2 1/2] net: phy: support PHY-autonomous EEE as an LPI provider James Hilliard
@ 2026-08-03 21:54 ` James Hilliard
  2026-08-03 22:08 ` [PATCH v2 0/2] net: phy: add PHY-autonomous EEE provider support Andrew Lunn
  2 siblings, 0 replies; 4+ messages in thread
From: James Hilliard @ 2026-08-03 21:54 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel, James Hilliard

Phylink currently treats a MAC which implements the EEE operations but
has no LPI capability for the attached interface as incapable of EEE. It
disables PHY EEE during attach and rejects ethtool EEE requests even when
the PHY can generate Tx LPI autonomously.

Use the phylib LPI-provider API during PHY bring-up. Prefer the MAC when
it supports LPI on the configured interface. For a PHY which can change
its interface with link speed, select the MAC only when every possible
interface supports LPI; otherwise keep one PHY provider across transitions.
Fall back to a configurable autonomous PHY when the MAC is unavailable.

When the PHY is selected, preserve its full EEE advertisement, route Tx
LPI configuration through phylib, and keep the MAC LPI path disabled. Only
restrict EEE modes to the MAC capability mask when the MAC is the selected
provider. Use the recorded provider for ethtool decisions rather than an
interface value which may not yet represent an established link.

If neither provider is usable, retain the existing EEE-disable and
-EOPNOTSUPP behaviour. This does not add a userspace provider selector; the
difference between MAC-managed and PHY-autonomous EEE remains an
implementation detail.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v1 -> v2:
  - use the explicit phylib LPI-provider selection API
  - evaluate MAC LPI support for the attached PHY interface
  - require MAC LPI support on every possible interface for PHYs that can
    change interface with link speed
  - fall back to autonomous PHY LPI only when supported and keep EEE
    unavailable when neither provider is usable
---
 drivers/net/phy/phylink.c | 89 +++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 78 insertions(+), 11 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 18d2ead97aa5..f644524d627e 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1512,6 +1512,47 @@ static void phylink_deactivate_lpi(struct phylink *pl)
 	}
 }
 
+static bool phylink_phy_supports_autonomous_eee(struct phy_device *phy)
+{
+	return phy && phy_has_autonomous_eee(phy);
+}
+
+static bool phylink_phy_manages_eee(struct phy_device *phy)
+{
+	return phy &&
+	       phy->eee_lpi_provider == PHY_EEE_LPI_PROVIDER_PHY;
+}
+
+static bool phylink_mac_manages_eee(struct phy_device *phy)
+{
+	return phy &&
+	       phy->eee_lpi_provider == PHY_EEE_LPI_PROVIDER_MAC;
+}
+
+static bool
+phylink_mac_supports_eee_interface(struct phylink *pl,
+				   phy_interface_t interface)
+{
+	return pl->mac_supports_eee &&
+	       test_bit(interface, pl->config->lpi_interfaces);
+}
+
+static bool phylink_mac_supports_eee_phy(struct phylink *pl,
+					 struct phy_device *phy,
+					 phy_interface_t interface)
+{
+	if (!pl->mac_supports_eee)
+		return false;
+
+	/* Keep one provider for PHYs which change their interface with speed. */
+	if (!phy_interface_empty(phy->possible_interfaces))
+		return bitmap_subset(phy->possible_interfaces,
+				     pl->config->lpi_interfaces,
+				     PHY_INTERFACE_MODE_MAX);
+
+	return phylink_mac_supports_eee_interface(pl, interface);
+}
+
 static void phylink_activate_lpi(struct phylink *pl)
 {
 	int err;
@@ -2090,6 +2131,8 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
 {
 	struct phylink_link_state config;
 	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+	bool phy_eee;
+	bool mac_eee;
 	char *irq_str;
 	int ret;
 
@@ -2106,6 +2149,8 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
 	linkmode_copy(supported, phy->supported);
 	linkmode_copy(config.advertising, phy->advertising);
 	config.interface = interface;
+	mac_eee = phylink_mac_supports_eee_phy(pl, phy, interface);
+	phy_eee = !mac_eee && phylink_phy_supports_autonomous_eee(phy);
 
 	ret = phylink_validate_phy(pl, phy, supported, &config);
 	if (ret) {
@@ -2141,10 +2186,21 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
 	/* Restrict the phy advertisement according to the MAC support. */
 	linkmode_copy(phy->advertising, config.advertising);
 
+	if (pl->mac_supports_eee) {
+		/* Convert the MAC's LPI capabilities to linkmodes. */
+		linkmode_zero(pl->supported_lpi);
+		phylink_caps_to_linkmodes(pl->supported_lpi,
+					  pl->config->lpi_capabilities);
+	}
+
 	/* If the MAC supports phylink managed EEE, restrict the EEE
 	 * advertisement according to the MAC's LPI capabilities.
 	 */
-	if (pl->mac_supports_eee) {
+	if (mac_eee) {
+		ret = phy_disable_autonomous_eee(phy);
+		if (ret)
+			goto out_unlock;
+
 		/* If EEE is enabled, then we need to call phy_support_eee()
 		 * to ensure that the advertising mask is appropriately set.
 		 * This also enables EEE at the PHY.
@@ -2155,24 +2211,30 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
 		phy->eee_cfg.tx_lpi_enabled = pl->eee_cfg.tx_lpi_enabled;
 		phy->eee_cfg.tx_lpi_timer = pl->eee_cfg.tx_lpi_timer;
 
-		/* Convert the MAC's LPI capabilities to linkmodes */
-		linkmode_zero(pl->supported_lpi);
-		phylink_caps_to_linkmodes(pl->supported_lpi,
-					  pl->config->lpi_capabilities);
-
 		/* Restrict the PHYs EEE support/advertisement to the modes
 		 * that the MAC supports.
 		 */
 		linkmode_and(phy->advertising_eee, phy->advertising_eee,
 			     pl->supported_lpi);
+	} else if (phy_eee) {
+		if (pl->eee_cfg.eee_enabled)
+			phy_advertise_eee_all(phy);
+
+		phy->eee_cfg = pl->eee_cfg;
+		ret = phy_support_autonomous_eee(phy);
+		if (ret)
+			goto out_unlock;
 	} else if (pl->mac_supports_eee_ops) {
 		/* MAC supports phylink EEE, but wants EEE always disabled. */
 		phy_disable_eee(phy);
 	}
 
+out_unlock:
 	mutex_unlock(&pl->state_mutex);
 	mutex_unlock(&phy->lock);
 	mutex_unlock(&pl->phydev_mutex);
+	if (ret)
+		return ret;
 
 	phylink_dbg(pl,
 		    "phy: %s setting supported %*pb advertising %*pb\n",
@@ -3300,17 +3362,20 @@ EXPORT_SYMBOL_GPL(phylink_get_eee_err);
  */
 int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_keee *eee)
 {
+	bool mac_eee;
 	int ret = -EOPNOTSUPP;
 
 	ASSERT_RTNL();
+	mac_eee = phylink_mac_manages_eee(pl->phydev);
 
-	if (pl->mac_supports_eee_ops && !pl->mac_supports_eee)
+	if (pl->mac_supports_eee_ops && !mac_eee &&
+	    !phylink_phy_manages_eee(pl->phydev))
 		return ret;
 
 	if (pl->phydev) {
 		ret = phy_ethtool_get_eee(pl->phydev, eee);
 		/* Restrict supported linkmode mask */
-		if (ret == 0 && pl->mac_supports_eee_ops)
+		if (ret == 0 && mac_eee)
 			linkmode_and(eee->supported, eee->supported,
 				     pl->supported_lpi);
 	}
@@ -3326,10 +3391,11 @@ EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee);
  */
 int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_keee *eee)
 {
-	bool mac_eee = pl->mac_supports_eee;
+	bool mac_eee;
 	int ret = -EOPNOTSUPP;
 
 	ASSERT_RTNL();
+	mac_eee = phylink_mac_manages_eee(pl->phydev);
 
 	phylink_dbg(pl, "mac %s phylink EEE%s, adv %*pbl, LPI%s timer %uus\n",
 		    mac_eee ? "supports" : "does not support",
@@ -3337,12 +3403,13 @@ int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_keee *eee)
 		    __ETHTOOL_LINK_MODE_MASK_NBITS, eee->advertised,
 		    eee->tx_lpi_enabled ? " enabled" : "", eee->tx_lpi_timer);
 
-	if (pl->mac_supports_eee_ops && !mac_eee)
+	if (pl->mac_supports_eee_ops && !mac_eee &&
+	    !phylink_phy_manages_eee(pl->phydev))
 		return ret;
 
 	if (pl->phydev) {
 		/* Restrict advertisement mask */
-		if (pl->mac_supports_eee_ops)
+		if (mac_eee)
 			linkmode_and(eee->advertised, eee->advertised,
 				     pl->supported_lpi);
 		ret = phy_ethtool_set_eee(pl->phydev, eee);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH v2 0/2] net: phy: add PHY-autonomous EEE provider support
  2026-08-03 21:54 [PATCH v2 0/2] net: phy: add PHY-autonomous EEE provider support James Hilliard
  2026-08-03 21:54 ` [PATCH v2 1/2] net: phy: support PHY-autonomous EEE as an LPI provider James Hilliard
  2026-08-03 21:54 ` [PATCH v2 2/2] net: phylink: use PHY-autonomous EEE when MAC LPI is unavailable James Hilliard
@ 2026-08-03 22:08 ` Andrew Lunn
  2 siblings, 0 replies; 4+ messages in thread
From: Andrew Lunn @ 2026-08-03 22:08 UTC (permalink / raw)
  To: James Hilliard
  Cc: Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, netdev, linux-kernel

On Mon, Aug 03, 2026 at 03:54:02PM -0600, James Hilliard wrote:
> Some PHYs implement a vendor-specific autonomous EEE mode. Once EEE is
> negotiated, the PHY can generate transmit Low Power Idle (LPI) without
> MAC assistance. Linux must select exactly one LPI provider: enabling both
> the MAC and PHY paths conflicts, while enabling neither makes a successful
> ethtool request ineffective.

Thanks for splitting these out.

For the moment, i suggest you drop them. Lets get very basic support
in first, then we can think about EEE and all the nice to have
features.

	Andrew

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-08-03 22:09 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03 21:54 [PATCH v2 0/2] net: phy: add PHY-autonomous EEE provider support James Hilliard
2026-08-03 21:54 ` [PATCH v2 1/2] net: phy: support PHY-autonomous EEE as an LPI provider James Hilliard
2026-08-03 21:54 ` [PATCH v2 2/2] net: phylink: use PHY-autonomous EEE when MAC LPI is unavailable James Hilliard
2026-08-03 22:08 ` [PATCH v2 0/2] net: phy: add PHY-autonomous EEE provider support Andrew Lunn

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox