[U-Boot, v1, 1/1] net: phy: Add the Airoha EN8811H PHY driver
Marek Vasut
marek.vasut at mailbox.org
Thu Jun 12 17:40:48 CEST 2025
On 5/11/25 7:14 AM, Lucien.Jheng wrote:
> Add the driver for the Airoha EN8811H 2.5 Gigabit PHY. The PHY supports
> 100/1000/2500 Mbps with auto negotiation only.
>
> The driver uses two firmware files, for which updated versions are added to
> linux-firmware already.
>
> Locating the AIROHA FW within the filesystem at the designated partition
> and path will trigger its automatic loading and writing to the PHY via MDIO.
> If need board specific loading override,
> please override the en8811h_read_fw function on board or architecture level.
>
> Linux upstream AIROHA EN8811H driver commit:
> 71e79430117d56c409c5ea485a263bc0d8083390
>
> Based on the Linux upstream AIROHA EN8811H driver code(air_en8811h.c),
> I have modified the relevant process to align with the U-Boot boot sequence.
> and have validated this on Banana Pi BPI-R3 Mini.
>
> Signed-off-by: Lucien.Jheng <lucienzx159 at gmail.com>
> ---
> drivers/net/phy/Kconfig | 43 ++
> drivers/net/phy/air_en8811h.c | 852 ++++++++++++++++++++++++++++++++++
> 2 files changed, 895 insertions(+)
> create mode 100644 drivers/net/phy/air_en8811h.c
>
> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> index 3132718e4f8..384cef845e1 100644
> --- a/drivers/net/phy/Kconfig
> +++ b/drivers/net/phy/Kconfig
> @@ -79,6 +79,49 @@ config PHY_ADIN
> help
> Add support for configuring RGMII on Analog Devices ADIN PHYs.
>
> +menuconfig PHY_AIROHA
> + bool "Airoha Ethernet PHYs support"
> +
> +config PHY_AIROHA_EN8811H
> + bool "Airoha Ethernet EN8811H support"
> + depends on PHY_AIROHA
> + help
> + AIROHA EN8811H supported.
Indent of help text is two spaces right of indent of 'help' Kconfig keyword.
> +
> +choice
> + prompt "Location of the Airoha PHY firmware"
> + default PHY_AIROHA_FW_IN_MMC
> + depends on PHY_AIROHA_EN8811H
> +
> +config PHY_AIROHA_FW_IN_MMC
> + bool "Airoha firmware in MMC partition"
> + help
> + Airoha PHYs use firmware which can be loaded automatically
> + from storage directly attached to the PHY, and then loaded
> + via MDIO commands by the boot loader. The firmware is loaded
> + from a file specified by the PHY_AIROHA_FW_PART,
> + PHY_AIROHA_FW_DM_FILEPATH and PHY_AIROHA_FW_DSP_FILEPATH options.
Indent, fix globally.
[...]
> diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c
[...]
> + #define EN8811H_FW_CTRL_2 0x800000
> + #define EN8811H_FW_CTRL_2_LOADING BIT(11)
> +
> + /* MII Registers */
> + #define AIR_AUX_CTRL_STATUS 0x1d
> + #define AIR_AUX_CTRL_STATUS_SPEED_MASK GENMASK(4, 2)
> + #define AIR_AUX_CTRL_STATUS_SPEED_100 0x4
> + #define AIR_AUX_CTRL_STATUS_SPEED_1000 0x8
> + #define AIR_AUX_CTRL_STATUS_SPEED_2500 0xc
Please fix indent, and be consistent about it:
#define<space>MACRO_NAME<tab><more tabs as needed>0xvalue
> +#define AIR_EXT_PAGE_ACCESS 0x1f
> +#define AIR_PHY_PAGE_STANDARD 0x0000
> +#define AIR_PHY_PAGE_EXTENDED_4 0x0004
[...]
> +int air_phy_restore_page(struct phy_device *phydev, int oldpage, int ret)
> +{
> + int r;
> +
> + if (oldpage >= 0) {
> + r = air_phy_write_page(phydev, oldpage);
> +
> + if (ret >= 0 && r < 0)
> + ret = r;
> + } else {
> + ret = oldpage;
Invert the conditionals to reduce indent
if (oldpage < 0)
return oldpage;
r = ...
> + }
> +
> + return ret;
> +}
> +
> +static int air_buckpbus_reg_write(struct phy_device *phydev,
> + u32 pbus_address, u32 pbus_data)
> +{
> + int ret, saved_page;
> +
> + saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
> +
> + if (saved_page >= 0) {
Invert the conditional (*):
if (saved_page < 0)
return saved_page; // this is OK, because air_phy_restore_page() will
// bail in case saved_page < 0 , see above
...
> + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
> + if (ret < 0)
> + goto restore_page;
> +
> + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
> + air_upper_16_bits(pbus_address));
> + if (ret < 0)
> + goto restore_page;
> +
> + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
> + air_lower_16_bits(pbus_address));
> + if (ret < 0)
> + goto restore_page;
> +
> + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH,
> + air_upper_16_bits(pbus_data));
> + if (ret < 0)
> + goto restore_page;
> +
> + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW,
> + air_lower_16_bits(pbus_data));
> + if (ret < 0)
> + goto restore_page;
> + }
> +
> +restore_page:
> + if (ret < 0)
> + printf("%s 0x%08x failed: %d\n", __func__,
> + pbus_address, ret);
> +
> + return air_phy_restore_page(phydev, saved_page, ret);
> +}
> +
> +static int air_buckpbus_reg_read(struct phy_device *phydev,
> + u32 pbus_address, u32 *pbus_data)
> +{
> + int pbus_data_low, pbus_data_high;
> + int ret = 0, saved_page;
> +
> + saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
Same change (*) applies here, see above.
> + if (saved_page >= 0) {
[...]
> +static int air_buckpbus_reg_modify(struct phy_device *phydev,
> + u32 pbus_address, u32 mask, u32 set)
> +{
> + int pbus_data_low, pbus_data_high;
> + u32 pbus_data_old, pbus_data_new;
> + int ret = 0, saved_page;
> +
> + saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
Same change (*) applies here, see above, fix globally.
> + if (saved_page >= 0) {
[...]
> +static void crc32_check(unsigned char *buf, u32 len)
> +{
> + u32 ca_crc32;
> +
> + ca_crc32 = crc32(0, buf, len);
> + printf("crc32 is 0x%x\n", ca_crc32);
Shouldn't this produce some return value , in case the check fails ?
> +}
> +
> +__weak int en8811h_read_fw(void **addr)
> +{
> + loff_t read;
> + int ret;
> +
> + printf("\nLoading Airoha FW from %s %s\n",
> + CONFIG_PHY_AIROHA_FW_PART,
> + CONFIG_PHY_AIROHA_FW_DM_FILEPATH);
debug() instead of printf() .
> + ret = fs_set_blk_dev("mmc", CONFIG_PHY_AIROHA_FW_PART, FS_TYPE_ANY);
> + if (ret < 0)
> + return ret;
> +
> + ret = fs_read(CONFIG_PHY_AIROHA_FW_DM_FILEPATH,
> + (ulong)*addr, 0, EN8811H_MD32_DM_SIZE, &read);
> + if (ret < 0)
> + return ret;
> +
> + /* Calculate the CRC32 */
> + crc32_check((unsigned char *)*addr, EN8811H_MD32_DM_SIZE);
> +
> + printf("Loading Airoha FW from %s %s\n",
> + CONFIG_PHY_AIROHA_FW_PART,
> + CONFIG_PHY_AIROHA_FW_DSP_FILEPATH);
> + ret = fs_set_blk_dev("mmc", CONFIG_PHY_AIROHA_FW_PART, FS_TYPE_ANY);
> + if (ret < 0)
> + return ret;
> +
> + ret = fs_read(CONFIG_PHY_AIROHA_FW_DSP_FILEPATH,
> + (ulong)*addr + EN8811H_MD32_DM_SIZE,
> + 0, EN8811H_MD32_DSP_SIZE, &read);
> + if (ret < 0)
> + return ret;
> +
> + /* Calculate the CRC32 */
> + crc32_check((unsigned char *)*addr + EN8811H_MD32_DM_SIZE,
> + EN8811H_MD32_DSP_SIZE);
> +
> + printf("Found Airoha Firmware.\n");
> +
> + return 0;
> +}
> +
> +static int en8811h_load_firmware(struct phy_device *phydev)
> +{
> + struct en8811h_priv *priv = phydev->priv;
> + void *addr = NULL;
> + int ret;
> +
> + if (IS_ENABLED(CONFIG_PHY_AIROHA_FW_IN_MMC)) {
This conditional should be in en8811h_read_fw() too , the malloc as well
, so user can completely override the loading and only return a buffer
with firmware from wherever they loaded it from .
This:
__weak int en8811h_read_fw(void **addr) {
if (!IS_ENABLED(CONFIG_PHY_AIROHA_FW_IN_MMC))
return -EOPNOTSUPP;
buffer = malloc();
...
load_the_firmware(buffer);
...
*addr = buffer;
...
return 0;
}
And here:
...
void *buffer;
...
ret = en8811h_read_fw(&buffer);
if (ret)
return ret;
...
> + u32 fw_length = EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE;
> +
> + addr = malloc(fw_length);
> + if (!addr) {
> + printf("Failed to allocate memory for firmware\n");
> + return -ENOMEM;
> + }
> +
> + ret = en8811h_read_fw(&addr);
> + if (ret < 0) {
> + free(addr);
> + return ret;
Remove the free() and goto en8811h_load_firmware_out;
> + }
> + } else {
> + puts("EN8811H firmware loading not implemented");
> + return -EOPNOTSUPP;
Invert the conditional, reduce indent.
> + }
> +
> + ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
> + EN8811H_FW_CTRL_1_START);
> + if (ret < 0)
> + goto en8811h_load_firmware_out;
> +
> + ret = air_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, EN8811H_MD32_DM_SIZE,
> + (unsigned char *)addr);
> + if (ret < 0)
> + goto en8811h_load_firmware_out;
> +
> + ret = air_write_buf(phydev, AIR_FW_ADDR_DSP, EN8811H_MD32_DSP_SIZE,
> + (unsigned char *)addr + EN8811H_MD32_DM_SIZE);
> + if (ret < 0)
> + goto en8811h_load_firmware_out;
> +
> + ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
> + EN8811H_FW_CTRL_2_LOADING, 0);
> + if (ret < 0)
> + goto en8811h_load_firmware_out;
> +
> + ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
> + EN8811H_FW_CTRL_1_FINISH);
> + if (ret < 0)
> + goto en8811h_load_firmware_out;
> +
> + ret = en8811h_wait_mcu_ready(phydev);
> +
> + air_buckpbus_reg_read(phydev, EN8811H_FW_VERSION,
> + &priv->firmware_version);
> + printf("MD32 firmware version: %08x\n",
> + priv->firmware_version);
> +
> +en8811h_load_firmware_out:
> + free(addr);
> + if (ret < 0)
> + printf("Firmware loading failed: %d\n", ret);
> +
> + return ret;
> +}
[...]
> +static int air_leds_init(struct phy_device *phydev, int num, int dur, int mode)
> +{
> + int ret, i;
> + struct en8811h_priv *priv = phydev->priv;
Use reverse xmas tree sorting for variables, i.e. 'int ret, i;' goes
below struct en88... , fix globally.
> + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_BLINK,
> + dur);
> + if (ret < 0)
> + return ret;
[...]
> +static int en8811h_config(struct phy_device *phydev)
> +{
> + ofnode node = phy_get_ofnode(phydev);
> + struct en8811h_priv *priv = phydev->priv;
> + int ret = 0;
> + u32 pbus_value = 0;
> +
> + /* 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 {
> + ret = en8811h_load_firmware(phydev);
> + if (ret) {
> + printf("Load firmware fail.\n");
> + return ret;
> + }
> + /* Next calls to .config() mcu needs to restart */
> + priv->mcu_needs_restart = true;
> + }
> +
> + ret = phy_write_mmd(phydev, 0x1e, 0x800c, 0x0);
> + ret |= phy_write_mmd(phydev, 0x1e, 0x800d, 0x0);
> + ret |= phy_write_mmd(phydev, 0x1e, 0x800e, 0x1101);
> + ret |= phy_write_mmd(phydev, 0x1e, 0x800f, 0x0002);
Bitwise operations do not work on signed integers , do proper error
checking on all four return values above.
> + if (ret < 0)
> + return ret;
> +
> + /* Serdes polarity */
> + pbus_value = 0;
> + if (ofnode_read_bool(node, "airoha,pnswap-rx"))
> + pbus_value |= EN8811H_POLARITY_RX_REVERSE;
> + else
> + pbus_value &= ~EN8811H_POLARITY_RX_REVERSE;
> + if (ofnode_read_bool(node, "airoha,pnswap-tx"))
> + pbus_value &= ~EN8811H_POLARITY_TX_NORMAL;
> + else
> + pbus_value |= EN8811H_POLARITY_TX_NORMAL;
> + ret = air_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
> + EN8811H_POLARITY_RX_REVERSE |
> + EN8811H_POLARITY_TX_NORMAL, pbus_value);
> + 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) {
> + printf("Failed to disable leds: %d\n", ret);
> + return ret;
> + }
> +
> + return 0;
> +}
[...]
--
Best regards,
Marek Vasut
More information about the U-Boot
mailing list