// SPDX-License-Identifier: GPL-2.0+ /* * Driver for the Airoha EN8811H 2.5 Gigabit PHY. * * Limitations of the EN8811H: * - 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 * * Copyright (C) 2023 Airoha Technology Corp. */ #include #include #include #include #include #define EN8811H_PHY_ID 0x03a2a411 #define EN8811H_MD32_DM "airoha/EthMD32.dm.bin" #define EN8811H_MD32_DSP "airoha/EthMD32.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_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 #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_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 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_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) /* Led definitions */ #define EN8811H_LED_COUNT 3 /* 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; }; struct en8811h_priv { u32 firmware_version; bool mcu_needs_restart; struct led led[EN8811H_LED_COUNT]; }; 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_phy_read_page(struct phy_device *phydev) { return __phy_read(phydev, AIR_EXT_PAGE_ACCESS); } static int air_phy_write_page(struct phy_device *phydev, int page) { return __phy_write(phydev, AIR_EXT_PAGE_ACCESS, page); } static int __air_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address, u32 pbus_data) { int ret; ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); if (ret < 0) return ret; ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH, upper_16_bits(pbus_address)); if (ret < 0) return ret; ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, lower_16_bits(pbus_address)); if (ret < 0) return ret; ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, upper_16_bits(pbus_data)); if (ret < 0) return ret; ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, lower_16_bits(pbus_data)); if (ret < 0) return ret; return 0; } static int air_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address, u32 pbus_data) { int saved_page; int ret = 0; saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); if (saved_page >= 0) { ret = __air_buckpbus_reg_write(phydev, pbus_address, pbus_data); if (ret < 0) phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, pbus_address, ret); } return phy_restore_page(phydev, saved_page, ret); } static int __air_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address, u32 *pbus_data) { int pbus_data_low, pbus_data_high; int ret; ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); if (ret < 0) return ret; ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH, upper_16_bits(pbus_address)); if (ret < 0) return ret; ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW, lower_16_bits(pbus_address)); if (ret < 0) return ret; pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH); if (pbus_data_high < 0) return pbus_data_high; pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW); if (pbus_data_low < 0) return pbus_data_low; *pbus_data = pbus_data_low | (pbus_data_high << 16); return 0; } static int air_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address, u32 *pbus_data) { int saved_page; int ret = 0; saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); if (saved_page >= 0) { ret = __air_buckpbus_reg_read(phydev, pbus_address, pbus_data); if (ret < 0) phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, pbus_address, ret); } return phy_restore_page(phydev, saved_page, ret); } static int __air_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address, u32 mask, u32 set) { int pbus_data_low, pbus_data_high; u32 pbus_data_old, pbus_data_new; int ret; ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); if (ret < 0) return ret; ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH, upper_16_bits(pbus_address)); if (ret < 0) return ret; ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW, lower_16_bits(pbus_address)); if (ret < 0) return ret; pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH); if (pbus_data_high < 0) return pbus_data_high; pbus_data_low = __phy_read(phydev, 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, AIR_BPBUS_WR_ADDR_HIGH, upper_16_bits(pbus_address)); if (ret < 0) return ret; ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, lower_16_bits(pbus_address)); if (ret < 0) return ret; ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, upper_16_bits(pbus_data_new)); if (ret < 0) return ret; ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, lower_16_bits(pbus_data_new)); if (ret < 0) return ret; return 0; } static int air_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address, u32 mask, u32 set) { int saved_page; int ret = 0; saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); if (saved_page >= 0) { ret = __air_buckpbus_reg_modify(phydev, pbus_address, mask, set); if (ret < 0) phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, pbus_address, ret); } return phy_restore_page(phydev, saved_page, ret); } 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; /* 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 en8811h_load_firmware(struct phy_device *phydev) { struct en8811h_priv *priv = phydev->priv; 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_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, 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_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); phydev_info(phydev, "MD32 firmware version: %08x\n", priv->firmware_version); 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; ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, EN8811H_FW_CTRL_1_START); if (ret < 0) return ret; ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, EN8811H_FW_CTRL_1_FINISH); 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 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; priv->led[0].rules = AIR_DEFAULT_TRIGGER_LED0; priv->led[1].rules = AIR_DEFAULT_TRIGGER_LED1; priv->led[2].rules = AIR_DEFAULT_TRIGGER_LED2; /* MDIO_DEVS1/2 empty, so set mmds_present bits here */ phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; 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; } /* Configure led gpio pins as output */ ret = air_buckpbus_reg_modify(phydev, EN8811H_GPIO_OUTPUT, EN8811H_GPIO_OUTPUT_345, EN8811H_GPIO_OUTPUT_345); if (ret < 0) return ret; return 0; } static int en8811h_config_init(struct phy_device *phydev) { struct en8811h_priv *priv = phydev->priv; struct device *dev = &phydev->mdio.dev; u32 pbus_value; 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; /* Serdes polarity */ pbus_value = 0; if (device_property_read_bool(dev, "airoha,pnswap-rx")) pbus_value |= EN8811H_POLARITY_RX_REVERSE; else pbus_value &= ~EN8811H_POLARITY_RX_REVERSE; if (device_property_read_bool(dev, "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) { 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; /* Get link partner 2.5GBASE-T ability from vendor register */ ret = air_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; } /* 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 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, .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 struct mdio_device_id __maybe_unused en8811h_tbl[] = { { PHY_ID_MATCH_MODEL(EN8811H_PHY_ID) }, { } }; MODULE_DEVICE_TABLE(mdio, en8811h_tbl); MODULE_FIRMWARE(EN8811H_MD32_DM); MODULE_FIRMWARE(EN8811H_MD32_DSP); MODULE_DESCRIPTION("Airoha EN8811H PHY drivers"); MODULE_AUTHOR("Airoha"); MODULE_AUTHOR("Eric Woudstra "); MODULE_LICENSE("GPL");