diff mbox series

[net-next,1/2] net: phy: Add support for new Aeonsemi PHYs

Message ID 20250323225439.32400-1-ansuelsmth@gmail.com (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series [net-next,1/2] net: phy: Add support for new Aeonsemi PHYs | expand

Checks

Context Check Description
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers warning 1 maintainers not CCed: andrew@lunn.ch
netdev/build_clang fail Errors and warnings before: 1 this patch: 4
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 4 this patch: 4
netdev/checkpatch warning WARNING: line length of 101 exceeds 80 columns WARNING: line length of 82 exceeds 80 columns WARNING: line length of 83 exceeds 80 columns WARNING: line length of 84 exceeds 80 columns WARNING: line length of 85 exceeds 80 columns WARNING: line length of 86 exceeds 80 columns WARNING: line length of 87 exceeds 80 columns WARNING: line length of 88 exceeds 80 columns WARNING: line length of 89 exceeds 80 columns WARNING: line length of 91 exceeds 80 columns WARNING: line length of 93 exceeds 80 columns WARNING: line length of 95 exceeds 80 columns WARNING: line length of 96 exceeds 80 columns WARNING: line length of 97 exceeds 80 columns WARNING: line length of 98 exceeds 80 columns WARNING: line length of 99 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Christian Marangi March 23, 2025, 10:54 p.m. UTC
Add support for new Aeonsemi 10G C45 PHYs. These PHYs intergate an IPC
to setup some configuration and require special handling to sync with
the parity bit. The parity bit is a way the IPC use to follow correct
order of command sent.

Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1,
AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1,
AS21210PB1 that all register with the PHY ID 0x7500 0x7500
before the firmware is loaded.

They all support up to 5 LEDs with various HW mode supported.

While implementing it was found some strange coincidence with using the
same logic for implementing C22 in MMD regs in Broadcom PHYs.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 MAINTAINERS               |   6 +
 drivers/net/phy/Kconfig   |  12 +
 drivers/net/phy/Makefile  |   1 +
 drivers/net/phy/as21xxx.c | 834 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 853 insertions(+)
 create mode 100644 drivers/net/phy/as21xxx.c

Comments

kernel test robot March 24, 2025, 3:19 a.m. UTC | #1
Hi Christian,

kernel test robot noticed the following build warnings:

[auto build test WARNING on net-next/main]

url:    https://github.com/intel-lab-lkp/linux/commits/Christian-Marangi/dt-bindings-net-Document-support-for-Aeonsemi-PHYs/20250324-065920
base:   net-next/main
patch link:    https://lore.kernel.org/r/20250323225439.32400-1-ansuelsmth%40gmail.com
patch subject: [net-next PATCH 1/2] net: phy: Add support for new Aeonsemi PHYs
config: hexagon-allyesconfig (https://download.01.org/0day-ci/archive/20250324/202503241125.KKDZ7C9F-lkp@intel.com/config)
compiler: clang version 21.0.0git (https://github.com/llvm/llvm-project c2692afc0a92cd5da140dfcdfff7818a5b8ce997)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250324/202503241125.KKDZ7C9F-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202503241125.KKDZ7C9F-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/net/phy/as21xxx.c:767:14: warning: variable 'val' is used uninitialized whenever 'for' loop exits because its condition is false [-Wsometimes-uninitialized]
     767 |         for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
         |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/net/phy/as21xxx.c:775:33: note: uninitialized use occurs here
     775 |                               VEND1_LED_REG_A_EVENT, val);
         |                                                      ^~~
   drivers/net/phy/as21xxx.c:767:14: note: remove the condition if it is always true
     767 |         for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
         |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/net/phy/as21xxx.c:761:9: note: initialize the variable 'val' to silence this warning
     761 |         u16 val;
         |                ^
         |                 = 0
   1 warning generated.


vim +767 drivers/net/phy/as21xxx.c

   757	
   758	static int as21xxx_led_hw_control_set(struct phy_device *phydev, u8 index,
   759					      unsigned long rules)
   760	{
   761		u16 val;
   762		int i;
   763	
   764		if (index > AEON_MAX_LDES)
   765			return -EINVAL;
   766	
 > 767		for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
   768			if (rules == as21xxx_led_supported_pattern[i].pattern) {
   769				val = as21xxx_led_supported_pattern[i].val;
   770				break;
   771			}
   772	
   773		return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
   774				      VEND1_LED_REG(index),
   775				      VEND1_LED_REG_A_EVENT, val);
   776	}
   777
Andrew Lunn March 24, 2025, 2:03 p.m. UTC | #2
> Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1,
> AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1,
> AS21210PB1 that all register with the PHY ID 0x7500 0x7500
> before the firmware is loaded.

Does the value change after the firmware is loaded? Is the same
firmware used for all variants?

> +++ b/drivers/net/phy/Kconfig
> @@ -121,6 +121,18 @@ config AMCC_QT2025_PHY
>  
>  source "drivers/net/phy/aquantia/Kconfig"
>  
> +config AS21XXX_PHY
> +	tristate "Aeonsemi AS21xxx PHYs"

The sorting is based on the tristate value, so that when you look at
'make menuconfig' the menu is in alphabetical order. So this goes
before aquantia.

> +/* 5 LED at step of 0x20
> + * FE: Fast-Ethernet (100)
> + * GE: Gigabit-Ethernet (1000)
> + * NG: New-Generation (2500/5000/10000)
> + * (Lovely ChatGPT managed to translate meaning of NG)

It might be a reference to NBase-T Gigabit.

Please add a comment somewhere about how locking works for IPCs. As
far as i see, the current locking scheme is that IPCs are only called
from probe, so no locking is actually required. But:

> +#define IPC_CMD_NG_TESTMODE		0x1b /* Set NG test mode and tone */
> +#define IPC_CMD_TEMP_MON		0x15 /* Temperature monitoring function */
> +#define IPC_CMD_SET_LED			0x23 /* Set led */

suggests IPCs might in the future be needed outside of probe, and then
a different locking scheme might be needed, particularly for
temperature monitoring.

> +static int as21xxx_get_features(struct phy_device *phydev)
> +{
> +	int ret;
> +
> +	ret = genphy_read_abilities(phydev);
> +	if (ret)
> +		return ret;
> +
> +	/* AS21xxx supports 100M/1G/2.5G/5G/10G speed. */
> +	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
> +			   phydev->supported);
> +	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
> +			   phydev->supported);
> +	linkmode_clear_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
> +			   phydev->supported);

Does this mean the registers genphy_read_abilities() reads are broken
and report link modes it does not actually support?

> +	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
> +			 phydev->supported);
> +	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
> +			 phydev->supported);

and it is also not reporting modes it does actually support? Is
genphy_read_abilities() actually doing anything useful? Some more
comments would be good here.

> +	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
> +			 phydev->supported);
> +	linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
> +			 phydev->supported);
> +	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
> +			 phydev->supported);

Does this mean genphy_c45_pma_read_abilities() also returns the wrong
values?

> +static int as21xxx_read_link(struct phy_device *phydev, int *bmcr)
> +{
> +	int status;
> +
> +	*bmcr = phy_read_mmd(phydev, MDIO_MMD_AN,
> +			     MDIO_AN_C22 + MII_BMCR);
> +	if (*bmcr < 0)
> +		return *bmcr;
> +
> +	/* Autoneg is being started, therefore disregard current
> +	 * link status and report link as down.
> +	 */
> +	if (*bmcr & BMCR_ANRESTART) {
> +		phydev->link = 0;
> +		return 0;
> +	}
> +
> +	status = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);

No MDIO_AN_C22 + here? Maybe add a comment about which C22 registers
are mapped into C45 space.

	Andrew
Christian Marangi March 24, 2025, 2:16 p.m. UTC | #3
On Mon, Mar 24, 2025 at 03:03:51PM +0100, Andrew Lunn wrote:
> > Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1,
> > AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1,
> > AS21210PB1 that all register with the PHY ID 0x7500 0x7500
> > before the firmware is loaded.
> 
> Does the value change after the firmware is loaded? Is the same
> firmware used for all variants?
>

Yes It does... Can PHY subsystem react on this? Like probe for the
generic one and then use the OPs for the real PHY ID?

> > +++ b/drivers/net/phy/Kconfig
> > @@ -121,6 +121,18 @@ config AMCC_QT2025_PHY
> >  
> >  source "drivers/net/phy/aquantia/Kconfig"
> >  
> > +config AS21XXX_PHY
> > +	tristate "Aeonsemi AS21xxx PHYs"
> 
> The sorting is based on the tristate value, so that when you look at
> 'make menuconfig' the menu is in alphabetical order. So this goes
> before aquantia.
> 

Tought it was only alphabetical, will move.

> > +/* 5 LED at step of 0x20
> > + * FE: Fast-Ethernet (100)
> > + * GE: Gigabit-Ethernet (1000)
> > + * NG: New-Generation (2500/5000/10000)
> > + * (Lovely ChatGPT managed to translate meaning of NG)
> 
> It might be a reference to NBase-T Gigabit.
> 

A better LED table was provided for this confirming ChatGPT assumption.
Will update the LED pattern as there were some mistake for activity
blink.

Also confirmed no way to make the LED only blink... It's sad cause the
blink time can be configured...

> Please add a comment somewhere about how locking works for IPCs. As
> far as i see, the current locking scheme is that IPCs are only called
> from probe, so no locking is actually required. But:
> 
> > +#define IPC_CMD_NG_TESTMODE		0x1b /* Set NG test mode and tone */
> > +#define IPC_CMD_TEMP_MON		0x15 /* Temperature monitoring function */
> > +#define IPC_CMD_SET_LED			0x23 /* Set led */
> 
> suggests IPCs might in the future be needed outside of probe, and then
> a different locking scheme might be needed, particularly for
> temperature monitoring.
> 

Indeed I will push temperature monitor in a followup patch so yes I will
add locking just for preparation of the additional feature.

> > +static int as21xxx_get_features(struct phy_device *phydev)
> > +{
> > +	int ret;
> > +
> > +	ret = genphy_read_abilities(phydev);
> > +	if (ret)
> > +		return ret;
> > +
> > +	/* AS21xxx supports 100M/1G/2.5G/5G/10G speed. */
> > +	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
> > +			   phydev->supported);
> > +	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
> > +			   phydev->supported);
> > +	linkmode_clear_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
> > +			   phydev->supported);
> 
> Does this mean the registers genphy_read_abilities() reads are broken
> and report link modes it does not actually support?
> 
> > +	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
> > +			 phydev->supported);
> > +	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
> > +			 phydev->supported);
> 
> and it is also not reporting modes it does actually support? Is
> genphy_read_abilities() actually doing anything useful? Some more
> comments would be good here.
> 
> > +	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
> > +			 phydev->supported);
> > +	linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
> > +			 phydev->supported);
> > +	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
> > +			 phydev->supported);
> 
> Does this mean genphy_c45_pma_read_abilities() also returns the wrong
> values?
> 

Honestly I followed what the SDK driver did for the get_feature. I will
check the register making sure correct bits are reported.

Looking at the get_status It might be conflicting as they map C22 in C45
vendor regs.

> > +static int as21xxx_read_link(struct phy_device *phydev, int *bmcr)
> > +{
> > +	int status;
> > +
> > +	*bmcr = phy_read_mmd(phydev, MDIO_MMD_AN,
> > +			     MDIO_AN_C22 + MII_BMCR);
> > +	if (*bmcr < 0)
> > +		return *bmcr;
> > +
> > +	/* Autoneg is being started, therefore disregard current
> > +	 * link status and report link as down.
> > +	 */
> > +	if (*bmcr & BMCR_ANRESTART) {
> > +		phydev->link = 0;
> > +		return 0;
> > +	}
> > +
> > +	status = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
> 
> No MDIO_AN_C22 + here? Maybe add a comment about which C22 registers
> are mapped into C45 space.
>

Nope, not a typo... They took the vendor route for some register but for
BMSR they used the expected one.

Just to make it clear, using the AN_C22 wasn't a choice... the C45 BMCR
reports inconsistent data compared to the vendor C22 one.
Russell King (Oracle) March 24, 2025, 2:55 p.m. UTC | #4
First, please include a cover message whenever you send a patch series.

On Sun, Mar 23, 2025 at 11:54:26PM +0100, Christian Marangi wrote:
> Add support for new Aeonsemi 10G C45 PHYs. These PHYs intergate an IPC
> to setup some configuration and require special handling to sync with
> the parity bit. The parity bit is a way the IPC use to follow correct
> order of command sent.
> 
> Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1,
> AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1,
> AS21210PB1 that all register with the PHY ID 0x7500 0x7500
> before the firmware is loaded.

Hmm. That behaviour is really not nice for the kernel to deal with. C45
PHYs have multiple IDs (there's registers 2,3 and also 14,15, each is
per MMD). Do they all have the same value? Do any of them indicate any
kind of valid OUI ?

If there is no way to sanely detect this PHY, then I would suggest that
it is beyond the ability of the kernel, and at the very least, an
initial firmware version needs to be loaded by board boot firmware so
the PHY _can_ be properly identified.

Basically, it isn't the kernel's job to fix such broken hardware.

> +#define VEND1_LED_REG(_n)		(0x1800 + ((_n) * 0x10))
> +#define   VEND1_LED_REG_A_EVENT		GENMASK(15, 11)
> +#define     VEND1_LED_REG_A_EVENT_ON_10 FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x0)
> +#define     VEND1_LED_REG_A_EVENT_ON_100 FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x1)

I really don't like the pattern of "define constants using
FIELD_PREP*()". It seems to me it misses the entire point of the
bitfield macros, which is to prepare and extract bitfields.

When I see:

	swith (foo & BLAH_MASK) {
	case BLAH_OPTION_1:
		...

where BLAH_OPTION_1 is defined using FIELD_PREP*(), it just
makes me shudder.

	SWITCH (FIELD_GET(BLAH_MASK, foo)) {
	case BLAH_OPTION_1:
		...

where BLAH_OPTION_1 is defined as the numerical field value is much
more how the bitfield stuff is supposed to be used.

> +enum {
> +	MDIO_AN_C22 = 0xffe0,

I'd suggest defining this in a driver private namespace, rather than
using the MDIO_xxx which is used by linux/mdio.h

> +	/* Exit condition logic:
> +	 * - Wait for parity bit equal
> +	 * - Wait for status success, error OR ready
> +	 */
> +	ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, VEND1_IPC_STS, val,
> +					FIELD_GET(AEON_IPC_STS_PARITY, val) ==
> +						curr_parity &&
> +					(val & AEON_IPC_STS_STATUS) !=
> +						AEON_IPC_STS_STATUS_RCVD &&
> +					(val & AEON_IPC_STS_STATUS) !=
> +						AEON_IPC_STS_STATUS_PROCESS &&
> +					(val & AEON_IPC_STS_STATUS) !=
> +						AEON_IPC_STS_STATUS_BUSY,
> +					10000, 2000000, false);

Use an inline function, and also please wrap a bit tighter, val seems to
wrap.

	ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, VEND1_IPC_STS,
					val, aeon_cmd_done(curr_parity, val),
					10000, 2000000, false);

> +	if (ret)
> +		return ret;
> +
> +	*ret_sts = val;
> +	if ((val & AEON_IPC_STS_STATUS) != AEON_IPC_STS_STATUS_SUCCESS)
> +		return -EFAULT;

EFAULT means "Bad address". Does not returning successful status mean
that there was a bad address? If not, please don't do this.

EFAULT is specifically used to return to userspace to tell it that it
passed the kernel a bad address.

> +
> +	return 0;
> +}
> +
> +static int aeon_ipc_send_msg(struct phy_device *phydev, u16 opcode,
> +			     u16 *data, unsigned int data_len, u16 *ret_sts)
> +{
> +	u32 cmd;
> +	int ret;
> +	int i;
> +
> +	if (data_len > AEON_IPC_DATA_MAX)
> +		return -EINVAL;
> +
> +	for (i = 0; i < data_len / sizeof(u16); i++)
> +		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i),
> +			      data[i]);

What ensures that this won't overflow the number of registers?

> +
> +	cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, data_len) |
> +	      FIELD_PREP(AEON_IPC_CMD_OPCODE, opcode);
> +	ret = aeon_ipc_send_cmd(phydev, cmd, ret_sts);
> +	if (ret)
> +		phydev_err(phydev, "failed to send ipc msg for %x: %d\n", opcode, ret);
> +
> +	return ret;
> +}
> +
> +static int aeon_ipc_rcv_msg(struct phy_device *phydev, u16 ret_sts,
> +			    u16 *data)
> +{
> +	unsigned int size = FIELD_GET(AEON_IPC_STS_SIZE, ret_sts);
> +	int ret;
> +	int i;
> +
> +	if ((ret_sts & AEON_IPC_STS_STATUS) == AEON_IPC_STS_STATUS_ERROR)
> +		return -EINVAL;
> +
> +	for (i = 0; i < DIV_ROUND_UP(size, sizeof(u16)); i++) {
> +		ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i));
> +		if (ret < 0)
> +			return ret;
> +
> +		data[i] = ret;

Unsafe. AEON_IPC_STS_SIZE is 5 bits in size, which means this can write
indexes 0..31. You pass in a buffer of 8 u16's on the stack. What stops
the hardware engaging in stack smashing... nothing. Please code more
carefully.

> +	}
> +
> +	return size;
> +}
> +
> +/* Logic to sync parity bit with IPC.
> + * We send 2 NOP cmd with same partity and we wait for IPC
> + * to handle the packet only for the second one. This way
> + * we make sure we are sync for every next cmd.
> + */
> +static int aeon_ipc_sync_parity(struct phy_device *phydev)
> +{
> +	struct as21xxx_priv *priv = phydev->priv;
> +	u16 ret_sts;
> +	u32 cmd;
> +	int ret;
> +
> +	/* Send NOP with no parity */
> +	cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, 0) |
> +	      FIELD_PREP(AEON_IPC_CMD_OPCODE, IPC_CMD_NOOP);
> +	aeon_ipc_send_cmd(phydev, cmd, NULL);
> +
> +	/* Reset packet parity */
> +	priv->parity_status = false;
> +
> +	/* Send second NOP with no parity */
> +	ret = aeon_ipc_send_cmd(phydev, cmd, &ret_sts);
> +	/* We expect to return -EFAULT */
> +	if (ret != -EFAULT)
> +		return ret;
> +
> +	if ((ret_sts & AEON_IPC_STS_STATUS) != AEON_IPC_STS_STATUS_READY)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static int aeon_ipc_get_fw_version(struct phy_device *phydev)
> +{
> +	u16 ret_data[8], data[1];
> +	u16 ret_sts;
> +	int ret;
> +
> +	data[0] = IPC_INFO_VERSION;
> +	ret = aeon_ipc_send_msg(phydev, IPC_CMD_INFO, data, sizeof(data),
> +				&ret_sts);
> +	if (ret)
> +		return ret;
> +
> +	ret = aeon_ipc_rcv_msg(phydev, ret_sts, ret_data);
> +	if (ret < 0)
> +		return ret;
> +
> +	phydev_info(phydev, "Firmware Version: %s\n", (char *)ret_data);
> +
> +	return 0;
> +}
> +
> +static int aeon_dpc_ra_enable(struct phy_device *phydev)
> +{
> +	u16 data[2];
> +	u16 ret_sts;
> +
> +	data[0] = IPC_CFG_PARAM_DIRECT;
> +	data[1] = IPC_CFG_PARAM_DIRECT_DPC_RA;
> +
> +	return aeon_ipc_send_msg(phydev, IPC_CMD_CFG_PARAM, data,
> +				 sizeof(data), &ret_sts);
> +}
> +
> +static int as21xxx_probe(struct phy_device *phydev)
> +{
> +	struct as21xxx_priv *priv;
> +	int ret;
> +
> +	phydev->priv = devm_kzalloc(&phydev->mdio.dev,
> +				    sizeof(*priv), GFP_KERNEL);
> +	if (!phydev->priv)
> +		return -ENOMEM;
> +
> +	ret = aeon_firmware_load(phydev);
> +	if (ret)
> +		return ret;
> +
> +	ret = aeon_ipc_sync_parity(phydev);
> +	if (ret)
> +		return ret;
> +
> +	/* Enable PTP clk if not already Enabled */
> +	ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_PTP_CLK,
> +			       VEND1_PTP_CLK_EN);
> +	if (ret)
> +		return ret;
> +
> +	ret = aeon_dpc_ra_enable(phydev);
> +	if (ret)
> +		return ret;
> +
> +	ret = aeon_ipc_get_fw_version(phydev);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int as21xxx_get_features(struct phy_device *phydev)
> +{
> +	int ret;
> +
> +	ret = genphy_read_abilities(phydev);
> +	if (ret)
> +		return ret;
> +
> +	/* AS21xxx supports 100M/1G/2.5G/5G/10G speed. */
> +	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
> +			   phydev->supported);
> +	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
> +			   phydev->supported);
> +	linkmode_clear_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
> +			   phydev->supported);
> +	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
> +			 phydev->supported);
> +	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
> +			 phydev->supported);
> +	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
> +			 phydev->supported);
> +	linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
> +			 phydev->supported);
> +	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
> +			 phydev->supported);

Given all this, does genphy_read_abilities() actually read anything
useful from the PHY?

> +static struct phy_driver as21xxx_drivers[] = {
> +	{
> +		PHY_ID_MATCH_EXACT(PHY_ID_AS21XXX),
> +		.name		= "Aeonsemi AS21xxx",
> +		.probe		= as21xxx_probe,
> +		.get_features	= as21xxx_get_features,
> +		.read_status	= as21xxx_read_status,
> +		.led_brightness_set = as21xxx_led_brightness_set,
> +		.led_hw_is_supported = as21xxx_led_hw_is_supported,
> +		.led_hw_control_set = as21xxx_led_hw_control_set,
> +		.led_hw_control_get = as21xxx_led_hw_control_get,
> +		.led_polarity_set = as21xxx_led_polarity_set,
> +	},

What if firmware was already loaded? I think you implied that this
ID is only present when firmware hasn't been loaded.

Thanks.
Andrew Lunn March 24, 2025, 3:16 p.m. UTC | #5
On Mon, Mar 24, 2025 at 03:16:08PM +0100, Christian Marangi wrote:
> On Mon, Mar 24, 2025 at 03:03:51PM +0100, Andrew Lunn wrote:
> > > Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1,
> > > AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1,
> > > AS21210PB1 that all register with the PHY ID 0x7500 0x7500
> > > before the firmware is loaded.
> > 
> > Does the value change after the firmware is loaded? Is the same
> > firmware used for all variants?
> >
> 
> Yes It does... Can PHY subsystem react on this? Like probe for the
> generic one and then use the OPs for the real PHY ID?

Multiple thoughts here....

If the firmware is in SPI, i assume by the time the MDIO bus is
probed, the PHY has its 'real' IDs. So you need entries for those as
well as the dummy ID.

I think this is the first PHY which changes its IDs at runtime. So we
don't have a generic solution to this. USB and PCI probably have
support for this, so maybe there is something we can copy. It could be
they support hotplug, so the device disappears and returns. That is
not really something we have in MDIO. So i don't know if we can reuse
ideas from there.

When the firmware is running, do the different variants all share the
same ID? Is there a way to tell them apart. We always have
phy_driver->match_phy_device(). It could look for 0x75007500, download
the firmware, and then match on the real IDs.

> > > +++ b/drivers/net/phy/Kconfig
> > > @@ -121,6 +121,18 @@ config AMCC_QT2025_PHY
> > >  
> > >  source "drivers/net/phy/aquantia/Kconfig"
> > >  
> > > +config AS21XXX_PHY
> > > +	tristate "Aeonsemi AS21xxx PHYs"
> > 
> > The sorting is based on the tristate value, so that when you look at
> > 'make menuconfig' the menu is in alphabetical order. So this goes
> > before aquantia.
> > 
> 
> Tought it was only alphabetical, will move.

Yes, it is not obvious, it should have a comment added at the top.
But the top is so far away, i guess many developers would miss it
anyway.

> > > +static int as21xxx_get_features(struct phy_device *phydev)
> > > +{
> > > +	int ret;
> > > +
> > > +	ret = genphy_read_abilities(phydev);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	/* AS21xxx supports 100M/1G/2.5G/5G/10G speed. */
> > > +	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
> > > +			   phydev->supported);
> > > +	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
> > > +			   phydev->supported);
> > > +	linkmode_clear_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
> > > +			   phydev->supported);
> > 
> > Does this mean the registers genphy_read_abilities() reads are broken
> > and report link modes it does not actually support?
> > 
> > > +	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
> > > +			 phydev->supported);
> > > +	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
> > > +			 phydev->supported);
> > 
> > and it is also not reporting modes it does actually support? Is
> > genphy_read_abilities() actually doing anything useful? Some more
> > comments would be good here.
> > 
> > > +	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
> > > +			 phydev->supported);
> > > +	linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
> > > +			 phydev->supported);
> > > +	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
> > > +			 phydev->supported);
> > 
> > Does this mean genphy_c45_pma_read_abilities() also returns the wrong
> > values?
> > 
> 
> Honestly I followed what the SDK driver did for the get_feature. I will
> check the register making sure correct bits are reported.
> 
> Looking at the get_status It might be conflicting as they map C22 in C45
> vendor regs.

If all the registers used for automatic feature detection are broken,
just comment on it and don't use genphy_read_abilities() etc.

> > > +static int as21xxx_read_link(struct phy_device *phydev, int *bmcr)
> > > +{
> > > +	int status;
> > > +
> > > +	*bmcr = phy_read_mmd(phydev, MDIO_MMD_AN,
> > > +			     MDIO_AN_C22 + MII_BMCR);
> > > +	if (*bmcr < 0)
> > > +		return *bmcr;
> > > +
> > > +	/* Autoneg is being started, therefore disregard current
> > > +	 * link status and report link as down.
> > > +	 */
> > > +	if (*bmcr & BMCR_ANRESTART) {
> > > +		phydev->link = 0;
> > > +		return 0;
> > > +	}
> > > +
> > > +	status = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
> > 
> > No MDIO_AN_C22 + here? Maybe add a comment about which C22 registers
> > are mapped into C45 space.
> >
> 
> Nope, not a typo... They took the vendor route for some register but for
> BMSR they used the expected one.
> 
> Just to make it clear, using the AN_C22 wasn't a choice... the C45 BMCR
> reports inconsistent data compared to the vendor C22 one.

Comments would be good.

Is there an open datasheet for this device?

	 Andrew
Christian Marangi March 25, 2025, 12:04 p.m. UTC | #6
On Mon, Mar 24, 2025 at 04:16:09PM +0100, Andrew Lunn wrote:
> On Mon, Mar 24, 2025 at 03:16:08PM +0100, Christian Marangi wrote:
> > On Mon, Mar 24, 2025 at 03:03:51PM +0100, Andrew Lunn wrote:
> > > > Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1,
> > > > AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1,
> > > > AS21210PB1 that all register with the PHY ID 0x7500 0x7500
> > > > before the firmware is loaded.
> > > 
> > > Does the value change after the firmware is loaded? Is the same
> > > firmware used for all variants?
> > >
> > 
> > Yes It does... Can PHY subsystem react on this? Like probe for the
> > generic one and then use the OPs for the real PHY ID?
> 
> Multiple thoughts here....
> 
> If the firmware is in SPI, i assume by the time the MDIO bus is
> probed, the PHY has its 'real' IDs. So you need entries for those as
> well as the dummy ID.
> 
> I think this is the first PHY which changes its IDs at runtime. So we
> don't have a generic solution to this. USB and PCI probably have
> support for this, so maybe there is something we can copy. It could be
> they support hotplug, so the device disappears and returns. That is
> not really something we have in MDIO. So i don't know if we can reuse
> ideas from there.
> 
> When the firmware is running, do the different variants all share the
> same ID? Is there a way to tell them apart. We always have
> phy_driver->match_phy_device(). It could look for 0x75007500, download
> the firmware, and then match on the real IDs.
>

Ok update on this... The PHY report 7500 7500 but on enabling PTP clock,
a more specific ""family"" ID is filled in MMD that is 0x7500 0x9410.

They all use the same firmware so matching for the family ID might not
be a bad idea... The alternative is either load the firmware in
match_phy_device or introduce some additional OPs to handle this
correctly...

Considering how the thing are evolving with PHY I really feel it's time
we start introducing specific OP for firmware loading and we might call
this OP before PHY ID matching is done (or maybe do it again).

Lets see how bad the thing goes API wise tho.

> > > > +++ b/drivers/net/phy/Kconfig
> > > > @@ -121,6 +121,18 @@ config AMCC_QT2025_PHY
> > > >  
> > > >  source "drivers/net/phy/aquantia/Kconfig"
> > > >  
> > > > +config AS21XXX_PHY
> > > > +	tristate "Aeonsemi AS21xxx PHYs"
> > > 
> > > The sorting is based on the tristate value, so that when you look at
> > > 'make menuconfig' the menu is in alphabetical order. So this goes
> > > before aquantia.
> > > 
> > 
> > Tought it was only alphabetical, will move.
> 
> Yes, it is not obvious, it should have a comment added at the top.
> But the top is so far away, i guess many developers would miss it
> anyway.
> 
> > > > +static int as21xxx_get_features(struct phy_device *phydev)
> > > > +{
> > > > +	int ret;
> > > > +
> > > > +	ret = genphy_read_abilities(phydev);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	/* AS21xxx supports 100M/1G/2.5G/5G/10G speed. */
> > > > +	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
> > > > +			   phydev->supported);
> > > > +	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
> > > > +			   phydev->supported);
> > > > +	linkmode_clear_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
> > > > +			   phydev->supported);
> > > 
> > > Does this mean the registers genphy_read_abilities() reads are broken
> > > and report link modes it does not actually support?
> > > 
> > > > +	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
> > > > +			 phydev->supported);
> > > > +	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
> > > > +			 phydev->supported);
> > > 
> > > and it is also not reporting modes it does actually support? Is
> > > genphy_read_abilities() actually doing anything useful? Some more
> > > comments would be good here.
> > > 
> > > > +	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
> > > > +			 phydev->supported);
> > > > +	linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
> > > > +			 phydev->supported);
> > > > +	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
> > > > +			 phydev->supported);
> > > 
> > > Does this mean genphy_c45_pma_read_abilities() also returns the wrong
> > > values?
> > > 
> > 
> > Honestly I followed what the SDK driver did for the get_feature. I will
> > check the register making sure correct bits are reported.
> > 
> > Looking at the get_status It might be conflicting as they map C22 in C45
> > vendor regs.
> 
> If all the registers used for automatic feature detection are broken,
> just comment on it and don't use genphy_read_abilities() etc.
> 
> > > > +static int as21xxx_read_link(struct phy_device *phydev, int *bmcr)
> > > > +{
> > > > +	int status;
> > > > +
> > > > +	*bmcr = phy_read_mmd(phydev, MDIO_MMD_AN,
> > > > +			     MDIO_AN_C22 + MII_BMCR);
> > > > +	if (*bmcr < 0)
> > > > +		return *bmcr;
> > > > +
> > > > +	/* Autoneg is being started, therefore disregard current
> > > > +	 * link status and report link as down.
> > > > +	 */
> > > > +	if (*bmcr & BMCR_ANRESTART) {
> > > > +		phydev->link = 0;
> > > > +		return 0;
> > > > +	}
> > > > +
> > > > +	status = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
> > > 
> > > No MDIO_AN_C22 + here? Maybe add a comment about which C22 registers
> > > are mapped into C45 space.
> > >
> > 
> > Nope, not a typo... They took the vendor route for some register but for
> > BMSR they used the expected one.
> > 
> > Just to make it clear, using the AN_C22 wasn't a choice... the C45 BMCR
> > reports inconsistent data compared to the vendor C22 one.
> 
> Comments would be good.
> 
> Is there an open datasheet for this device?
>

Sadly no and I don't even have datasheet, just some contacts to confirm
1-2 thing for the PHY.
Andrew Lunn March 25, 2025, 8:33 p.m. UTC | #7
On Tue, Mar 25, 2025 at 01:04:30PM +0100, Christian Marangi wrote:
> On Mon, Mar 24, 2025 at 04:16:09PM +0100, Andrew Lunn wrote:
> > On Mon, Mar 24, 2025 at 03:16:08PM +0100, Christian Marangi wrote:
> > > On Mon, Mar 24, 2025 at 03:03:51PM +0100, Andrew Lunn wrote:
> > > > > Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1,
> > > > > AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1,
> > > > > AS21210PB1 that all register with the PHY ID 0x7500 0x7500
> > > > > before the firmware is loaded.

Do you have details of how these different PHY differ? Do they have
different features?

> Ok update on this... The PHY report 7500 7500 but on enabling PTP clock,
> a more specific ""family"" ID is filled in MMD that is 0x7500 0x9410.

Do they all support PTP?

> They all use the same firmware so matching for the family ID might not
> be a bad idea... The alternative is either load the firmware in
> match_phy_device or introduce some additional OPs to handle this
> correctly...
> 
> Considering how the thing are evolving with PHY I really feel it's time
> we start introducing specific OP for firmware loading and we might call
> this OP before PHY ID matching is done (or maybe do it again).

You cannot download firmware before doing some sort of match, because
you have no idea what PHY you actually have until you do a match, and
if the PHY needs firmware.

match_phy_device() gives you a bit more flexibility. It will be called
for every PHY on the board, independent of the ID registers. So you
can read the ID registers, see if it is at least a vendor you know how
to download firmware to, do the download, and then look at the ID
registers again to see if it is the version of the PHY you want to
drive. If not, return -ENODEV, and the core will try the next driver
entry.

	Andrew
Christian Marangi March 25, 2025, 8:41 p.m. UTC | #8
On Tue, Mar 25, 2025 at 09:33:26PM +0100, Andrew Lunn wrote:
> On Tue, Mar 25, 2025 at 01:04:30PM +0100, Christian Marangi wrote:
> > On Mon, Mar 24, 2025 at 04:16:09PM +0100, Andrew Lunn wrote:
> > > On Mon, Mar 24, 2025 at 03:16:08PM +0100, Christian Marangi wrote:
> > > > On Mon, Mar 24, 2025 at 03:03:51PM +0100, Andrew Lunn wrote:
> > > > > > Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1,
> > > > > > AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1,
> > > > > > AS21210PB1 that all register with the PHY ID 0x7500 0x7500
> > > > > > before the firmware is loaded.
> 
> Do you have details of how these different PHY differ? Do they have
> different features?
>

No but I can ask more details. From what I can assume, gigabit, 2.5g 10g
and probably a PHY that expose multiple port (PHY package thing)

> > Ok update on this... The PHY report 7500 7500 but on enabling PTP clock,
> > a more specific ""family"" ID is filled in MMD that is 0x7500 0x9410.
> 
> Do they all support PTP?
> 

With PTP it's not the PTP we think but I guess it's something internal to
the PHY to actually start it. From comments it's called PTP Clock...

> > They all use the same firmware so matching for the family ID might not
> > be a bad idea... The alternative is either load the firmware in
> > match_phy_device or introduce some additional OPs to handle this
> > correctly...
> > 
> > Considering how the thing are evolving with PHY I really feel it's time
> > we start introducing specific OP for firmware loading and we might call
> > this OP before PHY ID matching is done (or maybe do it again).
> 
> You cannot download firmware before doing some sort of match, because
> you have no idea what PHY you actually have until you do a match, and
> if the PHY needs firmware.
> 
> match_phy_device() gives you a bit more flexibility. It will be called
> for every PHY on the board, independent of the ID registers. So you
> can read the ID registers, see if it is at least a vendor you know how
> to download firmware to, do the download, and then look at the ID
> registers again to see if it is the version of the PHY you want to
> drive. If not, return -ENODEV, and the core will try the next driver
> entry.
>

I'm finishing preparing V2 and I'm curious what you will think of the
implementation.

The approach I found works good is permit PHY device to register a
second time and the PHY driver toggle this.

This way in a PHY driver we register OPs for the to-be-init PHY and then
we probe the real one.
Russell King (Oracle) March 25, 2025, 9:36 p.m. UTC | #9
On Tue, Mar 25, 2025 at 01:04:30PM +0100, Christian Marangi wrote:
> On Mon, Mar 24, 2025 at 04:16:09PM +0100, Andrew Lunn wrote:
> > On Mon, Mar 24, 2025 at 03:16:08PM +0100, Christian Marangi wrote:
> > > On Mon, Mar 24, 2025 at 03:03:51PM +0100, Andrew Lunn wrote:
> > > > > Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1,
> > > > > AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1,
> > > > > AS21210PB1 that all register with the PHY ID 0x7500 0x7500
> > > > > before the firmware is loaded.
> > > > 
> > > > Does the value change after the firmware is loaded? Is the same
> > > > firmware used for all variants?
> > > >
> > > 
> > > Yes It does... Can PHY subsystem react on this? Like probe for the
> > > generic one and then use the OPs for the real PHY ID?
> > 
> > Multiple thoughts here....
> > 
> > If the firmware is in SPI, i assume by the time the MDIO bus is
> > probed, the PHY has its 'real' IDs. So you need entries for those as
> > well as the dummy ID.
> > 
> > I think this is the first PHY which changes its IDs at runtime. So we
> > don't have a generic solution to this. USB and PCI probably have
> > support for this, so maybe there is something we can copy. It could be
> > they support hotplug, so the device disappears and returns. That is
> > not really something we have in MDIO. So i don't know if we can reuse
> > ideas from there.
> > 
> > When the firmware is running, do the different variants all share the
> > same ID? Is there a way to tell them apart. We always have
> > phy_driver->match_phy_device(). It could look for 0x75007500, download
> > the firmware, and then match on the real IDs.
> 
> Ok update on this... The PHY report 7500 7500 but on enabling PTP clock,
> a more specific ""family"" ID is filled in MMD that is 0x7500 0x9410.
> 
> They all use the same firmware so matching for the family ID might not
> be a bad idea... The alternative is either load the firmware in
> match_phy_device or introduce some additional OPs to handle this
> correctly...
> 
> Considering how the thing are evolving with PHY I really feel it's time
> we start introducing specific OP for firmware loading and we might call
> this OP before PHY ID matching is done (or maybe do it again).

You're basically talking there about modifying the core driver model of
the kernel, which I think you're going to have an uphill battle with.

The match_phy_device() op is called by the core driver model when a new
device/driver is added to find a driver for an unbound device. It does
this calling the bus_type's .match() method for each unbound device
on the bus_type, and every device driver that is bound to the bus type.

In the case of a MDIO device, which phylib's PHYs are a sub-class of,
this is populated with mdio_bus_match(), which then goes on to call
the MDIO device's ->bus_match() method. For a PHY, that's
phy_bus_match(), which will either call the PHY driver's
.match_phy_device() method or will attempt to match the hardware ID
against what is given in the PHY driver structure.

So, the core driver model is responsible for trying each device driver
on the bus to find one that matches each device. This isn't a phylib
thing.

At the moment, I don't see how adding another PHY driver OP helps. We
still need to find the right PHY driver struct somehow. It seems to me
to be a chicken-and-egg problem.

That's why I suggested that maybe board firmware should be loading at
least _some_ kind of firmware to get the PHY to the point that the
kernel can properly identify it - the kernel can then read the
loaded firmware, compare it with the version that's locally available,
and if different, replace the firmware. The main thing is that board
firmware gets the PHY to the point that it can be properly identified.
kernel test robot March 29, 2025, 6:51 p.m. UTC | #10
Hi Christian,

kernel test robot noticed the following build warnings:

[auto build test WARNING on net-next/main]

url:    https://github.com/intel-lab-lkp/linux/commits/Christian-Marangi/dt-bindings-net-Document-support-for-Aeonsemi-PHYs/20250324-065920
base:   net-next/main
patch link:    https://lore.kernel.org/r/20250323225439.32400-1-ansuelsmth%40gmail.com
patch subject: [net-next PATCH 1/2] net: phy: Add support for new Aeonsemi PHYs
config: riscv-randconfig-r072-20250329 (https://download.01.org/0day-ci/archive/20250330/202503300205.g0FCozVG-lkp@intel.com/config)
compiler: clang version 19.1.7 (https://github.com/llvm/llvm-project cd708029e0b2869e80abe31ddb175f7c35361f90)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202503300205.g0FCozVG-lkp@intel.com/

smatch warnings:
drivers/net/phy/as21xxx.c:744 as21xxx_led_hw_control_get() warn: unsigned 'val' is never less than zero.
drivers/net/phy/as21xxx.c:775 as21xxx_led_hw_control_set() error: uninitialized symbol 'val'.
drivers/net/phy/as21xxx.c:802 as21xxx_led_polarity_set() error: uninitialized symbol 'led_active_low'.

vim +/val +744 drivers/net/phy/as21xxx.c

   733	
   734	static int as21xxx_led_hw_control_get(struct phy_device *phydev, u8 index,
   735					      unsigned long *rules)
   736	{
   737		u16 val;
   738		int i;
   739	
   740		if (index > AEON_MAX_LDES)
   741			return -EINVAL;
   742	
   743		val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_LED_REG(index));
 > 744		if (val < 0)
   745			return val;
   746	
   747		val &= VEND1_LED_REG_A_EVENT;
   748		for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
   749			if (val == as21xxx_led_supported_pattern[i].val) {
   750				*rules = as21xxx_led_supported_pattern[i].pattern;
   751				return 0;
   752			}
   753	
   754		/* Should be impossible */
   755		return -EINVAL;
   756	}
   757	
   758	static int as21xxx_led_hw_control_set(struct phy_device *phydev, u8 index,
   759					      unsigned long rules)
   760	{
   761		u16 val;
   762		int i;
   763	
   764		if (index > AEON_MAX_LDES)
   765			return -EINVAL;
   766	
   767		for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
   768			if (rules == as21xxx_led_supported_pattern[i].pattern) {
   769				val = as21xxx_led_supported_pattern[i].val;
   770				break;
   771			}
   772	
   773		return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
   774				      VEND1_LED_REG(index),
 > 775				      VEND1_LED_REG_A_EVENT, val);
   776	}
   777	
   778	static int as21xxx_led_polarity_set(struct phy_device *phydev, int index,
   779					    unsigned long modes)
   780	{
   781		bool led_active_low;
   782		u16 mask, val = 0;
   783		u32 mode;
   784	
   785		if (index > AEON_MAX_LDES)
   786			return -EINVAL;
   787	
   788		for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
   789			switch (mode) {
   790			case PHY_LED_ACTIVE_LOW:
   791				led_active_low = true;
   792				break;
   793			case PHY_LED_ACTIVE_HIGH: /* default mode */
   794				led_active_low = false;
   795				break;
   796			default:
   797				return -EINVAL;
   798			}
   799		}
   800	
   801		mask = VEND1_GLB_CPU_CTRL_LED_POLARITY(index);
 > 802		if (led_active_low)
   803			val = VEND1_GLB_CPU_CTRL_LED_POLARITY(index);
   804	
   805		return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
   806				      VEND1_GLB_REG_CPU_CTRL,
   807				      mask, val);
   808	}
   809
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 873aa2cce4d7..9a2df6d221bd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -645,6 +645,12 @@  F:	drivers/iio/accel/adxl380.h
 F:	drivers/iio/accel/adxl380_i2c.c
 F:	drivers/iio/accel/adxl380_spi.c
 
+AEONSEMI PHY DRIVER
+M:	Christian Marangi <ansuelsmth@gmail.com>
+L:	netdev@vger.kernel.org
+S:	Maintained
+F:	drivers/net/phy/as21xxx.c
+
 AF8133J THREE-AXIS MAGNETOMETER DRIVER
 M:	Ondřej Jirman <megi@xff.cz>
 S:	Maintained
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 41c15a2c2037..454715c651d6 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -121,6 +121,18 @@  config AMCC_QT2025_PHY
 
 source "drivers/net/phy/aquantia/Kconfig"
 
+config AS21XXX_PHY
+	tristate "Aeonsemi AS21xxx PHYs"
+	help
+	  Currently supports the Aeonsemi AS21xxx PHY.
+
+	  These are C45 PHYs 10G that require all a generic firmware.
+
+	  Supported PHYs AS21011JB1, AS21011PB1, AS21010JB1, AS21010PB1,
+	  AS21511JB1, AS21511PB1, AS21510JB1, AS21510PB1, AS21210JB1,
+	  AS21210PB1 that all register with the PHY ID 0x7500 0x7500
+	  before the firmware is loaded.
+
 config AX88796B_PHY
 	tristate "Asix PHYs"
 	help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index c8dac6e92278..14156bf11267 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -39,6 +39,7 @@  obj-$(CONFIG_AIR_EN8811H_PHY)   += air_en8811h.o
 obj-$(CONFIG_AMD_PHY)		+= amd.o
 obj-$(CONFIG_AMCC_QT2025_PHY)	+= qt2025.o
 obj-$(CONFIG_AQUANTIA_PHY)	+= aquantia/
+obj-$(CONFIG_AS21XXX_PHY)	+= as21xxx.o
 ifdef CONFIG_AX88796B_RUST_PHY
   obj-$(CONFIG_AX88796B_PHY)	+= ax88796b_rust.o
 else
diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c
new file mode 100644
index 000000000000..7aefe2fcf24f
--- /dev/null
+++ b/drivers/net/phy/as21xxx.c
@@ -0,0 +1,834 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Aeonsemi AS21XXxX PHY Driver
+ *
+ * Author: Christian Marangi <ansuelsmth@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+
+#define VEND1_GLB_REG_CPU_RESET_ADDR_LO_BASEADDR 0x3
+#define VEND1_GLB_REG_CPU_RESET_ADDR_HI_BASEADDR 0x4
+
+#define VEND1_GLB_REG_CPU_CTRL		0xe
+#define   VEND1_GLB_CPU_CTRL_MASK	GENMASK(4, 0)
+#define   VEND1_GLB_CPU_CTRL_LED_POLARITY_MASK GENMASK(12, 8)
+#define   VEND1_GLB_CPU_CTRL_LED_POLARITY(_n) FIELD_PREP(VEND1_GLB_CPU_CTRL_LED_POLARITY_MASK, \
+							 BIT(_n))
+
+#define VEND1_FW_START_ADDR		0x100
+
+#define VEND1_GLB_REG_MDIO_INDIRECT_ADDRCMD 0x101
+#define VEND1_GLB_REG_MDIO_INDIRECT_LOAD 0x102
+
+#define VEND1_GLB_REG_MDIO_INDIRECT_STATUS 0x103
+
+#define VEND1_PTP_CLK			0x142
+#define   VEND1_PTP_CLK_EN		BIT(6)
+
+/* 5 LED at step of 0x20
+ * FE: Fast-Ethernet (100)
+ * GE: Gigabit-Ethernet (1000)
+ * NG: New-Generation (2500/5000/10000)
+ * (Lovely ChatGPT managed to translate meaning of NG)
+ */
+#define VEND1_LED_REG(_n)		(0x1800 + ((_n) * 0x10))
+#define   VEND1_LED_REG_A_EVENT		GENMASK(15, 11)
+#define     VEND1_LED_REG_A_EVENT_ON_10 FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x0)
+#define     VEND1_LED_REG_A_EVENT_ON_100 FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x1)
+#define     VEND1_LED_REG_A_EVENT_ON_1000 FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x2)
+#define     VEND1_LED_REG_A_EVENT_ON_2500 FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x3)
+#define     VEND1_LED_REG_A_EVENT_ON_5000 FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x4)
+#define     VEND1_LED_REG_A_EVENT_ON_10000 FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x5)
+#define     VEND1_LED_REG_A_EVENT_ON_FE_GE FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x6)
+#define     VEND1_LED_REG_A_EVENT_ON_NG FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x7)
+#define     VEND1_LED_REG_A_EVENT_ON_FULL_DUPLEX FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x8)
+#define     VEND1_LED_REG_A_EVENT_ON_COLLISION FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x9)
+#define     VEND1_LED_REG_A_EVENT_BLINK_TX FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0xa)
+#define     VEND1_LED_REG_A_EVENT_BLINK_RX FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0xb)
+#define     VEND1_LED_REG_A_EVENT_BLINK_ACT FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0xc)
+#define     VEND1_LED_REG_A_EVENT_ON_LINK FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0xd)
+#define     VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_ACT FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0xe)
+#define     VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_RX FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0xf)
+#define     VEND1_LED_REG_A_EVENT_ON_FE_GE_BLINK_ACT FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x10)
+#define     VEND1_LED_REG_A_EVENT_ON_NG_BLINK_ACT FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x11)
+#define     VEND1_LED_REG_A_EVENT_ON_NG_BLINK_FE_GE FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x12)
+#define     VEND1_LED_REG_A_EVENT_ON_FD_BLINK_COLLISION FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x13)
+#define     VEND1_LED_REG_A_EVENT_ON	FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x14)
+#define     VEND1_LED_REG_A_EVENT_OFF	FIELD_PREP_CONST(VEND1_LED_REG_A_EVENT, 0x15)
+#define VEND1_LED_CONF			0x1881
+#define   VEND1_LED_CONFG_BLINK		GENMASK(7, 0)
+
+#define VEND1_SPEED_STATUS		0x4002
+#define   VEND1_SPEED_MASK		GENMASK(7, 0)
+#define   VEND1_SPEED_10000		FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x3)
+#define   VEND1_SPEED_5000		FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x5)
+#define   VEND1_SPEED_2500		FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x9)
+#define   VEND1_SPEED_1000		FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x10)
+#define   VEND1_SPEED_100		FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x20)
+#define   VEND1_SPEED_10		FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x0)
+
+#define VEND1_IPC_CMD			0x5801
+#define   AEON_IPC_CMD_PARITY		BIT(15)
+#define   AEON_IPC_CMD_SIZE		GENMASK(10, 6)
+#define   AEON_IPC_CMD_OPCODE		GENMASK(5, 0)
+
+#define IPC_CMD_NOOP			0x0  /* Do nothing */
+#define IPC_CMD_INFO			0x1  /* Get Firmware Version */
+#define IPC_CMD_SYS_CPU			0x2  /* SYS_CPU */
+#define IPC_CMD_BULK_DATA		0xa  /* Pass bulk data in ipc registers. */
+#define IPC_CMD_BULK_WRITE		0xc  /* Write bulk data to memory */
+#define IPC_CMD_CFG_PARAM		0x1a /* Write config parameters to memory */
+#define IPC_CMD_NG_TESTMODE		0x1b /* Set NG test mode and tone */
+#define IPC_CMD_TEMP_MON		0x15 /* Temperature monitoring function */
+#define IPC_CMD_SET_LED			0x23 /* Set led */
+
+#define VEND1_IPC_STS			0x5802
+#define   AEON_IPC_STS_PARITY		BIT(15)
+#define   AEON_IPC_STS_SIZE		GENMASK(14, 10)
+#define   AEON_IPC_STS_OPCODE		GENMASK(9, 4)
+#define   AEON_IPC_STS_STATUS		GENMASK(3, 0)
+#define   AEON_IPC_STS_STATUS_RCVD	FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x1)
+#define   AEON_IPC_STS_STATUS_PROCESS	FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x2)
+#define   AEON_IPC_STS_STATUS_SUCCESS	FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x4)
+#define   AEON_IPC_STS_STATUS_ERROR	FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x8)
+#define   AEON_IPC_STS_STATUS_BUSY	FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0xe)
+#define   AEON_IPC_STS_STATUS_READY	FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0xf)
+
+#define VEND1_IPC_DATA0			0x5808
+#define VEND1_IPC_DATA1			0x5809
+#define VEND1_IPC_DATA2			0x580a
+#define VEND1_IPC_DATA3			0x580b
+#define VEND1_IPC_DATA4			0x580c
+#define VEND1_IPC_DATA5			0x580d
+#define VEND1_IPC_DATA6			0x580e
+#define VEND1_IPC_DATA7			0x580f
+#define VEND1_IPC_DATA(_n)		(VEND1_IPC_DATA0 + (_n))
+
+/* Sub command of CMD_INFO */
+#define IPC_INFO_VERSION		0x1
+
+/* Sub command of CMD_SYS_CPU */
+#define IPC_SYS_CPU_REBOOT		0x3
+#define IPC_SYS_CPU_IMAGE_OFST		0x4
+#define IPC_SYS_CPU_IMAGE_CHECK		0x5
+#define IPC_SYS_CPU_PHY_ENABLE		0x6
+
+/* Sub command of CMD_CFG_PARAM */
+#define IPC_CFG_PARAM_DIRECT		0x4
+
+/* CFG DIRECT sub command */
+#define IPC_CFG_PARAM_DIRECT_NG_PHYCTRL	0x1
+#define IPC_CFG_PARAM_DIRECT_CU_AN	0x2
+#define IPC_CFG_PARAM_DIRECT_SDS_PCS	0x3
+#define IPC_CFG_PARAM_DIRECT_AUTO_EEE	0x4
+#define IPC_CFG_PARAM_DIRECT_SDS_PMA	0x5
+#define IPC_CFG_PARAM_DIRECT_DPC_RA	0x6
+#define IPC_CFG_PARAM_DIRECT_DPC_PKT_CHK 0x7
+#define IPC_CFG_PARAM_DIRECT_DPC_SDS_WAIT_ETH 0x8
+#define IPC_CFG_PARAM_DIRECT_WDT	0x9
+#define IPC_CFG_PARAM_DIRECT_SDS_RESTART_AN 0x10
+#define IPC_CFG_PARAM_DIRECT_TEMP_MON	0x11
+#define IPC_CFG_PARAM_DIRECT_WOL	0x12
+
+/* Sub command of CMD_TEMP_MON */
+#define IPC_CMD_TEMP_MON_GET		0x4
+
+#define PHY_ID_AS21010JB1		0x75009422
+#define PHY_ID_AS21XXX			0x75007500
+#define PHY_VENDOR_AEONSEMI		0x75009400
+
+#define AEON_MAX_LDES			5
+#define AEON_IPC_DATA_MAX		(8 * sizeof(u16))
+
+#define AEON_BOOT_ADDR			0x1000
+#define AEON_CPU_BOOT_ADDR		0x2000
+#define AEON_CPU_CTRL_FW_LOAD		(BIT(4) | BIT(2) | BIT(1) | BIT(0))
+#define AEON_CPU_CTRL_FW_START		BIT(0)
+
+enum {
+	MDIO_AN_C22 = 0xffe0,
+};
+
+struct as21xxx_led_pattern_info {
+	unsigned int pattern;
+	u16 val;
+};
+
+struct as21xxx_priv {
+	bool parity_status;
+};
+
+static struct as21xxx_led_pattern_info as21xxx_led_supported_pattern[] = {
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_10),
+		.val = VEND1_LED_REG_A_EVENT_ON_10
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_100),
+		.val = VEND1_LED_REG_A_EVENT_ON_100
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_1000),
+		.val = VEND1_LED_REG_A_EVENT_ON_1000
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_2500),
+		.val = VEND1_LED_REG_A_EVENT_ON_2500
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_5000),
+		.val = VEND1_LED_REG_A_EVENT_ON_5000
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_10000),
+		.val = VEND1_LED_REG_A_EVENT_ON_10000
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK),
+		.val = VEND1_LED_REG_A_EVENT_ON_LINK
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_100) |
+			   BIT(TRIGGER_NETDEV_LINK_1000),
+		.val = VEND1_LED_REG_A_EVENT_ON_FE_GE
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_2500) |
+			   BIT(TRIGGER_NETDEV_LINK_5000) |
+			   BIT(TRIGGER_NETDEV_LINK_10000),
+		.val = VEND1_LED_REG_A_EVENT_ON_NG
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_FULL_DUPLEX),
+		.val = VEND1_LED_REG_A_EVENT_ON_FULL_DUPLEX
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_TX),
+		.val = VEND1_LED_REG_A_EVENT_BLINK_TX
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_RX),
+		.val = VEND1_LED_REG_A_EVENT_BLINK_RX
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_TX) |
+			   BIT(TRIGGER_NETDEV_RX),
+		.val = VEND1_LED_REG_A_EVENT_BLINK_ACT
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_10) |
+			   BIT(TRIGGER_NETDEV_LINK_100) |
+			   BIT(TRIGGER_NETDEV_LINK_1000) |
+			   BIT(TRIGGER_NETDEV_LINK_2500) |
+			   BIT(TRIGGER_NETDEV_LINK_5000) |
+			   BIT(TRIGGER_NETDEV_LINK_10000),
+		.val = VEND1_LED_REG_A_EVENT_ON_LINK
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_10) |
+			   BIT(TRIGGER_NETDEV_LINK_100) |
+			   BIT(TRIGGER_NETDEV_LINK_1000) |
+			   BIT(TRIGGER_NETDEV_LINK_2500) |
+			   BIT(TRIGGER_NETDEV_LINK_5000) |
+			   BIT(TRIGGER_NETDEV_LINK_10000) |
+			   BIT(TRIGGER_NETDEV_TX) |
+			   BIT(TRIGGER_NETDEV_RX),
+		.val = VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_ACT
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_10) |
+			   BIT(TRIGGER_NETDEV_LINK_100) |
+			   BIT(TRIGGER_NETDEV_LINK_1000) |
+			   BIT(TRIGGER_NETDEV_LINK_2500) |
+			   BIT(TRIGGER_NETDEV_LINK_5000) |
+			   BIT(TRIGGER_NETDEV_LINK_10000) |
+			   BIT(TRIGGER_NETDEV_RX),
+		.val = VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_RX
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_100) |
+			   BIT(TRIGGER_NETDEV_LINK_1000) |
+			   BIT(TRIGGER_NETDEV_TX) |
+			   BIT(TRIGGER_NETDEV_RX),
+		.val = VEND1_LED_REG_A_EVENT_ON_FE_GE_BLINK_ACT
+	},
+	{
+		.pattern = BIT(TRIGGER_NETDEV_LINK_2500) |
+			   BIT(TRIGGER_NETDEV_LINK_5000) |
+			   BIT(TRIGGER_NETDEV_LINK_10000) |
+			   BIT(TRIGGER_NETDEV_TX) |
+			   BIT(TRIGGER_NETDEV_RX),
+		.val = VEND1_LED_REG_A_EVENT_ON_NG_BLINK_ACT
+	}
+};
+
+static int aeon_firmware_boot(struct phy_device *phydev, const u8 *data, size_t size)
+{
+	int i, ret;
+	u16 val;
+
+	ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_CPU_CTRL,
+			     VEND1_GLB_CPU_CTRL_MASK, AEON_CPU_CTRL_FW_LOAD);
+	if (ret)
+		return ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_FW_START_ADDR,
+			    AEON_BOOT_ADDR);
+	if (ret)
+		return ret;
+
+	ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_MDIO_INDIRECT_ADDRCMD,
+			     0x3ffc, 0xc000);
+	if (ret)
+		return ret;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_MDIO_INDIRECT_STATUS);
+	if (val > 1) {
+		phydev_err(phydev, "wrong origin mdio_indirect_status: %x\n", val);
+		return -EINVAL;
+	}
+
+	/* Firmware is always aligned to u16 */
+	for (i = 0; i < size; i += 2) {
+		val = data[i + 1] << 8 | data[i];
+
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_MDIO_INDIRECT_LOAD, val);
+		if (ret)
+			return ret;
+	}
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_CPU_RESET_ADDR_LO_BASEADDR,
+			    lower_16_bits(AEON_CPU_BOOT_ADDR));
+	if (ret)
+		return ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_CPU_RESET_ADDR_HI_BASEADDR,
+			    upper_16_bits(AEON_CPU_BOOT_ADDR));
+	if (ret)
+		return ret;
+
+	return phy_modify_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_CPU_CTRL,
+			      VEND1_GLB_CPU_CTRL_MASK, AEON_CPU_CTRL_FW_START);
+}
+
+static int aeon_firmware_load(struct phy_device *phydev)
+{
+	struct device *dev = &phydev->mdio.dev;
+	const struct firmware *fw;
+	const char *fw_name;
+	int ret;
+
+	ret = of_property_read_string(dev->of_node, "firmware-name",
+				      &fw_name);
+	if (ret)
+		return ret;
+
+	ret = request_firmware(&fw, fw_name, dev);
+	if (ret) {
+		phydev_err(phydev, "failed to find FW file %s (%d)\n",
+			   fw_name, ret);
+		return ret;
+	}
+
+	ret = aeon_firmware_boot(phydev, fw->data, fw->size);
+
+	release_firmware(fw);
+
+	return ret;
+}
+
+static int aeon_ipc_send_cmd(struct phy_device *phydev, u32 cmd,
+			     u16 *ret_sts)
+{
+	struct as21xxx_priv *priv = phydev->priv;
+	bool curr_parity;
+	u32 val;
+	int ret;
+
+	/* The IPC sync by using a single parity bit.
+	 * Each CMD have alternately this bit set or clear
+	 * to understand correct flow and packet order.
+	 */
+	curr_parity = priv->parity_status;
+	if (priv->parity_status)
+		cmd |= AEON_IPC_CMD_PARITY;
+
+	/* Always update parity for next packet */
+	priv->parity_status = !priv->parity_status;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_CMD, cmd);
+	if (ret)
+		return ret;
+
+	/* Wait for packet to be processed */
+	usleep_range(10000, 15000);
+
+	/* With no ret_sts, ignore waiting for packet completion
+	 * (ipc parity bit sync)
+	 */
+	if (!ret_sts)
+		return 0;
+
+	/* Exit condition logic:
+	 * - Wait for parity bit equal
+	 * - Wait for status success, error OR ready
+	 */
+	ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, VEND1_IPC_STS, val,
+					FIELD_GET(AEON_IPC_STS_PARITY, val) ==
+						curr_parity &&
+					(val & AEON_IPC_STS_STATUS) !=
+						AEON_IPC_STS_STATUS_RCVD &&
+					(val & AEON_IPC_STS_STATUS) !=
+						AEON_IPC_STS_STATUS_PROCESS &&
+					(val & AEON_IPC_STS_STATUS) !=
+						AEON_IPC_STS_STATUS_BUSY,
+					10000, 2000000, false);
+	if (ret)
+		return ret;
+
+	*ret_sts = val;
+	if ((val & AEON_IPC_STS_STATUS) != AEON_IPC_STS_STATUS_SUCCESS)
+		return -EFAULT;
+
+	return 0;
+}
+
+static int aeon_ipc_send_msg(struct phy_device *phydev, u16 opcode,
+			     u16 *data, unsigned int data_len, u16 *ret_sts)
+{
+	u32 cmd;
+	int ret;
+	int i;
+
+	if (data_len > AEON_IPC_DATA_MAX)
+		return -EINVAL;
+
+	for (i = 0; i < data_len / sizeof(u16); i++)
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i),
+			      data[i]);
+
+	cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, data_len) |
+	      FIELD_PREP(AEON_IPC_CMD_OPCODE, opcode);
+	ret = aeon_ipc_send_cmd(phydev, cmd, ret_sts);
+	if (ret)
+		phydev_err(phydev, "failed to send ipc msg for %x: %d\n", opcode, ret);
+
+	return ret;
+}
+
+static int aeon_ipc_rcv_msg(struct phy_device *phydev, u16 ret_sts,
+			    u16 *data)
+{
+	unsigned int size = FIELD_GET(AEON_IPC_STS_SIZE, ret_sts);
+	int ret;
+	int i;
+
+	if ((ret_sts & AEON_IPC_STS_STATUS) == AEON_IPC_STS_STATUS_ERROR)
+		return -EINVAL;
+
+	for (i = 0; i < DIV_ROUND_UP(size, sizeof(u16)); i++) {
+		ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i));
+		if (ret < 0)
+			return ret;
+
+		data[i] = ret;
+	}
+
+	return size;
+}
+
+/* Logic to sync parity bit with IPC.
+ * We send 2 NOP cmd with same partity and we wait for IPC
+ * to handle the packet only for the second one. This way
+ * we make sure we are sync for every next cmd.
+ */
+static int aeon_ipc_sync_parity(struct phy_device *phydev)
+{
+	struct as21xxx_priv *priv = phydev->priv;
+	u16 ret_sts;
+	u32 cmd;
+	int ret;
+
+	/* Send NOP with no parity */
+	cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, 0) |
+	      FIELD_PREP(AEON_IPC_CMD_OPCODE, IPC_CMD_NOOP);
+	aeon_ipc_send_cmd(phydev, cmd, NULL);
+
+	/* Reset packet parity */
+	priv->parity_status = false;
+
+	/* Send second NOP with no parity */
+	ret = aeon_ipc_send_cmd(phydev, cmd, &ret_sts);
+	/* We expect to return -EFAULT */
+	if (ret != -EFAULT)
+		return ret;
+
+	if ((ret_sts & AEON_IPC_STS_STATUS) != AEON_IPC_STS_STATUS_READY)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int aeon_ipc_get_fw_version(struct phy_device *phydev)
+{
+	u16 ret_data[8], data[1];
+	u16 ret_sts;
+	int ret;
+
+	data[0] = IPC_INFO_VERSION;
+	ret = aeon_ipc_send_msg(phydev, IPC_CMD_INFO, data, sizeof(data),
+				&ret_sts);
+	if (ret)
+		return ret;
+
+	ret = aeon_ipc_rcv_msg(phydev, ret_sts, ret_data);
+	if (ret < 0)
+		return ret;
+
+	phydev_info(phydev, "Firmware Version: %s\n", (char *)ret_data);
+
+	return 0;
+}
+
+static int aeon_dpc_ra_enable(struct phy_device *phydev)
+{
+	u16 data[2];
+	u16 ret_sts;
+
+	data[0] = IPC_CFG_PARAM_DIRECT;
+	data[1] = IPC_CFG_PARAM_DIRECT_DPC_RA;
+
+	return aeon_ipc_send_msg(phydev, IPC_CMD_CFG_PARAM, data,
+				 sizeof(data), &ret_sts);
+}
+
+static int as21xxx_probe(struct phy_device *phydev)
+{
+	struct as21xxx_priv *priv;
+	int ret;
+
+	phydev->priv = devm_kzalloc(&phydev->mdio.dev,
+				    sizeof(*priv), GFP_KERNEL);
+	if (!phydev->priv)
+		return -ENOMEM;
+
+	ret = aeon_firmware_load(phydev);
+	if (ret)
+		return ret;
+
+	ret = aeon_ipc_sync_parity(phydev);
+	if (ret)
+		return ret;
+
+	/* Enable PTP clk if not already Enabled */
+	ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_PTP_CLK,
+			       VEND1_PTP_CLK_EN);
+	if (ret)
+		return ret;
+
+	ret = aeon_dpc_ra_enable(phydev);
+	if (ret)
+		return ret;
+
+	ret = aeon_ipc_get_fw_version(phydev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int as21xxx_get_features(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = genphy_read_abilities(phydev);
+	if (ret)
+		return ret;
+
+	/* AS21xxx supports 100M/1G/2.5G/5G/10G speed. */
+	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+			   phydev->supported);
+	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+			   phydev->supported);
+	linkmode_clear_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
+			   phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+			 phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+			 phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+			 phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+			 phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+			 phydev->supported);
+
+	return 0;
+}
+
+static int as21xxx_read_link(struct phy_device *phydev, int *bmcr)
+{
+	int status;
+
+	*bmcr = phy_read_mmd(phydev, MDIO_MMD_AN,
+			     MDIO_AN_C22 + MII_BMCR);
+	if (*bmcr < 0)
+		return *bmcr;
+
+	/* Autoneg is being started, therefore disregard current
+	 * link status and report link as down.
+	 */
+	if (*bmcr & BMCR_ANRESTART) {
+		phydev->link = 0;
+		return 0;
+	}
+
+	status = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	if (status < 0)
+		return status;
+
+	phydev->link = !!(status & MDIO_STAT1_LSTATUS);
+
+	return 0;
+}
+
+static int as21xxx_read_c22_lpa(struct phy_device *phydev)
+{
+	int lpagb;
+
+	lpagb = phy_read_mmd(phydev, MDIO_MMD_AN,
+			     MDIO_AN_C22 + MII_STAT1000);
+	if (lpagb < 0)
+		return lpagb;
+
+	if (lpagb & LPA_1000MSFAIL) {
+		int adv = phy_read_mmd(phydev, MDIO_MMD_AN,
+				       MDIO_AN_C22 + MII_CTRL1000);
+
+		if (adv < 0)
+			return adv;
+
+		if (adv & CTL1000_ENABLE_MASTER)
+			phydev_err(phydev, "Master/Slave resolution failed, maybe conflicting manual settings?\n");
+		else
+			phydev_err(phydev, "Master/Slave resolution failed\n");
+		return -ENOLINK;
+	}
+
+	mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising,
+					lpagb);
+
+	return 0;
+}
+
+static int as21xxx_read_status(struct phy_device *phydev)
+{
+	int bmcr, old_link = phydev->link;
+	int ret;
+
+	ret = as21xxx_read_link(phydev, &bmcr);
+	if (ret)
+		return ret;
+
+	/* why bother the PHY if nothing can have changed */
+	if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
+		return 0;
+
+	phydev->speed = SPEED_UNKNOWN;
+	phydev->duplex = DUPLEX_UNKNOWN;
+	phydev->pause = 0;
+	phydev->asym_pause = 0;
+
+	if (phydev->autoneg == AUTONEG_ENABLE) {
+		ret = genphy_c45_read_lpa(phydev);
+		if (ret)
+			return ret;
+
+		ret = as21xxx_read_c22_lpa(phydev);
+		if (ret)
+			return ret;
+
+		phy_resolve_aneg_linkmode(phydev);
+	} else {
+		int speed;
+
+		linkmode_zero(phydev->lp_advertising);
+
+		speed = phy_read_mmd(phydev, MDIO_MMD_VEND1,
+				     VEND1_SPEED_STATUS);
+		if (speed < 0)
+			return speed;
+
+		switch (speed & VEND1_SPEED_STATUS) {
+		case VEND1_SPEED_10000:
+			phydev->speed = SPEED_10000;
+			phydev->duplex = DUPLEX_FULL;
+			break;
+		case VEND1_SPEED_5000:
+			phydev->speed = SPEED_5000;
+			phydev->duplex = DUPLEX_FULL;
+			break;
+		case VEND1_SPEED_2500:
+			phydev->speed = SPEED_2500;
+			phydev->duplex = DUPLEX_FULL;
+			break;
+		case VEND1_SPEED_1000:
+			phydev->speed = SPEED_1000;
+			if (bmcr & BMCR_FULLDPLX)
+				phydev->duplex = DUPLEX_FULL;
+			else
+				phydev->duplex = DUPLEX_HALF;
+			break;
+		case VEND1_SPEED_100:
+			phydev->speed = SPEED_100;
+			phydev->duplex = DUPLEX_FULL;
+			break;
+		case VEND1_SPEED_10:
+			phydev->speed = SPEED_10;
+			phydev->duplex = DUPLEX_FULL;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static int as21xxx_led_brightness_set(struct phy_device *phydev,
+				      u8 index, enum led_brightness value)
+{
+	u16 val = VEND1_LED_REG_A_EVENT_OFF;
+
+	if (index > AEON_MAX_LDES)
+		return -EINVAL;
+
+	if (value)
+		val = VEND1_LED_REG_A_EVENT_ON;
+
+	return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+			      VEND1_LED_REG(index),
+			      VEND1_LED_REG_A_EVENT, val);
+}
+
+static int as21xxx_led_hw_is_supported(struct phy_device *phydev, u8 index,
+				       unsigned long rules)
+{
+	int i;
+
+	if (index > AEON_MAX_LDES)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
+		if (rules == as21xxx_led_supported_pattern[i].pattern)
+			return 0;
+
+	return -EOPNOTSUPP;
+}
+
+static int as21xxx_led_hw_control_get(struct phy_device *phydev, u8 index,
+				      unsigned long *rules)
+{
+	u16 val;
+	int i;
+
+	if (index > AEON_MAX_LDES)
+		return -EINVAL;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_LED_REG(index));
+	if (val < 0)
+		return val;
+
+	val &= VEND1_LED_REG_A_EVENT;
+	for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
+		if (val == as21xxx_led_supported_pattern[i].val) {
+			*rules = as21xxx_led_supported_pattern[i].pattern;
+			return 0;
+		}
+
+	/* Should be impossible */
+	return -EINVAL;
+}
+
+static int as21xxx_led_hw_control_set(struct phy_device *phydev, u8 index,
+				      unsigned long rules)
+{
+	u16 val;
+	int i;
+
+	if (index > AEON_MAX_LDES)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
+		if (rules == as21xxx_led_supported_pattern[i].pattern) {
+			val = as21xxx_led_supported_pattern[i].val;
+			break;
+		}
+
+	return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+			      VEND1_LED_REG(index),
+			      VEND1_LED_REG_A_EVENT, val);
+}
+
+static int as21xxx_led_polarity_set(struct phy_device *phydev, int index,
+				    unsigned long modes)
+{
+	bool led_active_low;
+	u16 mask, val = 0;
+	u32 mode;
+
+	if (index > AEON_MAX_LDES)
+		return -EINVAL;
+
+	for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
+		switch (mode) {
+		case PHY_LED_ACTIVE_LOW:
+			led_active_low = true;
+			break;
+		case PHY_LED_ACTIVE_HIGH: /* default mode */
+			led_active_low = false;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	mask = VEND1_GLB_CPU_CTRL_LED_POLARITY(index);
+	if (led_active_low)
+		val = VEND1_GLB_CPU_CTRL_LED_POLARITY(index);
+
+	return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+			      VEND1_GLB_REG_CPU_CTRL,
+			      mask, val);
+}
+
+static struct phy_driver as21xxx_drivers[] = {
+	{
+		PHY_ID_MATCH_EXACT(PHY_ID_AS21XXX),
+		.name		= "Aeonsemi AS21xxx",
+		.probe		= as21xxx_probe,
+		.get_features	= as21xxx_get_features,
+		.read_status	= as21xxx_read_status,
+		.led_brightness_set = as21xxx_led_brightness_set,
+		.led_hw_is_supported = as21xxx_led_hw_is_supported,
+		.led_hw_control_set = as21xxx_led_hw_control_set,
+		.led_hw_control_get = as21xxx_led_hw_control_get,
+		.led_polarity_set = as21xxx_led_polarity_set,
+	},
+};
+module_phy_driver(as21xxx_drivers);
+
+static struct mdio_device_id __maybe_unused as21xxx_tbl[] = {
+	{ PHY_ID_MATCH_VENDOR(PHY_VENDOR_AEONSEMI) },
+	{ }
+};
+MODULE_DEVICE_TABLE(mdio, as21xxx_tbl);
+
+MODULE_DESCRIPTION("Aeonsemi AS21xxx PHY driver");
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_LICENSE("GPL");