* [PATCH net-next 1/3] net: phy: microchip_t1s: fix collision detection on PLCA status change
2026-09-01 13:09 [PATCH net-next 0/3] net: microchip_t1s: fix collision detection on PLCA status change Parthiban Veerasooran
@ 2026-09-01 13:09 ` Parthiban Veerasooran
2026-09-01 13:09 ` [PATCH net-next 2/3] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib Parthiban Veerasooran
2026-09-01 13:09 ` [PATCH net-next 3/3] net: phy: microchip_t1s: fix collision detection for LAN867X Rev.D0 Parthiban Veerasooran
2 siblings, 0 replies; 10+ messages in thread
From: Parthiban Veerasooran @ 2026-09-01 13:09 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux
Cc: netdev, UNGLinuxDriver, linux-kernel, Parthiban Veerasooran
The existing lan86xx_plca_set_cfg() adjusted collision detection
statically at the point the user configured PLCA via ethtool: disabled
when PLCA was enabled, re-enabled when PLCA was disabled. This only
handled the explicit user-driven mode change and missed the dynamic
transitions that the PHY performs autonomously.
In a 10BASE-T1S multidrop network, the PHY tracks BEACON availability
and continuously transitions between PLCA online (actively receiving
BEACONs from the coordinator) and PLCA offline (no BEACON present).
When PLCA goes offline after having been configured online, collision
detection remained disabled, causing the bus to operate in CSMA/CD mode
without collision detection — a silent and hard-to-diagnose error.
Fix this by monitoring the PLCA Status Changed (PSTC) interrupt. PSTC
fires on every PST bit transition in the PLCA Status register. Add
lan86xx_config_intr() to enable/disable the PSTCM mask bit in IMSK1
(bit 11, active-low enable) and lan86xx_handle_interrupt() to service
it. On each interrupt, PLCA operational status is retrieved via
genphy_c45_plca_get_status(). When PLCA comes online, collision
detection is disabled via COL_DET_CTRL0 (bit 15). When PLCA goes
offline, collision detection is re-enabled to restore correct CSMA/CD
operation.
Wire these handlers to all supported PHY variants: LAN867X Rev.B1, C1,
C2 and LAN865X Rev.B0/B1.
LAN867X PHYs may run with phydev->irq == PHY_POLL on boards where the
PHY interrupt is not routed to the host. The existing static CDEN write
in lan86xx_plca_set_cfg() is retained as a baseline so that collision
detection is correct even when the interrupt handler never runs. The
limitation is that autonomous PLCA mode transitions between ethtool
reconfigurations are not tracked on such boards. LAN865X is excluded
from the static write because its interrupt is always routed via the
MAC-PHY SPI driver and the interrupt handler always runs.
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 | 97 +++++++++++++++++++++++++++++++++
1 file changed, 97 insertions(+)
diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c
index 73c23d311d72..afb7e52594e7 100644
--- a/drivers/net/phy/microchip_t1s.c
+++ b/drivers/net/phy/microchip_t1s.c
@@ -27,6 +27,14 @@
#define LAN865X_REG_CFGPARAM_CTRL 0x00DA
#define LAN865X_REG_STS2 0x0019
+/* PHY interrupt status and mask registers (MDIO_MMD_VEND2). The status bits
+ * are read-to-clear; a mask bit is enabled by writing 0.
+ */
+#define LAN86XX_REG_STS1 0x0018
+#define LAN86XX_REG_IMSK1 0x001C
+
+#define LAN86XX_STS1_PLCA_STS_CHANGED BIT(11)
+
/* Collision Detector Control 0 Register */
#define LAN86XX_REG_COL_DET_CTRL0 0x0087
#define COL_DET_CTRL0_ENABLE_BIT_MASK BIT(15)
@@ -458,6 +466,16 @@ static int lan86xx_plca_set_cfg(struct phy_device *phydev,
if (ret)
return ret;
+ /* PHYs with routed interrupts handle CDEN dynamically via the interrupt
+ * handler, so skip the static write. PHYs running with PHY_POLL have no
+ * interrupt handler, so apply the static CDEN write as a baseline on
+ * every ethtool PLCA reconfiguration. The limitation is that autonomous
+ * PLCA mode transitions between ethtool reconfigurations are not
+ * tracked on such boards.
+ */
+ if (phydev->irq != PHY_POLL)
+ return 0;
+
if (plca_cfg->enabled)
return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
LAN86XX_REG_COL_DET_CTRL0,
@@ -506,6 +524,77 @@ static int lan86xx_read_status(struct phy_device *phydev)
return 0;
}
+static int lan86xx_config_intr(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ /* Read to clear any pending status before enabling. */
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_STS1);
+ if (ret < 0)
+ return ret;
+
+ /* A mask bit of 0 enables the corresponding interrupt. */
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
+ LAN86XX_REG_IMSK1,
+ LAN86XX_STS1_PLCA_STS_CHANGED);
+ }
+
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_IMSK1,
+ LAN86XX_STS1_PLCA_STS_CHANGED);
+ if (ret)
+ return ret;
+
+ /* Read to clear any pending status after disabling. */
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_STS1);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static irqreturn_t lan86xx_handle_interrupt(struct phy_device *phydev)
+{
+ struct phy_plca_status plca_st;
+ irqreturn_t ret_irq = IRQ_NONE;
+ int sts1, ret;
+
+ /* Reading the status register clears the latched event bits. */
+ sts1 = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_STS1);
+ if (sts1 < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (sts1 & LAN86XX_STS1_PLCA_STS_CHANGED) {
+ ret = genphy_c45_plca_get_status(phydev, &plca_st);
+ if (ret < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ /* AN1760/AN1699: disable collision detection in PLCA mode to
+ * improve signal quality; re-enable it in CSMA/CD mode.
+ *
+ * https://www.microchip.com/en-us/application-notes/an1760
+ * https://www.microchip.com/en-us/application-notes/an1699
+ */
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2,
+ LAN86XX_REG_COL_DET_CTRL0,
+ COL_DET_CTRL0_ENABLE_BIT_MASK,
+ plca_st.pst ? COL_DET_DISABLE :
+ COL_DET_ENABLE);
+ 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),
@@ -513,6 +602,8 @@ static struct phy_driver microchip_t1s_driver[] = {
.features = PHY_BASIC_T1S_P2MP_FEATURES,
.config_init = lan867x_revb1_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 = genphy_c45_plca_set_cfg,
.get_plca_status = genphy_c45_plca_get_status,
@@ -523,6 +614,8 @@ static struct phy_driver microchip_t1s_driver[] = {
.features = PHY_BASIC_T1S_P2MP_FEATURES,
.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,
@@ -533,6 +626,8 @@ static struct phy_driver microchip_t1s_driver[] = {
.features = PHY_BASIC_T1S_P2MP_FEATURES,
.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,
@@ -556,6 +651,8 @@ static struct phy_driver microchip_t1s_driver[] = {
.features = PHY_BASIC_T1S_P2MP_FEATURES,
.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 related [flat|nested] 10+ messages in thread* [PATCH net-next 2/3] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib
2026-09-01 13:09 [PATCH net-next 0/3] net: microchip_t1s: fix collision detection on PLCA status change Parthiban Veerasooran
2026-09-01 13:09 ` [PATCH net-next 1/3] net: phy: " Parthiban Veerasooran
@ 2026-09-01 13:09 ` Parthiban Veerasooran
2026-09-02 0:30 ` Andrew Lunn
2026-09-02 0:43 ` Andrew Lunn
2026-09-01 13:09 ` [PATCH net-next 3/3] net: phy: microchip_t1s: fix collision detection for LAN867X Rev.D0 Parthiban Veerasooran
2 siblings, 2 replies; 10+ messages in thread
From: Parthiban Veerasooran @ 2026-09-01 13:09 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux
Cc: netdev, UNGLinuxDriver, linux-kernel, Parthiban Veerasooran
The integrated PHY has no dedicated interrupt line; its interrupt is
delivered in-band as the PHYINT bit in STATUS0, which raises the MAC-PHY
SPI interrupt via the extended status. phy_mac_interrupt() only triggers
a link-status re-read and cannot make the PHY driver read and acknowledge
its interrupt source registers, so expose the PHY interrupt to phylib as a
nested virtual IRQ instead.
Use dummy_irq_chip as the irqchip, map a virtual IRQ and assign it to
phydev->irq before phy_connect_direct() so phylib enters interrupt mode
and uses the PHY driver's config_intr/handle_interrupt. Unmask PHYINT in
INT_MASK0, and when it is seen in the extended status, dispatch
handle_nested_irq() synchronously from the sleepable threaded IRQ. PHYINT
is level triggered, so acking the PHY source there clears it before the
next data chunk, avoiding a storm.
Signed-off-by: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
---
drivers/net/ethernet/oa_tc6.c | 69 +++++++++++++++++++++++++++++++++--
include/linux/oa_tc6.h | 2 +
2 files changed, 68 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 417c15d1ff42..6c93a61d8d2d 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -8,6 +8,8 @@
#include <linux/bitfield.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>
@@ -70,6 +72,8 @@ struct oa_tc6 {
struct phy_device *phydev;
struct mii_bus *mdiobus;
struct spi_device *spi;
+ struct irq_domain *phy_irq_domain;
+ int phy_virq;
struct mutex spi_ctrl_lock; /* Protects spi control transfer */
spinlock_t tx_skb_lock; /* Protects tx skb handling */
void *spi_ctrl_tx_buf;
@@ -575,6 +579,44 @@ static void oa_tc6_mdiobus_unregister(struct oa_tc6 *tc6)
mdiobus_free(tc6->mdiobus);
}
+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, &dummy_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)
+{
+ 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);
+ 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_phy_init(struct oa_tc6 *tc6)
{
int ret;
@@ -600,6 +642,16 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6)
return -ENODEV;
}
+ ret = oa_tc6_phy_irq_setup(tc6);
+ if (ret) {
+ oa_tc6_mdiobus_unregister(tc6);
+ return ret;
+ }
+
+ /* Deliver the PHY interrupt through the nested virtual IRQ. Set before
+ * phy_connect_direct() so phylib enters interrupt mode.
+ */
+ tc6->phydev->irq = tc6->phy_virq;
tc6->phydev->is_internal = true;
ret = phy_connect_direct(tc6->netdev, tc6->phydev,
&oa_tc6_handle_link_change,
@@ -607,6 +659,7 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6)
if (ret) {
netdev_err(tc6->netdev, "Can't attach PHY to %s\n",
tc6->mdiobus->id);
+ oa_tc6_phy_irq_teardown(tc6);
oa_tc6_mdiobus_unregister(tc6);
return ret;
}
@@ -622,6 +675,7 @@ static void oa_tc6_phy_exit(struct oa_tc6 *tc6)
return;
phy_disconnect(tc6->phydev);
+ oa_tc6_phy_irq_teardown(tc6);
oa_tc6_mdiobus_unregister(tc6);
}
@@ -661,7 +715,7 @@ static int oa_tc6_sw_reset_macphy(struct oa_tc6 *tc6)
return oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, regval);
}
-static int oa_tc6_unmask_macphy_error_interrupts(struct oa_tc6 *tc6)
+static int oa_tc6_unmask_interrupts(struct oa_tc6 *tc6)
{
u32 regval;
int ret;
@@ -670,7 +724,8 @@ static int oa_tc6_unmask_macphy_error_interrupts(struct oa_tc6 *tc6)
if (ret)
return ret;
- regval &= ~(OA_TC6_INT_MASK0_TX_PROTOCOL_ERR_MASK |
+ regval &= ~(OA_TC6_INT_MASK0_PHY_INT_MASK |
+ OA_TC6_INT_MASK0_TX_PROTOCOL_ERR_MASK |
OA_TC6_INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK |
OA_TC6_INT_MASK0_LOSS_OF_FRAME_ERR_MASK |
OA_TC6_INT_MASK0_HEADER_ERR_MASK);
@@ -763,6 +818,14 @@ 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. PHYINT is level
+ * triggered, so doing this synchronously here (in the sleepable
+ * threaded IRQ) clears the source before the next data chunk.
+ */
+ if (FIELD_GET(OA_TC6_STATUS0_PHY_INT, value))
+ handle_nested_irq(tc6->phy_virq);
+
if (FIELD_GET(OA_TC6_STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) {
tc6->rx_buf_overflow = true;
oa_tc6_cleanup_ongoing_rx_skb(tc6);
@@ -1400,7 +1463,7 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
return NULL;
}
- ret = oa_tc6_unmask_macphy_error_interrupts(tc6);
+ ret = oa_tc6_unmask_interrupts(tc6);
if (ret) {
dev_err(&tc6->spi->dev,
"MAC-PHY error interrupts unmask failed: %d\n", ret);
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 27f652d4920b..c470ddddc77f 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)
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH net-next 2/3] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib
2026-09-01 13:09 ` [PATCH net-next 2/3] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib Parthiban Veerasooran
@ 2026-09-02 0:30 ` Andrew Lunn
2026-09-03 13:23 ` Parthiban Veerasooran
2026-09-02 0:43 ` Andrew Lunn
1 sibling, 1 reply; 10+ messages in thread
From: Andrew Lunn @ 2026-09-02 0:30 UTC (permalink / raw)
To: Parthiban Veerasooran
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux,
netdev, UNGLinuxDriver, linux-kernel
> @@ -600,6 +642,16 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6)
> return -ENODEV;
> }
>
> + ret = oa_tc6_phy_irq_setup(tc6);
> + if (ret) {
> + oa_tc6_mdiobus_unregister(tc6);
> + return ret;
> + }
> +
> + /* Deliver the PHY interrupt through the nested virtual IRQ. Set before
> + * phy_connect_direct() so phylib enters interrupt mode.
> + */
> + tc6->phydev->irq = tc6->phy_virq;
I don't know how messy it will be, but it is better to set
mii_bus->irq[] to the interrupt number. phy_device_create() will then
copy it into phydev->irq.
Andrew
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH net-next 2/3] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib
2026-09-02 0:30 ` Andrew Lunn
@ 2026-09-03 13:23 ` Parthiban Veerasooran
2026-09-03 14:14 ` Andrew Lunn
0 siblings, 1 reply; 10+ messages in thread
From: Parthiban Veerasooran @ 2026-09-03 13:23 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux,
netdev, UNGLinuxDriver, linux-kernel
Hi Andrew,
On 02/09/26 6:00 am, Andrew Lunn wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
>> @@ -600,6 +642,16 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6)
>> return -ENODEV;
>> }
>>
>> + ret = oa_tc6_phy_irq_setup(tc6);
>> + if (ret) {
>> + oa_tc6_mdiobus_unregister(tc6);
>> + return ret;
>> + }
>> +
>> + /* Deliver the PHY interrupt through the nested virtual IRQ. Set before
>> + * phy_connect_direct() so phylib enters interrupt mode.
>> + */
>> + tc6->phydev->irq = tc6->phy_virq;
>
> I don't know how messy it will be, but it is better to set
> mii_bus->irq[] to the interrupt number. phy_device_create() will then
> copy it into phydev->irq.
Thanks for the suggestion. To use mii_bus->irq[] so that
phy_device_create() picks it up, we would need to set mii_bus->irq[addr]
with created virtual irq number before mdiobus_register(). However, the
PHY MDIO address is not known until phy_find_first() returns, so we
cannot pre-populate mii_bus->irq[addr] before the bus scan runs.
Am I misunderstanding your suggestion? Happy to rework if there's a
cleaner way.
Best regards,
Parthiban V
>
> Andrew
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH net-next 2/3] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib
2026-09-03 13:23 ` Parthiban Veerasooran
@ 2026-09-03 14:14 ` Andrew Lunn
0 siblings, 0 replies; 10+ messages in thread
From: Andrew Lunn @ 2026-09-03 14:14 UTC (permalink / raw)
To: Parthiban Veerasooran
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux,
netdev, UNGLinuxDriver, linux-kernel
On Thu, Sep 03, 2026 at 06:53:40PM +0530, Parthiban Veerasooran wrote:
> Hi Andrew,
>
> On 02/09/26 6:00 am, Andrew Lunn wrote:
> > EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> >
> > > @@ -600,6 +642,16 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6)
> > > return -ENODEV;
> > > }
> > >
> > > + ret = oa_tc6_phy_irq_setup(tc6);
> > > + if (ret) {
> > > + oa_tc6_mdiobus_unregister(tc6);
> > > + return ret;
> > > + }
> > > +
> > > + /* Deliver the PHY interrupt through the nested virtual IRQ. Set before
> > > + * phy_connect_direct() so phylib enters interrupt mode.
> > > + */
> > > + tc6->phydev->irq = tc6->phy_virq;
> >
> > I don't know how messy it will be, but it is better to set
> > mii_bus->irq[] to the interrupt number. phy_device_create() will then
> > copy it into phydev->irq.
> Thanks for the suggestion. To use mii_bus->irq[] so that phy_device_create()
> picks it up, we would need to set mii_bus->irq[addr] with created virtual
> irq number before mdiobus_register(). However, the PHY MDIO address is not
> known until phy_find_first() returns, so we cannot pre-populate
> mii_bus->irq[addr] before the bus scan runs.
This is why i made the comment, i did not know how messy it would be.
Where it becomes interesting is the recent patch:
https://patchwork.kernel.org/project/netdevbpf/patch/20260902080511.2211261-3-f@lex.la/
It just seems a bit brittle, phydev->irq says one thing, mii_bus->irq[]
says something else.
Maybe set all member of mii_bus->irq[]?
Andrew
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next 2/3] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib
2026-09-01 13:09 ` [PATCH net-next 2/3] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib Parthiban Veerasooran
2026-09-02 0:30 ` Andrew Lunn
@ 2026-09-02 0:43 ` Andrew Lunn
2026-09-03 13:02 ` Parthiban Veerasooran
1 sibling, 1 reply; 10+ messages in thread
From: Andrew Lunn @ 2026-09-02 0:43 UTC (permalink / raw)
To: Parthiban Veerasooran
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux,
netdev, UNGLinuxDriver, linux-kernel
On Tue, Sep 01, 2026 at 06:39:47PM +0530, Parthiban Veerasooran wrote:
> The integrated PHY has no dedicated interrupt line; its interrupt is
> delivered in-band as the PHYINT bit in STATUS0, which raises the MAC-PHY
> SPI interrupt via the extended status. phy_mac_interrupt() only triggers
> a link-status re-read and cannot make the PHY driver read and acknowledge
> its interrupt source registers, so expose the PHY interrupt to phylib as a
> nested virtual IRQ instead.
>
> Use dummy_irq_chip as the irqchip, map a virtual IRQ and assign it to
> phydev->irq before phy_connect_direct() so phylib enters interrupt mode
> and uses the PHY driver's config_intr/handle_interrupt. Unmask PHYINT in
> INT_MASK0, and when it is seen in the extended status, dispatch
> handle_nested_irq() synchronously from the sleepable threaded IRQ. PHYINT
> is level triggered, so acking the PHY source there clears it before the
> next data chunk, avoiding a storm.
9.2.8.7 PHYINT
Physical Layer Interrupt. When set, this bit indicates a service
request from the underlying physical layer block. Many physical
layer implementations support an interrupt output for signaling
events to the station controller. This bit is optional and will be
implemented only if the underlying physical layer supports
generating interrupts to a higher level. When implemented, this bit
shall be cleared by acknowledging the underlying physical layer
interrupt source(s). When not implemented, this bit shall be
reserved with a read- only value of zero.
At the moment, phylib is polling the PHY. That is guaranteed to work.
The standard indicates this interrupt is optional. It might not be
implemented. phylib assumes that if there is an interrupt, the
interrupt works, and it does not poll. So any hardware which does not
implement this interrupt is now broken.
Please find a way not to break other devices.
Andrew
---
pw-bot: cr
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH net-next 2/3] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib
2026-09-02 0:43 ` Andrew Lunn
@ 2026-09-03 13:02 ` Parthiban Veerasooran
2026-09-03 13:54 ` Andrew Lunn
0 siblings, 1 reply; 10+ messages in thread
From: Parthiban Veerasooran @ 2026-09-03 13:02 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux,
netdev, UNGLinuxDriver, linux-kernel
Hi Andrew,
Thank you for reviewing this patch series.
On 02/09/26 6:13 am, Andrew Lunn wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> On Tue, Sep 01, 2026 at 06:39:47PM +0530, Parthiban Veerasooran wrote:
>> The integrated PHY has no dedicated interrupt line; its interrupt is
>> delivered in-band as the PHYINT bit in STATUS0, which raises the MAC-PHY
>> SPI interrupt via the extended status. phy_mac_interrupt() only triggers
>> a link-status re-read and cannot make the PHY driver read and acknowledge
>> its interrupt source registers, so expose the PHY interrupt to phylib as a
>> nested virtual IRQ instead.
>>
>> Use dummy_irq_chip as the irqchip, map a virtual IRQ and assign it to
>> phydev->irq before phy_connect_direct() so phylib enters interrupt mode
>> and uses the PHY driver's config_intr/handle_interrupt. Unmask PHYINT in
>> INT_MASK0, and when it is seen in the extended status, dispatch
>> handle_nested_irq() synchronously from the sleepable threaded IRQ. PHYINT
>> is level triggered, so acking the PHY source there clears it before the
>> next data chunk, avoiding a storm.
>
>
> 9.2.8.7 PHYINT
>
> Physical Layer Interrupt. When set, this bit indicates a service
> request from the underlying physical layer block. Many physical
> layer implementations support an interrupt output for signaling
> events to the station controller. This bit is optional and will be
> implemented only if the underlying physical layer supports
> generating interrupts to a higher level. When implemented, this bit
> shall be cleared by acknowledging the underlying physical layer
> interrupt source(s). When not implemented, this bit shall be
> reserved with a read- only value of zero.
>
> At the moment, phylib is polling the PHY. That is guaranteed to work.
>
> The standard indicates this interrupt is optional. It might not be
> implemented. phylib assumes that if there is an interrupt, the
> interrupt works, and it does not poll. So any hardware which does not
> implement this interrupt is now broken.
>
> Please find a way not to break other devices.
Thank you for pointing it out. PHYINT is optional per the OA TC6
standard (section 9.2.8.7), and unconditionally setting up the virtual
IRQ breaks devices that do not implement it.
I checked both the OA TC6 standard (V1.1) and the LAN8650/1 datasheet,
and there is no capability register bit to detect PHYINT support at runtime.
The oa_tc6 driver already has a quirk_flags mechanism (enum
oa_tc6_quirk_flag, struct oa_tc6_quirks) used for the existing
OA_TC6_BROKEN_PHY flag. I plan to add a new flag to the same enum:
enum oa_tc6_quirk_flag {
OA_TC6_BROKEN_PHY = BIT(0),
OA_TC6_PHY_INT = BIT(1),
};
The virtual IRQ setup, PHYINT unmasking, and interrupt dispatch in
oa_tc6_process_extended_status() will all be guarded by OA_TC6_PHY_INT.
The LAN865X driver, which always implements PHYINT, will pass the
quirk_flags as OA_TC6_PHY_INT. Any other TC6 device that does not pass
this flag will remain in PHY_POLL mode safely.
Hope this is fine?
Best regards,
Parthiban V
>
>
> Andrew
>
> ---
> pw-bot: cr
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH net-next 2/3] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib
2026-09-03 13:02 ` Parthiban Veerasooran
@ 2026-09-03 13:54 ` Andrew Lunn
0 siblings, 0 replies; 10+ messages in thread
From: Andrew Lunn @ 2026-09-03 13:54 UTC (permalink / raw)
To: Parthiban Veerasooran
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux,
netdev, UNGLinuxDriver, linux-kernel
On Thu, Sep 03, 2026 at 06:32:26PM +0530, Parthiban Veerasooran wrote:
> Hi Andrew,
>
> Thank you for reviewing this patch series.
>
> On 02/09/26 6:13 am, Andrew Lunn wrote:
> > EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> >
> > On Tue, Sep 01, 2026 at 06:39:47PM +0530, Parthiban Veerasooran wrote:
> > > The integrated PHY has no dedicated interrupt line; its interrupt is
> > > delivered in-band as the PHYINT bit in STATUS0, which raises the MAC-PHY
> > > SPI interrupt via the extended status. phy_mac_interrupt() only triggers
> > > a link-status re-read and cannot make the PHY driver read and acknowledge
> > > its interrupt source registers, so expose the PHY interrupt to phylib as a
> > > nested virtual IRQ instead.
> > >
> > > Use dummy_irq_chip as the irqchip, map a virtual IRQ and assign it to
> > > phydev->irq before phy_connect_direct() so phylib enters interrupt mode
> > > and uses the PHY driver's config_intr/handle_interrupt. Unmask PHYINT in
> > > INT_MASK0, and when it is seen in the extended status, dispatch
> > > handle_nested_irq() synchronously from the sleepable threaded IRQ. PHYINT
> > > is level triggered, so acking the PHY source there clears it before the
> > > next data chunk, avoiding a storm.
> >
> >
> > 9.2.8.7 PHYINT
> >
> > Physical Layer Interrupt. When set, this bit indicates a service
> > request from the underlying physical layer block. Many physical
> > layer implementations support an interrupt output for signaling
> > events to the station controller. This bit is optional and will be
> > implemented only if the underlying physical layer supports
> > generating interrupts to a higher level. When implemented, this bit
> > shall be cleared by acknowledging the underlying physical layer
> > interrupt source(s). When not implemented, this bit shall be
> > reserved with a read- only value of zero.
> >
> > At the moment, phylib is polling the PHY. That is guaranteed to work.
> >
> > The standard indicates this interrupt is optional. It might not be
> > implemented. phylib assumes that if there is an interrupt, the
> > interrupt works, and it does not poll. So any hardware which does not
> > implement this interrupt is now broken.
> >
> > Please find a way not to break other devices.
> Thank you for pointing it out. PHYINT is optional per the OA TC6 standard
> (section 9.2.8.7), and unconditionally setting up the virtual IRQ breaks
> devices that do not implement it.
>
> I checked both the OA TC6 standard (V1.1) and the LAN8650/1 datasheet, and
> there is no capability register bit to detect PHYINT support at runtime.
I also did a quick check of the standard and could not find an
indication if interrupts were support. So a quirks flag does seem to
be the correct solution.
Andrew
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next 3/3] net: phy: microchip_t1s: fix collision detection for LAN867X Rev.D0
2026-09-01 13:09 [PATCH net-next 0/3] net: microchip_t1s: fix collision detection on PLCA status change Parthiban Veerasooran
2026-09-01 13:09 ` [PATCH net-next 1/3] net: phy: " Parthiban Veerasooran
2026-09-01 13:09 ` [PATCH net-next 2/3] net: ethernet: oa_tc6: deliver the PHY interrupt to phylib Parthiban Veerasooran
@ 2026-09-01 13:09 ` Parthiban Veerasooran
2 siblings, 0 replies; 10+ messages in thread
From: Parthiban Veerasooran @ 2026-09-01 13:09 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, hkallweit1, linux
Cc: netdev, UNGLinuxDriver, linux-kernel, Parthiban Veerasooran
LAN867X Rev.D0 introduces a Collision Counting and MAC Forwarding Control
field (CCMFC, bits 10:9) in the Collision Detector Control 0 register
(CDCTL0, 0x0087). When configured to the OA default value (0x1), the
hardware automatically 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 and forwarded when PLCA_Status is not
OK.
This eliminates the inherent delay between a PLCA status change and the
software interrupt handler toggling CDEN, which was a limitation on older
revisions that had no hardware alternative. Since CCMFC handles collision
gating autonomously, the PSTC interrupt handler for Rev.D0 only needs to
update the link status selection on each PLCA status transition.
Configure CCMFC to the OA default in lan867x_revd0_config_init(). Add
lan867x_revd0_handle_interrupt() to handle two separate events:
1. Link Status Change (LNKSTSC): Triggers the phylib state machine via
phy_trigger_machine() to re-evaluate link status and perform necessary
state transitions.
2. PLCA Status Change (PSTC): Reads the current PLCA operational status
via genphy_c45_plca_get_status() and calls
lan867x_revd0_link_active_selection() to update the link status
selection accordingly.
Unmask both link status change and PLCA status change interrupt masks for
Rev.D0 in lan86xx_config_intr(). Wire up .config_intr and .handle_interrupt
for Rev.D0 using the shared lan86xx_config_intr() and the new handler.
Fixes: e7e756779afa ("net: phy: microchip_t1s: add support for Microchip LAN867X Rev.D0 PHY")
Signed-off-by: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
---
drivers/net/phy/microchip_t1s.c | 68 +++++++++++++++++++++++++++++++--
1 file changed, 65 insertions(+), 3 deletions(-)
diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c
index afb7e52594e7..c3a738c7425b 100644
--- a/drivers/net/phy/microchip_t1s.c
+++ b/drivers/net/phy/microchip_t1s.c
@@ -33,6 +33,7 @@
#define LAN86XX_REG_STS1 0x0018
#define LAN86XX_REG_IMSK1 0x001C
+#define LAN86XX_STS1_LINK_STS_CHANGED BIT(13)
#define LAN86XX_STS1_PLCA_STS_CHANGED BIT(11)
/* Collision Detector Control 0 Register */
@@ -40,6 +41,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
@@ -502,6 +506,18 @@ static int lan867x_revd0_config_init(struct phy_device *phydev)
return ret;
}
+ /* AN1760: configure CCMFC to OA default so that the hardware
+ * automatically gates collision forwarding to the MAC based on
+ * PLCA_Status. Collisions are neither counted nor forwarded when
+ * PLCA_Status = OK, eliminating the need for software-driven CDEN
+ * toggling in the interrupt handler. CDEN 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.
@@ -526,8 +542,12 @@ static int lan86xx_read_status(struct phy_device *phydev)
static int lan86xx_config_intr(struct phy_device *phydev)
{
+ u16 mask = LAN86XX_STS1_PLCA_STS_CHANGED;
int ret;
+ if (phydev->phy_id == PHY_ID_LAN867X_REVD0)
+ mask |= LAN86XX_STS1_LINK_STS_CHANGED;
+
if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
/* Read to clear any pending status before enabling. */
ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_STS1);
@@ -536,12 +556,11 @@ static int lan86xx_config_intr(struct phy_device *phydev)
/* A mask bit of 0 enables the corresponding interrupt. */
return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
- LAN86XX_REG_IMSK1,
- LAN86XX_STS1_PLCA_STS_CHANGED);
+ LAN86XX_REG_IMSK1, mask);
}
ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_IMSK1,
- LAN86XX_STS1_PLCA_STS_CHANGED);
+ mask);
if (ret)
return ret;
@@ -595,6 +614,47 @@ static irqreturn_t lan86xx_handle_interrupt(struct phy_device *phydev)
return ret_irq;
}
+static irqreturn_t lan867x_revd0_handle_interrupt(struct phy_device *phydev)
+{
+ struct phy_plca_status plca_st;
+ irqreturn_t ret_irq = IRQ_NONE;
+ int sts1, ret;
+
+ /* Reading the status register clears the latched event bits. */
+ sts1 = phy_read_mmd(phydev, MDIO_MMD_VEND2, LAN86XX_REG_STS1);
+ 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) {
+ ret = genphy_c45_plca_get_status(phydev, &plca_st);
+ if (ret < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ /* Collision detection is handled autonomously by the hardware
+ * via CCMFC. Only the link status selection needs to be updated
+ * on each PLCA status transition.
+ */
+ ret = lan867x_revd0_link_active_selection(phydev, plca_st.pst);
+ 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),
@@ -637,6 +697,8 @@ static struct phy_driver microchip_t1s_driver[] = {
.name = "LAN867X Rev.D0",
.features = PHY_BASIC_T1S_P2MP_FEATURES,
.config_init = lan867x_revd0_config_init,
+ .config_intr = lan86xx_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 related [flat|nested] 10+ messages in thread