// 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 <linux/phy.h>
#include <linux/firmware.h>
#include <linux/property.h>
#include <linux/wordpart.h>
#include <asm/unaligned.h>
#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 <ericwouds@gmail.com>");
MODULE_LICENSE("GPL");