]> www.infradead.org Git - users/griffoul/linux.git/commitdiff
net: phy: micrel: Add support for lan8842
authorHoratiu Vultur <horatiu.vultur@microchip.com>
Mon, 18 Aug 2025 07:51:21 +0000 (09:51 +0200)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 21 Aug 2025 08:42:31 +0000 (10:42 +0200)
The LAN8842 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.

The LAN8842 supports industry-standard SGMII (Serial Gigabit Media
Independent Interface) providing chip-to-chip connection to a Gigabit
Ethernet MAC using a single serialized link (differential pair) in each
direction.

There are 2 variants of the lan8842. The one that supports timestamping
(lan8842) and one that doesn't have timestamping (lan8832).

Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/20250818075121.1298170-5-horatiu.vultur@microchip.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
drivers/net/phy/micrel.c
include/linux/micrel_phy.h

index c621ed465d2e50067f4dfb6c2da1593c3ce6fe3b..04bd744920b0d44fd018a8fb4ce4bb2857de7def 100644 (file)
@@ -448,6 +448,17 @@ struct kszphy_priv {
        struct kszphy_phy_stats phy_stats;
 };
 
+struct lan8842_phy_stats {
+       u64 rx_packets;
+       u64 rx_errors;
+       u64 tx_packets;
+       u64 tx_errors;
+};
+
+struct lan8842_priv {
+       struct lan8842_phy_stats phy_stats;
+};
+
 static const struct kszphy_type lan8814_type = {
        .led_mode_reg           = ~LAN8814_LED_CTRL_1,
        .cable_diag_reg         = LAN8814_CABLE_DIAG,
@@ -5768,6 +5779,188 @@ static int ksz9131_resume(struct phy_device *phydev)
        return kszphy_resume(phydev);
 }
 
+#define LAN8842_SELF_TEST                      14 /* 0x0e */
+#define LAN8842_SELF_TEST_RX_CNT_ENA           BIT(8)
+#define LAN8842_SELF_TEST_TX_CNT_ENA           BIT(4)
+
+static int lan8842_probe(struct phy_device *phydev)
+{
+       struct lan8842_priv *priv;
+       int ret;
+
+       priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       phydev->priv = priv;
+
+       /* Similar to lan8814 this PHY has a pin which needs to be pulled down
+        * to enable to pass any traffic through it. Therefore use the same
+        * function as lan8814
+        */
+       ret = lan8814_release_coma_mode(phydev);
+       if (ret)
+               return ret;
+
+       /* Enable to count the RX and TX packets */
+       ret = lanphy_write_page_reg(phydev, LAN8814_PAGE_PCS_DIGITAL,
+                                   LAN8842_SELF_TEST,
+                                   LAN8842_SELF_TEST_RX_CNT_ENA |
+                                   LAN8842_SELF_TEST_TX_CNT_ENA);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static int lan8842_config_init(struct phy_device *phydev)
+{
+       int ret;
+
+       /* Reset the PHY */
+       ret = lanphy_modify_page_reg(phydev, LAN8814_PAGE_COMMON_REGS,
+                                    LAN8814_QSGMII_SOFT_RESET,
+                                    LAN8814_QSGMII_SOFT_RESET_BIT,
+                                    LAN8814_QSGMII_SOFT_RESET_BIT);
+       if (ret < 0)
+               return ret;
+
+       /* To allow the PHY to control the LEDs the GPIOs of the PHY should have
+        * a function mode and not the GPIO. Apparently by default the value is
+        * GPIO and not function even though the datasheet it says that it is
+        * function. Therefore set this value.
+        */
+       return lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS,
+                                    LAN8814_GPIO_EN2, 0);
+}
+
+#define LAN8842_INTR_CTRL_REG                  52 /* 0x34 */
+
+static int lan8842_config_intr(struct phy_device *phydev)
+{
+       int err;
+
+       lanphy_write_page_reg(phydev, LAN8814_PAGE_COMMON_REGS,
+                             LAN8842_INTR_CTRL_REG,
+                             LAN8814_INTR_CTRL_REG_INTR_ENABLE);
+
+       /* enable / disable interrupts */
+       if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+               err = lan8814_ack_interrupt(phydev);
+               if (err)
+                       return err;
+
+               err = phy_write(phydev, LAN8814_INTC, LAN8814_INT_LINK);
+       } else {
+               err = phy_write(phydev, LAN8814_INTC, 0);
+               if (err)
+                       return err;
+
+               err = lan8814_ack_interrupt(phydev);
+       }
+
+       return err;
+}
+
+static unsigned int lan8842_inband_caps(struct phy_device *phydev,
+                                       phy_interface_t interface)
+{
+       /* Inband configuration can be enabled or disabled using the registers
+        * PCS1G_ANEG_CONFIG.
+        */
+       return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+}
+
+static int lan8842_config_inband(struct phy_device *phydev, unsigned int modes)
+{
+       bool enable;
+
+       if (modes == LINK_INBAND_DISABLE)
+               enable = false;
+       else
+               enable = true;
+
+       /* Disable or enable in-band autoneg with PCS Host side
+        * It has the same address as lan8814
+        */
+       return lanphy_modify_page_reg(phydev, LAN8814_PAGE_PORT_REGS,
+                                     LAN8814_QSGMII_PCS1G_ANEG_CONFIG,
+                                     LAN8814_QSGMII_PCS1G_ANEG_CONFIG_ANEG_ENA,
+                                     enable ? LAN8814_QSGMII_PCS1G_ANEG_CONFIG_ANEG_ENA : 0);
+}
+
+static irqreturn_t lan8842_handle_interrupt(struct phy_device *phydev)
+{
+       int ret = IRQ_NONE;
+       int irq_status;
+
+       irq_status = phy_read(phydev, LAN8814_INTS);
+       if (irq_status < 0) {
+               phy_error(phydev);
+               return IRQ_NONE;
+       }
+
+       if (irq_status & LAN8814_INT_LINK) {
+               phy_trigger_machine(phydev);
+               ret = IRQ_HANDLED;
+       }
+
+       return ret;
+}
+
+static u64 lan8842_get_stat(struct phy_device *phydev, int count, int *regs)
+{
+       u64 ret = 0;
+       int val;
+
+       for (int j = 0; j < count; ++j) {
+               val = lanphy_read_page_reg(phydev, LAN8814_PAGE_PCS_DIGITAL,
+                                          regs[j]);
+               if (val < 0)
+                       return U64_MAX;
+
+               ret <<= 16;
+               ret += val;
+       }
+       return ret;
+}
+
+static int lan8842_update_stats(struct phy_device *phydev)
+{
+       struct lan8842_priv *priv = phydev->priv;
+       int rx_packets_regs[] = {88, 61, 60};
+       int rx_errors_regs[] = {63, 62};
+       int tx_packets_regs[] = {89, 85, 84};
+       int tx_errors_regs[] = {87, 86};
+
+       priv->phy_stats.rx_packets = lan8842_get_stat(phydev,
+                                                     ARRAY_SIZE(rx_packets_regs),
+                                                     rx_packets_regs);
+       priv->phy_stats.rx_errors = lan8842_get_stat(phydev,
+                                                    ARRAY_SIZE(rx_errors_regs),
+                                                    rx_errors_regs);
+       priv->phy_stats.tx_packets = lan8842_get_stat(phydev,
+                                                     ARRAY_SIZE(tx_packets_regs),
+                                                     tx_packets_regs);
+       priv->phy_stats.tx_errors = lan8842_get_stat(phydev,
+                                                    ARRAY_SIZE(tx_errors_regs),
+                                                    tx_errors_regs);
+
+       return 0;
+}
+
+static void lan8842_get_phy_stats(struct phy_device *phydev,
+                                 struct ethtool_eth_phy_stats *eth_stats,
+                                 struct ethtool_phy_stats *stats)
+{
+       struct lan8842_priv *priv = phydev->priv;
+
+       stats->rx_packets = priv->phy_stats.rx_packets;
+       stats->rx_errors = priv->phy_stats.rx_errors;
+       stats->tx_packets = priv->phy_stats.tx_packets;
+       stats->tx_errors = priv->phy_stats.tx_errors;
+}
+
 static struct phy_driver ksphy_driver[] = {
 {
        PHY_ID_MATCH_MODEL(PHY_ID_KS8737),
@@ -5987,6 +6180,21 @@ static struct phy_driver ksphy_driver[] = {
        .resume         = lan8841_resume,
        .cable_test_start       = lan8814_cable_test_start,
        .cable_test_get_status  = ksz886x_cable_test_get_status,
+}, {
+       PHY_ID_MATCH_MODEL(PHY_ID_LAN8842),
+       .name           = "Microchip LAN8842 Gigabit PHY",
+       .flags          = PHY_POLL_CABLE_TEST,
+       .driver_data    = &lan8814_type,
+       .probe          = lan8842_probe,
+       .config_init    = lan8842_config_init,
+       .config_intr    = lan8842_config_intr,
+       .inband_caps    = lan8842_inband_caps,
+       .config_inband  = lan8842_config_inband,
+       .handle_interrupt = lan8842_handle_interrupt,
+       .get_phy_stats  = lan8842_get_phy_stats,
+       .update_stats   = lan8842_update_stats,
+       .cable_test_start       = lan8814_cable_test_start,
+       .cable_test_get_status  = ksz886x_cable_test_get_status,
 }, {
        PHY_ID_MATCH_MODEL(PHY_ID_KSZ9131),
        .name           = "Microchip KSZ9131 Gigabit PHY",
@@ -6082,6 +6290,7 @@ static const struct mdio_device_id __maybe_unused micrel_tbl[] = {
        { PHY_ID_MATCH_MODEL(PHY_ID_LAN8814) },
        { PHY_ID_MATCH_MODEL(PHY_ID_LAN8804) },
        { PHY_ID_MATCH_MODEL(PHY_ID_LAN8841) },
+       { PHY_ID_MATCH_MODEL(PHY_ID_LAN8842) },
        { }
 };
 
index 9af01bdd86d26d7009af1a805a9c99c0acc303f0..ca691641788b8976f72cc1307be9b5c4120b2815 100644 (file)
@@ -32,6 +32,7 @@
 #define PHY_ID_LAN8814         0x00221660
 #define PHY_ID_LAN8804         0x00221670
 #define PHY_ID_LAN8841         0x00221650
+#define PHY_ID_LAN8842         0x002216C0
 
 #define PHY_ID_KSZ886X         0x00221430
 #define PHY_ID_KSZ8863         0x00221435