// SPDX-License-Identifier: GPL-2.0+
/*
- * Driver for the Airoha EN8811H 2.5 Gigabit PHY.
+ * Driver for the Airoha EN8811H and AN8811HB 2.5 Gigabit PHY.
*
* Limitations of the EN8811H:
* - Only full duplex supported
*
* Source originated from linux air_en8811h.c
*
- * Copyright (C) 2025 Airoha Technology Corp.
+ * Copyright (C) 2025, 2026 Airoha Technology Corp.
*/
-
#include <phy.h>
#include <errno.h>
#include <log.h>
#include <asm/unaligned.h>
#include <linux/iopoll.h>
#include <linux/bitops.h>
+#include <linux/bitfield.h>
#include <linux/compat.h>
#include <dm/device_compat.h>
#include <u-boot/crc.h>
-#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_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
#define AIR_PHY_PAGE_STANDARD 0x0000
#define AIR_PHY_PAGE_EXTENDED_4 0x0004
+#define AIR_PBUS_MODE_ADDR_HIGH 0x1c
/* MII Registers Page 4 */
#define AIR_BPBUS_MODE 0x10
#define AIR_BPBUS_MODE_ADDR_FIXED 0x0000
#define AIR_BPBUS_RD_DATA_LOW 0x18
/* Registers on MDIO_MMD_VEND1 */
-#define EN8811H_PHY_FW_STATUS 0x8009
-#define EN8811H_PHY_READY 0x02
+#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_INTCLR 0x00e4
/* Registers on MDIO_MMD_VEND2 */
#define AIR_PHY_LED_BCR 0x021
#define AIR_PHY_LED_DUR_BLINK 0x023
-#define AIR_PHY_LED_ON(i) (0x024 + ((i) * 2))
+#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_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(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_2500TX BIT(10)
#define AIR_PHY_LED_BLINK_2500RX BIT(11)
+/* Registers on BUCKPBUS */
+#define AIR_PHY_CONTROL 0x3a9c
+#define AIR_PHY_CONTROL_SURGE_5R BIT(3)
+#define AIR_PHY_CONTROL_INTERNAL BIT(11)
+
+/* Led definitions */
+#define EN8811H_LED_COUNT 3
+
+/* Firmware registers */
+#define AIR_FW_ADDR_DM 0x00000000
+#define AIR_FW_ADDR_DSP 0x00100000
+#define EN8811H_FW_CTRL_1 0x0f0018
+#define EN8811H_FW_CTRL_1_START 0x0
+#define EN8811H_FW_CTRL_1_FINISH 0x1
+#define EN8811H_FW_CTRL_2 0x800000
+#define EN8811H_FW_CTRL_2_LOADING BIT(11)
+#define EN8811H_PHY_FW_STATUS 0x8009
+#define EN8811H_PHY_READY 0x02
+#define AIR_PHY_FW_STATUS 0x8009
+#define AIR_PHY_READY 0x02
+
+#define AIR_PHY_FW_CTRL_1 0x0f0018
+#define AIR_PHY_FW_CTRL_1_START 0x0
+#define AIR_PHY_FW_CTRL_1_FINISH 0x1
+
+/* EN8811H */
+#define EN8811H_PHY_ID 0x03a2a411
+#define EN8811H_MD32_DM_SIZE 0x4000
+#define EN8811H_MD32_DSP_SIZE 0x20000
#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 clear_bit(bit, bitmap) __clear_bit(bit, bitmap)
-
-/* Led definitions */
-#define EN8811H_LED_COUNT 3
+/* AN8811HB */
+#define AN8811HB_PHY_ID 0xc0ff04a0
+#define AIR_MD32_DM_SIZE 0x8000
+#define AIR_MD32_DSP_SIZE 0x20000
+#define AIR_PHY_MD32FW_VERSION 0x3b3c
+
+#define AN8811HB_GPIO_OUTPUT 0x5cf8b8
+#define AN8811HB_GPIO_OUTPUT_MASK GENMASK(15, 0)
+#define AN8811HB_GPIO_OUTPUT_345 (BIT(3) | BIT(4) | BIT(5))
+#define AN8811HB_GPIO_OUTPUT_0115 (BIT(0) | BIT(1) | BIT(15))
+#define AN8811HB_GPIO_SEL_1 0x5cf8bc
+#define AN8811HB_GPIO_SEL_1_0_MASK GENMASK(2, 0)
+#define AN8811HB_GPIO_SEL_1_1_MASK GENMASK(6, 4)
+#define AN8811HB_GPIO_SEL_1_0 FIELD_PREP(AN8811HB_GPIO_SEL_1_0_MASK, 1)
+#define AN8811HB_GPIO_SEL_1_1 FIELD_PREP(AN8811HB_GPIO_SEL_1_1_MASK, 0)
+#define AN8811HB_GPIO_SEL_2 0x5cf8c0
+#define AN8811HB_GPIO_SEL_2_15_MASK GENMASK(30, 28)
+#define AN8811HB_GPIO_SEL_2_15 FIELD_PREP(AN8811HB_GPIO_SEL_2_15_MASK, 2)
+
+#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_HWTRAP1 0x5cf910
+#define AN8811HB_HWTRAP2 0x5cf914
+#define AN8811HB_HWTRAP2_CKO BIT(28)
+#define AN8811HB_HWTRAP2_PKG (BIT(12) | BIT(13) | BIT(14))
+#define AN8811HB_PRO_ID 0x5cf920
+#define AN8811HB_PRO_ID_VERSION GENMASK(3, 0)
+
+#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)
+
+#define clear_bit(bit, bitmap) __clear_bit(bit, bitmap)
+
+#define SCRIPT_NAME(name) #name "_load_firmware"
struct led {
unsigned long rules;
#define AIR_PHY_LED_DUR (AIR_PHY_LED_DUR_UNIT << AIR_PHY_LED_DUR_BLINK_64MS)
struct en8811h_priv {
- int firmware_version;
+ u32 firmware_version;
bool mcu_needs_restart;
struct led led[EN8811H_LED_COUNT];
+ u32 pro_id;
+ u32 pkg_sel;
+ u32 mem_size;
+ const char *script_name;
};
+static int air_pbus_reg_write(struct phy_device *phydev,
+ u32 pbus_reg, u32 pbus_data)
+{
+ int pbus_addr = (phydev->addr) + 8;
+ struct mii_dev *bus = phydev->bus;
+ int ret;
+
+ ret = bus->write(bus, pbus_addr, MDIO_DEVAD_NONE,
+ AIR_EXT_PAGE_ACCESS,
+ (pbus_reg >> 16));
+ if (ret < 0)
+ return ret;
+
+ ret = bus->write(bus, pbus_addr, MDIO_DEVAD_NONE,
+ AIR_PBUS_MODE_ADDR_HIGH,
+ ((pbus_reg & GENMASK(15, 6)) >> 6));
+ if (ret < 0)
+ return ret;
+
+ ret = bus->write(bus, pbus_addr, MDIO_DEVAD_NONE,
+ ((pbus_reg & GENMASK(5, 2)) >> 2),
+ (pbus_data & GENMASK(15, 0)));
+ if (ret < 0)
+ return ret;
+
+ ret = bus->write(bus, pbus_addr, MDIO_DEVAD_NONE, 0x10,
+ ((pbus_data & GENMASK(31, 16)) >> 16));
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
static int air_buckpbus_reg_write(struct phy_device *phydev,
u32 pbus_address, u32 pbus_data)
{
static int air_write_buf(struct phy_device *phydev, unsigned long address,
unsigned long array_size, const unsigned char *buffer)
{
- unsigned int offset;
int ret, saved_page;
+ u32 offset;
u16 val;
saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
return ret;
}
-int en8811h_read_fw(void **fw, size_t *fwsize)
+static int an8811hb_check_crc(struct phy_device *phydev,
+ u32 set1, u32 mon2, u32 mon3)
+{
+ int ret, retry = 10;
+ u32 pbus_value;
+
+ /* Configure CRC */
+ ret = air_buckpbus_reg_modify(phydev, set1, AN8811HB_CRC_RD_EN,
+ AN8811HB_CRC_RD_EN);
+ if (ret < 0)
+ return ret;
+
+ ret = air_buckpbus_reg_read(phydev, set1, &pbus_value);
+ if (ret < 0)
+ return ret;
+
+ debug("%d: reg 0x%x val 0x%x!\n", __LINE__, set1, pbus_value);
+
+ do {
+ mdelay(300);
+
+ ret = air_buckpbus_reg_read(phydev, mon2, &pbus_value);
+ if (ret < 0)
+ return ret;
+
+ debug("%d: reg 0x%x val 0x%x!\n", __LINE__, mon2, pbus_value);
+
+ if (pbus_value & AN8811HB_CRC_ST) {
+ ret = air_buckpbus_reg_read(phydev, mon3, &pbus_value);
+ if (ret < 0)
+ return ret;
+
+ debug("%d: reg 0x%x val 0x%x!\n", __LINE__, mon3,
+ pbus_value);
+
+ if (pbus_value & AN8811HB_CRC_CHECK_PASS)
+ debug("CRC Check PASS!\n");
+ else
+ dev_err(phydev->dev, "CRC Check FAIL!(0x%lx)\n",
+ pbus_value & AN8811HB_CRC_CHECK_PASS);
+
+ break;
+ }
+
+ if (!retry) {
+ dev_err(phydev->dev,
+ "CRC Check is not ready.(Status %u)\n",
+ pbus_value);
+ return -ENODEV;
+ }
+ } while (--retry);
+
+ ret = air_buckpbus_reg_modify(phydev, set1, AN8811HB_CRC_RD_EN, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = air_buckpbus_reg_read(phydev, set1, &pbus_value);
+ if (ret < 0)
+ return ret;
+
+ debug("%d: reg 0x%x val 0x%x!\n", __LINE__, set1, pbus_value);
+
+ return ret;
+}
+
+static int an8811hb_mcu_assert(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = air_pbus_reg_write(phydev, AN8811HB_MCU_SW_RST,
+ AN8811HB_MCU_SW_RST_HOLD);
+ if (ret < 0)
+ return ret;
+
+ ret = air_pbus_reg_write(phydev, AN8811HB_MCU_SW_START, 0);
+ if (ret < 0)
+ return ret;
+
+ debug("MCU asserted\n");
+ mdelay(50);
+
+ return ret;
+}
+
+static int an8811hb_mcu_deassert(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = air_pbus_reg_write(phydev, AN8811HB_MCU_SW_START,
+ AN8811HB_MCU_SW_START_EN);
+ if (ret < 0)
+ return ret;
+
+ ret = air_pbus_reg_write(phydev, AN8811HB_MCU_SW_RST,
+ AN8811HB_MCU_SW_RST_RUN);
+ if (ret < 0)
+ return ret;
+
+ debug("MCU deasserted\n");
+ mdelay(50);
+
+ return ret;
+}
+
+static int an8811hb_surge_protect_cfg(struct phy_device *phydev)
+{
+ ofnode node = phy_get_ofnode(phydev);
+ int ret = 0;
+
+ if (!ofnode_read_bool(node, "airoha,surge-5r")) {
+ debug("Surge Protection mode - 0R\n");
+ return ret;
+ }
+
+ ret = air_buckpbus_reg_modify(phydev, AIR_PHY_CONTROL,
+ AIR_PHY_CONTROL_SURGE_5R,
+ AIR_PHY_CONTROL_SURGE_5R);
+ if (ret < 0)
+ return ret;
+
+ debug("Surge Protection mode - 5R\n");
+
+ return ret;
+}
+
+static int en8811h_read_fw(void **fw, size_t *fwsize, struct en8811h_priv *priv)
{
+ const char *script_name = priv->script_name;
+ u32 mem_size = priv->mem_size;
void *buffer;
int ret;
- buffer = malloc(EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE);
+ buffer = malloc(mem_size);
if (!buffer)
return -ENOMEM;
- ret = request_firmware_into_buf_via_script(buffer,
- EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE,
- "en8811h_load_firmware", fwsize);
+ ret = request_firmware_into_buf_via_script(buffer, mem_size,
+ script_name, fwsize);
if (ret) {
free(buffer);
return ret;
void *buffer;
int ret;
- ret = en8811h_read_fw(&buffer, &fw_size);
+ priv->script_name = SCRIPT_NAME(en8811h);
+ priv->mem_size = EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE;
+
+ ret = en8811h_read_fw(&buffer, &fw_size, priv);
if (ret < 0) {
dev_err(phydev->dev, "Failed to get firmware data\n");
return -EINVAL;
goto en8811h_load_firmware_out;
ret = en8811h_wait_mcu_ready(phydev);
+ if (ret < 0)
+ goto en8811h_load_firmware_out;
air_buckpbus_reg_read(phydev, EN8811H_FW_VERSION,
&priv->firmware_version);
+
dev_info(phydev->dev, "MD32 firmware version: %08x\n",
priv->firmware_version);
return ret;
}
+static int an8811hb_load_firmware(struct phy_device *phydev)
+{
+ struct en8811h_priv *priv = phydev->priv;
+ int ret, retry = 10;
+ size_t fw_size;
+ void *buffer;
+ u32 reg_val;
+
+ ret = an8811hb_mcu_assert(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = an8811hb_mcu_deassert(phydev);
+ if (ret < 0)
+ return ret;
+
+ priv->script_name = SCRIPT_NAME(an8811hb);
+ priv->mem_size = AIR_MD32_DM_SIZE + AIR_MD32_DSP_SIZE;
+
+ ret = en8811h_read_fw(&buffer, &fw_size, priv);
+ if (ret < 0)
+ goto an8811hb_load_firmware_out;
+
+ ret = air_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1,
+ AIR_PHY_FW_CTRL_1_START);
+ if (ret < 0)
+ goto an8811hb_load_firmware_out;
+
+ ret = air_write_buf(phydev, AIR_FW_ADDR_DM, AIR_MD32_DM_SIZE,
+ (unsigned char *)buffer);
+ if (ret < 0)
+ goto an8811hb_load_firmware_out;
+
+ ret = an8811hb_check_crc(phydev, AN8811HB_CRC_DM_SET1,
+ AN8811HB_CRC_DM_MON2, AN8811HB_CRC_DM_MON3);
+ if (ret < 0)
+ goto an8811hb_load_firmware_out;
+
+ ret = air_write_buf(phydev, AIR_FW_ADDR_DSP, AIR_MD32_DSP_SIZE,
+ (unsigned char *)buffer + AIR_MD32_DM_SIZE);
+ if (ret < 0)
+ goto an8811hb_load_firmware_out;
+
+ ret = an8811hb_check_crc(phydev, AN8811HB_CRC_PM_SET1,
+ AN8811HB_CRC_PM_MON2, AN8811HB_CRC_PM_MON3);
+ if (ret < 0)
+ goto an8811hb_load_firmware_out;
+
+ ret = air_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1,
+ AIR_PHY_FW_CTRL_1_FINISH);
+ if (ret < 0)
+ goto an8811hb_load_firmware_out;
+
+ ret = an8811hb_surge_protect_cfg(phydev);
+ if (ret < 0) {
+ dev_err(phydev->dev, "an8811hb_surge_protect_cfg fail. (ret=%d)\n", ret);
+ goto an8811hb_load_firmware_out;
+ }
+
+ do {
+ mdelay(300);
+
+ ret = air_buckpbus_reg_read(phydev, AIR_PHY_FW_CTRL_1, ®_val);
+ if (ret < 0)
+ goto an8811hb_load_firmware_out;
+
+ if (reg_val == AIR_PHY_FW_CTRL_1_FINISH)
+ break;
+
+ debug("%d: reg 0x%x val 0x%x!\n", __LINE__, AIR_PHY_FW_CTRL_1,
+ reg_val);
+
+ ret = air_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1,
+ AIR_PHY_FW_CTRL_1_FINISH);
+ if (ret < 0)
+ goto an8811hb_load_firmware_out;
+
+ } while (--retry);
+
+ ret = en8811h_wait_mcu_ready(phydev);
+ if (ret < 0)
+ goto an8811hb_load_firmware_out;
+
+ air_buckpbus_reg_read(phydev, AIR_PHY_MD32FW_VERSION,
+ &priv->firmware_version);
+
+ debug("MD32 firmware version: %08x\n", priv->firmware_version);
+
+an8811hb_load_firmware_out:
+ free(buffer);
+ if (ret < 0)
+ dev_err(phydev->dev, "Firmware loading failed: %d\n", ret);
+
+ return ret;
+}
+
+int an8811hb_cko_cfg(struct phy_device *phydev)
+{
+ ofnode node = phy_get_ofnode(phydev);
+ u32 pbus_value;
+ int ret = 0;
+
+ if (!ofnode_read_bool(node, "airoha,phy-output-clock")) {
+ ret = air_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV,
+ AN8811HB_CLK_DRV_CKO_MASK,
+ AN8811HB_CLK_DRV_CKOPWD |
+ AN8811HB_CLK_DRV_CKO_LDPWD |
+ AN8811HB_CLK_DRV_CKO_LPPWD);
+ if (ret < 0)
+ return ret;
+
+ debug("CKO Output mode - Disabled\n");
+ } else {
+ ret = air_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value);
+ if (ret < 0)
+ return ret;
+
+ debug("CKO Output %dMHz - Enabled\n",
+ (pbus_value & AN8811HB_HWTRAP2_CKO) ? 50 : 25);
+ }
+
+ return ret;
+}
+
static int en8811h_restart_mcu(struct phy_device *phydev)
{
int ret;
return 0;
}
+/**
+ * air_leds_init - Initialize and configure LEDs for a phy device.
+ *
+ * @phydev: Pointer to the phy_device structure.
+ * @num: Number of LEDs to initialize.
+ * @dur: Duration for LED blink in milliseconds. It sets the duration
+ * for both the ON and OFF periods (OFF period will be half of `dur`).
+ * @mode: LED operation mode. Supported modes are:
+ * - AIR_LED_MODE_DISABLE: Disables LED control.
+ * - AIR_LED_MODE_USER_DEFINE: Enables user-defined LED control.
+ *
+ * Initializes and configures LEDs on a phy device with a specified blink duration
+ * and mode. Supports disabling or enabling user-defined control.
+ * Return:
+ * On success, returns 0. On error, it returns a negative value that denotes
+ * the error code.
+ */
+
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);
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_BLINK, dur);
if (ret < 0)
return ret;
pbus_value |= EN8811H_POLARITY_TX_NORMAL;
ret = air_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
EN8811H_POLARITY_RX_REVERSE |
- EN8811H_POLARITY_TX_NORMAL, pbus_value);
+ 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) {
+ dev_err(phydev->dev, "Failed to disable leds: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int an8811hb_config(struct phy_device *phydev)
+{
+ struct en8811h_priv *priv = phydev->priv;
+ u32 pbus_value = 0;
+ ofnode node;
+ int ret = 0;
+
+ node = phy_get_ofnode(phydev);
+ if (!ofnode_valid(node))
+ return 0;
+
+ /* If restart happened in .probe(), no need to restart now */
+ if (priv->mcu_needs_restart) {
+ ret = an8811hb_mcu_assert(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = an8811hb_mcu_deassert(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = en8811h_restart_mcu(phydev);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = an8811hb_load_firmware(phydev);
+ if (ret) {
+ dev_err(phydev->dev, "Load firmware fail.\n");
+ return ret;
+ }
+ /* Next calls to .config() mcu needs to restart */
+ priv->mcu_needs_restart = true;
+ }
+
+ ret = air_buckpbus_reg_read(phydev, AN8811HB_PRO_ID, &pbus_value);
+ if (ret < 0)
+ return ret;
+ priv->pro_id = (pbus_value & AN8811HB_PRO_ID_VERSION) + 1;
+
+ ret = air_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value);
+ if (ret < 0)
+ return ret;
+ priv->pkg_sel = (pbus_value & AN8811HB_HWTRAP2_PKG) >> 12;
+ debug("%s(%d) Version: E%d\n",
+ priv->pkg_sel ? "AN8811HBCN" : "AN8811HBN", priv->pkg_sel,
+ priv->pro_id);
+
+ /* Serdes polarity */
+ pbus_value = 0;
+ if (ofnode_read_bool(node, "airoha,pnswap-rx"))
+ pbus_value &= ~AN8811HB_RX_POLARITY_NORMAL;
+ else
+ pbus_value |= AN8811HB_RX_POLARITY_NORMAL;
+
+ debug("1 pbus_value 0x%x\n", pbus_value);
+ ret = air_buckpbus_reg_modify(phydev, AN8811HB_RX_POLARITY,
+ AN8811HB_RX_POLARITY_NORMAL, pbus_value);
+ if (ret < 0)
+ return ret;
+
+ pbus_value = 0;
+ if (ofnode_read_bool(node, "airoha,pnswap-tx"))
+ pbus_value &= ~AN8811HB_TX_POLARITY_NORMAL;
+ else
+ pbus_value |= AN8811HB_TX_POLARITY_NORMAL;
+
+ debug("2 pbus_value 0x%x\n", pbus_value);
+ ret = air_buckpbus_reg_modify(phydev, AN8811HB_TX_POLARITY,
+ AN8811HB_TX_POLARITY_NORMAL, pbus_value);
if (ret < 0)
return ret;
+ /* Configure led gpio pins as output */
+ if (priv->pkg_sel) {
+ ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT,
+ AN8811HB_GPIO_OUTPUT_MASK,
+ AN8811HB_GPIO_OUTPUT_0115);
+ if (ret < 0)
+ return ret;
+ ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_SEL_1,
+ AN8811HB_GPIO_SEL_1_0_MASK |
+ AN8811HB_GPIO_SEL_1_1_MASK,
+ AN8811HB_GPIO_SEL_1_0 |
+ AN8811HB_GPIO_SEL_1_1);
+ if (ret < 0)
+ return ret;
+
+ ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_SEL_2,
+ AN8811HB_GPIO_SEL_2_15_MASK,
+ AN8811HB_GPIO_SEL_2_15);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT,
+ AN8811HB_GPIO_OUTPUT_345,
+ AN8811HB_GPIO_OUTPUT_345);
+ 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) {
return ret;
}
+ /* Co-Clock Output */
+ ret = an8811hb_cko_cfg(phydev);
+ if (ret)
+ return ret;
+
+ printf("AN8811HB initialize OK !\n");
+
+ return 0;
+}
+
+static int an8811hb_update_duplex(struct phy_device *phydev)
+{
+ int lpa;
+
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ lpa = phy_read(phydev, MDIO_DEVAD_NONE, MII_LPA);
+ if (lpa < 0)
+ return lpa;
+
+ switch (phydev->speed) {
+ case SPEED_2500:
+ case SPEED_1000:
+ phydev->duplex = DUPLEX_FULL;
+ break;
+ case SPEED_100:
+ phydev->duplex = (lpa & LPA_100FULL) ? DUPLEX_FULL :
+ DUPLEX_HALF;
+ break;
+ case SPEED_10:
+ phydev->duplex = (lpa & LPA_10FULL) ? DUPLEX_FULL :
+ DUPLEX_HALF;
+ break;
+ }
+ } else {
+ int bmcr = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
+
+ if (phydev->speed == SPEED_2500)
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL :
+ DUPLEX_HALF;
+ }
+
return 0;
}
+static int an8811hb_parse_status(struct phy_device *phydev)
+{
+ int ret = 0, reg_value;
+
+ 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;
+ case AIR_AUX_CTRL_STATUS_SPEED_10:
+ phydev->speed = SPEED_10;
+ break;
+ default:
+ dev_err(phydev->dev,
+ "Auto-neg error, defaulting to 2500M/FD\n");
+ phydev->speed = SPEED_2500;
+ phydev->duplex = DUPLEX_FULL;
+ return 0;
+ }
+
+ /* Update duplex mode based on speed and negotiation status */
+ ret = an8811hb_update_duplex(phydev);
+ if (ret < 0)
+ return ret;
+
+ debug("Speed: %d, %s duplex\n", phydev->speed,
+ (phydev->duplex) ? "full" : "half");
+ return ret;
+}
+
static int en8811h_parse_status(struct phy_device *phydev)
{
int ret = 0, reg_value;
phydev->speed = SPEED_100;
break;
default:
- dev_err(phydev->dev, "Auto-neg error, defaulting to 2500M/FD\n");
+ dev_err(phydev->dev,
+ "Auto-neg error, defaulting to 2500M/FD\n");
phydev->speed = SPEED_2500;
break;
}
static int en8811h_startup(struct phy_device *phydev)
{
+ u32 phy_id = phydev->phy_id;
int ret = 0;
ret = genphy_update_link(phydev);
if (ret)
return ret;
- return en8811h_parse_status(phydev);
+ if (phy_id == EN8811H_PHY_ID)
+ ret = en8811h_parse_status(phydev);
+ else if (phy_id == AN8811HB_PHY_ID)
+ ret = an8811hb_parse_status(phydev);
+
+ return ret;
}
static int en8811h_probe(struct phy_device *phydev)
{
struct en8811h_priv *priv;
+ int phy_id;
priv = malloc(sizeof(*priv));
if (!priv)
return -ENOMEM;
memset(priv, 0, sizeof(*priv));
+ debug("%s driver is probed.\n", phydev->drv->name);
+ get_phy_id(phydev->bus, phydev->addr, MDIO_DEVAD_NONE, &phy_id);
+ debug("phy id is 0x%x.\n", phy_id);
+
priv->led[0].rules = AIR_DEFAULT_TRIGGER_LED0;
priv->led[1].rules = AIR_DEFAULT_TRIGGER_LED1;
priv->led[2].rules = AIR_DEFAULT_TRIGGER_LED2;
return 0;
}
-static int en8811h_read_page(struct phy_device *phydev)
+static int air_phy_read_page(struct phy_device *phydev)
{
return phy_read(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS);
}
-static int en8811h_write_page(struct phy_device *phydev, int page)
+static int air_phy_write_page(struct phy_device *phydev, int page)
{
return phy_write(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS, page);
}
.mask = 0x0ffffff0,
.config = &en8811h_config,
.probe = &en8811h_probe,
- .read_page = &en8811h_read_page,
- .write_page = &en8811h_write_page,
+ .read_page = &air_phy_read_page,
+ .write_page = &air_phy_write_page,
+ .startup = &en8811h_startup,
+ .shutdown = &genphy_shutdown,
+};
+
+U_BOOT_PHY_DRIVER(an8811hb) = {
+ .name = "Airoha AN8811HB",
+ .uid = AN8811HB_PHY_ID,
+ .mask = 0x0ffffff0,
+ .config = &an8811hb_config,
+ .probe = &en8811h_probe,
+ .read_page = &air_phy_read_page,
+ .write_page = &air_phy_write_page,
.startup = &en8811h_startup,
.shutdown = &genphy_shutdown,
};