From: Jens Emil Schulz Østergaard <hidden> Date: 2026-01-23 07:51:52
LAN9645X is a family of switch chips with 5 internal copper phys. The
internal PHY is based on parts of LAN8832. This is a low-power, single
port triple-speed (10BASE-T/100BASE-TX/1000BASE-T) ethernet physical
layer transceiver (PHY) that supports transmission and reception of data
on standard CAT-5, as well as CAT-5e and CAT-6 Unshielded Twisted
Pair (UTP) cables.
Add support for the internal PHY of the lan9645x chip family.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Reviewed-by: Daniel Machon <daniel.machon@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <redacted>
---
drivers/net/phy/micrel.c | 142 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/micrel_phy.h | 1 +
2 files changed, 143 insertions(+)
@@ -6502,6 +6502,132 @@ static void lan8842_get_phy_stats(struct phy_device *phydev,stats->tx_errors=priv->phy_stats.tx_errors;}+#define LAN9645X_DAC_ICAS_AMP_POWER_DOWN 0x47+#define LAN9645X_BTRX_QBIAS_POWER_DOWN 0x46+#define LAN9645X_TX_LOW_I_CH_CD_POWER_MGMT 0x45+#define LAN9645X_TX_LOW_I_CH_B_POWER_MGMT 0x44+#define LAN9645X_TX_LOW_I_CH_A_POWER_MGMT 0x43++staticconststructlanphy_reg_dataforce_dac_tx_errata[]={+/* Force channel A/B/C/D TX on */+{LAN8814_PAGE_POWER_REGS,+LAN9645X_DAC_ICAS_AMP_POWER_DOWN,+0},+/* Force channel A/B/C/D QBias on */+{LAN8814_PAGE_POWER_REGS,+LAN9645X_BTRX_QBIAS_POWER_DOWN,+0xaa},+/* tx low I on channel C/D overwrite */+{LAN8814_PAGE_POWER_REGS,+LAN9645X_TX_LOW_I_CH_CD_POWER_MGMT,+0xbfff},+/* channel B low I overwrite */+{LAN8814_PAGE_POWER_REGS,+LAN9645X_TX_LOW_I_CH_B_POWER_MGMT,+0xabbf},+/* channel A low I overwrite */+{LAN8814_PAGE_POWER_REGS,+LAN9645X_TX_LOW_I_CH_A_POWER_MGMT,+0xbd3f},+};++staticintlan9645x_config_init(structphy_device*phydev)+{+intret;++/* Apply erratas. */+ret=lan8842_erratas(phydev);+if(ret<0)+returnret;++returnlanphy_write_reg_data(phydev,force_dac_tx_errata,+ARRAY_SIZE(force_dac_tx_errata));+}++staticintlan9645x_suspend(structphy_device*phydev)+{+intaneg_en_state,ret;++/* Software workaround from design to handle SPD. SPD will stop AFE+*clockfromAFEport,whichmakesthesystemMACfifounabletoflush.+*TheworkaroundistorestartANEGandwaitforflush,beforeissuing+*softwarepowerdown.+*/+aneg_en_state=phy_read(phydev,MII_BMCR)&BMCR_ANENABLE;++ret=phy_restart_aneg(phydev);+if(ret)+returnret;++/* Allow time for system FIFO flush data */+usleep_range(8*USEC_PER_MSEC,12*USEC_PER_MSEC);++ret=genphy_suspend(phydev);+if(ret)+returnret;++if(!aneg_en_state)+returnphy_modify(phydev,MII_BMCR,BMCR_ANENABLE,0);++return0;+}++staticintlan9645x_config_intr(structphy_device*phydev)+{+interr;++/* enable / disable interrupts */+if(phydev->interrupts==PHY_INTERRUPT_ENABLED){+/* This is an internal PHY of lan9645x and is not possible to+*changethepolarityofirqsourcesintheOIC(CPU_INTR)+*foundinlan9645x.Thereforechangethepolarityofthe+*interruptinthePHYfrombeingactivelowinsteadofactive+*high.+*/+phy_write(phydev,LAN8804_CONTROL,+LAN8804_CONTROL_INTR_POLARITY);++/* By default interrupt buffer is open-drain in which case the+*interruptcanbeactiveonlylow.Thereforechangethe+*interruptbuffertobepush-pulltobeabletochange+*interruptpolarity.+*/+phy_write(phydev,LAN8804_OUTPUT_CONTROL,+LAN8804_OUTPUT_CONTROL_INTR_BUFFER);++err=lan8814_ack_interrupt(phydev);+if(err)+returnerr;++err=phy_write(phydev,LAN8814_INTC,+LAN8814_INT_LINK|LAN8814_INT_FLF);+}else{+err=phy_write(phydev,LAN8814_INTC,0);+if(err)+returnerr;++err=lan8814_ack_interrupt(phydev);+}++returnerr;+}++staticirqreturn_tlan9645x_handle_interrupt(structphy_device*phydev)+{+intstatus;++status=phy_read(phydev,LAN8814_INTS);+if(status<0){+phy_error(phydev);+returnIRQ_NONE;+}++if(status&(LAN8814_INT_LINK|LAN8814_INT_FLF))+phy_trigger_machine(phydev);++returnIRQ_HANDLED;+}+staticstructphy_driverksphy_driver[]={{PHY_ID_MATCH_MODEL(PHY_ID_KS8737),
From: Maxime Chevallier <maxime.chevallier@bootlin.com> Date: 2026-01-23 08:57:39
Hi Jens,
On 23/01/2026 08:50, Jens Emil Schulz Østergaard wrote:
LAN9645X is a family of switch chips with 5 internal copper phys. The
internal PHY is based on parts of LAN8832. This is a low-power, single
port triple-speed (10BASE-T/100BASE-TX/1000BASE-T) ethernet physical
layer transceiver (PHY) that supports transmission and reception of data
on standard CAT-5, as well as CAT-5e and CAT-6 Unshielded Twisted
Pair (UTP) cables.
Add support for the internal PHY of the lan9645x chip family.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Reviewed-by: Daniel Machon <daniel.machon@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <redacted>
---
[...]
+static int lan9645x_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ /* enable / disable interrupts */
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ /* This is an internal PHY of lan9645x and is not possible to
+ * change the polarity of irq sources in the OIC (CPU_INTR)
+ * found in lan9645x. Therefore change the polarity of the
+ * interrupt in the PHY from being active low instead of active
+ * high.
+ */
+ phy_write(phydev, LAN8804_CONTROL,
+ LAN8804_CONTROL_INTR_POLARITY);
+
+ /* By default interrupt buffer is open-drain in which case the
+ * interrupt can be active only low. Therefore change the
+ * interrupt buffer to be push-pull to be able to change
+ * interrupt polarity.
+ */
+ phy_write(phydev, LAN8804_OUTPUT_CONTROL,
+ LAN8804_OUTPUT_CONTROL_INTR_BUFFER);
Small nit from me, you're missing error checks on the 2 above reads.
Thanks,
Maxime
Hi Jens,
On Fri, Jan 23, 2026 at 08:50:55AM +0100, Jens Emil Schulz Østergaard wrote:
LAN9645X is a family of switch chips with 5 internal copper phys. The
internal PHY is based on parts of LAN8832. This is a low-power, single
port triple-speed (10BASE-T/100BASE-TX/1000BASE-T) ethernet physical
layer transceiver (PHY) that supports transmission and reception of data
on standard CAT-5, as well as CAT-5e and CAT-6 Unshielded Twisted
Pair (UTP) cables.
Add support for the internal PHY of the lan9645x chip family.
Looks like interesting switch for our use cases :)
+
+static int lan9645x_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Apply erratas. */
+ ret = lan8842_erratas(phydev);
+ if (ret < 0)
+ return ret;
+
+ return lanphy_write_reg_data(phydev, force_dac_tx_errata,
+ ARRAY_SIZE(force_dac_tx_errata));
+}
+
+static int lan9645x_suspend(struct phy_device *phydev)
+{
+ int aneg_en_state, ret;
+
+ /* Software workaround from design to handle SPD. SPD will stop AFE
+ * clock from AFE port, which makes the system MAC fifo unable to flush.
+ * The workaround is to restart ANEG and wait for flush, before issuing
+ * software power down.
+ */
+ aneg_en_state = phy_read(phydev, MII_BMCR) & BMCR_ANENABLE;
+
+ ret = phy_restart_aneg(phydev);
+ if (ret)
+ return ret;
+
+ /* Allow time for system FIFO flush data */
+ usleep_range(8 * USEC_PER_MSEC, 12 * USEC_PER_MSEC);
MAC and PHY power management are not always fully coupled (implementation
specific), are there other ways to sync them with each other, except of
unconditional sleep in the PHY driver. I expect that someone making
changes on the MAC driver may miss this nuance.
Best Regards,
Oleksij
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
From: Jens Emil Schulz Ostergaard <hidden> Date: 2026-01-23 13:41:24
Hi Maxime,
On Fri, 2026-01-23 at 09:57 +0100, Maxime Chevallier wrote:
Hi Jens,
On 23/01/2026 08:50, Jens Emil Schulz Østergaard wrote:
quoted
LAN9645X is a family of switch chips with 5 internal copper phys.
The
internal PHY is based on parts of LAN8832. This is a low-power,
single
port triple-speed (10BASE-T/100BASE-TX/1000BASE-T) ethernet
physical
layer transceiver (PHY) that supports transmission and reception of
data
on standard CAT-5, as well as CAT-5e and CAT-6 Unshielded Twisted
Pair (UTP) cables.
Add support for the internal PHY of the lan9645x chip family.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Reviewed-by: Daniel Machon <daniel.machon@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard
[off-list ref]
---
[...]
quoted
+static int lan9645x_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ /* enable / disable interrupts */
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ /* This is an internal PHY of lan9645x and is not
possible to
+ * change the polarity of irq sources in the OIC
(CPU_INTR)
+ * found in lan9645x. Therefore change the polarity
of the
+ * interrupt in the PHY from being active low instead
of active
+ * high.
+ */
+ phy_write(phydev, LAN8804_CONTROL,
+ LAN8804_CONTROL_INTR_POLARITY);
+
+ /* By default interrupt buffer is open-drain in which
case the
+ * interrupt can be active only low. Therefore change
the
+ * interrupt buffer to be push-pull to be able to
change
+ * interrupt polarity.
+ */
+ phy_write(phydev, LAN8804_OUTPUT_CONTROL,
+ LAN8804_OUTPUT_CONTROL_INTR_BUFFER);
Small nit from me, you're missing error checks on the 2 above reads.
Thanks,
Maxime
Thank you, I will add error checks in the next version.
Thanks,
Emil
From: Jens Emil Schulz Ostergaard <hidden> Date: 2026-01-23 15:12:31
Hi Oleksij,
On Fri, 2026-01-23 at 10:55 +0100, Oleksij Rempel wrote:
Hi Jens,
On Fri, Jan 23, 2026 at 08:50:55AM +0100, Jens Emil Schulz Østergaard wrote:
quoted
LAN9645X is a family of switch chips with 5 internal copper phys. The
internal PHY is based on parts of LAN8832. This is a low-power, single
port triple-speed (10BASE-T/100BASE-TX/1000BASE-T) ethernet physical
layer transceiver (PHY) that supports transmission and reception of data
on standard CAT-5, as well as CAT-5e and CAT-6 Unshielded Twisted
Pair (UTP) cables.
Add support for the internal PHY of the lan9645x chip family.
Looks like interesting switch for our use cases :)
You are right, I think they ought to be documented in the official erratas
list with a more thorough description. I will get a hold of the right
people and ask them to add it.
quoted
+
+static int lan9645x_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ /* Apply erratas. */
+ ret = lan8842_erratas(phydev);
+ if (ret < 0)
+ return ret;
+
+ return lanphy_write_reg_data(phydev, force_dac_tx_errata,
+ ARRAY_SIZE(force_dac_tx_errata));
+}
+
+static int lan9645x_suspend(struct phy_device *phydev)
+{
+ int aneg_en_state, ret;
+
+ /* Software workaround from design to handle SPD. SPD will stop AFE
+ * clock from AFE port, which makes the system MAC fifo unable to flush.
+ * The workaround is to restart ANEG and wait for flush, before issuing
+ * software power down.
+ */
+ aneg_en_state = phy_read(phydev, MII_BMCR) & BMCR_ANENABLE;
+
+ ret = phy_restart_aneg(phydev);
+ if (ret)
+ return ret;
+
+ /* Allow time for system FIFO flush data */
+ usleep_range(8 * USEC_PER_MSEC, 12 * USEC_PER_MSEC);
MAC and PHY power management are not always fully coupled (implementation
specific), are there other ways to sync them with each other, except of
unconditional sleep in the PHY driver. I expect that someone making
changes on the MAC driver may miss this nuance.
The MAC side will be handled in a yet-to-be-sent DSA driver, which is also
authored by my team.
During development I was seeing issues where SPD sometimes causing
interference on neighbouring PHYs. The PHY team came up with the tx dac
errata and the aneg restart workaround as a solution.
To my knowledge there was no alternative workaround, but I have reached out
to the PHY team to make sure. If there is a better way to fix these issues,
I will add it in the next version.
Best Regards,
Oleksij
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
From: Andrew Lunn <andrew@lunn.ch> Date: 2026-01-23 16:11:47
+static int lan9645x_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ /* enable / disable interrupts */
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ /* This is an internal PHY of lan9645x and is not possible to
+ * change the polarity of irq sources in the OIC (CPU_INTR)
+ * found in lan9645x. Therefore change the polarity of the
+ * interrupt in the PHY from being active low instead of active
+ * high.
+ */
+ phy_write(phydev, LAN8804_CONTROL,
+ LAN8804_CONTROL_INTR_POLARITY);
+
+ /* By default interrupt buffer is open-drain in which case the
+ * interrupt can be active only low. Therefore change the
+ * interrupt buffer to be push-pull to be able to change
+ * interrupt polarity.
+ */
It is not stated here, but i assume that there are multiple of these
PHYs in the switch, and each PHY has its own independent connection to
the parent interrupt controller? There is no interrupt sharing going
on, which is common with discreet PHYs.
Andrew
From: Jens Emil Schulz Ostergaard <hidden> Date: 2026-01-27 08:49:58
Hi Andrew,
On Fri, 2026-01-23 at 17:11 +0100, Andrew Lunn wrote:
quoted
+static int lan9645x_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ /* enable / disable interrupts */
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ /* This is an internal PHY of lan9645x and is not possible to
+ * change the polarity of irq sources in the OIC (CPU_INTR)
+ * found in lan9645x. Therefore change the polarity of the
+ * interrupt in the PHY from being active low instead of active
+ * high.
+ */
+ phy_write(phydev, LAN8804_CONTROL,
+ LAN8804_CONTROL_INTR_POLARITY);
+
+ /* By default interrupt buffer is open-drain in which case the
+ * interrupt can be active only low. Therefore change the
+ * interrupt buffer to be push-pull to be able to change
+ * interrupt polarity.
+ */
It is not stated here, but i assume that there are multiple of these
PHYs in the switch, and each PHY has its own independent connection to
the parent interrupt controller? There is no interrupt sharing going
on, which is common with discreet PHYs.
Andrew
Yes you are right, there are 5 of these PHYs in the chip, each with their
on connection to the interrupt controller. It is similar to lan966x,
which has two internal PHYs of an older generation.
Thanks,
Emil