Commit 5afda1d7 authored by Bjørn Mork's avatar Bjørn Mork Committed by Jakub Kicinski
Browse files

net: phy: air_en8811h: add Airoha AN8811HB support



The Airoha AN8811HB is mostly compatible with the EN8811H, adding 10Base-T
support and reducing power consumption.

This driver is based on the air_an8811hb v0.0.4 out-of-tree driver
written by "Lucien.Jheng <lucien.jheng@airoha.com>"

Firmware is available in linux-firmware. The driver has been tested with
firmware version 25110702

Signed-off-by: default avatarBjørn Mork <bjorn@mork.no>
Link: https://patch.msgid.link/20260127125547.1475164-3-bjorn@mork.no


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 28693bce
Loading
Loading
Loading
Loading
+269 −11
Original line number Diff line number Diff line
// 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 PHYs.
 *
 * Limitations of the EN8811H:
 * 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 Airoha Technology Corp.
 * Copyright (C) 2023, 2026 Airoha Technology Corp.
 */

#include <linux/clk.h>
@@ -21,9 +22,12 @@
#include <linux/unaligned.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
@@ -31,6 +35,7 @@
/* 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
@@ -56,6 +61,7 @@
#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
@@ -65,6 +71,10 @@
#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 */
@@ -106,6 +116,9 @@
#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)

@@ -129,6 +142,34 @@
#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)

/* Led definitions */
#define EN8811H_LED_COUNT	3

@@ -466,6 +507,43 @@ static int en8811h_wait_mcu_ready(struct phy_device *phydev)
	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_buckpbus_reg_modify(phydev, set1,
				      AN8811HB_CRC_RD_EN,
				      AN8811HB_CRC_RD_EN);
	if (ret < 0)
		return ret;
	air_buckpbus_reg_read(phydev, set1, &pbus_value);

	do {
		msleep(300);
		air_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_buckpbus_reg_read(phydev, mon3, &pbus_value);
			phydev_dbg(phydev, "CRC Check %s!\n",
				   pbus_value & AN8811HB_CRC_CHECK_PASS ?
					"PASS" : "FAIL");
			return air_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;
@@ -476,6 +554,54 @@ static void en8811h_print_fw_version(struct phy_device *phydev)
		    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_load_firmware(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 = 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;
@@ -939,6 +1065,45 @@ static int en8811h_leds_setup(struct phy_device *phydev)
	return ret;
}

static int an8811hb_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 = an8811hb_load_firmware(phydev);
	if (ret < 0) {
		phydev_err(phydev, "Load firmware failed: %d\n", ret);
		return ret;
	}

	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)
		return ret;

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

	return 0;
}

static int en8811h_probe(struct phy_device *phydev)
{
	struct en8811h_priv *priv;
@@ -980,6 +1145,37 @@ static int en8811h_probe(struct phy_device *phydev)
	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_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_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;
@@ -1016,6 +1212,33 @@ static int en8811h_config_serdes_polarity(struct phy_device *phydev)
				       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;
@@ -1129,13 +1352,23 @@ static int en8811h_read_status(struct phy_device *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_buckpbus_reg_read(phydev, EN8811H_2P5G_LPA, &pbus_value);
		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);
@@ -1157,6 +1390,9 @@ static int en8811h_read_status(struct phy_device *phydev)
	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.
@@ -1241,20 +1477,42 @@ static struct phy_driver en8811h_driver[] = {
	.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,
	.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,
	.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 PHY drivers");
MODULE_DESCRIPTION("Airoha EN8811H and AN8811HB PHY drivers");
MODULE_AUTHOR("Airoha");
MODULE_AUTHOR("Eric Woudstra <ericwouds@gmail.com>");
MODULE_LICENSE("GPL");