mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change
@ 2026-09-29 12:59 Parthiban Veerasooran
  2026-09-29 12:59 ` [PATCH net-next v4 1/4] net: phy: " Parthiban Veerasooran
                   ` (4 more replies)
  0 siblings, 5 replies; 7+ messages in thread
From: Parthiban Veerasooran @ 2026-09-29 12:59 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux
  Cc: netdev, linux-kernel, UNGLinuxDriver, Parthiban.Veerasooran,
	Parthiban Veerasooran


In a 10BASE-T1S multidrop network, the PHY can autonomously change its PLCA
state based on BEACON availability. The existing collision detection logic
only synchronized CDEN on explicit PLCA configuration changes, leaving it
stale across autonomous PLCA status transitions.

This series fixes that gap by tracking PLCA status changes through the PHY
interrupt path, providing a polling fallback for PHYs without a routed
interrupt, and improving collision detection and link-status handling for
LAN867X Rev.D0.

The OA TC6 MAC-PHY SPI driver delivers the in-band PHY interrupt to phylib
through a nested virtual IRQ. LAN865X enables this path for its internal
PHY, which has no dedicated interrupt line. This allows the PHY driver to
receive PLCA status change interrupts through the MAC-PHY SPI interface.

For LAN86XX PHYs, collision detection state is synchronized across explicit
PLCA configuration changes, PHY interrupt handling, and the polling status
path. A per-PHY mutex serializes collision-detection control updates
between PHY configuration and interrupt handling.

LAN867X Rev.D0 uses its hardware CCMFC mechanism to autonomously gate
collision forwarding based on live PLCA_Status, avoiding the software CDEN
toggling used on older revisions. Its link-status handling also accounts
for the optional PRSCTL1 CSMA/CD fallback, selecting the semaphore source
when the PHY can autonomously fall back to CSMA/CD.

Changes in v4:
Addresses Sashiko AI review feedback on v3.
- Add a polling fallback to synchronize CDEN from the live PLCA status for
  PHYs without a routed PHY interrupt.
- Serialize accesses to the collision-detection control register across
  PLCA configuration, interrupt configuration, interrupt handling, and
  status polling to avoid races.
- Preserve the tri-state semantics of plca_cfg->enabled, so an ethtool
  request with the attribute set to -1 does not unintentionally modify CDEN
  or Rev.D0 link-status configuration.
- Fix the OA TC6 virtual IRQ masking path so disabling the nested PHY IRQ
  also masks the in-band PHY interrupt source, and retain deferred dispatch
  to phylib.
- Enable the OA TC6 virtual PHY interrupt for LAN865X and clarify that this
  completes the collision-detection fix for LAN865X. Add the corresponding
  Fixes tag.
- Update LAN867X Rev.D0 interrupt handling to use the cached PLCA enable
  state and current CSMA/CD fallback configuration instead of re-reading
  the complete PLCA configuration on every PLCA status change.
- Preserve cable-test polling when the Rev.D0 PHY interrupt path is
  enabled.
- Clarify the Rev.D0 CDEN/CCMFC behavior and document that CDEN remains
  enabled by default while CCMFC autonomously gates collision forwarding
  from PLCA_Status.
- Correct commit-message and register-comment details identified during
  review.

The LAN865X fix depends on the OA TC6 virtual IRQ support, so the series
should be applied together.

Changes in v3:
Addresses Sashiko AI review feedback on v2.
- Patch 1: Synchronize CDEN with live PLCA status before unmasking
  PSTCM, closing a window where a status change could be silently
  dropped. Use phy_interrupt_is_valid() instead of testing PHY_POLL
  alone. Treat plca_cfg->enabled as tri-state so an ethtool call that
  omits the enable attribute no longer disables collision detection.
  Factor shared STS1/IMSK1 sequences into helpers reused by patch 4.
- Patch 2: Replace dummy_irq_chip with a proper irq_chip implementing
  mask/unmask via bus_lock/bus_sync_unlock, closing an interrupt-storm
  risk. Select IRQ_DOMAIN in Kconfig. Defer PHY interrupt dispatch to a
  workqueue so the chunk-processing thread stays independent of
  phydev->lock.
- Patch 3: Add Fixes: 78341049fbcd, since this patch is required for
  the fix to take effect on LAN865X. Document that CDEN correctness
  relies on the hardware reset default.
- Patch 4: Give Rev.D0 its own config_intr() instead of branching
  inside the shared one, so CCMFC-owned CDEN can never be touched by
  the shared resync. Skip link-status updates when enabled == -1.
  Correct the AN1760 -> AN1699 reference. Resync Rev.D0 link status on
  interrupt (re-)enable, closing the same dropped-edge window as patch
  1. Account for Rev.D0's autonomous PLCA-to-CSMA/CD fallback
  (PRSCTL1): force semaphore mode when that fallback is enabled, since
  PLCA_Status is meaningless once the PHY has already fallen back.

Changes in v2:
- Patch 2: Introduce OA_TC6_PHY_INT quirk flag to guard the virtual IRQ
  infrastructure; PHYINT is optional per the OA TC6 standard. Move
  oa_tc6_phy_irq_setup()/oa_tc6_phy_irq_teardown() into
  oa_tc6_mdiobus_register()/oa_tc6_mdiobus_unregister(). Populate all
  mii_bus->irq[] entries with the virtual IRQ before mdiobus_register()
  so phy_device_create() picks it up regardless of the PHY's MDIO
  address, keeping mii_bus->irq[] and phydev->irq consistent.
- Patch 3 (new): Pass OA_TC6_PHY_INT quirk flag from the LAN865X driver
  to enable the virtual IRQ infrastructure for LAN865X.


Parthiban Veerasooran (4):
  net: phy: microchip_t1s: fix collision detection on PLCA status change
  net: ethernet: oa_tc6: deliver the PHY interrupt to phylib
  microchip: lan865x: enable PHY interrupt via virtual IRQ
  net: phy: microchip_t1s: fix collision detection for LAN867X Rev.D0

 drivers/net/ethernet/Kconfig                  |   1 +
 .../net/ethernet/microchip/lan865x/lan865x.c  |   4 +-
 drivers/net/ethernet/oa_tc6.c                 | 227 +++++++++++
 drivers/net/phy/microchip_t1s.c               | 365 +++++++++++++++++-
 include/linux/oa_tc6.h                        |   3 +
 5 files changed, 587 insertions(+), 13 deletions(-)


base-commit: c66d93e68728cfb5f40b40d0f24129d7768faf43
-- 
2.43.0


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

* [PATCH net-next v4 1/4] net: phy: microchip_t1s: fix collision detection on PLCA status change
  2026-09-29 12:59 [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change Parthiban Veerasooran
@ 2026-09-29 12:59 ` Parthiban Veerasooran
  2026-09-29 12:59 ` [PATCH net-next v4 2/4] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib Parthiban Veerasooran
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Parthiban Veerasooran @ 2026-09-29 12:59 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux
  Cc: netdev, linux-kernel, UNGLinuxDriver, Parthiban.Veerasooran,
	Parthiban Veerasooran

Collision detection was only updated on explicit ethtool PLCA changes,
missing autonomous PLCA-status transitions driven by BEACON presence.
When PLCA went offline, CDEN stayed disabled, leaving CSMA/CD running
unprotected.

Add lan86xx_config_intr()/lan86xx_handle_interrupt() to track PSTC and
update CDEN in COL_DET_CTRL0 accordingly, resyncing before unmasking to
avoid racing a genuine interrupt. CDEN also depends on PRSCTL1's
autonomous CSMA/CD fallback bit: a missing BEACON only means CSMA/CD is
active if fallback is enabled. read_status() also resynchronizes CDEN,
providing a polling-based fallback when the PHY interrupt is
unavailable. A per-PHY lock serializes COL_DET_CTRL0 access, since
config_intr() isn't guaranteed to run under phydev->lock while
handle_interrupt() is.

Also fix plca_cfg->enabled being treated as boolean instead of tri-state
(-1 = "don't change"), which could disable CDEN on an unrelated ethtool
write.

Wired to LAN867X Rev.C1, C2 and LAN865X Rev.B0/B1. Rev.B1 is
unsupported/undocumented silicon and stays out of scope. Rev.D0 is
handled separately (follow-on patch).

Fixes: 78341049fbcd ("net: phy: microchip_t1s: configure collision detection based on PLCA mode")
Signed-off-by: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
---
 drivers/net/phy/microchip_t1s.c | 238 ++++++++++++++++++++++++++++++--
 1 file changed, 227 insertions(+), 11 deletions(-)

diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c
index 73c23d311d72..5ce0304bf095 100644
--- a/drivers/net/phy/microchip_t1s.c
+++ b/drivers/net/phy/microchip_t1s.c
@@ -18,8 +18,8 @@
 /* Both Rev.B0 and B1 clause 22 PHYID's are same due to B1 chip limitation */
 #define PHY_ID_LAN865X_REVB 0x0007C1B3
 
+/* PHY interrupt status 2 register */
 #define LAN867X_REG_STS2 0x0019
-
 #define LAN867x_RESET_COMPLETE_STS BIT(11)
 
 #define LAN865X_REG_CFGPARAM_ADDR 0x00D8
@@ -27,6 +27,21 @@
 #define LAN865X_REG_CFGPARAM_CTRL 0x00DA
 #define LAN865X_REG_STS2 0x0019
 
+/* PHY interrupt status 1 register */
+#define LAN86XX_REG_STS1		0x0018
+#define LAN86XX_STS1_PLCA_STS_CHANGED	BIT(11)
+
+/* PHY interrupt mask 1 register */
+#define LAN86XX_REG_IMSK1		0x001C
+
+/* PLCA Reconciliation Sublayer Control 1 Register (PRSCTL1). Bit 10 controls
+ * whether the PHY autonomously falls back to CSMA/CD mode when no BEACON is
+ * observed while PLCA is enabled, versus staying pinned to PLCA mode regardless
+ * of BEACON presence.
+ */
+#define LAN86XX_REG_PRSCTL1               0x0035
+#define PRSCTL1_PLCA_FALLB_TO_CSMACD_EN   BIT(10)
+
 /* Collision Detector Control 0 Register */
 #define LAN86XX_REG_COL_DET_CTRL0	0x0087
 #define COL_DET_CTRL0_ENABLE_BIT_MASK	BIT(15)
@@ -136,6 +151,30 @@ static const u16 lan867x_revd0_fixup_values[8] = {
 	0x001C, 0x0C0B, 0x8C07, 0x9660,
 };
 
+struct lan86xx_priv {
+	/* Serializes CDEN state synchronization. */
+	struct mutex cden_lock;
+	int plca_enabled;
+};
+
+static int lan86xx_probe(struct phy_device *phydev)
+{
+	struct lan86xx_priv *priv;
+	int ret;
+
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	ret = devm_mutex_init(&phydev->mdio.dev, &priv->cden_lock);
+	if (ret)
+		return ret;
+
+	phydev->priv = priv;
+
+	return 0;
+}
+
 /* Pulled from AN1760 describing 'indirect read'
  *
  * write_register(0x4, 0x00D8, addr)
@@ -430,6 +469,57 @@ static int lan867x_revd0_link_active_selection(struct phy_device *phydev,
 			     LAN867X_REG_LINK_STATUS_CTRL, value);
 }
 
+static int lan86xx_fallback_to_csmacd(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_PRSCTL1);
+	if (ret < 0)
+		return ret;
+
+	return !!(ret & PRSCTL1_PLCA_FALLB_TO_CSMACD_EN);
+}
+
+/* Collision detection must stay disabled while the device is actually operating
+ * in PLCA mode, and enabled while it is actually operating in CSMA/CD.
+ * A missing BEACON (pst == 0) only means the device is running CSMA/CD if
+ * autonomous fallback is enabled (PRSCTL1 bit 10); if fallback is disabled,
+ * the device stays pinned to PLCA mode regardless of BEACON presence,
+ * so collision detection must remain disabled.
+ */
+static int lan86xx_update_cden(struct phy_device *phydev)
+{
+	struct lan86xx_priv *priv = phydev->priv;
+	struct phy_plca_status plca_st;
+	int fallback, ret;
+	u16 cden;
+
+	fallback = lan86xx_fallback_to_csmacd(phydev);
+	if (fallback < 0)
+		return fallback;
+
+	ret = genphy_c45_plca_get_status(phydev, &plca_st);
+	if (ret < 0)
+		return ret;
+
+	/* PLCA disabled                          -> CDEN enabled
+	 * PLCA enabled + BEACON                  -> CDEN disabled
+	 * PLCA enabled + no BEACON + fallback    -> CDEN enabled
+	 * PLCA enabled + no BEACON + no fallback -> CDEN disabled
+	 */
+	if (!priv->plca_enabled)
+		cden = COL_DET_ENABLE;
+	else if (plca_st.pst)
+		cden = COL_DET_DISABLE;
+	else if (fallback)
+		cden = COL_DET_ENABLE;
+	else
+		cden = COL_DET_DISABLE;
+
+	return phy_modify_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_COL_DET_CTRL0,
+			      COL_DET_CTRL0_ENABLE_BIT_MASK, cden);
+}
+
 /* As per LAN8650/1 Rev.B0/B1 AN1760 (Revision F (DS60001760G - June 2024)) and
  * LAN8670/1/2 Rev.C1/C2 AN1699 (Revision E (DS60001699F - June 2024)), under
  * normal operation, the device should be operated in PLCA mode. Disabling
@@ -444,10 +534,14 @@ static int lan867x_revd0_link_active_selection(struct phy_device *phydev,
 static int lan86xx_plca_set_cfg(struct phy_device *phydev,
 				const struct phy_plca_cfg *plca_cfg)
 {
+	struct lan86xx_priv *priv = phydev->priv;
 	int ret;
 
-	/* Link status selection must be configured for LAN8670/1/2 Rev.D0 */
-	if (phydev->phy_id == PHY_ID_LAN867X_REVD0) {
+	/* Link status selection must be configured for LAN8670/1/2 Rev.D0.
+	 * Only update link status selection if enabled is explicitly specified
+	 * (not -1, which means "don't change").
+	 */
+	if (phydev->phy_id == PHY_ID_LAN867X_REVD0 && plca_cfg->enabled != -1) {
 		ret = lan867x_revd0_link_active_selection(phydev,
 							  plca_cfg->enabled);
 		if (ret)
@@ -458,14 +552,18 @@ static int lan86xx_plca_set_cfg(struct phy_device *phydev,
 	if (ret)
 		return ret;
 
-	if (plca_cfg->enabled)
-		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
-				      LAN86XX_REG_COL_DET_CTRL0,
-				      COL_DET_CTRL0_ENABLE_BIT_MASK,
-				      COL_DET_DISABLE);
+	if (plca_cfg->enabled != -1)
+		priv->plca_enabled = plca_cfg->enabled;
 
-	return phy_modify_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_COL_DET_CTRL0,
-			      COL_DET_CTRL0_ENABLE_BIT_MASK, COL_DET_ENABLE);
+	if (plca_cfg->enabled != -1) {
+		mutex_lock(&priv->cden_lock);
+		ret = lan86xx_update_cden(phydev);
+		mutex_unlock(&priv->cden_lock);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 
 static int lan867x_revd0_config_init(struct phy_device *phydev)
@@ -493,6 +591,9 @@ static int lan867x_revd0_config_init(struct phy_device *phydev)
 
 static int lan86xx_read_status(struct phy_device *phydev)
 {
+	struct lan86xx_priv *priv = phydev->priv;
+	int ret;
+
 	/* The phy has some limitations, namely:
 	 *  - always reports link up
 	 *  - only supports 10MBit half duplex
@@ -503,7 +604,112 @@ static int lan86xx_read_status(struct phy_device *phydev)
 	phydev->speed = SPEED_10;
 	phydev->autoneg = AUTONEG_DISABLE;
 
-	return 0;
+	/* LAN867X Rev.B1 is unsupported/undocumented silicon (absent from the
+	 * current AN1699 and datasheet) and is kept out of scope for the CDEN
+	 * tracking below.
+	 */
+	if (phydev->phy_id == PHY_ID_LAN867X_REVB1)
+		return 0;
+
+	/* When no PHY interrupt is available, phylib polls read_status().
+	 * Use the PLCA status from that poll to resync CDEN.
+	 */
+	mutex_lock(&priv->cden_lock);
+	ret = lan86xx_update_cden(phydev);
+	mutex_unlock(&priv->cden_lock);
+	return ret;
+}
+
+/* Read LAN86XX_REG_STS1, which clears the latched status bits on read. */
+static int lan86xx_read_clear_sts1(struct phy_device *phydev)
+{
+	return phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_STS1);
+}
+
+/* Mask (mask bit = 1) or unmask (mask bit = 0) the given STS1 bits in
+ * IMSK1.
+ */
+static int lan86xx_set_intr_mask(struct phy_device *phydev, u16 mask,
+				 bool enable)
+{
+	if (enable)
+		/* A mask bit of 0 enables the corresponding interrupt. */
+		return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
+					  LAN86XX_REG_IMSK1, mask);
+
+	return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_IMSK1,
+				mask);
+}
+
+static int lan86xx_config_intr(struct phy_device *phydev)
+{
+	struct lan86xx_priv *priv = phydev->priv;
+	int ret;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		/* Read to clear any pending status before enabling. */
+		ret = lan86xx_read_clear_sts1(phydev);
+		if (ret < 0)
+			return ret;
+
+		/* STS1 may have cleared a PSTC event that occurred while the
+		 * interrupt was masked, so synchronize CDEN with the current
+		 * PLCA state before enabling PSTC.
+		 */
+		mutex_lock(&priv->cden_lock);
+		ret = lan86xx_update_cden(phydev);
+		mutex_unlock(&priv->cden_lock);
+		if (ret)
+			return ret;
+
+		return lan86xx_set_intr_mask(phydev,
+					     LAN86XX_STS1_PLCA_STS_CHANGED,
+					     true);
+	}
+
+	ret = lan86xx_set_intr_mask(phydev, LAN86XX_STS1_PLCA_STS_CHANGED,
+				    false);
+	if (ret)
+		return ret;
+
+	/* Read to clear any pending status after disabling. */
+	ret = lan86xx_read_clear_sts1(phydev);
+	return ret < 0 ? ret : 0;
+}
+
+static irqreturn_t lan86xx_handle_interrupt(struct phy_device *phydev)
+{
+	struct lan86xx_priv *priv = phydev->priv;
+	irqreturn_t ret_irq = IRQ_NONE;
+	int sts1, ret;
+
+	/* Reading the status register clears the latched event bits. */
+	sts1 = lan86xx_read_clear_sts1(phydev);
+	if (sts1 < 0) {
+		phy_error(phydev);
+		return IRQ_NONE;
+	}
+
+	if (sts1 & LAN86XX_STS1_PLCA_STS_CHANGED) {
+		/* AN1760/AN1699: disable collision detection while actually
+		 * operating in PLCA mode; re-enable it only once actually
+		 * operating in CSMA/CD (see lan86xx_update_cden()).
+		 *
+		 * https://www.microchip.com/en-us/application-notes/an1760
+		 * https://www.microchip.com/en-us/application-notes/an1699
+		 */
+		mutex_lock(&priv->cden_lock);
+		ret = lan86xx_update_cden(phydev);
+		mutex_unlock(&priv->cden_lock);
+		if (ret < 0) {
+			phy_error(phydev);
+			return IRQ_NONE;
+		}
+
+		ret_irq = IRQ_HANDLED;
+	}
+
+	return ret_irq;
 }
 
 static struct phy_driver microchip_t1s_driver[] = {
@@ -521,8 +727,11 @@ static struct phy_driver microchip_t1s_driver[] = {
 		PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVC1),
 		.name               = "LAN867X Rev.C1",
 		.features           = PHY_BASIC_T1S_P2MP_FEATURES,
+		.probe              = lan86xx_probe,
 		.config_init        = lan867x_revc_config_init,
 		.read_status        = lan86xx_read_status,
+		.config_intr        = lan86xx_config_intr,
+		.handle_interrupt   = lan86xx_handle_interrupt,
 		.get_plca_cfg	    = genphy_c45_plca_get_cfg,
 		.set_plca_cfg	    = lan86xx_plca_set_cfg,
 		.get_plca_status    = genphy_c45_plca_get_status,
@@ -531,8 +740,11 @@ static struct phy_driver microchip_t1s_driver[] = {
 		PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVC2),
 		.name               = "LAN867X Rev.C2",
 		.features           = PHY_BASIC_T1S_P2MP_FEATURES,
+		.probe              = lan86xx_probe,
 		.config_init        = lan867x_revc_config_init,
 		.read_status        = lan86xx_read_status,
+		.config_intr        = lan86xx_config_intr,
+		.handle_interrupt   = lan86xx_handle_interrupt,
 		.get_plca_cfg	    = genphy_c45_plca_get_cfg,
 		.set_plca_cfg	    = lan86xx_plca_set_cfg,
 		.get_plca_status    = genphy_c45_plca_get_status,
@@ -541,6 +753,7 @@ static struct phy_driver microchip_t1s_driver[] = {
 		PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVD0),
 		.name               = "LAN867X Rev.D0",
 		.features           = PHY_BASIC_T1S_P2MP_FEATURES,
+		.probe              = lan86xx_probe,
 		.config_init        = lan867x_revd0_config_init,
 		.get_plca_cfg	    = genphy_c45_plca_get_cfg,
 		.set_plca_cfg	    = lan86xx_plca_set_cfg,
@@ -554,8 +767,11 @@ static struct phy_driver microchip_t1s_driver[] = {
 		PHY_ID_MATCH_EXACT(PHY_ID_LAN865X_REVB),
 		.name               = "LAN865X Rev.B0/B1 Internal Phy",
 		.features           = PHY_BASIC_T1S_P2MP_FEATURES,
+		.probe              = lan86xx_probe,
 		.config_init        = lan865x_revb_config_init,
 		.read_status        = lan86xx_read_status,
+		.config_intr        = lan86xx_config_intr,
+		.handle_interrupt   = lan86xx_handle_interrupt,
 		.read_mmd           = genphy_read_mmd_c45,
 		.write_mmd          = genphy_write_mmd_c45,
 		.get_plca_cfg	    = genphy_c45_plca_get_cfg,
-- 
2.43.0


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

* [PATCH net-next v4 2/4] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib
  2026-09-29 12:59 [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change Parthiban Veerasooran
  2026-09-29 12:59 ` [PATCH net-next v4 1/4] net: phy: " Parthiban Veerasooran
@ 2026-09-29 12:59 ` Parthiban Veerasooran
  2026-09-29 12:59 ` [PATCH net-next v4 3/4] microchip: lan865x: enable PHY interrupt via virtual IRQ Parthiban Veerasooran
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Parthiban Veerasooran @ 2026-09-29 12:59 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux
  Cc: netdev, linux-kernel, UNGLinuxDriver, Parthiban.Veerasooran,
	Parthiban Veerasooran

The OA TC6 standard defines PHY interrupt delivery via the SPI status
register as optional. The PHY has no dedicated interrupt line in that
case; its interrupt is signalled through the MAC-PHY SPI interface, but
phy_mac_interrupt() can't make the PHY driver read and acknowledge its
own status registers. Expose it to phylib instead as a nested virtual
IRQ, gated by a new OA_TC6_PHY_INT quirk flag for drivers that route
PHY interrupts in-band.

When set, a nested virtual IRQ is created in oa_tc6_mdiobus_register()
before mdiobus_register(), and all mii_bus->irq[] entries are populated
with it so phy_device_create() picks it up regardless of MDIO address.
Teardown is integrated into oa_tc6_mdiobus_unregister().

A custom irq_chip (oa_tc6_phy_irq_chip) implements mask/unmask via
irq_bus_lock/irq_bus_sync_unlock, writing the mask bit to hardware over
SPI. The interrupt starts masked (hardware reset default) and is only
unmasked when phylib requests it, so disabling the nested IRQ actually
masks the hardware source too, preventing interrupt storms.

Dispatch is deferred to a workqueue rather than run synchronously from
the threaded IRQ: phy_interrupt() takes phydev->lock and PHY
handle_interrupt() issues synchronous SPI transfers, either of which
would otherwise stall the single thread pumping every TX/RX data chunk.
PHYINT is level triggered and stays asserted until acked, so a no-op
reschedule on an already-pending work item can't lose or duplicate an
event.

Select IRQ_DOMAIN in Kconfig for the irq_domain APIs used here.

Prerequisite for "net: phy: microchip_t1s: fix collision detection on
PLCA status change" (Fixes: 78341049fbcd) to fully cover the LAN865X
internal PHY.

Signed-off-by: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
---
 drivers/net/ethernet/Kconfig  |   1 +
 drivers/net/ethernet/oa_tc6.c | 227 ++++++++++++++++++++++++++++++++++
 include/linux/oa_tc6.h        |   3 +
 3 files changed, 231 insertions(+)

diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index c2b0161d0bec..9b102a91c36a 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -151,6 +151,7 @@ config OA_TC6
 	tristate "OPEN Alliance TC6 10BASE-T1x MAC-PHY support" if COMPILE_TEST
 	depends on SPI
 	select PHYLIB
+	select IRQ_DOMAIN
 	help
 	  This library implements OPEN Alliance TC6 10BASE-T1x MAC-PHY
 	  Serial Interface protocol for supporting 10BASE-T1x MAC-PHYs.
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 364027c39fa4..74fe65b76359 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -10,6 +10,8 @@
 #include <linux/gpio/consumer.h>
 #include <linux/iopoll.h>
 #include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
 #include <linux/mdio.h>
 #include <linux/phy.h>
 #include <linux/oa_tc6.h>
@@ -72,6 +74,11 @@ struct oa_tc6 {
 	struct phy_device *phydev;
 	struct mii_bus *mdiobus;
 	struct spi_device *spi;
+	struct mutex phy_irq_lock; /* Serialises irq_bus_lock/sync_unlock */
+	bool phy_irq_masked; /* Shadow of OA_TC6_INT_MASK0_PHY_INT_MASK */
+	struct irq_domain *phy_irq_domain;
+	int phy_virq;
+	struct work_struct phy_irq_work;
 	struct mutex spi_ctrl_lock; /* Protects spi control transfer */
 	spinlock_t tx_skb_lock; /* Protects tx skb handling */
 	void *spi_ctrl_tx_buf;
@@ -531,6 +538,178 @@ int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
 }
 EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_write_c45);
 
+static int oa_tc6_phy_irq_unmask_hw(struct oa_tc6 *tc6)
+{
+	u32 regval;
+	int ret;
+
+	mutex_lock(&tc6->phy_irq_lock);
+
+	if (READ_ONCE(tc6->phy_irq_masked)) {
+		ret = 0;
+		goto unlock;
+	}
+
+	ret = oa_tc6_read_register(tc6, OA_TC6_REG_INT_MASK0, &regval);
+	if (ret)
+		goto unlock;
+
+	regval &= ~OA_TC6_INT_MASK0_PHY_INT_MASK;
+	ret = oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
+
+unlock:
+	mutex_unlock(&tc6->phy_irq_lock);
+
+	return ret;
+}
+
+static void oa_tc6_phy_irq_work(struct work_struct *work)
+{
+	struct oa_tc6 *tc6 = container_of(work, struct oa_tc6, phy_irq_work);
+	int ret;
+
+	/* Dispatched off the SPI chunk-processing thread so that
+	 * phy_interrupt() taking phydev->lock and issuing synchronous SPI
+	 * control transfers from PHY handle_interrupt() cannot stall the single
+	 * thread pumping TX/RX data chunks.
+	 */
+	handle_nested_irq(tc6->phy_virq);
+
+	ret = oa_tc6_phy_irq_unmask_hw(tc6);
+	if (ret)
+		dev_err(&tc6->spi->dev, "Failed to unmask PHY interrupt: %d\n",
+			ret);
+}
+
+static int oa_tc6_phy_irq_mask_hw(struct oa_tc6 *tc6)
+{
+	u32 regval;
+	int ret;
+
+	mutex_lock(&tc6->phy_irq_lock);
+
+	ret = oa_tc6_read_register(tc6, OA_TC6_REG_INT_MASK0, &regval);
+	if (ret)
+		goto unlock;
+
+	regval |= OA_TC6_INT_MASK0_PHY_INT_MASK;
+	ret = oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
+
+unlock:
+	mutex_unlock(&tc6->phy_irq_lock);
+
+	return ret;
+}
+
+static void oa_tc6_phy_irq_mask(struct irq_data *irqd)
+{
+	struct oa_tc6 *tc6 = irq_data_get_irq_chip_data(irqd);
+
+	WRITE_ONCE(tc6->phy_irq_masked, true);
+}
+
+static void oa_tc6_phy_irq_unmask(struct irq_data *irqd)
+{
+	struct oa_tc6 *tc6 = irq_data_get_irq_chip_data(irqd);
+
+	WRITE_ONCE(tc6->phy_irq_masked, false);
+}
+
+static void oa_tc6_phy_irq_disable(struct irq_data *irqd)
+{
+	struct oa_tc6 *tc6 = irq_data_get_irq_chip_data(irqd);
+
+	WRITE_ONCE(tc6->phy_irq_masked, true);
+}
+
+static void oa_tc6_phy_irq_bus_lock(struct irq_data *irqd)
+{
+	struct oa_tc6 *tc6 = irq_data_get_irq_chip_data(irqd);
+
+	mutex_lock(&tc6->phy_irq_lock);
+}
+
+static void oa_tc6_phy_irq_bus_sync_unlock(struct irq_data *irqd)
+{
+	struct oa_tc6 *tc6 = irq_data_get_irq_chip_data(irqd);
+	u32 regval;
+	int ret;
+
+	ret = oa_tc6_read_register(tc6, OA_TC6_REG_INT_MASK0, &regval);
+	if (ret) {
+		dev_err(&tc6->spi->dev, "Failed to read INT_MASK0: %d\n", ret);
+		goto unlock;
+	}
+
+	if (READ_ONCE(tc6->phy_irq_masked))
+		regval |= OA_TC6_INT_MASK0_PHY_INT_MASK;
+	else
+		regval &= ~OA_TC6_INT_MASK0_PHY_INT_MASK;
+
+	ret = oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
+	if (ret) {
+		dev_err(&tc6->spi->dev, "Failed to write INT_MASK0: %d\n", ret);
+		/* Note: on SPI failure, mask state is undefined until next
+		 * sync. This follows genirq's regmap_irq_sync_unlock() pattern
+		 * since the callback returns void and has nowhere to propagate
+		 * errors.
+		 */
+	}
+
+unlock:
+	mutex_unlock(&tc6->phy_irq_lock);
+}
+
+static struct irq_chip oa_tc6_phy_irq_chip = {
+	.name                   = "oa_tc6_phy",
+	.irq_mask               = oa_tc6_phy_irq_mask,
+	.irq_unmask             = oa_tc6_phy_irq_unmask,
+	.irq_disable            = oa_tc6_phy_irq_disable,
+	.irq_bus_lock           = oa_tc6_phy_irq_bus_lock,
+	.irq_bus_sync_unlock    = oa_tc6_phy_irq_bus_sync_unlock,
+};
+
+static int oa_tc6_phy_irq_map(struct irq_domain *domain, unsigned int irq,
+			      irq_hw_number_t hwirq)
+{
+	irq_set_chip_data(irq, domain->host_data);
+	irq_set_chip_and_handler(irq, &oa_tc6_phy_irq_chip, handle_simple_irq);
+	irq_set_nested_thread(irq, true);
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops oa_tc6_phy_irq_domain_ops = {
+	.map = oa_tc6_phy_irq_map,
+};
+
+static int oa_tc6_phy_irq_setup(struct oa_tc6 *tc6)
+{
+	INIT_WORK(&tc6->phy_irq_work, oa_tc6_phy_irq_work);
+
+	tc6->phy_irq_domain =
+		irq_domain_create_linear(NULL, 1,
+					 &oa_tc6_phy_irq_domain_ops, tc6);
+	if (!tc6->phy_irq_domain)
+		return -ENOMEM;
+
+	tc6->phy_virq = irq_create_mapping(tc6->phy_irq_domain, 0);
+	WRITE_ONCE(tc6->phy_irq_masked, true);
+	if (!tc6->phy_virq) {
+		irq_domain_remove(tc6->phy_irq_domain);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static void oa_tc6_phy_irq_teardown(struct oa_tc6 *tc6)
+{
+	irq_dispose_mapping(tc6->phy_virq);
+	irq_domain_remove(tc6->phy_irq_domain);
+}
+
 static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
 {
 	int ret;
@@ -562,9 +741,25 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
 	snprintf(tc6->mdiobus->id, ARRAY_SIZE(tc6->mdiobus->id), "%s",
 		 dev_name(&tc6->spi->dev));
 
+	if (tc6->quirk_flags & OA_TC6_PHY_INT) {
+		ret = oa_tc6_phy_irq_setup(tc6);
+		if (ret) {
+			mdiobus_free(tc6->mdiobus);
+			return ret;
+		}
+		/* Populate all irq[] entries before registration so
+		 * phy_device_create() picks up the virtual IRQ regardless of
+		 * the PHY's MDIO address.
+		 */
+		for (int i = 0; i < PHY_MAX_ADDR; i++)
+			tc6->mdiobus->irq[i] = tc6->phy_virq;
+	}
+
 	ret = mdiobus_register(tc6->mdiobus);
 	if (ret) {
 		netdev_err(tc6->netdev, "Could not register MDIO bus\n");
+		if (tc6->quirk_flags & OA_TC6_PHY_INT)
+			oa_tc6_phy_irq_teardown(tc6);
 		mdiobus_free(tc6->mdiobus);
 		return ret;
 	}
@@ -575,6 +770,8 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
 static void oa_tc6_mdiobus_unregister(struct oa_tc6 *tc6)
 {
 	mdiobus_unregister(tc6->mdiobus);
+	if (tc6->quirk_flags & OA_TC6_PHY_INT)
+		oa_tc6_phy_irq_teardown(tc6);
 	mdiobus_free(tc6->mdiobus);
 }
 
@@ -624,6 +821,7 @@ static void oa_tc6_phy_exit(struct oa_tc6 *tc6)
 	if (tc6->quirk_flags & OA_TC6_BROKEN_PHY)
 		return;
 
+	cancel_work_sync(&tc6->phy_irq_work);
 	phy_disconnect(tc6->phydev);
 	oa_tc6_mdiobus_unregister(tc6);
 }
@@ -780,7 +978,12 @@ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
 	netif_tx_disable(tc6->netdev);
 	oa_tc6_drop_tx_skb(tc6, skb);
 	oa_tc6_free_ongoing_skbs(tc6);
+	/* Serialize INT_MASK0 write with phylib's mask/unmask to prevent
+	 * read-modify-write races in oa_tc6_phy_irq_bus_sync_unlock().
+	 */
+	mutex_lock(&tc6->phy_irq_lock);
 	oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
+	mutex_unlock(&tc6->phy_irq_lock);
 	oa_tc6_read_register(tc6, OA_TC6_REG_STATUS0, &regval);
 	oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, regval);
 	dev_err(&tc6->spi->dev, "Device interrupt disabled to avoid interrupt storm");
@@ -813,6 +1016,29 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
 		return ret;
 	}
 
+	/* Dispatch the PHY interrupt to phylib via the nested virtual IRQ so
+	 * the PHY driver reads and acknowledges its status. This is deferred
+	 * to a workqueue rather than dispatched synchronously here, since
+	 * phy_interrupt() takes phydev->lock and PHY handle_interrupt() issues
+	 * synchronous SPI control transfers, which would otherwise block this
+	 * thread.
+	 *
+	 * Mask the hardware interrupt immediately to avoid wasting SPI cycles
+	 * on redundant STATUS0 reads until the worker runs and phylib acks it.
+	 * PHYINT is level triggered and stays asserted until acked, so every
+	 * RX chunk footer would re-read STATUS0 until the worker schedules.
+	 * Gate on phy_virq (the actual resource) rather than just the flag to
+	 * be self-consistent if OA_TC6_BROKEN_PHY skips initialization.
+	 */
+	if (tc6->phy_virq && FIELD_GET(OA_TC6_STATUS0_PHY_INT, value)) {
+		ret = oa_tc6_phy_irq_mask_hw(tc6);
+		if (ret)
+			dev_err(&tc6->spi->dev,
+				"Failed to mask PHY interrupt: %d\n", ret);
+		else
+			schedule_work(&tc6->phy_irq_work);
+	}
+
 	if (FIELD_GET(OA_TC6_STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) {
 		oa_tc6_look_for_new_frame(tc6);
 		net_err_ratelimited("%s: Receive buffer overflow error\n",
@@ -1471,6 +1697,7 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
 	tc6->spi = spi;
 	tc6->netdev = netdev;
 	SET_NETDEV_DEV(netdev, &spi->dev);
+	mutex_init(&tc6->phy_irq_lock);
 	mutex_init(&tc6->spi_ctrl_lock);
 	spin_lock_init(&tc6->tx_skb_lock);
 
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 27f652d4920b..1c8cc263c5e5 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -30,6 +30,7 @@
 
 /* Status Register #0 */
 #define OA_TC6_REG_STATUS0			0x0008
+#define OA_TC6_STATUS0_PHY_INT			BIT(7)
 #define OA_TC6_STATUS0_RESETC			BIT(6)	/* Reset Complete */
 #define OA_TC6_STATUS0_HEADER_ERROR		BIT(5)
 #define OA_TC6_STATUS0_LOSS_OF_FRAME_ERROR	BIT(4)
@@ -43,6 +44,7 @@
 
 /* Interrupt Mask Register #0 */
 #define OA_TC6_REG_INT_MASK0				0x000C
+#define OA_TC6_INT_MASK0_PHY_INT_MASK			BIT(7)
 #define OA_TC6_INT_MASK0_HEADER_ERR_MASK		BIT(5)
 #define OA_TC6_INT_MASK0_LOSS_OF_FRAME_ERR_MASK		BIT(4)
 #define OA_TC6_INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK	BIT(3)
@@ -68,6 +70,7 @@ struct oa_tc6;
 
 enum oa_tc6_quirk_flag {
 	OA_TC6_BROKEN_PHY = BIT(0),
+	OA_TC6_PHY_INT    = BIT(1),
 };
 
 struct oa_tc6_quirks {
-- 
2.43.0


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

* [PATCH net-next v4 3/4] microchip: lan865x: enable PHY interrupt via virtual IRQ
  2026-09-29 12:59 [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change Parthiban Veerasooran
  2026-09-29 12:59 ` [PATCH net-next v4 1/4] net: phy: " Parthiban Veerasooran
  2026-09-29 12:59 ` [PATCH net-next v4 2/4] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib Parthiban Veerasooran
@ 2026-09-29 12:59 ` Parthiban Veerasooran
  2026-09-29 12:59 ` [PATCH net-next v4 4/4] net: phy: microchip_t1s: fix collision detection for LAN867X Rev.D0 Parthiban Veerasooran
  2026-09-29 13:05 ` [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change netdev-bot+sinfo
  4 siblings, 0 replies; 7+ messages in thread
From: Parthiban Veerasooran @ 2026-09-29 12:59 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux
  Cc: netdev, linux-kernel, UNGLinuxDriver, Parthiban.Veerasooran,
	Parthiban Veerasooran

The LAN865X integrates the PHY directly into the MAC-PHY SPI device.
The PHY has no dedicated interrupt line; its interrupt is always
delivered as the PHYINT bit in STATUS0.

Pass the OA_TC6_PHY_INT quirk flag to oa_tc6_init() to enable the
virtual IRQ infrastructure. This lets phylib operate in interrupt mode
and drives the PHY driver's config_intr/handle_interrupt callbacks for
the LAN865X internal PHY.

This completes the collision-detection fix started in "net: phy:
microchip_t1s: fix collision detection on PLCA status change"; without
this patch (and its prerequisite, "net: ethernet: oa_tc6: deliver the
PHY interrupt to phylib"), LAN865X stays on that commit's polling-based
CDEN synchronization/fallback instead of tracking PLCA transitions
dynamically.

CDEN is synchronized to the live PLCA status when lan86xx_config_intr()
runs during phy_enable_interrupts(), then tracked on each subsequent
PLCA transition via lan86xx_handle_interrupt(). This PHY interrupt path
is critical for keeping CDEN correct across PLCA transitions.

Fixes: 78341049fbcd ("net: phy: microchip_t1s: configure collision detection based on PLCA mode")
Signed-off-by: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
---
 drivers/net/ethernet/microchip/lan865x/lan865x.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c
index 127afb9e9f14..d47560f9847d 100644
--- a/drivers/net/ethernet/microchip/lan865x/lan865x.c
+++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c
@@ -332,6 +332,7 @@ static const struct net_device_ops lan865x_netdev_ops = {
 
 static int lan865x_probe(struct spi_device *spi)
 {
+	struct oa_tc6_quirks tc6_quirks = {};
 	struct net_device *netdev;
 	struct lan865x_priv *priv;
 	int ret;
@@ -346,7 +347,8 @@ static int lan865x_probe(struct spi_device *spi)
 	spi_set_drvdata(spi, priv);
 	INIT_WORK(&priv->multicast_work, lan865x_multicast_work_handler);
 
-	priv->tc6 = oa_tc6_init(spi, netdev, NULL);
+	tc6_quirks.quirk_flags = OA_TC6_PHY_INT;
+	priv->tc6 = oa_tc6_init(spi, netdev, &tc6_quirks);
 	if (IS_ERR(priv->tc6)) {
 		ret = PTR_ERR(priv->tc6);
 		goto free_netdev;
-- 
2.43.0


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

* [PATCH net-next v4 4/4] net: phy: microchip_t1s: fix collision detection for LAN867X Rev.D0
  2026-09-29 12:59 [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change Parthiban Veerasooran
                   ` (2 preceding siblings ...)
  2026-09-29 12:59 ` [PATCH net-next v4 3/4] microchip: lan865x: enable PHY interrupt via virtual IRQ Parthiban Veerasooran
@ 2026-09-29 12:59 ` Parthiban Veerasooran
  2026-09-29 13:05 ` [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change netdev-bot+sinfo
  4 siblings, 0 replies; 7+ messages in thread
From: Parthiban Veerasooran @ 2026-09-29 12:59 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux
  Cc: netdev, linux-kernel, UNGLinuxDriver, Parthiban.Veerasooran,
	Parthiban Veerasooran

LAN867X Rev.D0 adds a Collision Counting and MAC Forwarding Control
field (CCMFC, bits 10:9) in COL_DET_CTRL0 (0x0087). Set to the OA
default (0x1), the hardware autonomously gates collision forwarding
based on live PLCA_Status, removing the delay a software-driven CDEN
toggle had on older revisions. The PSTC interrupt handler for Rev.D0
therefore only needs to update the link status selection on each PLCA
transition.

Configure CCMFC to the OA default in lan867x_revd0_config_init(). Add
lan867x_revd0_handle_interrupt() for two events: Link Status Change
triggers the phylib state machine; PLCA Status Change re-evaluates the
link status selection using the cached PLCA enable state and current
CSMA/CD fallback configuration.

Rev.D0 can also be configured (PRSCTL1, 0x0035, bit 10) to autonomously
fall back to CSMA/CD when no BEACON is seen. When that's active, the
PHY's own hardware transition already handles CSMA/CD correctly, so
driving link status from PLCA_Status reports nothing meaningful. Force
the semaphore (forced-active) source whenever fallback is enabled; only
when fallback is disabled - the PHY stays pinned to PLCA mode - does
tracking PLCA_Status serve its purpose.

Wire up .config_intr/.handle_interrupt for Rev.D0 via a dedicated
lan867x_revd0_config_intr(), reusing the STS1/IMSK1 helpers from the
other LAN86XX PHYs.

Preserve cable-test polling when Rev.D0 uses its PHY interrupt path.
Once interrupt handling is enabled, phydev->irq may be valid and the
normal PHY polling path is no longer used, so PHY_POLL_CABLE_TEST
ensures cable-test state continues to be polled while the test is active.

Fixes: 07f5765f26c3 ("net: phy: microchip_t1s: configure link status control for LAN867x Rev.D0")
Signed-off-by: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
---
 drivers/net/phy/microchip_t1s.c | 127 +++++++++++++++++++++++++++++++-
 1 file changed, 126 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c
index 5ce0304bf095..d667f57aa4a8 100644
--- a/drivers/net/phy/microchip_t1s.c
+++ b/drivers/net/phy/microchip_t1s.c
@@ -29,6 +29,7 @@
 
 /* PHY interrupt status 1 register */
 #define LAN86XX_REG_STS1		0x0018
+#define LAN86XX_STS1_LINK_STS_CHANGED	BIT(13)
 #define LAN86XX_STS1_PLCA_STS_CHANGED	BIT(11)
 
 /* PHY interrupt mask 1 register */
@@ -47,6 +48,9 @@
 #define COL_DET_CTRL0_ENABLE_BIT_MASK	BIT(15)
 #define COL_DET_ENABLE			BIT(15)
 #define COL_DET_DISABLE			0x0000
+#define COL_DET_CTRL0_CCMFC_MASK	GENMASK(10, 9)
+/* OA default: collisions gated by PLCA_Status in hardware */
+#define COL_DET_CTRL0_CCMFC_OA_DEFAULT	BIT(9)
 
 /* LAN8670/1/2 Rev.D0 Link Status Selection Register */
 #define LAN867X_REG_LINK_STATUS_CTRL	0x0012
@@ -520,6 +524,29 @@ static int lan86xx_update_cden(struct phy_device *phydev)
 			      COL_DET_CTRL0_ENABLE_BIT_MASK, cden);
 }
 
+/* When the PHY autonomously falls back to CSMA/CD once BEACONs stop (PRSCTL1
+ * bit 10 set), the hardware fallback already provides correct CSMA/CD
+ * operation; selecting link status from PLCA_STATUS in that case reports
+ * nothing meaningful, since the PHY may already be running CSMA/CD regardless
+ * of the stale PLCA_STATUS value. Force the semaphore (forced-active) source
+ * in that case instead. Only when fallback is disabled - the PHY is pinned
+ * to PLCA mode - does tracking PLCA_STATUS serve its intended purpose.
+ */
+static int lan867x_revd0_update_link_selection(struct phy_device *phydev,
+					       int plca_enabled)
+{
+	int fallback;
+
+	fallback = lan86xx_fallback_to_csmacd(phydev);
+	if (fallback < 0)
+		return fallback;
+
+	if (fallback)
+		return lan867x_revd0_link_active_selection(phydev, false);
+
+	return lan867x_revd0_link_active_selection(phydev, plca_enabled);
+}
+
 /* As per LAN8650/1 Rev.B0/B1 AN1760 (Revision F (DS60001760G - June 2024)) and
  * LAN8670/1/2 Rev.C1/C2 AN1699 (Revision E (DS60001699F - June 2024)), under
  * normal operation, the device should be operated in PLCA mode. Disabling
@@ -528,6 +555,11 @@ static int lan86xx_update_cden(struct phy_device *phydev)
  * distortion cause poor signal quality. Collision detection must be re-enabled
  * if the device is configured to operate in CSMA/CD mode.
  *
+ * LAN867X Rev.D0 has autonomous collision detection gating via CCMFC and
+ * does not toggle CDEN in the interrupt handler. CDEN remains permanently
+ * enabled in config_init(), so no software-driven CDEN toggling is needed
+ * here.
+ *
  * AN1760: https://www.microchip.com/en-us/application-notes/an1760
  * AN1699: https://www.microchip.com/en-us/application-notes/an1699
  */
@@ -542,7 +574,7 @@ static int lan86xx_plca_set_cfg(struct phy_device *phydev,
 	 * (not -1, which means "don't change").
 	 */
 	if (phydev->phy_id == PHY_ID_LAN867X_REVD0 && plca_cfg->enabled != -1) {
-		ret = lan867x_revd0_link_active_selection(phydev,
+		ret = lan867x_revd0_update_link_selection(phydev,
 							  plca_cfg->enabled);
 		if (ret)
 			return ret;
@@ -555,6 +587,12 @@ static int lan86xx_plca_set_cfg(struct phy_device *phydev,
 	if (plca_cfg->enabled != -1)
 		priv->plca_enabled = plca_cfg->enabled;
 
+	/* LAN867X Rev.D0 uses CCMFC for autonomous collision detection
+	 * gating; CDEN remains enabled and does not require software toggling.
+	 */
+	if (phydev->phy_id == PHY_ID_LAN867X_REVD0)
+		return 0;
+
 	if (plca_cfg->enabled != -1) {
 		mutex_lock(&priv->cden_lock);
 		ret = lan86xx_update_cden(phydev);
@@ -582,6 +620,20 @@ static int lan867x_revd0_config_init(struct phy_device *phydev)
 			return ret;
 	}
 
+	/* AN1699: Configure CCMFC (Collision Counting and MAC Forwarding
+	 * Control) to OA default (0x1) so that the hardware autonomously gates
+	 * collision forwarding to the MAC based on the live PLCA_Status:
+	 * collisions are neither counted nor forwarded when PLCA_Status is OK,
+	 * and are counted/forwarded when not OK. This eliminates the need for
+	 * software-driven CDEN toggling. CDEN defaults to enabled on Rev.D0
+	 * and remains enabled.
+	 */
+	ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_COL_DET_CTRL0,
+			     COL_DET_CTRL0_CCMFC_MASK,
+			     COL_DET_CTRL0_CCMFC_OA_DEFAULT);
+	if (ret)
+		return ret;
+
 	/* Initially the PHY will be in CSMA/CD mode by default. So it is
 	 * required to set the link always active as it doesn't support
 	 * autoneg.
@@ -712,6 +764,76 @@ static irqreturn_t lan86xx_handle_interrupt(struct phy_device *phydev)
 	return ret_irq;
 }
 
+static int lan867x_revd0_config_intr(struct phy_device *phydev)
+{
+	u16 mask = LAN86XX_STS1_PLCA_STS_CHANGED |
+		   LAN86XX_STS1_LINK_STS_CHANGED;
+	struct lan86xx_priv *priv = phydev->priv;
+	int sts1, ret;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		/* Read to clear any pending status before enabling. */
+		sts1 = lan86xx_read_clear_sts1(phydev);
+		if (sts1 < 0)
+			return sts1;
+
+		/* STS1 may have cleared a pending PSTC while masked, and a
+		 * missed PSTC leaves no trace to key off, so unconditionally
+		 * resync the link-status-selection source from the current
+		 * PLCA enable state and fallback configuration.
+		 */
+		ret = lan867x_revd0_update_link_selection(phydev,
+							  priv->plca_enabled);
+		if (ret < 0)
+			return ret;
+
+		return lan86xx_set_intr_mask(phydev, mask, true);
+	}
+
+	ret = lan86xx_set_intr_mask(phydev, mask, false);
+	if (ret)
+		return ret;
+
+	/* Read to clear any pending status after disabling. */
+	ret = lan86xx_read_clear_sts1(phydev);
+	return ret < 0 ? ret : 0;
+}
+
+static irqreturn_t lan867x_revd0_handle_interrupt(struct phy_device *phydev)
+{
+	struct lan86xx_priv *priv = phydev->priv;
+	irqreturn_t ret_irq = IRQ_NONE;
+	int sts1, ret;
+
+	sts1 = lan86xx_read_clear_sts1(phydev);
+	if (sts1 < 0) {
+		phy_error(phydev);
+		return IRQ_NONE;
+	}
+
+	if (sts1 & LAN86XX_STS1_LINK_STS_CHANGED) {
+		phy_trigger_machine(phydev);
+		ret_irq = IRQ_HANDLED;
+	}
+
+	if (sts1 & LAN86XX_STS1_PLCA_STS_CHANGED) {
+		/* Re-evaluate the link-status selection when PLCA status
+		 * changes. A resulting link-status change raises
+		 * LINK_STS_CHANGED, which triggers the PHY state machine.
+		 */
+		ret = lan867x_revd0_update_link_selection(phydev,
+							  priv->plca_enabled);
+		if (ret < 0) {
+			phy_error(phydev);
+			return IRQ_NONE;
+		}
+
+		ret_irq = IRQ_HANDLED;
+	}
+
+	return ret_irq;
+}
+
 static struct phy_driver microchip_t1s_driver[] = {
 	{
 		PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1),
@@ -754,7 +876,10 @@ static struct phy_driver microchip_t1s_driver[] = {
 		.name               = "LAN867X Rev.D0",
 		.features           = PHY_BASIC_T1S_P2MP_FEATURES,
 		.probe              = lan86xx_probe,
+		.flags              = PHY_POLL_CABLE_TEST,
 		.config_init        = lan867x_revd0_config_init,
+		.config_intr        = lan867x_revd0_config_intr,
+		.handle_interrupt   = lan867x_revd0_handle_interrupt,
 		.get_plca_cfg	    = genphy_c45_plca_get_cfg,
 		.set_plca_cfg	    = lan86xx_plca_set_cfg,
 		.get_plca_status    = genphy_c45_plca_get_status,
-- 
2.43.0


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

* Re: [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change
  2026-09-29 12:59 [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change Parthiban Veerasooran
                   ` (3 preceding siblings ...)
  2026-09-29 12:59 ` [PATCH net-next v4 4/4] net: phy: microchip_t1s: fix collision detection for LAN867X Rev.D0 Parthiban Veerasooran
@ 2026-09-29 13:05 ` netdev-bot+sinfo
  2026-09-30 10:01   ` Parthiban Veerasooran
  4 siblings, 1 reply; 7+ messages in thread
From: netdev-bot+sinfo @ 2026-09-29 13:05 UTC (permalink / raw)
  To: Parthiban Veerasooran
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux,
	netdev, linux-kernel, UNGLinuxDriver

Hi!

This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:

 - How the issue was discovered, e.g. hit in production, hit during
   development, syzbot report, manual code inspection, LLM or static
   analysis tool scan.

 - Whether the issue was actually triggered, or is only theoretical
   (e.g. found by code inspection). If it was triggered please include
   the symptoms, like the stack trace or error messages.

 - What hardware the change was tested on. For driver fixes please
   mention the device (and if relevant firmware version) used for
   testing, or say that the change was not tested on real hardware.

Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.

The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.

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

* Re: [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change
  2026-09-29 13:05 ` [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change netdev-bot+sinfo
@ 2026-09-30 10:01   ` Parthiban Veerasooran
  0 siblings, 0 replies; 7+ messages in thread
From: Parthiban Veerasooran @ 2026-09-30 10:01 UTC (permalink / raw)
  To: netdev-bot+sinfo
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux,
	netdev, linux-kernel, UNGLinuxDriver

Hi,

Thanks for the feedback.

The issue was identified during production level testing of the
LAN865X/LAN867X-based system. The issue was observed when the device
transitioned between PLCA mode and CSMA/CD mode based on BEACON
availability. The existing collision-detection handling did not
correctly track these autonomous PLCA state transitions.

The changes were tested on a Raspberry Pi 4 with the LAN865X/LAN867X
devices.

Best regards,
Parthiban V

On 29/09/26 6:35 pm, netdev-bot+sinfo@kernel.org wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
> Hi!
> 
> This is an automated message. This series looks like a fix, but its
> commit messages seem to be missing some information:
> 
>   - How the issue was discovered, e.g. hit in production, hit during
>     development, syzbot report, manual code inspection, LLM or static
>     analysis tool scan.
> 
>   - Whether the issue was actually triggered, or is only theoretical
>     (e.g. found by code inspection). If it was triggered please include
>     the symptoms, like the stack trace or error messages.
> 
>   - What hardware the change was tested on. For driver fixes please
>     mention the device (and if relevant firmware version) used for
>     testing, or say that the change was not tested on real hardware.
> 
> Please do not repost the series just to address the above. Instead,
> reply to this email with the missing information, so that reviewers
> can take it into account. If the series needs another revision for
> other reasons, please include the information in the commit messages
> then.
> 
> The evaluation is done by an LLM so it may be wrong, if you think
> that is the case please reply and explain.


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

end of thread, other threads:[~2026-09-30 10:01 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-29 12:59 [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change Parthiban Veerasooran
2026-09-29 12:59 ` [PATCH net-next v4 1/4] net: phy: " Parthiban Veerasooran
2026-09-29 12:59 ` [PATCH net-next v4 2/4] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib Parthiban Veerasooran
2026-09-29 12:59 ` [PATCH net-next v4 3/4] microchip: lan865x: enable PHY interrupt via virtual IRQ Parthiban Veerasooran
2026-09-29 12:59 ` [PATCH net-next v4 4/4] net: phy: microchip_t1s: fix collision detection for LAN867X Rev.D0 Parthiban Veerasooran
2026-09-29 13:05 ` [PATCH net-next v4 0/4] net: microchip_t1s: fix collision detection on PLCA status change netdev-bot+sinfo
2026-09-30 10:01   ` Parthiban Veerasooran

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®