]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net: phy: air_en8811h: add Airoha AN8811HB support
authorBjørn Mork <bjorn@mork.no>
Tue, 27 Jan 2026 12:55:46 +0000 (13:55 +0100)
committerJakub Kicinski <kuba@kernel.org>
Fri, 30 Jan 2026 03:15:46 +0000 (19:15 -0800)
The Airoha AN8811HB is mostly compatible with the EN8811H, adding 10Base-T
support and reducing power consumption.

This driver is based on the air_an8811hb v0.0.4 out-of-tree driver
written by "Lucien.Jheng <lucien.jheng@airoha.com>"

Firmware is available in linux-firmware. The driver has been tested with
firmware version 25110702

Signed-off-by: Bjørn Mork <bjorn@mork.no>
Link: https://patch.msgid.link/20260127125547.1475164-3-bjorn@mork.no
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/phy/air_en8811h.c

index 39255269276297d8788f01d2ee08c8f12f4ee1c4..67a1bf60255f535457dfdc63e3804ba6de766e12 100644 (file)
@@ -1,14 +1,15 @@
 // SPDX-License-Identifier: GPL-2.0+
 /*
- * Driver for the Airoha EN8811H 2.5 Gigabit PHY.
+ * Driver for the Airoha EN8811H and AN8811HB 2.5 Gigabit PHYs.
  *
- * Limitations of the EN8811H:
+ * Limitations:
  * - Only full duplex supported
  * - Forced speed (AN off) is not supported by hardware (100Mbps)
  *
  * Source originated from airoha's en8811h.c and en8811h.h v1.2.1
+ * with AN8811HB bits from air_an8811hb.c v0.0.4
  *
- * Copyright (C) 2023 Airoha Technology Corp.
+ * Copyright (C) 2023, 2026 Airoha Technology Corp.
  */
 
 #include <linux/clk.h>
 #include <linux/unaligned.h>
 
 #define EN8811H_PHY_ID         0x03a2a411
+#define AN8811HB_PHY_ID                0xc0ff04a0
 
 #define EN8811H_MD32_DM                "airoha/EthMD32.dm.bin"
 #define EN8811H_MD32_DSP       "airoha/EthMD32.DSP.bin"
+#define AN8811HB_MD32_DM       "airoha/an8811hb/EthMD32_CRC.DM.bin"
+#define AN8811HB_MD32_DSP      "airoha/an8811hb/EthMD32_CRC.DSP.bin"
 
 #define AIR_FW_ADDR_DM 0x00000000
 #define AIR_FW_ADDR_DSP        0x00100000
@@ -31,6 +35,7 @@
 /* MII Registers */
 #define AIR_AUX_CTRL_STATUS            0x1d
 #define   AIR_AUX_CTRL_STATUS_SPEED_MASK       GENMASK(4, 2)
+#define   AIR_AUX_CTRL_STATUS_SPEED_10         0x0
 #define   AIR_AUX_CTRL_STATUS_SPEED_100                0x4
 #define   AIR_AUX_CTRL_STATUS_SPEED_1000       0x8
 #define   AIR_AUX_CTRL_STATUS_SPEED_2500       0xc
@@ -56,6 +61,7 @@
 #define EN8811H_PHY_FW_STATUS          0x8009
 #define   EN8811H_PHY_READY                    0x02
 
+#define AIR_PHY_MCU_CMD_0              0x800b
 #define AIR_PHY_MCU_CMD_1              0x800c
 #define AIR_PHY_MCU_CMD_1_MODE1                        0x0
 #define AIR_PHY_MCU_CMD_2              0x800d
 #define AIR_PHY_MCU_CMD_3_DOCMD                        0x1100
 #define AIR_PHY_MCU_CMD_4              0x800f
 #define AIR_PHY_MCU_CMD_4_MODE1                        0x0002
+#define AIR_PHY_MCU_CMD_4_CABLE_PAIR_A         0x00d7
+#define AIR_PHY_MCU_CMD_4_CABLE_PAIR_B         0x00d8
+#define AIR_PHY_MCU_CMD_4_CABLE_PAIR_C         0x00d9
+#define AIR_PHY_MCU_CMD_4_CABLE_PAIR_D         0x00da
 #define AIR_PHY_MCU_CMD_4_INTCLR               0x00e4
 
 /* Registers on MDIO_MMD_VEND2 */
 #define   AIR_PHY_LED_BLINK_2500RX             BIT(11)
 
 /* Registers on BUCKPBUS */
+#define AIR_PHY_CONTROL                        0x3a9c
+#define   AIR_PHY_CONTROL_INTERNAL             BIT(11)
+
 #define EN8811H_2P5G_LPA               0x3b30
 #define   EN8811H_2P5G_LPA_2P5G                        BIT(0)
 
 #define EN8811H_FW_CTRL_2              0x800000
 #define EN8811H_FW_CTRL_2_LOADING              BIT(11)
 
+#define AN8811HB_CRC_PM_SET1           0xf020c
+#define AN8811HB_CRC_PM_MON2           0xf0218
+#define AN8811HB_CRC_PM_MON3           0xf021c
+#define AN8811HB_CRC_DM_SET1           0xf0224
+#define AN8811HB_CRC_DM_MON2           0xf0230
+#define AN8811HB_CRC_DM_MON3           0xf0234
+#define   AN8811HB_CRC_RD_EN                   BIT(0)
+#define   AN8811HB_CRC_ST                      (BIT(0) | BIT(1))
+#define   AN8811HB_CRC_CHECK_PASS              BIT(0)
+
+#define AN8811HB_TX_POLARITY           0x5ce004
+#define   AN8811HB_TX_POLARITY_NORMAL          BIT(7)
+#define AN8811HB_RX_POLARITY           0x5ce61c
+#define   AN8811HB_RX_POLARITY_NORMAL          BIT(7)
+
+#define AN8811HB_GPIO_OUTPUT           0x5cf8b8
+#define   AN8811HB_GPIO_OUTPUT_345             (BIT(3) | BIT(4) | BIT(5))
+
+#define AN8811HB_HWTRAP1               0x5cf910
+#define AN8811HB_HWTRAP2               0x5cf914
+#define   AN8811HB_HWTRAP2_CKO                 BIT(28)
+
+#define AN8811HB_CLK_DRV               0x5cf9e4
+#define AN8811HB_CLK_DRV_CKO_MASK              GENMASK(14, 12)
+#define   AN8811HB_CLK_DRV_CKOPWD              BIT(12)
+#define   AN8811HB_CLK_DRV_CKO_LDPWD           BIT(13)
+#define   AN8811HB_CLK_DRV_CKO_LPPWD           BIT(14)
+
 /* Led definitions */
 #define EN8811H_LED_COUNT      3
 
@@ -466,6 +507,43 @@ static int en8811h_wait_mcu_ready(struct phy_device *phydev)
        return 0;
 }
 
+static int an8811hb_check_crc(struct phy_device *phydev, u32 set1,
+                             u32 mon2, u32 mon3)
+{
+       u32 pbus_value;
+       int retry = 25;
+       int ret;
+
+       /* Configure CRC */
+       ret = air_buckpbus_reg_modify(phydev, set1,
+                                     AN8811HB_CRC_RD_EN,
+                                     AN8811HB_CRC_RD_EN);
+       if (ret < 0)
+               return ret;
+       air_buckpbus_reg_read(phydev, set1, &pbus_value);
+
+       do {
+               msleep(300);
+               air_buckpbus_reg_read(phydev, mon2, &pbus_value);
+
+               /* We do not know what errors this check is supposed
+                * catch or what to do about a failure. So print the
+                * result and continue like the vendor driver does.
+                */
+               if (pbus_value & AN8811HB_CRC_ST) {
+                       air_buckpbus_reg_read(phydev, mon3, &pbus_value);
+                       phydev_dbg(phydev, "CRC Check %s!\n",
+                                  pbus_value & AN8811HB_CRC_CHECK_PASS ?
+                                       "PASS" : "FAIL");
+                       return air_buckpbus_reg_modify(phydev, set1,
+                                                      AN8811HB_CRC_RD_EN, 0);
+               }
+       } while (--retry);
+
+       phydev_err(phydev, "CRC Check is not ready (%u)\n", pbus_value);
+       return -ENODEV;
+}
+
 static void en8811h_print_fw_version(struct phy_device *phydev)
 {
        struct en8811h_priv *priv = phydev->priv;
@@ -476,6 +554,54 @@ static void en8811h_print_fw_version(struct phy_device *phydev)
                    priv->firmware_version);
 }
 
+static int an8811hb_load_file(struct phy_device *phydev, const char *name,
+                             u32 address)
+{
+       struct device *dev = &phydev->mdio.dev;
+       const struct firmware *fw;
+       int ret;
+
+       ret = request_firmware_direct(&fw, name, dev);
+       if (ret < 0)
+               return ret;
+
+       ret = air_write_buf(phydev, address,  fw);
+       release_firmware(fw);
+       return ret;
+}
+
+static int an8811hb_load_firmware(struct phy_device *phydev)
+{
+       int ret;
+
+       ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
+                                    EN8811H_FW_CTRL_1_START);
+       if (ret < 0)
+               return ret;
+
+       ret = an8811hb_load_file(phydev, AN8811HB_MD32_DM, AIR_FW_ADDR_DM);
+       if (ret < 0)
+               return ret;
+
+       ret = an8811hb_check_crc(phydev, AN8811HB_CRC_DM_SET1,
+                                AN8811HB_CRC_DM_MON2,
+                                AN8811HB_CRC_DM_MON3);
+       if (ret < 0)
+               return ret;
+
+       ret = an8811hb_load_file(phydev, AN8811HB_MD32_DSP, AIR_FW_ADDR_DSP);
+       if (ret < 0)
+               return ret;
+
+       ret = an8811hb_check_crc(phydev, AN8811HB_CRC_PM_SET1,
+                                AN8811HB_CRC_PM_MON2,
+                                AN8811HB_CRC_PM_MON3);
+       if (ret < 0)
+               return ret;
+
+       return en8811h_wait_mcu_ready(phydev);
+}
+
 static int en8811h_load_firmware(struct phy_device *phydev)
 {
        struct device *dev = &phydev->mdio.dev;
@@ -939,6 +1065,45 @@ static int en8811h_leds_setup(struct phy_device *phydev)
        return ret;
 }
 
+static int an8811hb_probe(struct phy_device *phydev)
+{
+       struct en8811h_priv *priv;
+       int ret;
+
+       priv = devm_kzalloc(&phydev->mdio.dev, sizeof(struct en8811h_priv),
+                           GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+       phydev->priv = priv;
+
+       ret = an8811hb_load_firmware(phydev);
+       if (ret < 0) {
+               phydev_err(phydev, "Load firmware failed: %d\n", ret);
+               return ret;
+       }
+
+       en8811h_print_fw_version(phydev);
+
+       /* mcu has just restarted after firmware load */
+       priv->mcu_needs_restart = false;
+
+       /* MDIO_DEVS1/2 empty, so set mmds_present bits here */
+       phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
+
+       ret = en8811h_leds_setup(phydev);
+       if (ret < 0)
+               return ret;
+
+       /* Configure led gpio pins as output */
+       ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT,
+                                     AN8811HB_GPIO_OUTPUT_345,
+                                     AN8811HB_GPIO_OUTPUT_345);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
 static int en8811h_probe(struct phy_device *phydev)
 {
        struct en8811h_priv *priv;
@@ -980,6 +1145,37 @@ static int en8811h_probe(struct phy_device *phydev)
        return 0;
 }
 
+static int an8811hb_config_serdes_polarity(struct phy_device *phydev)
+{
+       struct device *dev = &phydev->mdio.dev;
+       u32 pbus_value = 0;
+       unsigned int pol;
+       int ret;
+
+       ret = phy_get_manual_rx_polarity(dev_fwnode(dev),
+                                        phy_modes(phydev->interface), &pol);
+       if (ret)
+               return ret;
+       if (pol == PHY_POL_NORMAL)
+               pbus_value |= AN8811HB_RX_POLARITY_NORMAL;
+       ret = air_buckpbus_reg_modify(phydev, AN8811HB_RX_POLARITY,
+                                     AN8811HB_RX_POLARITY_NORMAL,
+                                     pbus_value);
+       if (ret < 0)
+               return ret;
+
+       ret = phy_get_manual_tx_polarity(dev_fwnode(dev),
+                                        phy_modes(phydev->interface), &pol);
+       if (ret)
+               return ret;
+       pbus_value = 0;
+       if (pol == PHY_POL_NORMAL)
+               pbus_value |= AN8811HB_TX_POLARITY_NORMAL;
+       return air_buckpbus_reg_modify(phydev, AN8811HB_TX_POLARITY,
+                                      AN8811HB_TX_POLARITY_NORMAL,
+                                      pbus_value);
+}
+
 static int en8811h_config_serdes_polarity(struct phy_device *phydev)
 {
        struct device *dev = &phydev->mdio.dev;
@@ -1016,6 +1212,33 @@ static int en8811h_config_serdes_polarity(struct phy_device *phydev)
                                       EN8811H_POLARITY_TX_NORMAL, pbus_value);
 }
 
+static int an8811hb_config_init(struct phy_device *phydev)
+{
+       struct en8811h_priv *priv = phydev->priv;
+       int ret;
+
+       /* If restart happened in .probe(), no need to restart now */
+       if (priv->mcu_needs_restart) {
+               ret = en8811h_restart_mcu(phydev);
+               if (ret < 0)
+                       return ret;
+       } else {
+               /* Next calls to .config_init() mcu needs to restart */
+               priv->mcu_needs_restart = true;
+       }
+
+       ret = an8811hb_config_serdes_polarity(phydev);
+       if (ret < 0)
+               return ret;
+
+       ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR,
+                           AIR_LED_MODE_USER_DEFINE);
+       if (ret < 0)
+               phydev_err(phydev, "Failed to initialize leds: %d\n", ret);
+
+       return ret;
+}
+
 static int en8811h_config_init(struct phy_device *phydev)
 {
        struct en8811h_priv *priv = phydev->priv;
@@ -1129,13 +1352,23 @@ static int en8811h_read_status(struct phy_device *phydev)
        if (ret < 0)
                return ret;
 
-       /* Get link partner 2.5GBASE-T ability from vendor register */
-       ret = air_buckpbus_reg_read(phydev, EN8811H_2P5G_LPA, &pbus_value);
-       if (ret < 0)
-               return ret;
-       linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
-                        phydev->lp_advertising,
-                        pbus_value & EN8811H_2P5G_LPA_2P5G);
+       if (phy_id_compare_model(phydev->phy_id, AN8811HB_PHY_ID)) {
+               val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+               if (val < 0)
+                       return val;
+               linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+                                phydev->lp_advertising,
+                                val & MDIO_AN_10GBT_STAT_LP2_5G);
+       } else {
+               /* Get link partner 2.5GBASE-T ability from vendor register */
+               ret = air_buckpbus_reg_read(phydev, EN8811H_2P5G_LPA,
+                                           &pbus_value);
+               if (ret < 0)
+                       return ret;
+               linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+                                phydev->lp_advertising,
+                                pbus_value & EN8811H_2P5G_LPA_2P5G);
+       }
 
        if (phydev->autoneg_complete)
                phy_resolve_aneg_pause(phydev);
@@ -1157,6 +1390,9 @@ static int en8811h_read_status(struct phy_device *phydev)
        case AIR_AUX_CTRL_STATUS_SPEED_100:
                phydev->speed = SPEED_100;
                break;
+       case AIR_AUX_CTRL_STATUS_SPEED_10:
+               phydev->speed = SPEED_10;
+               break;
        }
 
        /* Firmware before version 24011202 has no vendor register 2P5G_LPA.
@@ -1241,20 +1477,42 @@ static struct phy_driver en8811h_driver[] = {
        .led_brightness_set     = air_led_brightness_set,
        .led_hw_control_set     = air_led_hw_control_set,
        .led_hw_control_get     = air_led_hw_control_get,
+},
+{
+       PHY_ID_MATCH_MODEL(AN8811HB_PHY_ID),
+       .name                   = "Airoha AN8811HB",
+       .probe                  = an8811hb_probe,
+       .get_features           = en8811h_get_features,
+       .config_init            = an8811hb_config_init,
+       .get_rate_matching      = en8811h_get_rate_matching,
+       .config_aneg            = en8811h_config_aneg,
+       .read_status            = en8811h_read_status,
+       .config_intr            = en8811h_clear_intr,
+       .handle_interrupt       = en8811h_handle_interrupt,
+       .led_hw_is_supported    = en8811h_led_hw_is_supported,
+       .read_page              = air_phy_read_page,
+       .write_page             = air_phy_write_page,
+       .led_blink_set          = air_led_blink_set,
+       .led_brightness_set     = air_led_brightness_set,
+       .led_hw_control_set     = air_led_hw_control_set,
+       .led_hw_control_get     = air_led_hw_control_get,
 } };
 
 module_phy_driver(en8811h_driver);
 
 static const struct mdio_device_id __maybe_unused en8811h_tbl[] = {
        { PHY_ID_MATCH_MODEL(EN8811H_PHY_ID) },
+       { PHY_ID_MATCH_MODEL(AN8811HB_PHY_ID) },
        { }
 };
 
 MODULE_DEVICE_TABLE(mdio, en8811h_tbl);
 MODULE_FIRMWARE(EN8811H_MD32_DM);
 MODULE_FIRMWARE(EN8811H_MD32_DSP);
+MODULE_FIRMWARE(AN8811HB_MD32_DM);
+MODULE_FIRMWARE(AN8811HB_MD32_DSP);
 
-MODULE_DESCRIPTION("Airoha EN8811H PHY drivers");
+MODULE_DESCRIPTION("Airoha EN8811H and AN8811HB PHY drivers");
 MODULE_AUTHOR("Airoha");
 MODULE_AUTHOR("Eric Woudstra <ericwouds@gmail.com>");
 MODULE_LICENSE("GPL");