[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