+#endif /* __EN8801S_H */
--- /dev/null
+++ b/drivers/net/phy/air_en8811h.c
-@@ -0,0 +1,725 @@
-+// SPDX-License-Identifier: GPL-2.0
-+/*************************************************
-+ * FILE NAME: air_en8811h.c
-+ * PURPOSE:
-+ * EN8811H PHY Driver for Uboot
-+ * NOTES:
+@@ -0,0 +1,886 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Driver for the Airoha EN8811H 2.5 Gigabit PHY.
+ *
-+ * Copyright (C) 2023 Airoha Technology Corp.
-+ *************************************************/
-+
-+/* INCLUDE FILE DECLARATIONS
-+*/
-+#include <config.h>
-+#include <eth_phy.h>
++ * Limitations of the EN8811H:
++ * - Only full duplex supported
++ * - Forced speed (AN off) is not supported by hardware (100Mbps)
++ *
++ * Source originated from linux air_en8811h.c
++ *
++ * Copyright (C) 2025 Airoha Technology Corp.
++ */
+#include <phy.h>
+#include <errno.h>
++#include <log.h>
++#include <env.h>
+#include <malloc.h>
-+#include <version.h>
-+#include "air_en8811h.h"
++#include <fs.h>
++#include <asm/unaligned.h>
++#include <linux/iopoll.h>
++#include <linux/bitops.h>
++#include <linux/compat.h>
++#include <dm/device_compat.h>
++#include <u-boot/crc.h>
+
+#ifdef CONFIG_PHY_AIROHA_FW_IN_UBI
+#include <ubi_uboot.h>
+#include <mtd.h>
+#endif
+
-+#if AIR_UBOOT_REVISION > 0x202004
-+#include <linux/delay.h>
-+#endif
++#define EN8811H_PHY_ID 0x03a2a411
++
++#define AIR_FW_ADDR_DM 0x00000000
++#define AIR_FW_ADDR_DSP 0x00100000
++
++#define EN8811H_MD32_DM_SIZE 0x4000
++#define EN8811H_MD32_DSP_SIZE 0x20000
++
++ #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)
++
++ /* 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
++
++#define AIR_EXT_PAGE_ACCESS 0x1f
++#define AIR_PHY_PAGE_STANDARD 0x0000
++#define AIR_PHY_PAGE_EXTENDED_4 0x0004
++
++/* MII Registers Page 4*/
++#define AIR_BPBUS_MODE 0x10
++#define AIR_BPBUS_MODE_ADDR_FIXED 0x0000
++#define AIR_BPBUS_MODE_ADDR_INCR BIT(15)
++#define AIR_BPBUS_WR_ADDR_HIGH 0x11
++#define AIR_BPBUS_WR_ADDR_LOW 0x12
++#define AIR_BPBUS_WR_DATA_HIGH 0x13
++#define AIR_BPBUS_WR_DATA_LOW 0x14
++#define AIR_BPBUS_RD_ADDR_HIGH 0x15
++#define AIR_BPBUS_RD_ADDR_LOW 0x16
++#define AIR_BPBUS_RD_DATA_HIGH 0x17
++#define AIR_BPBUS_RD_DATA_LOW 0x18
++
++/* Registers on MDIO_MMD_VEND1 */
++#define EN8811H_PHY_FW_STATUS 0x8009
++#define EN8811H_PHY_READY 0x02
++
++/* Registers on MDIO_MMD_VEND2 */
++#define AIR_PHY_LED_BCR 0x021
++#define AIR_PHY_LED_BCR_MODE_MASK GENMASK(1, 0)
++#define AIR_PHY_LED_BCR_TIME_TEST BIT(2)
++#define AIR_PHY_LED_BCR_CLK_EN BIT(3)
++#define AIR_PHY_LED_BCR_EXT_CTRL BIT(15)
++
++#define AIR_PHY_LED_DUR_ON 0x022
++
++#define AIR_PHY_LED_DUR_BLINK 0x023
++
++#define AIR_PHY_LED_ON(i) (0x024 + ((i) * 2))
++#define AIR_PHY_LED_ON_MASK (GENMASK(6, 0) | BIT(8))
++#define AIR_PHY_LED_ON_LINK1000 BIT(0)
++#define AIR_PHY_LED_ON_LINK100 BIT(1)
++#define AIR_PHY_LED_ON_LINK10 BIT(2)
++#define AIR_PHY_LED_ON_LINKDOWN BIT(3)
++#define AIR_PHY_LED_ON_FDX BIT(4) /* Full duplex */
++#define AIR_PHY_LED_ON_HDX BIT(5) /* Half duplex */
++#define AIR_PHY_LED_ON_FORCE_ON BIT(6)
++#define AIR_PHY_LED_ON_LINK2500 BIT(8)
++#define AIR_PHY_LED_ON_POLARITY BIT(14)
++#define AIR_PHY_LED_ON_ENABLE BIT(15)
++
++#define AIR_PHY_LED_BLINK(i) (0x025 + ((i) * 2))
++#define AIR_PHY_LED_BLINK_1000TX BIT(0)
++#define AIR_PHY_LED_BLINK_1000RX BIT(1)
++#define AIR_PHY_LED_BLINK_100TX BIT(2)
++#define AIR_PHY_LED_BLINK_100RX BIT(3)
++#define AIR_PHY_LED_BLINK_10TX BIT(4)
++#define AIR_PHY_LED_BLINK_10RX BIT(5)
++#define AIR_PHY_LED_BLINK_COLLISION BIT(6)
++#define AIR_PHY_LED_BLINK_RX_CRC_ERR BIT(7)
++#define AIR_PHY_LED_BLINK_RX_IDLE_ERR BIT(8)
++#define AIR_PHY_LED_BLINK_FORCE_BLINK BIT(9)
++#define AIR_PHY_LED_BLINK_2500TX BIT(10)
++#define AIR_PHY_LED_BLINK_2500RX BIT(11)
++
++#define EN8811H_FW_VERSION 0x3b3c
++
++#define EN8811H_POLARITY 0xca0f8
++#define EN8811H_POLARITY_TX_NORMAL BIT(0)
++#define EN8811H_POLARITY_RX_REVERSE BIT(1)
++
++#define EN8811H_CLK_CGM 0xcf958
++#define EN8811H_CLK_CGM_CKO BIT(26)
++#define EN8811H_HWTRAP1 0xcf914
++#define EN8811H_HWTRAP1_CKO BIT(12)
++
++#define air_upper_16_bits(n) ((u16)((n) >> 16))
++#define air_lower_16_bits(n) ((u16)((n) & 0xffff))
++#define clear_bit(bit, bitmap) __clear_bit(bit, bitmap)
++
++/* Led definitions */
++#define EN8811H_LED_COUNT 3
++
++struct led {
++ unsigned long rules;
++ unsigned long state;
++};
+
-+/**************************
-+ * GPIO5 <-> BASE_T_LED0,
-+ * GPIO4 <-> BASE_T_LED1,
-+ * GPIO3 <-> BASE_T_LED2,
-+ **************************/
-+/* User-defined.B */
-+#define AIR_LED_SUPPORT
-+#ifdef AIR_LED_SUPPORT
-+static const struct air_base_t_led_cfg_s led_cfg[3] = {
-+/*********************************************************************
-+ *Enable, GPIO, LED Polarity, LED ON, LED Blink
-+**********************************************************************/
-+ {1, AIR_LED0_GPIO5, AIR_ACTIVE_HIGH, AIR_LED0_ON, AIR_LED0_BLK},
-+ {1, AIR_LED1_GPIO4, AIR_ACTIVE_HIGH, AIR_LED1_ON, AIR_LED1_BLK},
-+ {1, AIR_LED2_GPIO3, AIR_ACTIVE_HIGH, AIR_LED2_ON, AIR_LED2_BLK},
++enum {
++ AIR_PHY_LED_STATE_FORCE_ON,
++ AIR_PHY_LED_STATE_FORCE_BLINK,
+};
-+static const u16 led_dur = UNIT_LED_BLINK_DURATION << AIR_LED_BLK_DUR_64M;
-+#endif
-+/* User-defined.E */
-+/*************************************************************
-+ * F U N C T I O N S
-+ **************************************************************/
-+/* Airoha MII read function */
-+static int air_mii_cl22_read(struct mii_dev *bus, int phy_addr, int phy_register)
-+{
-+ int read_data = bus->read(bus, phy_addr, MDIO_DEVAD_NONE, phy_register);
+
-+ if (read_data < 0)
-+ return -EIO;
-+ return read_data;
-+}
++enum {
++ AIR_PHY_LED_DUR_BLINK_32MS,
++ AIR_PHY_LED_DUR_BLINK_64MS,
++ AIR_PHY_LED_DUR_BLINK_128MS,
++ AIR_PHY_LED_DUR_BLINK_256MS,
++ AIR_PHY_LED_DUR_BLINK_512MS,
++ AIR_PHY_LED_DUR_BLINK_1024MS,
++};
+
-+/* Airoha MII write function */
-+static int air_mii_cl22_write(struct mii_dev *bus, int phy_addr, int phy_register, int write_data)
-+{
-+ int ret = 0;
++enum {
++ AIR_LED_DISABLE,
++ AIR_LED_ENABLE,
++};
+
-+ ret = bus->write(bus, phy_addr, MDIO_DEVAD_NONE, phy_register, write_data);
-+ if (ret < 0) {
-+ printf("bus->write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ return ret;
-+}
++enum {
++ AIR_ACTIVE_LOW,
++ AIR_ACTIVE_HIGH,
++};
+
-+static int air_mii_cl45_read(struct phy_device *phydev, int devad, u16 reg)
-+{
-+ int ret = 0;
-+ int data;
++enum {
++ AIR_LED_MODE_DISABLE,
++ AIR_LED_MODE_USER_DEFINE,
++};
+
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, devad);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return INVALID_DATA;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG, reg);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return INVALID_DATA;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, MMD_OP_MODE_DATA | devad);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return INVALID_DATA;
-+ }
-+ data = phy_read(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG);
-+ return data;
-+}
++/* Trigger specific enum */
++enum air_led_trigger_netdev_modes {
++ AIR_TRIGGER_NETDEV_LINK = 0,
++ AIR_TRIGGER_NETDEV_LINK_10,
++ AIR_TRIGGER_NETDEV_LINK_100,
++ AIR_TRIGGER_NETDEV_LINK_1000,
++ AIR_TRIGGER_NETDEV_LINK_2500,
++ AIR_TRIGGER_NETDEV_LINK_5000,
++ AIR_TRIGGER_NETDEV_LINK_10000,
++ AIR_TRIGGER_NETDEV_HALF_DUPLEX,
++ AIR_TRIGGER_NETDEV_FULL_DUPLEX,
++ AIR_TRIGGER_NETDEV_TX,
++ AIR_TRIGGER_NETDEV_RX,
++ AIR_TRIGGER_NETDEV_TX_ERR,
++ AIR_TRIGGER_NETDEV_RX_ERR,
++
++ /* Keep last */
++ __AIR_TRIGGER_NETDEV_MAX,
++};
+
-+static int air_mii_cl45_write(struct phy_device *phydev, int devad, u16 reg, u16 write_data)
-+{
-+ int ret = 0;
++/* Default LED setup:
++ * GPIO5 <-> LED0 On: Link detected, blink Rx/Tx
++ * GPIO4 <-> LED1 On: Link detected at 2500 and 1000 Mbps
++ * GPIO3 <-> LED2 On: Link detected at 2500 and 100 Mbps
++ */
++#define AIR_DEFAULT_TRIGGER_LED0 (BIT(AIR_TRIGGER_NETDEV_LINK) | \
++ BIT(AIR_TRIGGER_NETDEV_RX) | \
++ BIT(AIR_TRIGGER_NETDEV_TX))
++#define AIR_DEFAULT_TRIGGER_LED1 (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | \
++ BIT(AIR_TRIGGER_NETDEV_LINK_1000))
++#define AIR_DEFAULT_TRIGGER_LED2 (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | \
++ BIT(AIR_TRIGGER_NETDEV_LINK_100))
++
++#define AIR_PHY_LED_DUR_UNIT 781
++#define AIR_PHY_LED_DUR (AIR_PHY_LED_DUR_UNIT << AIR_PHY_LED_DUR_BLINK_64MS)
++
++struct en8811h_priv {
++ int firmware_version;
++ bool mcu_needs_restart;
++ struct led led[EN8811H_LED_COUNT];
++};
+
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, devad);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG, reg);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, MMD_OP_MODE_DATA | devad);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG, write_data);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ return 0;
-+}
-+/* Use default PBUS_PHY_ID */
-+/* EN8811H PBUS write function */
-+static int air_pbus_reg_write(struct phy_device *phydev, unsigned long pbus_address, unsigned long pbus_data)
++static int air_phy_read_page(struct phy_device *phydev)
+{
-+ int ret = 0;
-+ struct mii_dev *mbus = phydev->bus;
-+
-+ ret = air_mii_cl22_write(mbus, ((phydev->addr) + 8), 0x1F, (unsigned int)(pbus_address >> 6));
-+ if (ret < 0)
-+ return ret;
-+ ret = air_mii_cl22_write(mbus, ((phydev->addr) + 8), (unsigned int)((pbus_address >> 2) & 0xf), (unsigned int)(pbus_data & 0xFFFF));
-+ if (ret < 0)
-+ return ret;
-+ ret = air_mii_cl22_write(mbus, ((phydev->addr) + 8), 0x10, (unsigned int)(pbus_data >> 16));
-+ if (ret < 0)
-+ return ret;
-+ return 0;
++ return phy_read(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS);
+}
+
-+/* EN8811H BUCK write function */
-+static int air_buckpbus_reg_write(struct phy_device *phydev, unsigned long pbus_address, unsigned int pbus_data)
++static int air_phy_write_page(struct phy_device *phydev, int page)
+{
-+ int ret = 0;
-+
-+ /* page 4 */
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, (unsigned int)4);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x10, (unsigned int)0);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x11, (unsigned int)((pbus_address >> 16) & 0xffff));
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x12, (unsigned int)(pbus_address & 0xffff));
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x13, (unsigned int)((pbus_data >> 16) & 0xffff));
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x14, (unsigned int)(pbus_data & 0xffff));
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ return 0;
++ return phy_write(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS, page);
+}
+
-+/* EN8811H BUCK read function */
-+static unsigned int air_buckpbus_reg_read(struct phy_device *phydev, unsigned long pbus_address)
++int air_phy_select_page(struct phy_device *phydev, int page)
+{
-+ unsigned int pbus_data = 0, pbus_data_low, pbus_data_high;
-+ int ret = 0;
++ int ret, oldpage;
+
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, (unsigned int)4); /* page 4 */
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return PBUS_INVALID_DATA;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x10, (unsigned int)0);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return PBUS_INVALID_DATA;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x15, (unsigned int)((pbus_address >> 16) & 0xffff));
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return PBUS_INVALID_DATA;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x16, (unsigned int)(pbus_address & 0xffff));
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return PBUS_INVALID_DATA;
-+ }
++ oldpage = air_phy_read_page(phydev);
++ if (oldpage < 0)
++ return oldpage;
+
-+ pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, 0x17);
-+ pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, 0x18);
-+ pbus_data = (pbus_data_high << 16) + pbus_data_low;
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, (unsigned int)0);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ return pbus_data;
++ if (oldpage != page) {
++ ret = air_phy_write_page(phydev, page);
++ if (ret < 0)
++ return ret;
++ }
++
++ return oldpage;
+}
+
-+static int MDIOWriteBuf(struct phy_device *phydev, unsigned long address, unsigned long array_size, const unsigned char *buffer)
++int air_phy_restore_page(struct phy_device *phydev, int oldpage, int ret)
+{
-+ unsigned int write_data, offset ;
-+ int ret = 0;
++ int r;
+
-+ /* page 4 */
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, (unsigned int)4);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ /* address increment*/
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x10, (unsigned int)0x8000);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x11, (unsigned int)((address >> 16) & 0xffff));
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x12, (unsigned int)(address & 0xffff));
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
++ if (oldpage < 0)
++ return oldpage;
+
-+ for (offset = 0; offset < array_size; offset += 4) {
-+ write_data = (buffer[offset + 3] << 8) | buffer[offset + 2];
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x13, write_data);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ write_data = (buffer[offset + 1] << 8) | buffer[offset];
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x14, write_data);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ }
-+ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, (unsigned int)0);
-+ if (ret < 0) {
-+ printf("phy_write, ret: %d\n", ret);
-+ return ret;
-+ }
-+ return 0;
++ r = air_phy_write_page(phydev, oldpage);
++ if (ret >= 0 && r < 0)
++ ret = r;
++
++ return ret;
+}
+
-+#ifdef AIR_LED_SUPPORT
-+static int airoha_led_set_usr_def(struct phy_device *phydev, u8 entity, int polar,
-+ u16 on_evt, u16 blk_evt)
++static int air_buckpbus_reg_write(struct phy_device *phydev,
++ u32 pbus_address, u32 pbus_data)
+{
-+ int ret = 0;
-+
-+ if (AIR_ACTIVE_HIGH == polar)
-+ on_evt |= LED_ON_POL;
-+ else
-+ on_evt &= ~LED_ON_POL;
-+
-+ ret = air_mii_cl45_write(phydev, 0x1f, LED_ON_CTRL(entity), on_evt | LED_ON_EN);
-+ if (ret < 0)
-+ return ret;
-+ ret = air_mii_cl45_write(phydev, 0x1f, LED_BLK_CTRL(entity), blk_evt);
-+ if (ret < 0)
-+ return ret;
-+ return 0;
++ int ret, saved_page;
++
++ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
++ if (saved_page < 0)
++ return saved_page;
++
++ 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 airoha_led_set_mode(struct phy_device *phydev, u8 mode)
++static int air_buckpbus_reg_read(struct phy_device *phydev,
++ u32 pbus_address, u32 *pbus_data)
+{
-+ u16 cl45_data;
-+ int err = 0;
-+
-+ cl45_data = air_mii_cl45_read(phydev, 0x1f, LED_BCR);
-+ switch (mode) {
-+ case AIR_LED_MODE_DISABLE:
-+ cl45_data &= ~LED_BCR_EXT_CTRL;
-+ cl45_data &= ~LED_BCR_MODE_MASK;
-+ cl45_data |= LED_BCR_MODE_DISABLE;
-+ break;
-+ case AIR_LED_MODE_USER_DEFINE:
-+ cl45_data |= LED_BCR_EXT_CTRL;
-+ cl45_data |= LED_BCR_CLK_EN;
-+ break;
-+ default:
-+ printf("LED mode%d is not supported!\n", mode);
-+ return -EINVAL;
-+ }
-+ err = air_mii_cl45_write(phydev, 0x1f, LED_BCR, cl45_data);
-+ if (err < 0)
-+ return err;
-+ return 0;
++ int pbus_data_low, pbus_data_high;
++ int ret = 0, saved_page;
++
++ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
++ if (saved_page < 0)
++ return saved_page;
++
++ 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_RD_ADDR_HIGH,
++ air_upper_16_bits(pbus_address));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW,
++ air_lower_16_bits(pbus_address));
++ if (ret < 0)
++ goto restore_page;
++
++ pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH);
++ if (pbus_data_high < 0) {
++ ret = pbus_data_high;
++ goto restore_page;
++ }
++
++ pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW);
++ if (pbus_data_low < 0) {
++ ret = pbus_data_low;
++ goto restore_page;
++ }
++
++ *pbus_data = pbus_data_low | (pbus_data_high << 16);
++
++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 airoha_led_set_state(struct phy_device *phydev, u8 entity, u8 state)
++static int air_buckpbus_reg_modify(struct phy_device *phydev,
++ u32 pbus_address, u32 mask, u32 set)
+{
-+ u16 cl45_data;
-+ int err;
-+
-+ cl45_data = air_mii_cl45_read(phydev, 0x1f, LED_ON_CTRL(entity));
-+ if (LED_ENABLE == state)
-+ cl45_data |= LED_ON_EN;
-+ else
-+ cl45_data &= ~LED_ON_EN;
-+
-+ err = air_mii_cl45_write(phydev, 0x1f, LED_ON_CTRL(entity), cl45_data);
-+ if (err < 0)
-+ return err;
-+ return 0;
++ 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);
++ if (saved_page < 0)
++ return saved_page;
++
++ 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_RD_ADDR_HIGH,
++ air_upper_16_bits(pbus_address));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW,
++ air_lower_16_bits(pbus_address));
++ if (ret < 0)
++ goto restore_page;
++
++ pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH);
++ if (pbus_data_high < 0)
++ return pbus_data_high;
++
++ pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW);
++ if (pbus_data_low < 0)
++ return pbus_data_low;
++
++ pbus_data_old = pbus_data_low | (pbus_data_high << 16);
++ pbus_data_new = (pbus_data_old & ~mask) | set;
++ if (pbus_data_new == pbus_data_old)
++ return 0;
++
++ 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_new));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW,
++ air_lower_16_bits(pbus_data_new));
++ 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 en8811h_led_init(struct phy_device *phydev)
++static int air_write_buf(struct phy_device *phydev, unsigned long address,
++ unsigned long array_size, const unsigned char *buffer)
+{
-+ unsigned int led_gpio = 0, reg_value = 0;
-+ u16 cl45_data = led_dur;
-+ int ret, led_id;
-+
-+ cl45_data = UNIT_LED_BLINK_DURATION << AIR_LED_BLK_DUR_64M;
-+ ret = air_mii_cl45_write(phydev, 0x1f, LED_BLK_DUR, cl45_data);
-+ if (ret < 0)
-+ return ret;
-+ cl45_data >>= 1;
-+ ret = air_mii_cl45_write(phydev, 0x1f, LED_ON_DUR, cl45_data);
-+ if (ret < 0)
-+ return ret;
++ unsigned int offset;
++ int ret, saved_page;
++ u16 val;
++
++ saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
++ if (saved_page < 0)
++ return saved_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
++ AIR_BPBUS_MODE_ADDR_INCR);
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
++ air_upper_16_bits(address));
++ if (ret < 0)
++ goto restore_page;
++
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
++ air_lower_16_bits(address));
++ if (ret < 0)
++ goto restore_page;
++
++ for (offset = 0; offset < array_size; offset += 4) {
++ val = get_unaligned_le16(&buffer[offset + 2]);
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, val);
++ if (ret < 0)
++ goto restore_page;
++
++ val = get_unaligned_le16(&buffer[offset]);
++ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, val);
++ if (ret < 0)
++ goto restore_page;
++ }
++
++restore_page:
++ if (ret < 0)
++ printf("%s 0x%08lx failed: %d\n", __func__,
++ address, ret);
++
++ return air_phy_restore_page(phydev, saved_page, ret);
++}
+
-+ ret = airoha_led_set_mode(phydev, AIR_LED_MODE_USER_DEFINE);
-+ if (ret != 0) {
-+ printf("LED fail to set mode, ret %d !\n", ret);
-+ return ret;
-+ }
-+ for(led_id = 0; led_id < EN8811H_LED_COUNT; led_id++)
-+ {
-+ /* LED0 <-> GPIO5, LED1 <-> GPIO4, LED0 <-> GPIO3 */
-+ if ( led_cfg[led_id].gpio != (led_id + (AIR_LED0_GPIO5 - (2 * led_id)))) {
-+ printf("LED%d uses incorrect GPIO%d !\n", led_id, led_cfg[led_id].gpio);
-+ return -EINVAL;
-+ }
-+ reg_value = 0;
-+ if (led_cfg[led_id].en == LED_ENABLE)
-+ {
-+ led_gpio |= BIT(led_cfg[led_id].gpio);
-+ ret = airoha_led_set_state(phydev, led_id, led_cfg[led_id].en);
-+ if (ret != 0) {
-+ printf("LED fail to set state, ret %d !\n", ret);
-+ return ret;
-+ }
-+ ret = airoha_led_set_usr_def(phydev, led_id, led_cfg[led_id].pol, led_cfg[led_id].on_cfg, led_cfg[led_id].blk_cfg);
-+ if (ret != 0) {
-+ printf("LED fail to set default, ret %d !\n", ret);
-+ return ret;
-+ }
-+ }
-+ }
-+ ret = air_buckpbus_reg_write(phydev, 0xcf8b8, led_gpio);
-+ if (ret < 0)
-+ return ret;
-+ printf("LED initialize OK !\n");
-+ return 0;
++static int en8811h_wait_mcu_ready(struct phy_device *phydev)
++{
++ int ret, reg_value;
++
++ /* 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) {
++ printf("MCU not ready: 0x%x\n", reg_value);
++ return -ENODEV;
++ }
++
++ return 0;
+}
-+#endif /* AIR_LED_SUPPORT */
+
-+static char *firmware_buf;
-+static int en8811h_load_firmware(struct phy_device *phydev)
++__weak int en8811h_read_fw(void **addr)
+{
-+ u32 pbus_value;
-+ int ret = 0;
++ u32 ca_crc32;
++ void *buffer;
++ int ret;
+
-+ if (!firmware_buf) {
-+ firmware_buf = malloc(EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE);
-+ if (!firmware_buf) {
-+ printf("[Airoha] cannot allocated buffer for firmware.\n");
-+ return -ENOMEM;
-+ }
++ /* Allocate memory to store both DM and DSP firmware */
++ buffer = malloc(EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE);
++ if (!buffer) {
++ printf("Failed to allocate memory for firmware\n");
++ return -ENOMEM;
++ }
+
+#ifdef CONFIG_PHY_AIROHA_FW_IN_UBI
-+ ret = ubi_volume_read("en8811h-fw", firmware_buf, 0, EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE);
-+ if (ret) {
-+ printf("[Airoha] read firmware from UBI failed.\n");
-+ free(firmware_buf);
-+ firmware_buf = NULL;
-+ return ret;
-+ }
++ ret = ubi_volume_read("en8811h-fw", buffer, 0, EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE);
++ if (ret) {
++ printf("[Airoha] read firmware from UBI failed.\n");
++ free(buffer);
++ buffer = NULL;
++ return ret;
++ }
+#elif defined(CONFIG_PHY_AIROHA_FW_IN_MMC)
-+ struct mmc *mmc = find_mmc_device(0);
-+ if (!mmc) {
-+ printf("[Airoha] opening MMC device failed.\n");
-+ free(firmware_buf);
-+ firmware_buf = NULL;
-+ return -ENODEV;
-+ }
-+ if (mmc_init(mmc)) {
-+ printf("[Airoha] initializing MMC device failed.\n");
-+ free(firmware_buf);
-+ firmware_buf = NULL;
-+ return -ENODEV;
-+ }
-+ if (IS_SD(mmc)) {
-+ printf("[Airoha] SD card is not supported.\n");
-+ free(firmware_buf);
-+ firmware_buf = NULL;
-+ return -EINVAL;
-+ }
-+ ret = mmc_set_part_conf(mmc, 1, 2, 2);
-+ if (ret) {
-+ printf("[Airoha] cannot access eMMC boot1 hw partition.\n");
-+ free(firmware_buf);
-+ firmware_buf = NULL;
-+ return ret;
-+ }
-+ ret = blk_dread(mmc_get_blk_desc(mmc), 0, 0x120, firmware_buf);
-+ mmc_set_part_conf(mmc, 1, 1, 0);
-+ if (ret != 0x120) {
-+ printf("[Airoha] cannot read firmware from eMMC.\n");
-+ free(firmware_buf);
-+ firmware_buf = NULL;
-+ return -EIO;
-+ }
++ struct mmc *mmc = find_mmc_device(0);
++ if (!mmc) {
++ printf("[Airoha] opening MMC device failed.\n");
++ free(buffer);
++ buffer = NULL;
++ return -ENODEV;
++ }
++ if (mmc_init(mmc)) {
++ printf("[Airoha] initializing MMC device failed.\n");
++ free(buffer);
++ buffer = NULL;
++ return -ENODEV;
++ }
++ if (IS_SD(mmc)) {
++ printf("[Airoha] SD card is not supported.\n");
++ free(buffer);
++ buffer = NULL;
++ return -EINVAL;
++ }
++ ret = mmc_set_part_conf(mmc, 1, 2, 2);
++ if (ret) {
++ printf("[Airoha] cannot access eMMC boot1 hw partition.\n");
++ free(buffer);
++ buffer = NULL;
++ return ret;
++ }
++ ret = blk_dread(mmc_get_blk_desc(mmc), 0, 0x120, buffer);
++ mmc_set_part_conf(mmc, 1, 1, 0);
++ if (ret != 0x120) {
++ printf("[Airoha] cannot read firmware from eMMC.\n");
++ free(buffer);
++ buffer = NULL;
++ return -EIO;
++ }
+#else
+#warning EN8811H firmware loading not implemented
-+ free(firmware_buf);
-+ firmware_buf = NULL;
-+ return -EOPNOTSUPP;
++ free(buffer);
++ buffer = NULL;
++ return -EOPNOTSUPP;
+#endif
-+ }
+
-+ ret = air_buckpbus_reg_write(phydev, 0x0f0018, 0x0);
-+ if (ret < 0)
-+ return ret;
-+ pbus_value = air_buckpbus_reg_read(phydev, 0x800000);
-+ pbus_value |= BIT(11);
-+ ret = air_buckpbus_reg_write(phydev, 0x800000, pbus_value);
-+ if (ret < 0)
-+ return ret;
-+ /* Download DM */
-+ ret = MDIOWriteBuf(phydev, 0x00000000, EN8811H_MD32_DM_SIZE, firmware_buf);
-+ if (ret < 0) {
-+ printf("[Airoha] MDIOWriteBuf 0x00000000 fail.\n");
-+ return ret;
-+ }
-+ /* Download PM */
-+ ret = MDIOWriteBuf(phydev, 0x00100000, EN8811H_MD32_DSP_SIZE, firmware_buf + EN8811H_MD32_DM_SIZE);
-+ if (ret < 0) {
-+ printf("[Airoha] MDIOWriteBuf 0x00100000 fail.\n");
-+ return ret;
-+ }
-+ pbus_value = air_buckpbus_reg_read(phydev, 0x800000);
-+ pbus_value &= ~BIT(11);
-+ ret = air_buckpbus_reg_write(phydev, 0x800000, pbus_value);
-+ if (ret < 0)
-+ return ret;
-+ ret = air_buckpbus_reg_write(phydev, 0x0f0018, 0x01);
-+ if (ret < 0)
-+ return ret;
-+ return 0;
-+}
-+
-+static int en8811h_config(struct phy_device *phydev)
-+{
-+ int ret = 0;
-+ int pid1 = 0, pid2 = 0;
++ /* Calculate CRC32 of DM firmware for verification */
++ ca_crc32 = crc32(0, (unsigned char *)buffer, EN8811H_MD32_DM_SIZE);
++ debug("DM crc32: 0x%x\n", ca_crc32);
+
-+ ret = air_pbus_reg_write(phydev, 0xcf928 , 0x0);
-+ if (ret < 0)
-+ return ret;
++ /* Calculate CRC32 of DSP firmware for verification */
++ ca_crc32 = crc32(0, (unsigned char *)buffer + EN8811H_MD32_DM_SIZE,
++ EN8811H_MD32_DSP_SIZE);
++ debug("DSP crc32: 0x%x\n", ca_crc32);
+
-+ pid1 = phy_read(phydev, MDIO_DEVAD_NONE, MII_PHYSID1);
-+ pid2 = phy_read(phydev, MDIO_DEVAD_NONE, MII_PHYSID2);
-+ if ((EN8811H_PHY_ID1 != pid1) || (EN8811H_PHY_ID2 != pid2)) {
-+ printf("EN8811H does not exist !\n");
-+ return -ENODEV;
-+ }
++ *addr = buffer;
++ debug("Found Airoha Firmware.\n");
+
-+ return 0;
++ return 0;
+}
+
-+static int en8811h_get_autonego(struct phy_device *phydev, int *an)
++static int en8811h_load_firmware(struct phy_device *phydev)
+{
-+ int reg;
-+ reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
-+ if (reg < 0)
-+ return -EINVAL;
-+ if (reg & BMCR_ANENABLE)
-+ *an = AUTONEG_ENABLE;
-+ else
-+ *an = AUTONEG_DISABLE;
-+ return 0;
++ struct en8811h_priv *priv = phydev->priv;
++ void *buffer;
++ int ret;
++
++ ret = en8811h_read_fw(&buffer);
++ if (ret < 0)
++ goto en8811h_load_firmware_out;
++
++ 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 *)buffer);
++ if (ret < 0)
++ goto en8811h_load_firmware_out;
++
++ ret = air_write_buf(phydev, AIR_FW_ADDR_DSP, EN8811H_MD32_DSP_SIZE,
++ (unsigned char *)buffer + 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(buffer);
++ if (ret < 0)
++ printf("Firmware loading failed: %d\n", ret);
++
++ return ret;
+}
+
-+static int en8811h_startup(struct phy_device *phydev)
++static int en8811h_restart_mcu(struct phy_device *phydev)
+{
-+ ofnode node = phy_get_ofnode(phydev);
-+ int ret = 0, lpagb = 0, lpa = 0, common_adv_gb = 0, common_adv = 0, advgb = 0, adv = 0, reg = 0, an = AUTONEG_DISABLE, bmcr = 0, reg_value;
-+ int old_link = phydev->link;
-+ u32 pbus_value = 0, retry;
-+
-+ eth_phy_reset(phydev->dev, 1);
-+ mdelay(10);
-+ eth_phy_reset(phydev->dev, 0);
-+ mdelay(1);
-+
-+ ret = en8811h_load_firmware(phydev);
-+ if (ret) {
-+ printf("EN8811H load firmware fail.\n");
-+ return ret;
-+ }
-+ retry = MAX_RETRY;
-+ do {
-+ mdelay(300);
-+ reg_value = air_mii_cl45_read(phydev, 0x1e, 0x8009);
-+ if (EN8811H_PHY_READY == reg_value) {
-+ printf("EN8811H PHY ready!\n");
-+ break;
-+ }
-+ retry--;
-+ } while (retry);
-+ if (0 == retry) {
-+ printf("EN8811H PHY is not ready. (MD32 FW Status reg: 0x%x)\n", reg_value);
-+ pbus_value = air_buckpbus_reg_read(phydev, 0x3b3c);
-+ printf("Check MD32 FW Version(0x3b3c) : %08x\n", pbus_value);
-+ printf("EN8811H initialize fail!\n");
-+ return 0;
-+ }
-+ /* Mode selection*/
-+ printf("EN8811H Mode 1 !\n");
-+ ret = air_mii_cl45_write(phydev, 0x1e, 0x800c, 0x0);
-+ if (ret < 0)
-+ return ret;
-+ ret = air_mii_cl45_write(phydev, 0x1e, 0x800d, 0x0);
-+ if (ret < 0)
-+ return ret;
-+ ret = air_mii_cl45_write(phydev, 0x1e, 0x800e, 0x1101);
-+ if (ret < 0)
-+ return ret;
-+ ret = air_mii_cl45_write(phydev, 0x1e, 0x800f, 0x0002);
-+ if (ret < 0)
-+ return ret;
-+
-+ /* Serdes polarity */
-+ pbus_value = air_buckpbus_reg_read(phydev, 0xca0f8);
-+ pbus_value &= 0xfffffffc;
-+ pbus_value |= ofnode_read_bool(node, "airoha,rx-pol-reverse") ?
-+ EN8811H_RX_POLARITY_REVERSE : EN8811H_RX_POLARITY_NORMAL;
-+ pbus_value |= ofnode_read_bool(node, "airoha,tx-pol-reverse") ?
-+ EN8811H_TX_POLARITY_REVERSE : EN8811H_TX_POLARITY_NORMAL;
-+ ret = air_buckpbus_reg_write(phydev, 0xca0f8, pbus_value);
-+ if (ret < 0)
-+ return ret;
-+ pbus_value = air_buckpbus_reg_read(phydev, 0xca0f8);
-+ printf("Tx, Rx Polarity(0xca0f8): %08x\n", pbus_value);
-+ pbus_value = air_buckpbus_reg_read(phydev, 0x3b3c);
-+ printf("MD32 FW Version(0x3b3c) : %08x\n", pbus_value);
-+#if defined(AIR_LED_SUPPORT)
-+ ret = en8811h_led_init(phydev);
-+ if (ret < 0) {
-+ printf("en8811h_led_init fail\n");
-+ }
-+#endif
-+ printf("EN8811H initialize OK ! (%s)\n", EN8811H_DRIVER_VERSION);
-+
-+ ret = genphy_update_link(phydev);
-+ if (ret)
-+ {
-+ printf("ret %d!\n", ret);
-+ return ret;
-+ }
-+
-+ ret = genphy_parse_link(phydev);
-+ if (ret)
-+ {
-+ printf("ret %d!\n", ret);
-+ return ret;
-+ }
++ int ret;
+
-+ if (old_link && phydev->link)
-+ return 0;
++ ret = phy_write_mmd(phydev, 0x1e, 0x8009, 0x0);
++ if (ret < 0)
++ return ret;
+
-+ phydev->speed = SPEED_100;
-+ phydev->duplex = DUPLEX_FULL;
-+ phydev->pause = 0;
-+ phydev->asym_pause = 0;
++ ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
++ EN8811H_FW_CTRL_1_START);
++ if (ret < 0)
++ return ret;
+
-+ reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);
-+ if (reg < 0)
-+ {
-+ printf("MII_BMSR reg %d!\n", reg);
-+ return reg;
-+ }
-+ reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);
-+ if (reg < 0)
-+ {
-+ printf("MII_BMSR reg %d!\n", reg);
-+ return reg;
-+ }
-+ if(reg & BMSR_LSTATUS)
-+ {
-+ pbus_value = air_buckpbus_reg_read(phydev, 0x109D4);
-+ if (0x10 & pbus_value) {
-+ phydev->speed = SPEED_2500;
-+ phydev->duplex = DUPLEX_FULL;
-+ }
-+ else
-+ {
-+ ret = en8811h_get_autonego(phydev, &an);
-+ if ((AUTONEG_ENABLE == an) && (0 == ret))
-+ {
-+ printf("AN mode!\n");
-+ printf("SPEED 1000/100!\n");
-+ lpagb = phy_read(phydev, MDIO_DEVAD_NONE, MII_STAT1000);
-+ if (lpagb < 0 )
-+ return lpagb;
-+ advgb = phy_read(phydev, MDIO_DEVAD_NONE, MII_CTRL1000);
-+ if (adv < 0 )
-+ return adv;
-+ common_adv_gb = (lpagb & (advgb << 2));
-+
-+ lpa = phy_read(phydev, MDIO_DEVAD_NONE, MII_LPA);
-+ if (lpa < 0 )
-+ return lpa;
-+ adv = phy_read(phydev, MDIO_DEVAD_NONE, MII_ADVERTISE);
-+ if (adv < 0 )
-+ return adv;
-+ common_adv = (lpa & adv);
-+
-+ phydev->speed = SPEED_10;
-+ phydev->duplex = DUPLEX_HALF;
-+ if (common_adv_gb & (LPA_1000FULL | LPA_1000HALF))
-+ {
-+ phydev->speed = SPEED_1000;
-+ if (common_adv_gb & LPA_1000FULL)
-+
-+ phydev->duplex = DUPLEX_FULL;
-+ }
-+ else if (common_adv & (LPA_100FULL | LPA_100HALF))
-+ {
-+ phydev->speed = SPEED_100;
-+ if (common_adv & LPA_100FULL)
-+ phydev->duplex = DUPLEX_FULL;
-+ }
-+ else
-+ {
-+ if (common_adv & LPA_10FULL)
-+ phydev->duplex = DUPLEX_FULL;
-+ }
-+ }
-+ else
-+ {
-+ printf("Force mode!\n");
-+ bmcr = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
-+
-+ if (bmcr < 0)
-+ return bmcr;
-+
-+ if (bmcr & BMCR_FULLDPLX)
-+ phydev->duplex = DUPLEX_FULL;
-+ else
-+ phydev->duplex = DUPLEX_HALF;
-+
-+ if (bmcr & BMCR_SPEED1000)
-+ phydev->speed = SPEED_1000;
-+ else if (bmcr & BMCR_SPEED100)
-+ phydev->speed = SPEED_100;
-+ else
-+ phydev->speed = SPEED_100;
-+ }
-+ }
-+ }
-+
-+ return ret;
++ return air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
++ EN8811H_FW_CTRL_1_FINISH);
+}
+
-+#if AIR_UBOOT_REVISION > 0x202303
-+U_BOOT_PHY_DRIVER(en8811h) = {
-+ .name = "Airoha EN8811H",
-+ .uid = EN8811H_PHY_ID,
-+ .mask = 0x0ffffff0,
-+ .config = &en8811h_config,
-+ .startup = &en8811h_startup,
-+ .shutdown = &genphy_shutdown,
-+};
-+#else
-+static struct phy_driver AIR_EN8811H_driver = {
-+ .name = "Airoha EN8811H",
-+ .uid = EN8811H_PHY_ID,
-+ .mask = 0x0ffffff0,
-+ .config = &en8811h_config,
-+ .startup = &en8811h_startup,
-+ .shutdown = &genphy_shutdown,
++static int air_led_hw_control_set(struct phy_device *phydev, u8 index,
++ unsigned long rules)
++{
++ struct en8811h_priv *priv = phydev->priv;
++ u16 on = 0, blink = 0;
++ int ret;
++
++ if (index >= EN8811H_LED_COUNT)
++ return -EINVAL;
++
++ priv->led[index].rules = rules;
++
++ if (rules & BIT(AIR_TRIGGER_NETDEV_FULL_DUPLEX))
++ on |= AIR_PHY_LED_ON_FDX;
++
++ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_10) | BIT(AIR_TRIGGER_NETDEV_LINK)))
++ on |= AIR_PHY_LED_ON_LINK10;
++
++ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_100) | BIT(AIR_TRIGGER_NETDEV_LINK)))
++ on |= AIR_PHY_LED_ON_LINK100;
++
++ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_1000) | BIT(AIR_TRIGGER_NETDEV_LINK)))
++ on |= AIR_PHY_LED_ON_LINK1000;
++
++ if (rules & (BIT(AIR_TRIGGER_NETDEV_LINK_2500) | BIT(AIR_TRIGGER_NETDEV_LINK)))
++ on |= AIR_PHY_LED_ON_LINK2500;
++
++ if (rules & BIT(AIR_TRIGGER_NETDEV_RX)) {
++ blink |= AIR_PHY_LED_BLINK_10RX |
++ AIR_PHY_LED_BLINK_100RX |
++ AIR_PHY_LED_BLINK_1000RX |
++ AIR_PHY_LED_BLINK_2500RX;
++ }
++
++ if (rules & BIT(AIR_TRIGGER_NETDEV_TX)) {
++ blink |= AIR_PHY_LED_BLINK_10TX |
++ AIR_PHY_LED_BLINK_100TX |
++ AIR_PHY_LED_BLINK_1000TX |
++ AIR_PHY_LED_BLINK_2500TX;
++ }
++
++ if (blink || on) {
++ /* switch hw-control on, so led-on and led-blink are off */
++ clear_bit(AIR_PHY_LED_STATE_FORCE_ON,
++ &priv->led[index].state);
++ clear_bit(AIR_PHY_LED_STATE_FORCE_BLINK,
++ &priv->led[index].state);
++ } else {
++ priv->led[index].rules = 0;
++ }
++
++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_ON(index),
++ AIR_PHY_LED_ON_MASK, on);
++
++ if (ret < 0)
++ return ret;
++
++ return phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BLINK(index),
++ blink);
+};
+
-+int phy_air_en8811h_init(void)
++static int air_led_init(struct phy_device *phydev, u8 index, u8 state, u8 pol)
+{
-+ phy_register(&AIR_EN8811H_driver);
-+ return 0;
-+}
-+#endif
---- /dev/null
-+++ b/drivers/net/phy/air_en8811h.h
-@@ -0,0 +1,163 @@
-+/* SPDX-License-Identifier: GPL-2.0 */
-+/*************************************************
-+ * FILE NAME: air_en8811h.h
-+ * PURPOSE:
-+ * EN8811H PHY Driver for Uboot
-+ * NOTES:
-+ *
-+ * Copyright (C) 2023 Airoha Technology Corp.
-+ *************************************************/
-+
-+#ifndef __EN8811H_H
-+#define __EN8811H_H
-+
-+#define AIR_UBOOT_REVISION ((((U_BOOT_VERSION_NUM / 1000) % 10) << 20) | \
-+ (((U_BOOT_VERSION_NUM / 100) % 10) << 16) | \
-+ (((U_BOOT_VERSION_NUM / 10) % 10) << 12) | \
-+ ((U_BOOT_VERSION_NUM % 10) << 8) | \
-+ (((U_BOOT_VERSION_NUM_PATCH / 10) % 10) << 4) | \
-+ ((U_BOOT_VERSION_NUM_PATCH % 10) << 0))
-+
-+#define EN8811H_PHY_ID1 0x03a2
-+#define EN8811H_PHY_ID2 0xa411
-+#define EN8811H_PHY_ID ((EN8811H_PHY_ID1 << 16) | EN8811H_PHY_ID2)
-+#define EN8811H_SPEED_2500 0x03
-+#define EN8811H_PHY_READY 0x02
-+#define MAX_RETRY 5
-+
-+#define EN8811H_MD32_DM_SIZE 0x4000
-+#define EN8811H_MD32_DSP_SIZE 0x20000
++ int val = 0;
++ int err;
+
-+#define EN8811H_TX_POLARITY_NORMAL 0x1
-+#define EN8811H_TX_POLARITY_REVERSE 0x0
++ if (index >= EN8811H_LED_COUNT)
++ return -EINVAL;
+
-+#define EN8811H_RX_POLARITY_NORMAL (0x0 << 1)
-+#define EN8811H_RX_POLARITY_REVERSE (0x1 << 1)
++ if (state == AIR_LED_ENABLE)
++ val |= AIR_PHY_LED_ON_ENABLE;
++ else
++ val &= ~AIR_PHY_LED_ON_ENABLE;
+
-+#ifndef BIT
-+#define BIT(nr) (1UL << (nr))
-+#endif
++ if (pol == AIR_ACTIVE_HIGH)
++ val |= AIR_PHY_LED_ON_POLARITY;
++ else
++ val &= ~AIR_PHY_LED_ON_POLARITY;
+
-+/* CL45 MDIO control */
-+#define MII_MMD_ACC_CTL_REG 0x0d
-+#define MII_MMD_ADDR_DATA_REG 0x0e
-+#define MMD_OP_MODE_DATA BIT(14)
-+/* MultiGBASE-T AN register */
-+#define MULTIG_ANAR_2500M (0x0080)
-+#define MULTIG_LPAR_2500M (0x0020)
-+
-+#define EN8811H_DRIVER_VERSION "v1.0.4"
-+
-+/************************************************************
-+ * For reference only
-+ * LED0 Link 2500/Blink 2500 TxRx (GPIO5) <-> BASE_T_LED0,
-+ * LED1 Link 1000/Blink 1000 TxRx (GPIO4) <-> BASE_T_LED1,
-+ * LED2 Link 100/Blink 100 TxRx (GPIO3) <-> BASE_T_LED2,
-+ ************************************************************/
-+/* User-defined.B */
-+#define AIR_LED0_ON (LED_ON_EVT_LINK_2500M)
-+#define AIR_LED0_BLK (LED_BLK_EVT_2500M_TX_ACT | LED_BLK_EVT_2500M_RX_ACT)
-+#define AIR_LED1_ON (LED_ON_EVT_LINK_1000M)
-+#define AIR_LED1_BLK (LED_BLK_EVT_1000M_TX_ACT | LED_BLK_EVT_1000M_RX_ACT)
-+#define AIR_LED2_ON (LED_ON_EVT_LINK_100M)
-+#define AIR_LED2_BLK (LED_BLK_EVT_100M_TX_ACT | LED_BLK_EVT_100M_RX_ACT)
-+/* User-defined.E */
++ err = phy_write_mmd(phydev, 0x1f, AIR_PHY_LED_ON(index), val);
++ if (err < 0)
++ return err;
+
-+#define LED_ON_CTRL(i) (0x024 + ((i)*2))
-+#define LED_ON_EN (1 << 15)
-+#define LED_ON_POL (1 << 14)
-+#define LED_ON_EVT_MASK (0x1ff)
-+/* LED ON Event Option.B */
-+#define LED_ON_EVT_LINK_2500M (1 << 8)
-+#define LED_ON_EVT_FORCE (1 << 6)
-+#define LED_ON_EVT_HDX (1 << 5)
-+#define LED_ON_EVT_FDX (1 << 4)
-+#define LED_ON_EVT_LINK_DOWN (1 << 3)
-+#define LED_ON_EVT_LINK_100M (1 << 1)
-+#define LED_ON_EVT_LINK_1000M (1 << 0)
-+/* LED ON Event Option.E */
-+
-+#define LED_BLK_CTRL(i) (0x025 + ((i)*2))
-+#define LED_BLK_EVT_MASK (0xfff)
-+/* LED Blinking Event Option.B*/
-+#define LED_BLK_EVT_2500M_RX_ACT (1 << 11)
-+#define LED_BLK_EVT_2500M_TX_ACT (1 << 10)
-+#define LED_BLK_EVT_FORCE (1 << 9)
-+#define LED_BLK_EVT_100M_RX_ACT (1 << 3)
-+#define LED_BLK_EVT_100M_TX_ACT (1 << 2)
-+#define LED_BLK_EVT_1000M_RX_ACT (1 << 1)
-+#define LED_BLK_EVT_1000M_TX_ACT (1 << 0)
-+/* LED Blinking Event Option.E*/
-+#define LED_ENABLE 1
-+#define LED_DISABLE 0
++ return 0;
++}
+
-+#define EN8811H_LED_COUNT 3
++static int air_leds_init(struct phy_device *phydev, int num, u16 dur, int mode)
++{
++ struct en8811h_priv *priv = phydev->priv;
++ int ret, i;
++
++ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_BLINK,
++ dur);
++ if (ret < 0)
++ return ret;
++
++ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_ON,
++ dur >> 1);
++ if (ret < 0)
++ return ret;
++
++ switch (mode) {
++ case AIR_LED_MODE_DISABLE:
++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR,
++ AIR_PHY_LED_BCR_EXT_CTRL |
++ AIR_PHY_LED_BCR_MODE_MASK, 0);
++ break;
++ case AIR_LED_MODE_USER_DEFINE:
++ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR,
++ AIR_PHY_LED_BCR_EXT_CTRL |
++ AIR_PHY_LED_BCR_CLK_EN,
++ AIR_PHY_LED_BCR_EXT_CTRL |
++ AIR_PHY_LED_BCR_CLK_EN);
++ if (ret < 0)
++ return ret;
++ break;
++ default:
++ printf("LED mode %d is not supported\n", mode);
++ return -EINVAL;
++ }
++
++ for (i = 0; i < num; ++i) {
++ ret = air_led_init(phydev, i, AIR_LED_ENABLE, AIR_ACTIVE_HIGH);
++ if (ret < 0) {
++ printf("LED%d init failed: %d\n", i, ret);
++ return ret;
++ }
++ air_led_hw_control_set(phydev, i, priv->led[i].rules);
++ }
++
++ return 0;
++}
+
-+#define LED_BCR (0x021)
-+#define LED_BCR_EXT_CTRL (1 << 15)
-+#define LED_BCR_CLK_EN (1 << 3)
-+#define LED_BCR_TIME_TEST (1 << 2)
-+#define LED_BCR_MODE_MASK (3)
-+#define LED_BCR_MODE_DISABLE (0)
-+#define LED_BCR_MODE_2LED (1)
-+#define LED_BCR_MODE_3LED_1 (2)
-+#define LED_BCR_MODE_3LED_2 (3)
++static int en8811h_config(struct phy_device *phydev)
++{
++ struct en8811h_priv *priv = phydev->priv;
++ ofnode node = phy_get_ofnode(phydev);
++ u32 pbus_value = 0;
++ int ret = 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);
++ if (ret < 0)
++ return ret;
++ ret = phy_write_mmd(phydev, 0x1e, 0x800d, 0x0);
++ if (ret < 0)
++ return ret;
++ ret = phy_write_mmd(phydev, 0x1e, 0x800e, 0x1101);
++ if (ret < 0)
++ return ret;
++ ret = phy_write_mmd(phydev, 0x1e, 0x800f, 0x0002);
++ 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;
++}
+
-+#define LED_ON_DUR (0x022)
-+#define LED_ON_DUR_MASK (0xffff)
++static int en8811h_parse_status(struct phy_device *phydev)
++{
++ int ret = 0, reg_value;
++
++ phydev->duplex = DUPLEX_FULL;
++
++ reg_value = phy_read(phydev, MDIO_DEVAD_NONE, AIR_AUX_CTRL_STATUS);
++ if (reg_value < 0)
++ return reg_value;
++
++ switch (reg_value & AIR_AUX_CTRL_STATUS_SPEED_MASK) {
++ case AIR_AUX_CTRL_STATUS_SPEED_2500:
++ phydev->speed = SPEED_2500;
++ break;
++ case AIR_AUX_CTRL_STATUS_SPEED_1000:
++ phydev->speed = SPEED_1000;
++ break;
++ case AIR_AUX_CTRL_STATUS_SPEED_100:
++ phydev->speed = SPEED_100;
++ break;
++ default:
++ printf("Auto-neg error, defaulting to 100M/FD\n");
++ phydev->speed = SPEED_100;
++ break;
++ }
++
++ return ret;
++}
+
-+#define LED_BLK_DUR (0x023)
-+#define LED_BLK_DUR_MASK (0xffff)
++static int en8811h_startup(struct phy_device *phydev)
++{
++ int ret = 0;
+
-+#define LED_GPIO_SEL_MASK 0x7FFFFFF
++ ret = genphy_update_link(phydev);
++ if (ret)
++ return ret;
+
-+#define UNIT_LED_BLINK_DURATION 1024
++ return en8811h_parse_status(phydev);
++}
+
-+#define INVALID_DATA 0xffff
-+#define PBUS_INVALID_DATA 0xffffffff
++static int en8811h_probe(struct phy_device *phydev)
++{
++ struct en8811h_priv *priv;
+
-+struct air_base_t_led_cfg_s {
-+ u16 en;
-+ u16 gpio;
-+ u16 pol;
-+ u16 on_cfg;
-+ u16 blk_cfg;
-+};
++ priv = malloc(sizeof(*priv));
++ if (!priv)
++ return -ENOMEM;
++ memset(priv, 0, sizeof(*priv));
+
-+enum {
-+ AIR_LED2_GPIO3 = 3,
-+ AIR_LED1_GPIO4,
-+ AIR_LED0_GPIO5,
-+ AIR_LED_LAST
-+};
++ priv->led[0].rules = AIR_DEFAULT_TRIGGER_LED0;
++ priv->led[1].rules = AIR_DEFAULT_TRIGGER_LED1;
++ priv->led[2].rules = AIR_DEFAULT_TRIGGER_LED2;
+
-+enum {
-+ AIR_BASE_T_LED0,
-+ AIR_BASE_T_LED1,
-+ AIR_BASE_T_LED2,
-+ AIR_BASE_T_LED3
-+};
++ /* mcu has just restarted after firmware load */
++ priv->mcu_needs_restart = false;
+
-+enum {
-+ AIR_LED_BLK_DUR_32M,
-+ AIR_LED_BLK_DUR_64M,
-+ AIR_LED_BLK_DUR_128M,
-+ AIR_LED_BLK_DUR_256M,
-+ AIR_LED_BLK_DUR_512M,
-+ AIR_LED_BLK_DUR_1024M,
-+ AIR_LED_BLK_DUR_LAST
-+};
++ phydev->priv = priv;
+
-+enum {
-+ AIR_ACTIVE_LOW,
-+ AIR_ACTIVE_HIGH,
-+};
++ return 0;
++}
+
-+enum {
-+ AIR_LED_MODE_DISABLE,
-+ AIR_LED_MODE_USER_DEFINE,
-+ AIR_LED_MODE_LAST
++U_BOOT_PHY_DRIVER(en8811h) = {
++ .name = "Airoha EN8811H",
++ .uid = EN8811H_PHY_ID,
++ .mask = 0x0ffffff0,
++ .config = &en8811h_config,
++ .probe = &en8811h_probe,
++ .startup = &en8811h_startup,
++ .shutdown = &genphy_shutdown,
+};
-+
-+#endif /* End of __EN8811H_MD32_H */
-+
---- a/drivers/net/eth-phy-uclass.c
-+++ b/drivers/net/eth-phy-uclass.c
-@@ -154,7 +154,7 @@ static int eth_phy_of_to_plat(struct ude
- return 0;
- }
-
--static void eth_phy_reset(struct udevice *dev, int value)
-+void eth_phy_reset(struct udevice *dev, int value)
- {
- struct eth_phy_device_priv *uc_priv = dev_get_uclass_priv(dev);
- u32 delay;
---- a/include/eth_phy.h
-+++ b/include/eth_phy.h
-@@ -14,5 +14,6 @@ int eth_phy_binds_nodes(struct udevice *
- int eth_phy_set_mdio_bus(struct udevice *eth_dev, struct mii_dev *mdio_bus);
- struct mii_dev *eth_phy_get_mdio_bus(struct udevice *eth_dev);
- int eth_phy_get_addr(struct udevice *dev);
-+void eth_phy_reset(struct udevice *dev, int value);
-
- #endif