root/drivers/net/phy/air_en8811h.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Driver for the Airoha EN8811H and AN8811HB 2.5 Gigabit PHYs.
 *
 * 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, 2026 Airoha Technology Corp.
 */

#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/phy.h>
#include <linux/phy/phy-common-props.h>
#include <linux/firmware.h>
#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)
#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

/* 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
#define AIR_PHY_MCU_CMD_1_MODE1                 0x0
#define AIR_PHY_MCU_CMD_2               0x800d
#define AIR_PHY_MCU_CMD_2_MODE1                 0x0
#define AIR_PHY_MCU_CMD_3               0x800e
#define AIR_PHY_MCU_CMD_3_MODE1                 0x1101
#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_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)

/* 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_VERSION              0x3b3c

#define EN8811H_POLARITY                0xca0f8
#define   EN8811H_POLARITY_TX_NORMAL            BIT(0)
#define   EN8811H_POLARITY_RX_REVERSE           BIT(1)

#define EN8811H_GPIO_OUTPUT             0xcf8b8
#define   EN8811H_GPIO_OUTPUT_345               (BIT(3) | BIT(4) | BIT(5))

#define EN8811H_HWTRAP1                 0xcf914
#define   EN8811H_HWTRAP1_CKO                   BIT(12)
#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
#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)

#define AN8811HB_MCU_SW_RST             0x5cf9f8
#define   AN8811HB_MCU_SW_RST_HOLD              BIT(16)
#define   AN8811HB_MCU_SW_RST_RUN               (BIT(16) | BIT(0))
#define AN8811HB_MCU_SW_START           0x5cf9fc
#define   AN8811HB_MCU_SW_START_EN              BIT(16)

/* MII register constants for PBUS access (PHY addr + 8) */
#define AIR_PBUS_ADDR_HIGH              0x1c
#define AIR_PBUS_DATA_HIGH              0x10
#define AIR_PBUS_REG_ADDR_HIGH_MASK     GENMASK(15, 6)
#define AIR_PBUS_REG_ADDR_LOW_MASK      GENMASK(5, 2)

/* Led definitions */
#define EN8811H_LED_COUNT       3

#define EN8811H_PBUS_ADDR_OFFS  8

/* Default LED setup:
 * GPIO5 <-> LED0  On: Link detected, blink Rx/Tx
 * GPIO4 <-> LED1  On: Link detected at 2500 or 1000 Mbps
 * GPIO3 <-> LED2  On: Link detected at 2500 or  100 Mbps
 */
#define AIR_DEFAULT_TRIGGER_LED0 (BIT(TRIGGER_NETDEV_LINK)      | \
                                  BIT(TRIGGER_NETDEV_RX)        | \
                                  BIT(TRIGGER_NETDEV_TX))
#define AIR_DEFAULT_TRIGGER_LED1 (BIT(TRIGGER_NETDEV_LINK_2500) | \
                                  BIT(TRIGGER_NETDEV_LINK_1000))
#define AIR_DEFAULT_TRIGGER_LED2 (BIT(TRIGGER_NETDEV_LINK_2500) | \
                                  BIT(TRIGGER_NETDEV_LINK_100))

struct led {
        unsigned long rules;
        unsigned long state;
};

#define clk_hw_to_en8811h_priv(_hw)                     \
        container_of(_hw, struct en8811h_priv, hw)

struct en8811h_priv {
        u32                     firmware_version;
        bool                    mcu_needs_restart;
        struct led              led[EN8811H_LED_COUNT];
        struct clk_hw           hw;
        struct phy_device       *phydev;
        unsigned int            cko_is_enabled;
        struct mdio_device      *pbusdev;
};

enum {
        AIR_PHY_LED_STATE_FORCE_ON,
        AIR_PHY_LED_STATE_FORCE_BLINK,
};

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,
};

enum {
        AIR_LED_DISABLE,
        AIR_LED_ENABLE,
};

enum {
        AIR_ACTIVE_LOW,
        AIR_ACTIVE_HIGH,
};

enum {
        AIR_LED_MODE_DISABLE,
        AIR_LED_MODE_USER_DEFINE,
};

#define AIR_PHY_LED_DUR_UNIT    1024
#define AIR_PHY_LED_DUR (AIR_PHY_LED_DUR_UNIT << AIR_PHY_LED_DUR_BLINK_64MS)

static const unsigned long en8811h_led_trig = BIT(TRIGGER_NETDEV_FULL_DUPLEX) |
                                              BIT(TRIGGER_NETDEV_LINK)        |
                                              BIT(TRIGGER_NETDEV_LINK_10)     |
                                              BIT(TRIGGER_NETDEV_LINK_100)    |
                                              BIT(TRIGGER_NETDEV_LINK_1000)   |
                                              BIT(TRIGGER_NETDEV_LINK_2500)   |
                                              BIT(TRIGGER_NETDEV_RX)          |
                                              BIT(TRIGGER_NETDEV_TX);

static int __air_pbus_reg_write(struct mdio_device *mdiodev,
                                u32 pbus_reg, u32 pbus_data)
{
        int ret;

        ret = __mdiobus_write(mdiodev->bus, mdiodev->addr, AIR_EXT_PAGE_ACCESS,
                              upper_16_bits(pbus_reg));
        if (ret < 0)
                return ret;

        ret = __mdiobus_write(mdiodev->bus, mdiodev->addr, AIR_PBUS_ADDR_HIGH,
                              FIELD_GET(AIR_PBUS_REG_ADDR_HIGH_MASK, pbus_reg));
        if (ret < 0)
                return ret;

        ret = __mdiobus_write(mdiodev->bus, mdiodev->addr,
                              FIELD_GET(AIR_PBUS_REG_ADDR_LOW_MASK, pbus_reg),
                              lower_16_bits(pbus_data));
        if (ret < 0)
                return ret;

        return __mdiobus_write(mdiodev->bus, mdiodev->addr, AIR_PBUS_DATA_HIGH,
                               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;
}

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_phy_buckpbus_reg_modify(phydev, set1,
                                          AN8811HB_CRC_RD_EN,
                                          AN8811HB_CRC_RD_EN);
        if (ret < 0)
                return ret;
        air_phy_buckpbus_reg_read(phydev, set1, &pbus_value);

        do {
                msleep(300);
                air_phy_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_phy_buckpbus_reg_read(phydev, mon3, &pbus_value);
                        phydev_dbg(phydev, "CRC Check %s!\n",
                                   pbus_value & AN8811HB_CRC_CHECK_PASS ?
                                        "PASS" : "FAIL");
                        return air_phy_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;

        air_phy_buckpbus_reg_read(phydev, EN8811H_FW_VERSION,
                                  &priv->firmware_version);
        phydev_info(phydev, "MD32 firmware version: %08x\n",
                    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_mcu_assert(struct phy_device *phydev)
{
        struct en8811h_priv *priv = phydev->priv;
        int ret;

        phy_lock_mdio_bus(phydev);

        ret = __air_pbus_reg_write(priv->pbusdev, AN8811HB_MCU_SW_RST,
                                   AN8811HB_MCU_SW_RST_HOLD);
        if (ret < 0)
                goto unlock;

        ret = __air_pbus_reg_write(priv->pbusdev, AN8811HB_MCU_SW_START, 0);
        if (ret < 0)
                goto unlock;

        msleep(50);
        phydev_dbg(phydev, "MCU asserted\n");

unlock:
        phy_unlock_mdio_bus(phydev);
        return ret;
}

static int an8811hb_mcu_deassert(struct phy_device *phydev)
{
        struct en8811h_priv *priv = phydev->priv;
        int ret;

        phy_lock_mdio_bus(phydev);

        ret = __air_pbus_reg_write(priv->pbusdev, AN8811HB_MCU_SW_START,
                                   AN8811HB_MCU_SW_START_EN);
        if (ret < 0)
                goto unlock;

        ret = __air_pbus_reg_write(priv->pbusdev, AN8811HB_MCU_SW_RST,
                                   AN8811HB_MCU_SW_RST_RUN);
        if (ret < 0)
                goto unlock;

        msleep(50);
        phydev_dbg(phydev, "MCU deasserted\n");

unlock:
        phy_unlock_mdio_bus(phydev);
        return ret;
}

static int an8811hb_load_firmware(struct phy_device *phydev)
{
        int ret;

        ret = an8811hb_mcu_assert(phydev);
        if (ret < 0)
                return ret;

        ret = an8811hb_mcu_deassert(phydev);
        if (ret < 0)
                return ret;

        ret = air_phy_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;
        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 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);

        if (ret < 0)
                phydev_err(phydev, "Load firmware failed: %d\n", ret);

        return ret;
}

static int en8811h_restart_mcu(struct phy_device *phydev)
{
        int ret;

        if (phy_id_compare_model(phydev->phy_id, AN8811HB_PHY_ID)) {
                ret = an8811hb_mcu_assert(phydev);
                if (ret < 0)
                        return ret;

                ret = an8811hb_mcu_deassert(phydev);
                if (ret < 0)
                        return ret;
        }

        ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
                                         EN8811H_FW_CTRL_1_START);
        if (ret < 0)
                return ret;

        return en8811h_wait_mcu_ready(phydev);
}

static int air_hw_led_on_set(struct phy_device *phydev, u8 index, bool on)
{
        struct en8811h_priv *priv = phydev->priv;
        bool changed;

        if (index >= EN8811H_LED_COUNT)
                return -EINVAL;

        if (on)
                changed = !test_and_set_bit(AIR_PHY_LED_STATE_FORCE_ON,
                                            &priv->led[index].state);
        else
                changed = !!test_and_clear_bit(AIR_PHY_LED_STATE_FORCE_ON,
                                               &priv->led[index].state);

        changed |= (priv->led[index].rules != 0);

        /* clear netdev trigger rules in case LED_OFF has been set */
        if (!on)
                priv->led[index].rules = 0;

        if (changed)
                return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
                                      AIR_PHY_LED_ON(index),
                                      AIR_PHY_LED_ON_MASK,
                                      on ? AIR_PHY_LED_ON_FORCE_ON : 0);

        return 0;
}

static int air_hw_led_blink_set(struct phy_device *phydev, u8 index,
                                bool blinking)
{
        struct en8811h_priv *priv = phydev->priv;
        bool changed;

        if (index >= EN8811H_LED_COUNT)
                return -EINVAL;

        if (blinking)
                changed = !test_and_set_bit(AIR_PHY_LED_STATE_FORCE_BLINK,
                                            &priv->led[index].state);
        else
                changed = !!test_and_clear_bit(AIR_PHY_LED_STATE_FORCE_BLINK,
                                               &priv->led[index].state);

        changed |= (priv->led[index].rules != 0);

        if (changed)
                return phy_write_mmd(phydev, MDIO_MMD_VEND2,
                                     AIR_PHY_LED_BLINK(index),
                                     blinking ?
                                     AIR_PHY_LED_BLINK_FORCE_BLINK : 0);
        else
                return 0;
}

static int air_led_blink_set(struct phy_device *phydev, u8 index,
                             unsigned long *delay_on,
                             unsigned long *delay_off)
{
        struct en8811h_priv *priv = phydev->priv;
        bool blinking = false;
        int err;

        if (index >= EN8811H_LED_COUNT)
                return -EINVAL;

        if (delay_on && delay_off && (*delay_on > 0) && (*delay_off > 0)) {
                blinking = true;
                *delay_on = 50;
                *delay_off = 50;
        }

        err = air_hw_led_blink_set(phydev, index, blinking);
        if (err)
                return err;

        /* led-blink set, so switch led-on off */
        err = air_hw_led_on_set(phydev, index, false);
        if (err)
                return err;

        /* hw-control is off*/
        if (!!test_bit(AIR_PHY_LED_STATE_FORCE_BLINK, &priv->led[index].state))
                priv->led[index].rules = 0;

        return 0;
}

static int air_led_brightness_set(struct phy_device *phydev, u8 index,
                                  enum led_brightness value)
{
        struct en8811h_priv *priv = phydev->priv;
        int err;

        if (index >= EN8811H_LED_COUNT)
                return -EINVAL;

        /* led-on set, so switch led-blink off */
        err = air_hw_led_blink_set(phydev, index, false);
        if (err)
                return err;

        err = air_hw_led_on_set(phydev, index, (value != LED_OFF));
        if (err)
                return err;

        /* hw-control is off */
        if (!!test_bit(AIR_PHY_LED_STATE_FORCE_ON, &priv->led[index].state))
                priv->led[index].rules = 0;

        return 0;
}

static int air_led_hw_control_get(struct phy_device *phydev, u8 index,
                                  unsigned long *rules)
{
        struct en8811h_priv *priv = phydev->priv;

        if (index >= EN8811H_LED_COUNT)
                return -EINVAL;

        *rules = priv->led[index].rules;

        return 0;
};

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(TRIGGER_NETDEV_FULL_DUPLEX))
                on |= AIR_PHY_LED_ON_FDX;

        if (rules & (BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK)))
                on |= AIR_PHY_LED_ON_LINK10;

        if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK)))
                on |= AIR_PHY_LED_ON_LINK100;

        if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK)))
                on |= AIR_PHY_LED_ON_LINK1000;

        if (rules & (BIT(TRIGGER_NETDEV_LINK_2500) | BIT(TRIGGER_NETDEV_LINK)))
                on |= AIR_PHY_LED_ON_LINK2500;

        if (rules & BIT(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(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);
};

static int air_led_init(struct phy_device *phydev, u8 index, u8 state, u8 pol)
{
        int val = 0;
        int err;

        if (index >= EN8811H_LED_COUNT)
                return -EINVAL;

        if (state == AIR_LED_ENABLE)
                val |= AIR_PHY_LED_ON_ENABLE;
        else
                val &= ~AIR_PHY_LED_ON_ENABLE;

        if (pol == AIR_ACTIVE_HIGH)
                val |= AIR_PHY_LED_ON_POLARITY;
        else
                val &= ~AIR_PHY_LED_ON_POLARITY;

        err = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_ON(index),
                             AIR_PHY_LED_ON_ENABLE |
                             AIR_PHY_LED_ON_POLARITY, val);

        if (err < 0)
                return err;

        return 0;
}

static int air_leds_init(struct phy_device *phydev, int num, int 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);
                if (ret < 0)
                        return ret;
                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:
                phydev_err(phydev, "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) {
                        phydev_err(phydev, "LED%d init failed: %d\n", i, ret);
                        return ret;
                }
                air_led_hw_control_set(phydev, i, priv->led[i].rules);
        }

        return 0;
}

static int en8811h_led_hw_is_supported(struct phy_device *phydev, u8 index,
                                       unsigned long rules)
{
        if (index >= EN8811H_LED_COUNT)
                return -EINVAL;

        /* All combinations of the supported triggers are allowed */
        if (rules & ~en8811h_led_trig)
                return -EOPNOTSUPP;

        return 0;
};

static unsigned long an8811hb_clk_recalc_rate(struct clk_hw *hw,
                                              unsigned long parent)
{
        struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);
        struct phy_device *phydev = priv->phydev;
        u32 pbus_value;
        int ret;

        ret = air_phy_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value);
        if (ret < 0)
                return ret;

        return (pbus_value & AN8811HB_HWTRAP2_CKO) ? 50000000 : 25000000;
}

static int an8811hb_clk_enable(struct clk_hw *hw)
{
        struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);
        struct phy_device *phydev = priv->phydev;

        return air_phy_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV,
                                           AN8811HB_CLK_DRV_CKO_MASK,
                                           AN8811HB_CLK_DRV_CKO_MASK);
}

static void an8811hb_clk_disable(struct clk_hw *hw)
{
        struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);
        struct phy_device *phydev = priv->phydev;

        air_phy_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV,
                                    AN8811HB_CLK_DRV_CKO_MASK, 0);
}

static int an8811hb_clk_is_enabled(struct clk_hw *hw)
{
        struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);
        struct phy_device *phydev = priv->phydev;
        u32 pbus_value;
        int ret;

        ret = air_phy_buckpbus_reg_read(phydev, AN8811HB_CLK_DRV, &pbus_value);
        if (ret < 0)
                return ret;

        return (pbus_value & AN8811HB_CLK_DRV_CKO_MASK);
}

static int an8811hb_clk_save_context(struct clk_hw *hw)
{
        struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);

        priv->cko_is_enabled = an8811hb_clk_is_enabled(hw);

        return 0;
}

static void an8811hb_clk_restore_context(struct clk_hw *hw)
{
        struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);

        if (!priv->cko_is_enabled)
                an8811hb_clk_disable(hw);
}

static const struct clk_ops an8811hb_clk_ops = {
        .recalc_rate            = an8811hb_clk_recalc_rate,
        .enable                 = an8811hb_clk_enable,
        .disable                = an8811hb_clk_disable,
        .is_enabled             = an8811hb_clk_is_enabled,
        .save_context           = an8811hb_clk_save_context,
        .restore_context        = an8811hb_clk_restore_context,
};

static int an8811hb_clk_provider_setup(struct device *dev, struct clk_hw *hw)
{
        struct clk_init_data init;
        int ret;

        if (!IS_ENABLED(CONFIG_COMMON_CLK))
                return 0;

        init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-cko",
                                   fwnode_get_name(dev_fwnode(dev)));
        if (!init.name)
                return -ENOMEM;

        init.ops = &an8811hb_clk_ops;
        init.flags = 0;
        init.num_parents = 0;
        hw->init = &init;

        ret = devm_clk_hw_register(dev, hw);
        if (ret)
                return ret;

        return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw);
}

static unsigned long en8811h_clk_recalc_rate(struct clk_hw *hw,
                                             unsigned long parent)
{
        struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);
        struct phy_device *phydev = priv->phydev;
        u32 pbus_value;
        int ret;

        ret = air_phy_buckpbus_reg_read(phydev, EN8811H_HWTRAP1, &pbus_value);
        if (ret < 0)
                return ret;

        return (pbus_value & EN8811H_HWTRAP1_CKO) ? 50000000 : 25000000;
}

static int en8811h_clk_enable(struct clk_hw *hw)
{
        struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);
        struct phy_device *phydev = priv->phydev;

        return air_phy_buckpbus_reg_modify(phydev, EN8811H_CLK_CGM,
                                           EN8811H_CLK_CGM_CKO,
                                           EN8811H_CLK_CGM_CKO);
}

static void en8811h_clk_disable(struct clk_hw *hw)
{
        struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);
        struct phy_device *phydev = priv->phydev;

        air_phy_buckpbus_reg_modify(phydev, EN8811H_CLK_CGM,
                                    EN8811H_CLK_CGM_CKO, 0);
}

static int en8811h_clk_is_enabled(struct clk_hw *hw)
{
        struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);
        struct phy_device *phydev = priv->phydev;
        u32 pbus_value;
        int ret;

        ret = air_phy_buckpbus_reg_read(phydev, EN8811H_CLK_CGM, &pbus_value);
        if (ret < 0)
                return ret;

        return (pbus_value & EN8811H_CLK_CGM_CKO);
}

static int en8811h_clk_save_context(struct clk_hw *hw)
{
        struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);

        priv->cko_is_enabled = en8811h_clk_is_enabled(hw);

        return 0;
}

static void en8811h_clk_restore_context(struct clk_hw *hw)
{
        struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw);

        if (!priv->cko_is_enabled)
                en8811h_clk_disable(hw);
}

static const struct clk_ops en8811h_clk_ops = {
        .recalc_rate            = en8811h_clk_recalc_rate,
        .enable                 = en8811h_clk_enable,
        .disable                = en8811h_clk_disable,
        .is_enabled             = en8811h_clk_is_enabled,
        .save_context           = en8811h_clk_save_context,
        .restore_context        = en8811h_clk_restore_context,
};

static int en8811h_clk_provider_setup(struct device *dev, struct clk_hw *hw)
{
        struct clk_init_data init;
        int ret;

        if (!IS_ENABLED(CONFIG_COMMON_CLK))
                return 0;

        init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-cko",
                                   fwnode_get_name(dev_fwnode(dev)));
        if (!init.name)
                return -ENOMEM;

        init.ops = &en8811h_clk_ops;
        init.flags = 0;
        init.num_parents = 0;
        hw->init = &init;

        ret = devm_clk_hw_register(dev, hw);
        if (ret)
                return ret;

        return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw);
}

static int en8811h_leds_setup(struct phy_device *phydev)
{
        struct en8811h_priv *priv = phydev->priv;
        int ret;

        priv->led[0].rules = AIR_DEFAULT_TRIGGER_LED0;
        priv->led[1].rules = AIR_DEFAULT_TRIGGER_LED1;
        priv->led[2].rules = AIR_DEFAULT_TRIGGER_LED2;

        ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR,
                            AIR_LED_MODE_DISABLE);
        if (ret < 0)
                phydev_err(phydev, "Failed to disable leds: %d\n", ret);

        return ret;
}

static int an8811hb_probe(struct phy_device *phydev)
{
        struct mdio_device *mdiodev;
        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;

        /*
         * The AN8811HB PHY address is restricted to 8-15 (decimal),
         * depending on the board hardware strapping.
         * This means the PBUS address is only in the range 16-21 (decimal),
         * so we do not need to handle the case
         * where the PBUS address exceeds 31 (decimal).
         */
        mdiodev = mdio_device_create(phydev->mdio.bus,
                                     phydev->mdio.addr + EN8811H_PBUS_ADDR_OFFS);
        if (IS_ERR(mdiodev))
                return PTR_ERR(mdiodev);

        ret = mdio_device_register(mdiodev);
        if (ret)
                goto err_dev_free;

        priv->pbusdev = mdiodev;

        ret = an8811hb_load_firmware(phydev);
        if (ret < 0) {
                phydev_err(phydev, "Load firmware failed: %d\n", ret);
                goto err_dev_create;
        }

        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)
                goto err_dev_create;

        priv->phydev = phydev;
        /* Co-Clock Output */
        ret = an8811hb_clk_provider_setup(&phydev->mdio.dev, &priv->hw);
        if (ret)
                goto err_dev_create;

        /* Configure led gpio pins as output */
        ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT,
                                          AN8811HB_GPIO_OUTPUT_345,
                                          AN8811HB_GPIO_OUTPUT_345);
        if (ret < 0)
                goto err_dev_create;

        return 0;

err_dev_create:
        mdio_device_remove(mdiodev);

err_dev_free:
        mdio_device_free(mdiodev);
        return ret;
}

static int en8811h_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 = en8811h_load_firmware(phydev);
        if (ret < 0)
                return ret;

        /* 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;

        priv->phydev = phydev;
        /* Co-Clock Output */
        ret = en8811h_clk_provider_setup(&phydev->mdio.dev, &priv->hw);
        if (ret)
                return ret;

        /* Configure led gpio pins as output */
        ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_GPIO_OUTPUT,
                                          EN8811H_GPIO_OUTPUT_345,
                                          EN8811H_GPIO_OUTPUT_345);
        if (ret < 0)
                return ret;

        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_phy_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_phy_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;
        unsigned int pol, default_pol;
        u32 pbus_value = 0;
        int ret;

        default_pol = PHY_POL_NORMAL;
        if (device_property_read_bool(dev, "airoha,pnswap-rx"))
                default_pol = PHY_POL_INVERT;

        ret = phy_get_rx_polarity(dev_fwnode(dev), phy_modes(phydev->interface),
                                  BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
                                  default_pol, &pol);
        if (ret)
                return ret;
        if (pol == PHY_POL_INVERT)
                pbus_value |= EN8811H_POLARITY_RX_REVERSE;

        default_pol = PHY_POL_NORMAL;
        if (device_property_read_bool(dev, "airoha,pnswap-tx"))
                default_pol = PHY_POL_INVERT;

        ret = phy_get_tx_polarity(dev_fwnode(dev), phy_modes(phydev->interface),
                                  BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
                                  default_pol, &pol);
        if (ret)
                return ret;
        if (pol == PHY_POL_NORMAL)
                pbus_value |= EN8811H_POLARITY_TX_NORMAL;

        return air_phy_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
                                           EN8811H_POLARITY_RX_REVERSE |
                                           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;
        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;
        }

        /* Select mode 1, the only mode supported.
         * Configures the SerDes for 2500Base-X with rate adaptation
         */
        ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_1,
                            AIR_PHY_MCU_CMD_1_MODE1);
        if (ret < 0)
                return ret;
        ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_2,
                            AIR_PHY_MCU_CMD_2_MODE1);
        if (ret < 0)
                return ret;
        ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_3,
                            AIR_PHY_MCU_CMD_3_MODE1);
        if (ret < 0)
                return ret;
        ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_4,
                            AIR_PHY_MCU_CMD_4_MODE1);
        if (ret < 0)
                return ret;

        ret = en8811h_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;
        }

        return 0;
}

static int en8811h_get_features(struct phy_device *phydev)
{
        linkmode_set_bit_array(phy_basic_ports_array,
                               ARRAY_SIZE(phy_basic_ports_array),
                               phydev->supported);

        return genphy_c45_pma_read_abilities(phydev);
}

static int en8811h_get_rate_matching(struct phy_device *phydev,
                                     phy_interface_t iface)
{
        return RATE_MATCH_PAUSE;
}

static int en8811h_config_aneg(struct phy_device *phydev)
{
        bool changed = false;
        int ret;
        u32 adv;

        if (phydev->autoneg == AUTONEG_DISABLE) {
                phydev_warn(phydev, "Disabling autoneg is not supported\n");
                return -EINVAL;
        }

        adv = linkmode_adv_to_mii_10gbt_adv_t(phydev->advertising);

        ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
                                     MDIO_AN_10GBT_CTRL_ADV2_5G, adv);
        if (ret < 0)
                return ret;
        if (ret > 0)
                changed = true;

        return __genphy_config_aneg(phydev, changed);
}

static int en8811h_read_status(struct phy_device *phydev)
{
        struct en8811h_priv *priv = phydev->priv;
        u32 pbus_value;
        int ret, val;

        ret = genphy_update_link(phydev);
        if (ret)
                return ret;

        phydev->master_slave_get = MASTER_SLAVE_CFG_UNSUPPORTED;
        phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED;
        phydev->speed = SPEED_UNKNOWN;
        phydev->duplex = DUPLEX_UNKNOWN;
        phydev->pause = 0;
        phydev->asym_pause = 0;
        phydev->rate_matching = RATE_MATCH_PAUSE;

        ret = genphy_read_master_slave(phydev);
        if (ret < 0)
                return ret;

        ret = genphy_read_lpa(phydev);
        if (ret < 0)
                return ret;

        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_phy_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);

        if (!phydev->link)
                return 0;

        /* Get real speed from vendor register */
        val = phy_read(phydev, AIR_AUX_CTRL_STATUS);
        if (val < 0)
                return val;
        switch (val & 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;
        case AIR_AUX_CTRL_STATUS_SPEED_10:
                phydev->speed = SPEED_10;
                break;
        }

        /* Firmware before version 24011202 has no vendor register 2P5G_LPA.
         * Assume link partner advertised it if connected at 2500Mbps.
         */
        if (priv->firmware_version < 0x24011202) {
                linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
                                 phydev->lp_advertising,
                                 phydev->speed == SPEED_2500);
        }

        /* Only supports full duplex */
        phydev->duplex = DUPLEX_FULL;

        return 0;
}

static int en8811h_clear_intr(struct phy_device *phydev)
{
        int ret;

        ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_3,
                            AIR_PHY_MCU_CMD_3_DOCMD);
        if (ret < 0)
                return ret;

        ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_4,
                            AIR_PHY_MCU_CMD_4_INTCLR);
        if (ret < 0)
                return ret;

        return 0;
}

static irqreturn_t en8811h_handle_interrupt(struct phy_device *phydev)
{
        int ret;

        ret = en8811h_clear_intr(phydev);
        if (ret < 0) {
                phy_error(phydev);
                return IRQ_NONE;
        }

        phy_trigger_machine(phydev);

        return IRQ_HANDLED;
}

static int en8811h_resume(struct phy_device *phydev)
{
        clk_restore_context();

        return genphy_resume(phydev);
}

static int en8811h_suspend(struct phy_device *phydev)
{
        clk_save_context();

        return genphy_suspend(phydev);
}

static void an8811hb_remove(struct phy_device *phydev)
{
        struct en8811h_priv *priv = phydev->priv;

        if (priv->pbusdev) {
                mdio_device_remove(priv->pbusdev);
                mdio_device_free(priv->pbusdev);
        }
}

static struct phy_driver en8811h_driver[] = {
{
        PHY_ID_MATCH_MODEL(EN8811H_PHY_ID),
        .name                   = "Airoha EN8811H",
        .probe                  = en8811h_probe,
        .get_features           = en8811h_get_features,
        .config_init            = en8811h_config_init,
        .get_rate_matching      = en8811h_get_rate_matching,
        .config_aneg            = en8811h_config_aneg,
        .read_status            = en8811h_read_status,
        .resume                 = en8811h_resume,
        .suspend                = en8811h_suspend,
        .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,
},
{
        PHY_ID_MATCH_MODEL(AN8811HB_PHY_ID),
        .name                   = "Airoha AN8811HB",
        .probe                  = an8811hb_probe,
        .remove                 = an8811hb_remove,
        .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,
        .resume                 = en8811h_resume,
        .suspend                = en8811h_suspend,
        .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 and AN8811HB PHY drivers");
MODULE_AUTHOR("Airoha");
MODULE_AUTHOR("Eric Woudstra <ericwouds@gmail.com>");
MODULE_LICENSE("GPL");