[RFC PATCH net-next 4/9] net: phy: air: move the EN8811H firmware download into the library
From: Aleksei Sviridkin
Date: Sat Aug 29 2026 - 01:29:15 EST
The EN8811H firmware loader is welded to the PHY driver, but the next
patch adds an MDIO device driver that must perform the same download
before any phy_device exists.
Move the download engine into the shared library, typed on the bus
and address like the buckpbus core: the paged transfer, the FW_CTRL
sequencing, the MCU-ready wait and the version readout. The bus-level
helpers take the MDIO bus lock themselves and save and restore the
page register directly, since phy_select_page() needs a phy_device.
The MMD status poll goes through mmd_phy_read(), which already
handles both C22 indirection and C45.
The PHY driver keeps thin wrappers with its old behavior, including
the AN8811HB path, which retains its own CRC-checked loader and only
shares the paged buffer write.
No functional change.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@xxxxxx>
---
drivers/net/phy/air_en8811h.c | 143 +-----------------
drivers/net/phy/air_phy_lib.c | 268 ++++++++++++++++++++++++++++++++++
drivers/net/phy/air_phy_lib.h | 26 ++++
3 files changed, 302 insertions(+), 135 deletions(-)
diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c
index edd49c193e47..fdc64362a565 100644
--- a/drivers/net/phy/air_en8811h.c
+++ b/drivers/net/phy/air_en8811h.c
@@ -20,21 +20,15 @@
#include <linux/bitfield.h>
#include <linux/property.h>
#include <linux/wordpart.h>
-#include <linux/unaligned.h>
#include "air_phy_lib.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
-
/* MII Registers */
#define AIR_AUX_CTRL_STATUS 0x1d
#define AIR_AUX_CTRL_STATUS_SPEED_MASK GENMASK(4, 2)
@@ -44,8 +38,6 @@
#define AIR_AUX_CTRL_STATUS_SPEED_2500 0xc
/* Registers on MDIO_MMD_VEND1 */
-#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
@@ -108,8 +100,6 @@
#define EN8811H_2P5G_LPA 0x3b30
#define EN8811H_2P5G_LPA_2P5G BIT(0)
-#define EN8811H_FW_VERSION 0x3b3c
-
#define EN8811H_POLARITY 0xca0f8
#define EN8811H_POLARITY_TX_NORMAL BIT(0)
#define EN8811H_POLARITY_RX_REVERSE BIT(1)
@@ -122,12 +112,6 @@
#define EN8811H_CLK_CGM 0xcf958
#define EN8811H_CLK_CGM_CKO BIT(26)
-#define EN8811H_FW_CTRL_1 0x0f0018
-#define EN8811H_FW_CTRL_1_START 0x0
-#define EN8811H_FW_CTRL_1_FINISH 0x1
-#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
@@ -270,80 +254,10 @@ static int __air_pbus_reg_write(struct mdio_device *mdiodev,
upper_16_bits(pbus_data));
}
-static int __air_write_buf(struct phy_device *phydev, u32 address,
- const struct firmware *fw)
-{
- unsigned int offset;
- int ret;
- u16 val;
-
- ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_INCR);
- if (ret < 0)
- return ret;
-
- ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH,
- upper_16_bits(address));
- if (ret < 0)
- return ret;
-
- ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW,
- lower_16_bits(address));
- if (ret < 0)
- return ret;
-
- for (offset = 0; offset < fw->size; offset += 4) {
- val = get_unaligned_le16(&fw->data[offset + 2]);
- ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, val);
- if (ret < 0)
- return ret;
-
- val = get_unaligned_le16(&fw->data[offset]);
- ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, val);
- if (ret < 0)
- return ret;
- }
-
- return 0;
-}
-
-static int air_write_buf(struct phy_device *phydev, u32 address,
- const struct firmware *fw)
-{
- int saved_page;
- int ret = 0;
-
- saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
-
- if (saved_page >= 0) {
- ret = __air_write_buf(phydev, address, fw);
- if (ret < 0)
- phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__,
- address, ret);
- }
-
- return phy_restore_page(phydev, saved_page, ret);
-}
-
static int en8811h_wait_mcu_ready(struct phy_device *phydev)
{
- int ret, reg_value;
-
- ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
- EN8811H_FW_CTRL_1_FINISH);
- if (ret)
- return ret;
-
- /* Because of mdio-lock, may have to wait for multiple loads */
- ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
- EN8811H_PHY_FW_STATUS, reg_value,
- reg_value == EN8811H_PHY_READY,
- 20000, 7500000, true);
- if (ret) {
- phydev_err(phydev, "MCU not ready: 0x%x\n", reg_value);
- return -ENODEV;
- }
-
- return 0;
+ return air_en8811h_wait_mcu_ready(phydev->mdio.bus, phydev->mdio.addr,
+ phydev->is_c45, &phydev->mdio.dev);
}
static int an8811hb_check_crc(struct phy_device *phydev, u32 set1,
@@ -405,7 +319,8 @@ static int an8811hb_load_file(struct phy_device *phydev, const char *name,
if (ret < 0)
return ret;
- ret = air_write_buf(phydev, address, fw);
+ ret = air_fw_write_buf(phydev->mdio.bus, phydev->mdio.addr, address,
+ fw);
release_firmware(fw);
return ret;
}
@@ -501,54 +416,12 @@ static int an8811hb_load_firmware(struct phy_device *phydev)
static int en8811h_load_firmware(struct phy_device *phydev)
{
- struct device *dev = &phydev->mdio.dev;
- const struct firmware *fw1, *fw2;
+ struct en8811h_priv *priv = phydev->priv;
int ret;
- ret = request_firmware_direct(&fw1, EN8811H_MD32_DM, dev);
- if (ret < 0)
- return ret;
-
- ret = request_firmware_direct(&fw2, EN8811H_MD32_DSP, dev);
- if (ret < 0)
- goto en8811h_load_firmware_rel1;
-
- ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
- EN8811H_FW_CTRL_1_START);
- if (ret < 0)
- goto en8811h_load_firmware_out;
-
- ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
- EN8811H_FW_CTRL_2_LOADING,
- EN8811H_FW_CTRL_2_LOADING);
- if (ret < 0)
- goto en8811h_load_firmware_out;
-
- ret = air_write_buf(phydev, AIR_FW_ADDR_DM, fw1);
- if (ret < 0)
- goto en8811h_load_firmware_out;
-
- ret = air_write_buf(phydev, AIR_FW_ADDR_DSP, fw2);
- if (ret < 0)
- goto en8811h_load_firmware_out;
-
- ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
- EN8811H_FW_CTRL_2_LOADING, 0);
- if (ret < 0)
- goto en8811h_load_firmware_out;
-
- ret = en8811h_wait_mcu_ready(phydev);
- if (ret < 0)
- goto en8811h_load_firmware_out;
-
- en8811h_print_fw_version(phydev);
-
-en8811h_load_firmware_out:
- release_firmware(fw2);
-
-en8811h_load_firmware_rel1:
- release_firmware(fw1);
-
+ ret = air_en8811h_fw_download(phydev->mdio.bus, phydev->mdio.addr,
+ phydev->is_c45, &phydev->mdio.dev,
+ &priv->firmware_version);
if (ret < 0)
phydev_err(phydev, "Load firmware failed: %d\n", ret);
diff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c
index e0fca5f285d2..1ed5c69d7073 100644
--- a/drivers/net/phy/air_phy_lib.c
+++ b/drivers/net/phy/air_phy_lib.c
@@ -8,11 +8,16 @@
*/
#include <linux/export.h>
+#include <linux/firmware.h>
+#include <linux/iopoll.h>
+#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/phy.h>
+#include <linux/unaligned.h>
#include <linux/wordpart.h>
#include "air_phy_lib.h"
+#include "phylib.h"
static int __air_buckpbus_reg_read(struct mii_bus *bus, int addr,
u32 pbus_address, u32 *pbus_data)
@@ -201,6 +206,269 @@ int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,
}
EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_modify);
+static int __air_write_buf(struct mii_bus *bus, int addr, u32 address,
+ const struct firmware *fw)
+{
+ unsigned int offset;
+ int ret;
+ u16 val;
+
+ ret = __mdiobus_write(bus, addr, AIR_BPBUS_MODE,
+ AIR_BPBUS_MODE_ADDR_INCR);
+ if (ret < 0)
+ return ret;
+
+ ret = __mdiobus_write(bus, addr, AIR_BPBUS_WR_ADDR_HIGH,
+ upper_16_bits(address));
+ if (ret < 0)
+ return ret;
+
+ ret = __mdiobus_write(bus, addr, AIR_BPBUS_WR_ADDR_LOW,
+ lower_16_bits(address));
+ if (ret < 0)
+ return ret;
+
+ for (offset = 0; offset < fw->size; offset += 4) {
+ val = get_unaligned_le16(&fw->data[offset + 2]);
+ ret = __mdiobus_write(bus, addr, AIR_BPBUS_WR_DATA_HIGH, val);
+ if (ret < 0)
+ return ret;
+
+ val = get_unaligned_le16(&fw->data[offset]);
+ ret = __mdiobus_write(bus, addr, AIR_BPBUS_WR_DATA_LOW, val);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/* The phy_select_page() path is not usable here: these run before any
+ * phy_device exists. Callers hold the bus lock across select/op/restore.
+ */
+static int air_mdio_select_page(struct mii_bus *bus, int addr, int page)
+{
+ int saved_page, ret;
+
+ saved_page = __mdiobus_read(bus, addr, AIR_EXT_PAGE_ACCESS);
+ if (saved_page < 0)
+ return saved_page;
+
+ if (saved_page != page) {
+ ret = __mdiobus_write(bus, addr, AIR_EXT_PAGE_ACCESS, page);
+ if (ret < 0)
+ return ret;
+ }
+
+ return saved_page;
+}
+
+static int air_mdio_restore_page(struct mii_bus *bus, int addr,
+ int saved_page, int page, int ret)
+{
+ int restore;
+
+ if (saved_page != page) {
+ restore = __mdiobus_write(bus, addr, AIR_EXT_PAGE_ACCESS,
+ saved_page);
+ if (ret >= 0 && restore < 0)
+ ret = restore;
+ }
+
+ return ret;
+}
+
+int air_fw_write_buf(struct mii_bus *bus, int addr, u32 address,
+ const struct firmware *fw)
+{
+ int saved_page, ret;
+
+ mutex_lock(&bus->mdio_lock);
+
+ saved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);
+ if (saved_page < 0) {
+ ret = saved_page;
+ } else {
+ ret = __air_write_buf(bus, addr, address, fw);
+ ret = air_mdio_restore_page(bus, addr, saved_page,
+ AIR_PHY_PAGE_EXTENDED_4, ret);
+ }
+
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(air_fw_write_buf);
+
+static int air_mdio_buckpbus_reg_read(struct mii_bus *bus, int addr,
+ u32 pbus_address, u32 *pbus_data)
+{
+ int saved_page, ret;
+
+ mutex_lock(&bus->mdio_lock);
+
+ saved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);
+ if (saved_page < 0) {
+ ret = saved_page;
+ } else {
+ ret = __air_buckpbus_reg_read(bus, addr, pbus_address,
+ pbus_data);
+ ret = air_mdio_restore_page(bus, addr, saved_page,
+ AIR_PHY_PAGE_EXTENDED_4, ret);
+ }
+
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
+static int air_mdio_buckpbus_reg_write(struct mii_bus *bus, int addr,
+ u32 pbus_address, u32 pbus_data)
+{
+ int saved_page, ret;
+
+ mutex_lock(&bus->mdio_lock);
+
+ saved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);
+ if (saved_page < 0) {
+ ret = saved_page;
+ } else {
+ ret = __air_buckpbus_reg_write(bus, addr, pbus_address,
+ pbus_data);
+ ret = air_mdio_restore_page(bus, addr, saved_page,
+ AIR_PHY_PAGE_EXTENDED_4, ret);
+ }
+
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
+static int air_mdio_buckpbus_reg_modify(struct mii_bus *bus, int addr,
+ u32 pbus_address, u32 mask, u32 set)
+{
+ int saved_page, ret;
+
+ mutex_lock(&bus->mdio_lock);
+
+ saved_page = air_mdio_select_page(bus, addr, AIR_PHY_PAGE_EXTENDED_4);
+ if (saved_page < 0) {
+ ret = saved_page;
+ } else {
+ ret = __air_buckpbus_reg_modify(bus, addr, pbus_address,
+ mask, set);
+ ret = air_mdio_restore_page(bus, addr, saved_page,
+ AIR_PHY_PAGE_EXTENDED_4, ret);
+ }
+
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
+static int air_mmd_status_read(struct mii_bus *bus, int addr, bool is_c45)
+{
+ int ret;
+
+ mutex_lock(&bus->mdio_lock);
+ ret = mmd_phy_read(bus, addr, is_c45, MDIO_MMD_VEND1,
+ EN8811H_PHY_FW_STATUS);
+ mutex_unlock(&bus->mdio_lock);
+
+ return ret;
+}
+
+int air_en8811h_wait_mcu_ready(struct mii_bus *bus, int addr, bool is_c45,
+ struct device *dev)
+{
+ int ret, reg_value;
+
+ ret = air_mdio_buckpbus_reg_write(bus, addr, EN8811H_FW_CTRL_1,
+ EN8811H_FW_CTRL_1_FINISH);
+ if (ret)
+ return ret;
+
+ /* Because of mdio-lock, may have to wait for multiple loads. A read
+ * error ends the poll at once, like phy_read_mmd_poll_timeout()
+ * would: the bus is not going to heal within the timeout.
+ */
+ ret = read_poll_timeout(air_mmd_status_read, reg_value,
+ reg_value < 0 ||
+ reg_value == EN8811H_PHY_READY,
+ 20000, 7500000, true, bus, addr, is_c45);
+ if (reg_value < 0)
+ return reg_value;
+ if (ret) {
+ dev_err(dev, "MCU not ready: 0x%x\n", reg_value);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(air_en8811h_wait_mcu_ready);
+
+int air_en8811h_fw_download(struct mii_bus *bus, int addr, bool is_c45,
+ struct device *dev, u32 *fw_version)
+{
+ const struct firmware *fw1, *fw2;
+ int ret;
+
+ ret = request_firmware_direct(&fw1, EN8811H_MD32_DM, dev);
+ if (ret < 0)
+ return ret;
+
+ ret = request_firmware_direct(&fw2, EN8811H_MD32_DSP, dev);
+ if (ret < 0)
+ goto air_fw_download_rel1;
+
+ ret = air_mdio_buckpbus_reg_write(bus, addr, EN8811H_FW_CTRL_1,
+ EN8811H_FW_CTRL_1_START);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_mdio_buckpbus_reg_modify(bus, addr, EN8811H_FW_CTRL_2,
+ EN8811H_FW_CTRL_2_LOADING,
+ EN8811H_FW_CTRL_2_LOADING);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_fw_write_buf(bus, addr, AIR_FW_ADDR_DM, fw1);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_fw_write_buf(bus, addr, AIR_FW_ADDR_DSP, fw2);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_mdio_buckpbus_reg_modify(bus, addr, EN8811H_FW_CTRL_2,
+ EN8811H_FW_CTRL_2_LOADING, 0);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_en8811h_wait_mcu_ready(bus, addr, is_c45, dev);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ ret = air_mdio_buckpbus_reg_read(bus, addr, EN8811H_FW_VERSION,
+ fw_version);
+ if (ret < 0)
+ goto air_fw_download_out;
+
+ dev_info(dev, "MD32 firmware version: %08x\n", *fw_version);
+
+air_fw_download_out:
+ release_firmware(fw2);
+
+air_fw_download_rel1:
+ release_firmware(fw1);
+
+ /* No error print here: the callers retry or log on their own terms,
+ * and a poller retrying a half-installed firmware package would turn
+ * a print at this level into a permanent drumbeat.
+ */
+ return ret;
+}
+EXPORT_SYMBOL_GPL(air_en8811h_fw_download);
+
+MODULE_FIRMWARE(EN8811H_MD32_DM);
+MODULE_FIRMWARE(EN8811H_MD32_DSP);
+
int air_phy_read_page(struct phy_device *phydev)
{
return __phy_read(phydev, AIR_EXT_PAGE_ACCESS);
diff --git a/drivers/net/phy/air_phy_lib.h b/drivers/net/phy/air_phy_lib.h
index 01bb32e7c7c9..6b11dbeaea9b 100644
--- a/drivers/net/phy/air_phy_lib.h
+++ b/drivers/net/phy/air_phy_lib.h
@@ -29,6 +29,23 @@
#define AIR_BPBUS_RD_DATA_HIGH 0x17
#define AIR_BPBUS_RD_DATA_LOW 0x18
+#define EN8811H_MD32_DM "airoha/EthMD32.dm.bin"
+#define EN8811H_MD32_DSP "airoha/EthMD32.DSP.bin"
+
+#define AIR_FW_ADDR_DM 0x00000000
+#define AIR_FW_ADDR_DSP 0x00100000
+
+#define EN8811H_FW_CTRL_1 0x0f0018
+#define EN8811H_FW_CTRL_1_START 0x0
+#define EN8811H_FW_CTRL_1_FINISH 0x1
+#define EN8811H_FW_CTRL_2 0x800000
+#define EN8811H_FW_CTRL_2_LOADING BIT(11)
+
+#define EN8811H_PHY_FW_STATUS 0x8009
+#define EN8811H_PHY_READY 0x02
+
+#define EN8811H_FW_VERSION 0x3b3c
+
int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,
u32 mask, u32 set);
int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address,
@@ -38,4 +55,13 @@ int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address,
int air_phy_read_page(struct phy_device *phydev);
int air_phy_write_page(struct phy_device *phydev, int page);
+struct firmware;
+
+int air_fw_write_buf(struct mii_bus *bus, int addr, u32 address,
+ const struct firmware *fw);
+int air_en8811h_wait_mcu_ready(struct mii_bus *bus, int addr, bool is_c45,
+ struct device *dev);
+int air_en8811h_fw_download(struct mii_bus *bus, int addr, bool is_c45,
+ struct device *dev, u32 *fw_version);
+
#endif /* __AIR_PHY_LIB_H */
--
2.53.0