diff mbox series

[2/2] net: phy: Add driver for Motorcomm yt8821 2.5G ethernet phy

Message ID 20240727092031.1108690-1-Frank.Sae@motor-comm.com (mailing list archive)
State Deferred
Delegated to: Netdev Maintainers
Headers show
Series net: phy: Add driver for Motorcomm yt8821 2.5G ethernet phy | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Guessed tree name to be 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 fail Errors and warnings before: 42 this patch: 43
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers success CCed 8 of 8 maintainers
netdev/build_clang fail Errors and warnings before: 43 this patch: 45
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 fail Errors and warnings before: 43 this patch: 44
netdev/checkpatch warning WARNING: line length of 81 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 90 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc fail Errors and warnings before: 35 this patch: 48
netdev/source_inline success Was 0 now: 0

Commit Message

Frank Sae July 27, 2024, 9:20 a.m. UTC
Add a driver for the motorcomm yt8821 2.5G ethernet phy.
 Verified the driver on
 BPI-R3(with MediaTek MT7986(Filogic 830) SoC) development board,
 which is developed by Guangdong Bipai Technology Co., Ltd..
 On the board, yt8821 2.5G ethernet phy works in
 AUTO_BX2500_SGMII or FORCE_BX2500 interface,
 supports 2.5G/1000M/100M/10M speeds, and wol(magic package).
 Since some functions of yt8821 are similar to YT8521
 so some functions for yt8821 can be reused.

Signed-off-by: Frank.Sae <Frank.Sae@motor-comm.com>
---
 drivers/net/phy/motorcomm.c | 639 +++++++++++++++++++++++++++++++++++-
 1 file changed, 636 insertions(+), 3 deletions(-)

Comments

Andrew Lunn July 27, 2024, 11:36 a.m. UTC | #1
On Sat, Jul 27, 2024 at 02:20:31AM -0700, Frank.Sae wrote:
>  Add a driver for the motorcomm yt8821 2.5G ethernet phy.
>  Verified the driver on
>  BPI-R3(with MediaTek MT7986(Filogic 830) SoC) development board,
>  which is developed by Guangdong Bipai Technology Co., Ltd..
>  On the board, yt8821 2.5G ethernet phy works in
>  AUTO_BX2500_SGMII or FORCE_BX2500 interface,
>  supports 2.5G/1000M/100M/10M speeds, and wol(magic package).
>  Since some functions of yt8821 are similar to YT8521
>  so some functions for yt8821 can be reused.

No leading space please.

> 
> Signed-off-by: Frank.Sae <Frank.Sae@motor-comm.com>
> ---
>  drivers/net/phy/motorcomm.c | 639 +++++++++++++++++++++++++++++++++++-
>  1 file changed, 636 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c
> index 7a11fdb687cc..a432b27dd849 100644
> --- a/drivers/net/phy/motorcomm.c
> +++ b/drivers/net/phy/motorcomm.c
> @@ -1,6 +1,6 @@
>  // SPDX-License-Identifier: GPL-2.0+
>  /*
> - * Motorcomm 8511/8521/8531/8531S PHY driver.
> + * Motorcomm 8511/8521/8531/8531S/8821 PHY driver.
>   *
>   * Author: Peter Geis <pgwipeout@gmail.com>
>   * Author: Frank <Frank.Sae@motor-comm.com>
> @@ -16,7 +16,7 @@
>  #define PHY_ID_YT8521		0x0000011a
>  #define PHY_ID_YT8531		0x4f51e91b
>  #define PHY_ID_YT8531S		0x4f51e91a
> -
> +#define PHY_ID_YT8821		0x4f51ea19
>  /* YT8521/YT8531S Register Overview
>   *	UTP Register space	|	FIBER Register space
>   *  ------------------------------------------------------------
> @@ -52,6 +52,15 @@
>  #define YTPHY_SSR_SPEED_10M			0x0
>  #define YTPHY_SSR_SPEED_100M			0x1
>  #define YTPHY_SSR_SPEED_1000M			0x2
> +/* bit9 as speed_mode[2], bit15:14 as Speed_mode[1:0]
> + * Speed_mode[2:0]:
> + * 100 = 2P5G
> + * 011 = 10G
> + * 010 = 1000 Mbps
> + * 001 = 100 Mbps
> + * 000 = 10 Mbps
> + */
> +#define YT8821_SSR_SPEED_2500M			0x4

If these bits are spread around, why 0x4? Ahh, because you extract the
bits and reform the value. Maybe:

#define YTPHY_SSR_SPEED_10M			(0x0 << 14)
#define YTPHY_SSR_SPEED_100M			(0x1 << 14)
#define YTPHY_SSR_SPEED_1000M			(0x2 << 14)
#define YTPHY_SSR_SPEED_10G			(0x3 << 14)
#define YT8821_SSR_SPEED_2500M			(0x0 << 14) | BIT(9)
#define YTPHY_SSR_SPEED_MASK			(0x3 << 14) | BIT(9)

> +#define YT8821_SDS_EXT_CSR_CTRL_REG			0x23
> +#define YT8821_SDS_EXT_CSR_PLL_SETTING			0x8605
> +#define YT8821_UTP_EXT_FFE_IPR_CTRL_REG			0x34E
> +#define YT8821_UTP_EXT_FFE_SETTING			0x8080
> +#define YT8821_UTP_EXT_VGA_LPF1_CAP_CTRL_REG		0x4D2
> +#define YT8821_UTP_EXT_VGA_LPF1_CAP_SHT_SETTING		0x5200
> +#define YT8821_UTP_EXT_VGA_LPF2_CAP_CTRL_REG		0x4D3
> +#define YT8821_UTP_EXT_VGA_LPF2_CAP_SHT_SETTING		0x5200
> +#define YT8821_UTP_EXT_TRACE_CTRL_REG			0x372
> +#define YT8821_UTP_EXT_TRACE_LNG_MED_GAIN_THR_SETTING	0x5A3C
> +#define YT8821_UTP_EXT_IPR_CTRL_REG			0x374
> +#define YT8821_UTP_EXT_IPR_ALPHA_IPR_SETTING		0x7C6C
> +#define YT8821_UTP_EXT_ECHO_CTRL_REG			0x336
> +#define YT8821_UTP_EXT_ECHO_SETTING			0xAA0A
> +#define YT8821_UTP_EXT_GAIN_CTRL_REG			0x340
> +#define YT8821_UTP_EXT_AGC_MED_GAIN_SETTING		0x3022
> +#define YT8821_UTP_EXT_TH_20DB_2500_CTRL_REG		0x36A
> +#define YT8821_UTP_EXT_TH_20DB_2500_SETTING		0x8000
> +#define YT8821_UTP_EXT_MU_COARSE_FR_CTRL_REG		0x4B3
> +#define YT8821_UTP_EXT_MU_COARSE_FR_FFE_GN_DC_SETTING	0x7711
> +#define YT8821_UTP_EXT_MU_FINE_FR_CTRL_REG		0x4B5
> +#define YT8821_UTP_EXT_MU_FINE_FR_FFE_GN_DC_SETTING	0x2211
> +#define YT8821_UTP_EXT_ANALOG_CFG7_CTRL_REG		0x56
> +#define YT8821_UTP_EXT_ANALOG_CFG7_RESET		0x20
> +#define YT8821_UTP_EXT_ANALOG_CFG7_PI_CLK_SEL_AFE	0x3F
> +#define YT8821_UTP_EXT_VCT_CFG6_CTRL_REG		0x97
> +#define YT8821_UTP_EXT_VCT_CFG6_FECHO_AMP_TH_SETTING	0x380C
> +#define YT8821_UTP_EXT_TXGE_NFR_FR_THP_CTRL_REG		0x660
> +#define YT8821_UTP_EXT_TXGE_NFR_FR_SETTING		0x112A
> +#define YT8821_UTP_EXT_PLL_CTRL_REG			0x450
> +#define YT8821_UTP_EXT_PLL_SPARE_SETTING		0xE9
> +#define YT8821_UTP_EXT_DAC_IMID_CHANNEL23_CTRL_REG	0x466
> +#define YT8821_UTP_EXT_DAC_IMID_CHANNEL23_SETTING	0x6464
> +#define YT8821_UTP_EXT_DAC_IMID_CHANNEL01_CTRL_REG	0x467
> +#define YT8821_UTP_EXT_DAC_IMID_CHANNEL01_SETTING	0x6464
> +#define YT8821_UTP_EXT_DAC_IMSB_CHANNEL23_CTRL_REG	0x468
> +#define YT8821_UTP_EXT_DAC_IMSB_CHANNEL23_SETTING	0x6464
> +#define YT8821_UTP_EXT_DAC_IMSB_CHANNEL01_CTRL_REG	0x469
> +#define YT8821_UTP_EXT_DAC_IMSB_CHANNEL01_SETTING	0x6464

All these _SETTING are magic numbers. Can you document any of them?

> +/**
> + * yt8821_probe() - read dts to get chip mode
> + * @phydev: a pointer to a &struct phy_device
> + *
> + * returns 0 or negative errno code

kerneldoc requires a : after returns.

> + */
> +static int yt8821_probe(struct phy_device *phydev)
> +{
> +	struct device_node *node = phydev->mdio.dev.of_node;
> +	struct device *dev = &phydev->mdio.dev;
> +	struct yt8821_priv *priv;
> +	u8 chip_mode;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	phydev->priv = priv;
> +
> +	if (of_property_read_u8(node, "motorcomm,chip-mode", &chip_mode))
> +		chip_mode = YT8821_CHIP_MODE_FORCE_BX2500;
> +
> +	switch (chip_mode) {
> +	case YT8821_CHIP_MODE_AUTO_BX2500_SGMII:
> +		priv->chip_mode = YT8821_CHIP_MODE_AUTO_BX2500_SGMII;
> +		break;
> +	case YT8821_CHIP_MODE_FORCE_BX2500:
> +		priv->chip_mode = YT8821_CHIP_MODE_FORCE_BX2500;
> +		break;
> +	default:
> +		phydev_warn(phydev, "chip_mode err:%d\n", chip_mode);
> +		return -EINVAL;

Didn't the binding say it defaults to forced? Yet here it gives an
error?

> + * yt8821_get_rate_matching - read register to get phy chip mode

Why? You have it in priv?

> +/**
> + * yt8821gen_init_paged() - generic initialization according to page
> + * @phydev: a pointer to a &struct phy_device
> + * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to
> + * operate.
> + *
> + * returns 0 or negative errno code
> + */
> +static int yt8821gen_init_paged(struct phy_device *phydev, int page)
> +{
> +	int old_page;
> +	int ret = 0;
> +
> +	old_page = phy_select_page(phydev, page & YT8521_RSSR_SPACE_MASK);
> +	if (old_page < 0)
> +		goto err_restore_page;
> +
> +	if (page & YT8521_RSSR_SPACE_MASK) {
> +		/* sds init */
> +		ret = __phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0);
> +		if (ret < 0)
> +			goto err_restore_page;
> +
> +		ret = ytphy_write_ext(phydev, YT8821_SDS_EXT_CSR_CTRL_REG,
> +				      YT8821_SDS_EXT_CSR_PLL_SETTING);
> +		if (ret < 0)
> +			goto err_restore_page;
> +	} else {
> +		/* utp init */
> +		ret = ytphy_write_ext(phydev, YT8821_UTP_EXT_FFE_IPR_CTRL_REG,
> +				      YT8821_UTP_EXT_FFE_SETTING);
> +		if (ret < 0)
> +			goto err_restore_page;
> +

...

> +	}
> +
> +err_restore_page:
> +	return phy_restore_page(phydev, old_page, ret);
> +}
> +
> +/**
> + * yt8821gen_init() - generic initialization
> + * @phydev: a pointer to a &struct phy_device
> + *
> + * returns 0 or negative errno code
> + */
> +static int yt8821gen_init(struct phy_device *phydev)
> +{
> +	int ret = 0;
> +
> +	ret = yt8821gen_init_paged(phydev, YT8521_RSSR_FIBER_SPACE);
> +	if (ret < 0)
> +		return ret;
> +
> +	return yt8821gen_init_paged(phydev, YT8521_RSSR_UTP_SPACE);

That is odd. Why not have two functions, rather than one with a
parameter. You get better functions names then, making it clearer what
each function is doing.

> +}
> +
> +/**
> + * yt8821_auto_sleep_config() - phy auto sleep config
> + * @phydev: a pointer to a &struct phy_device
> + * @enable: true enable auto sleep, false disable auto sleep
> + *
> + * returns 0 or negative errno code
> + */
> +static int yt8821_auto_sleep_config(struct phy_device *phydev, bool enable)
> +{
> +	int old_page;
> +	int ret = 0;
> +
> +	old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
> +	if (old_page < 0)
> +		goto err_restore_page;
> +
> +	ret = ytphy_modify_ext(phydev,
> +			       YT8521_EXTREG_SLEEP_CONTROL1_REG,
> +			       YT8521_ESC1R_SLEEP_SW,
> +			       enable ? 1 : 0);

So each page has its own extension registers?

> +	if (ret < 0)
> +		goto err_restore_page;
> +
> +err_restore_page:
> +	return phy_restore_page(phydev, old_page, ret);
> +}
> +

> +/**
> + * yt8821_config_init() - phy initializatioin
> + * @phydev: a pointer to a &struct phy_device
> + *
> + * returns 0 or negative errno code
> + */
> +static int yt8821_config_init(struct phy_device *phydev)
> +{
> +	struct yt8821_priv *priv = phydev->priv;
> +	int ret, val;
> +
> +	phydev->irq = PHY_POLL;

Why do this?

> +
> +	val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);
> +	if (priv->chip_mode == YT8821_CHIP_MODE_AUTO_BX2500_SGMII) {
> +		ret = ytphy_modify_ext_with_lock(phydev,
> +						 YT8521_CHIP_CONFIG_REG,
> +						 YT8521_CCR_MODE_SEL_MASK,
> +						 FIELD_PREP(YT8521_CCR_MODE_SEL_MASK, 0));
> +		if (ret < 0)
> +			return ret;
> +
> +		__assign_bit(PHY_INTERFACE_MODE_2500BASEX,
> +			     phydev->possible_interfaces,
> +			     true);
> +		__assign_bit(PHY_INTERFACE_MODE_SGMII,
> +			     phydev->possible_interfaces,
> +			     true);
> +
> +		phydev->rate_matching = RATE_MATCH_NONE;
> +	} else if (priv->chip_mode == YT8821_CHIP_MODE_FORCE_BX2500) {
> +		ret = ytphy_modify_ext_with_lock(phydev,
> +						 YT8521_CHIP_CONFIG_REG,
> +						 YT8521_CCR_MODE_SEL_MASK,
> +						 FIELD_PREP(YT8521_CCR_MODE_SEL_MASK, 1));
> +		if (ret < 0)
> +			return ret;
> +
> +		phydev->rate_matching = RATE_MATCH_PAUSE;
> +	}

The idea of this phydev->possible_interfaces is to allow the core to
figure out what mode is most appropriate. So i would drop the mode in
DT, default to auto, and let the core tell you it wants 2500 BaseX if
that is all the MAC can do.

> +static int yt8821_read_status(struct phy_device *phydev)
> +{
> +	struct yt8821_priv *priv = phydev->priv;
> +	int old_page;
> +	int ret = 0;
> +	int link;
> +	int val;
> +
> +	if (phydev->autoneg == AUTONEG_ENABLE) {
> +		int lpadv = phy_read_mmd(phydev,
> +					 MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
> +
> +		if (lpadv < 0)
> +			return lpadv;
> +
> +		mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising,
> +						  lpadv);
> +	}
> +
> +	ret = ytphy_write_ext_with_lock(phydev,
> +					YT8521_REG_SPACE_SELECT_REG,
> +					YT8521_RSSR_UTP_SPACE);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = genphy_read_status(phydev);
> +	if (ret < 0)
> +		return ret;
> +
> +	old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
> +	if (old_page < 0)
> +		goto err_restore_page;
> +
> +	val = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG);
> +	if (val < 0) {
> +		ret = val;
> +		goto err_restore_page;
> +	}
> +
> +	link = val & YTPHY_SSR_LINK;
> +	if (link)
> +		yt8821_adjust_status(phydev, val);
> +
> +	if (link) {
> +		if (phydev->link == 0)
> +			phydev_info(phydev,
> +				    "%s, phy addr: %d, link up, mii reg 0x%x = 0x%x\n",
> +				    __func__, phydev->mdio.addr,
> +				    YTPHY_SPECIFIC_STATUS_REG,
> +				    (unsigned int)val);

phydev_dbg()?


> +		phydev->link = 1;
> +	} else {
> +		if (phydev->link == 1)
> +			phydev_info(phydev, "%s, phy addr: %d, link down\n",
> +				    __func__, phydev->mdio.addr);

phydev_dbg()?

	Andrew
Simon Horman July 28, 2024, 7:36 a.m. UTC | #2
On Sat, Jul 27, 2024 at 02:20:31AM -0700, Frank.Sae wrote:
>  Add a driver for the motorcomm yt8821 2.5G ethernet phy.
>  Verified the driver on
>  BPI-R3(with MediaTek MT7986(Filogic 830) SoC) development board,
>  which is developed by Guangdong Bipai Technology Co., Ltd..
>  On the board, yt8821 2.5G ethernet phy works in
>  AUTO_BX2500_SGMII or FORCE_BX2500 interface,
>  supports 2.5G/1000M/100M/10M speeds, and wol(magic package).
>  Since some functions of yt8821 are similar to YT8521
>  so some functions for yt8821 can be reused.
> 
> Signed-off-by: Frank.Sae <Frank.Sae@motor-comm.com>

Hi Frank,

This is not a full review. And setting up expectations,
as per the form letter below, net-next is currently closed.
But nonetheless I've provided some minor review below.

## Form letter - net-next-closed

(Adapted from text by Jakub)

The merge window for v6.11 has begun and therefore net-next is closed
for new drivers, features, code refactoring and optimizations.
We are currently accepting bug fixes only.

Please repost when net-next reopens after 29th July.

RFC patches sent for review only are welcome at any time.

See: https://www.kernel.org/doc/html/next/process/maintainer-netdev.html#development-cycle

pw-bot: defer

> ---
>  drivers/net/phy/motorcomm.c | 639 +++++++++++++++++++++++++++++++++++-
>  1 file changed, 636 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c

...

> +/**
> + * yt8821_probe() - read dts to get chip mode
> + * @phydev: a pointer to a &struct phy_device
> + *
> + * returns 0 or negative errno code
> + */

nit: please document return values using a "Return:" or "Returns:" section.

Flagged by W=1 allmodconfig builds and ./scripts/kernel-doc -none -Warn

...

> +/**
> + * yt8821_config_init() - phy initializatioin
> + * @phydev: a pointer to a &struct phy_device
> + *
> + * returns 0 or negative errno code
> + */
> +static int yt8821_config_init(struct phy_device *phydev)
> +{
> +	struct yt8821_priv *priv = phydev->priv;
> +	int ret, val;
> +
> +	phydev->irq = PHY_POLL;
> +
> +	val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);

val is set here but otherwise unused.
Should val be checked for an error here?

Flagged by W=1 builds.

> +	if (priv->chip_mode == YT8821_CHIP_MODE_AUTO_BX2500_SGMII) {
> +		ret = ytphy_modify_ext_with_lock(phydev,
> +						 YT8521_CHIP_CONFIG_REG,
> +						 YT8521_CCR_MODE_SEL_MASK,
> +						 FIELD_PREP(YT8521_CCR_MODE_SEL_MASK, 0));
> +		if (ret < 0)
> +			return ret;
> +
> +		__assign_bit(PHY_INTERFACE_MODE_2500BASEX,
> +			     phydev->possible_interfaces,
> +			     true);
> +		__assign_bit(PHY_INTERFACE_MODE_SGMII,
> +			     phydev->possible_interfaces,
> +			     true);
> +
> +		phydev->rate_matching = RATE_MATCH_NONE;
> +	} else if (priv->chip_mode == YT8821_CHIP_MODE_FORCE_BX2500) {
> +		ret = ytphy_modify_ext_with_lock(phydev,
> +						 YT8521_CHIP_CONFIG_REG,
> +						 YT8521_CCR_MODE_SEL_MASK,
> +						 FIELD_PREP(YT8521_CCR_MODE_SEL_MASK, 1));
> +		if (ret < 0)
> +			return ret;
> +
> +		phydev->rate_matching = RATE_MATCH_PAUSE;
> +	}
> +
> +	ret = yt8821gen_init(phydev);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* disable auto sleep */
> +	ret = yt8821_auto_sleep_config(phydev, false);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* soft reset */
> +	yt8821_soft_reset(phydev);
> +
> +	return ret;
> +}
> +
> +/**
> + * yt8821_adjust_status() - update speed and duplex to phydev
> + * @phydev: a pointer to a &struct phy_device
> + * @val: read from YTPHY_SPECIFIC_STATUS_REG
> + */
> +static void yt8821_adjust_status(struct phy_device *phydev, int val)
> +{
> +	int speed_mode, duplex;
> +	int speed_mode_low, speed_mode_high;
> +	int speed = SPEED_UNKNOWN;

nit: Please consider arranging local variables in reverse xmas tree order -
     longest line to shortest.

     This can be helpful: https://github.com/ecree-solarflare/xmastree

> +
> +	duplex = FIELD_GET(YTPHY_SSR_DUPLEX, val);
> +
> +	speed_mode_low = FIELD_GET(GENMASK(15, 14), val);
> +	speed_mode_high = FIELD_GET(BIT(9), val);
> +	speed_mode = FIELD_PREP(BIT(2), speed_mode_high) |
> +			FIELD_PREP(GENMASK(1, 0), speed_mode_low);
> +	switch (speed_mode) {
> +	case YTPHY_SSR_SPEED_10M:
> +		speed = SPEED_10;
> +		break;
> +	case YTPHY_SSR_SPEED_100M:
> +		speed = SPEED_100;
> +		break;
> +	case YTPHY_SSR_SPEED_1000M:
> +		speed = SPEED_1000;
> +		break;
> +	case YT8821_SSR_SPEED_2500M:
> +		speed = SPEED_2500;
> +		break;
> +	default:
> +		speed = SPEED_UNKNOWN;
> +		break;
> +	}
> +
> +	phydev->speed = speed;
> +	phydev->duplex = duplex;
> +}

...
Russell King (Oracle) July 29, 2024, 11:41 a.m. UTC | #3
On Sat, Jul 27, 2024 at 02:20:31AM -0700, Frank.Sae wrote:
> +/**
> + * yt8821_config_init() - phy initializatioin
> + * @phydev: a pointer to a &struct phy_device
> + *
> + * returns 0 or negative errno code
> + */
> +static int yt8821_config_init(struct phy_device *phydev)
> +{
> +	struct yt8821_priv *priv = phydev->priv;
> +	int ret, val;
> +
> +	phydev->irq = PHY_POLL;
> +
> +	val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);
> +	if (priv->chip_mode == YT8821_CHIP_MODE_AUTO_BX2500_SGMII) {
> +		ret = ytphy_modify_ext_with_lock(phydev,
> +						 YT8521_CHIP_CONFIG_REG,
> +						 YT8521_CCR_MODE_SEL_MASK,
> +						 FIELD_PREP(YT8521_CCR_MODE_SEL_MASK, 0));
> +		if (ret < 0)
> +			return ret;
> +
> +		__assign_bit(PHY_INTERFACE_MODE_2500BASEX,
> +			     phydev->possible_interfaces,
> +			     true);
> +		__assign_bit(PHY_INTERFACE_MODE_SGMII,
> +			     phydev->possible_interfaces,
> +			     true);

Before each and every call to .config_init, phydev->possible_interfaces
will be cleared. So, please use __set_bit() here.

> +static int yt8821_read_status(struct phy_device *phydev)
> +{
> +	struct yt8821_priv *priv = phydev->priv;
> +	int old_page;
> +	int ret = 0;
> +	int link;
> +	int val;
> +
> +	if (phydev->autoneg == AUTONEG_ENABLE) {
> +		int lpadv = phy_read_mmd(phydev,
> +					 MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
> +
> +		if (lpadv < 0)
> +			return lpadv;
> +
> +		mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising,
> +						  lpadv);
> +	}
> +
> +	ret = ytphy_write_ext_with_lock(phydev,
> +					YT8521_REG_SPACE_SELECT_REG,
> +					YT8521_RSSR_UTP_SPACE);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = genphy_read_status(phydev);
> +	if (ret < 0)
> +		return ret;
> +
> +	old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
> +	if (old_page < 0)
> +		goto err_restore_page;
> +
> +	val = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG);
> +	if (val < 0) {
> +		ret = val;
> +		goto err_restore_page;
> +	}
> +
> +	link = val & YTPHY_SSR_LINK;

What link status is this reporting? For interface switching to work,
phydev->link must _only_ indicate whether the _media_ side interface
is up or down. It must _not_ include the status of the MAC facing
interface from the PHY.

Why? The interface configuration of the MAC is only performed when
the _media_ link comes up, denoted by phydev->link becoming true.
If the MAC interface configuration mismatches the PHY interface
configuration, then the MAC facing interface of the PHY will
remain down, and if phydev->link is forced to false, then the link
will never come up.

So, I hope that this isn't testing the MAC facing interface status
of the PHY!
Russell King (Oracle) July 29, 2024, 11:44 a.m. UTC | #4
On Sat, Jul 27, 2024 at 01:36:25PM +0200, Andrew Lunn wrote:
> The idea of this phydev->possible_interfaces is to allow the core to
> figure out what mode is most appropriate. So i would drop the mode in
> DT, default to auto, and let the core tell you it wants 2500 BaseX if
> that is all the MAC can do.

phydev->possible_interfaces reports the bitmap of interfaces that the
PHY _will_ switch between on its MAC facing side depending on the
negotiated media-side link.

It would be nice if this driver always filled in
phydev->possible_interfaces, even if there is only one interface
when it's using rate matching.
Frank Sae Aug. 11, 2024, 1:47 p.m. UTC | #5
On 7/27/24 04:36, Andrew Lunn wrote:
> On Sat, Jul 27, 2024 at 02:20:31AM -0700, Frank.Sae wrote:
>>   Add a driver for the motorcomm yt8821 2.5G ethernet phy.
>>   Verified the driver on
>>   BPI-R3(with MediaTek MT7986(Filogic 830) SoC) development board,
>>   which is developed by Guangdong Bipai Technology Co., Ltd..
>>   On the board, yt8821 2.5G ethernet phy works in
>>   AUTO_BX2500_SGMII or FORCE_BX2500 interface,
>>   supports 2.5G/1000M/100M/10M speeds, and wol(magic package).
>>   Since some functions of yt8821 are similar to YT8521
>>   so some functions for yt8821 can be reused.
> No leading space please.
>
>> Signed-off-by: Frank.Sae <Frank.Sae@motor-comm.com>
>> ---
>>   drivers/net/phy/motorcomm.c | 639 +++++++++++++++++++++++++++++++++++-
>>   1 file changed, 636 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c
>> index 7a11fdb687cc..a432b27dd849 100644
>> --- a/drivers/net/phy/motorcomm.c
>> +++ b/drivers/net/phy/motorcomm.c
>> @@ -1,6 +1,6 @@
>>   // SPDX-License-Identifier: GPL-2.0+
>>   /*
>> - * Motorcomm 8511/8521/8531/8531S PHY driver.
>> + * Motorcomm 8511/8521/8531/8531S/8821 PHY driver.
>>    *
>>    * Author: Peter Geis <pgwipeout@gmail.com>
>>    * Author: Frank <Frank.Sae@motor-comm.com>
>> @@ -16,7 +16,7 @@
>>   #define PHY_ID_YT8521		0x0000011a
>>   #define PHY_ID_YT8531		0x4f51e91b
>>   #define PHY_ID_YT8531S		0x4f51e91a
>> -
>> +#define PHY_ID_YT8821		0x4f51ea19
>>   /* YT8521/YT8531S Register Overview
>>    *	UTP Register space	|	FIBER Register space
>>    *  ------------------------------------------------------------
>> @@ -52,6 +52,15 @@
>>   #define YTPHY_SSR_SPEED_10M			0x0
>>   #define YTPHY_SSR_SPEED_100M			0x1
>>   #define YTPHY_SSR_SPEED_1000M			0x2
>> +/* bit9 as speed_mode[2], bit15:14 as Speed_mode[1:0]
>> + * Speed_mode[2:0]:
>> + * 100 = 2P5G
>> + * 011 = 10G
>> + * 010 = 1000 Mbps
>> + * 001 = 100 Mbps
>> + * 000 = 10 Mbps
>> + */
>> +#define YT8821_SSR_SPEED_2500M			0x4
> If these bits are spread around, why 0x4? Ahh, because you extract the
> bits and reform the value. Maybe:
>
> #define YTPHY_SSR_SPEED_10M			(0x0 << 14)
> #define YTPHY_SSR_SPEED_100M			(0x1 << 14)
> #define YTPHY_SSR_SPEED_1000M			(0x2 << 14)
> #define YTPHY_SSR_SPEED_10G			(0x3 << 14)
> #define YT8821_SSR_SPEED_2500M			(0x0 << 14) | BIT(9)
> #define YTPHY_SSR_SPEED_MASK			(0x3 << 14) | BIT(9)
>
please help to confirm it is ok?
#define YT8821_SSR_SPEED_2500M	((BIT(9) >> 9) << 2) | ((0x0 << 14) >> 14)

>> +#define YT8821_SDS_EXT_CSR_CTRL_REG			0x23
>> +#define YT8821_SDS_EXT_CSR_PLL_SETTING			0x8605
>> +#define YT8821_UTP_EXT_FFE_IPR_CTRL_REG			0x34E
>> +#define YT8821_UTP_EXT_FFE_SETTING			0x8080
>> +#define YT8821_UTP_EXT_VGA_LPF1_CAP_CTRL_REG		0x4D2
>> +#define YT8821_UTP_EXT_VGA_LPF1_CAP_SHT_SETTING		0x5200
>> +#define YT8821_UTP_EXT_VGA_LPF2_CAP_CTRL_REG		0x4D3
>> +#define YT8821_UTP_EXT_VGA_LPF2_CAP_SHT_SETTING		0x5200
>> +#define YT8821_UTP_EXT_TRACE_CTRL_REG			0x372
>> +#define YT8821_UTP_EXT_TRACE_LNG_MED_GAIN_THR_SETTING	0x5A3C
>> +#define YT8821_UTP_EXT_IPR_CTRL_REG			0x374
>> +#define YT8821_UTP_EXT_IPR_ALPHA_IPR_SETTING		0x7C6C
>> +#define YT8821_UTP_EXT_ECHO_CTRL_REG			0x336
>> +#define YT8821_UTP_EXT_ECHO_SETTING			0xAA0A
>> +#define YT8821_UTP_EXT_GAIN_CTRL_REG			0x340
>> +#define YT8821_UTP_EXT_AGC_MED_GAIN_SETTING		0x3022
>> +#define YT8821_UTP_EXT_TH_20DB_2500_CTRL_REG		0x36A
>> +#define YT8821_UTP_EXT_TH_20DB_2500_SETTING		0x8000
>> +#define YT8821_UTP_EXT_MU_COARSE_FR_CTRL_REG		0x4B3
>> +#define YT8821_UTP_EXT_MU_COARSE_FR_FFE_GN_DC_SETTING	0x7711
>> +#define YT8821_UTP_EXT_MU_FINE_FR_CTRL_REG		0x4B5
>> +#define YT8821_UTP_EXT_MU_FINE_FR_FFE_GN_DC_SETTING	0x2211
>> +#define YT8821_UTP_EXT_ANALOG_CFG7_CTRL_REG		0x56
>> +#define YT8821_UTP_EXT_ANALOG_CFG7_RESET		0x20
>> +#define YT8821_UTP_EXT_ANALOG_CFG7_PI_CLK_SEL_AFE	0x3F
>> +#define YT8821_UTP_EXT_VCT_CFG6_CTRL_REG		0x97
>> +#define YT8821_UTP_EXT_VCT_CFG6_FECHO_AMP_TH_SETTING	0x380C
>> +#define YT8821_UTP_EXT_TXGE_NFR_FR_THP_CTRL_REG		0x660
>> +#define YT8821_UTP_EXT_TXGE_NFR_FR_SETTING		0x112A
>> +#define YT8821_UTP_EXT_PLL_CTRL_REG			0x450
>> +#define YT8821_UTP_EXT_PLL_SPARE_SETTING		0xE9
>> +#define YT8821_UTP_EXT_DAC_IMID_CHANNEL23_CTRL_REG	0x466
>> +#define YT8821_UTP_EXT_DAC_IMID_CHANNEL23_SETTING	0x6464
>> +#define YT8821_UTP_EXT_DAC_IMID_CHANNEL01_CTRL_REG	0x467
>> +#define YT8821_UTP_EXT_DAC_IMID_CHANNEL01_SETTING	0x6464
>> +#define YT8821_UTP_EXT_DAC_IMSB_CHANNEL23_CTRL_REG	0x468
>> +#define YT8821_UTP_EXT_DAC_IMSB_CHANNEL23_SETTING	0x6464
>> +#define YT8821_UTP_EXT_DAC_IMSB_CHANNEL01_CTRL_REG	0x469
>> +#define YT8821_UTP_EXT_DAC_IMSB_CHANNEL01_SETTING	0x6464
> All these _SETTING are magic numbers. Can you document any of them?
>
please help to confirm it is ok?
#define YT8821_SDS_EXT_CSR_CTRL_REG			0x23
#define YT8821_SDS_EXT_CSR_VCO_LDO_EN			BIT(15)
#define YT8821_SDS_EXT_CSR_VCO_BIAS_LPF_EN		BIT(8)

#define YT8821_UTP_EXT_RPDN_CTRL_REG			0x34E
#define YT8821_UTP_EXT_RPDN_BP_FFE_LNG_2500		BIT(15)
#define YT8821_UTP_EXT_RPDN_BP_FFE_SHT_2500		BIT(7)
#define YT8821_UTP_EXT_RPDN_IPR_SHT_2500		GENMASK(6, 0)

#define YT8821_UTP_EXT_VGA_LPF1_CAP_CTRL_REG		0x4D2
#define YT8821_UTP_EXT_VGA_LPF1_CAP_OTHER		GENMASK(7, 4)
#define YT8821_UTP_EXT_VGA_LPF1_CAP_2500		GENMASK(3, 0)

#define YT8821_UTP_EXT_VGA_LPF2_CAP_CTRL_REG		0x4D3
#define YT8821_UTP_EXT_VGA_LPF2_CAP_OTHER		GENMASK(7, 4)
#define YT8821_UTP_EXT_VGA_LPF2_CAP_2500		GENMASK(3, 0)

#define YT8821_UTP_EXT_TRACE_CTRL_REG			0x372
#define YT8821_UTP_EXT_TRACE_LNG_GAIN_THE_2500		GENMASK(14, 8)
#define YT8821_UTP_EXT_TRACE_MED_GAIN_THE_2500		GENMASK(6, 0)

#define YT8821_UTP_EXT_ALPHA_IPR_CTRL_REG		0x374
#define YT8821_UTP_EXT_ALPHA_SHT_2500			GENMASK(14, 8)
#define YT8821_UTP_EXT_IPR_LNG_2500			GENMASK(6, 0)

#define YT8821_UTP_EXT_ECHO_CTRL_REG			0x336
#define YT8821_UTP_EXT_TRACE_LNG_GAIN_THR_1000		GENMASK(14, 8)

#define YT8821_UTP_EXT_GAIN_CTRL_REG			0x340
#define YT8821_UTP_EXT_TRACE_MED_GAIN_THR_1000		GENMASK(6, 0)

#define YT8821_UTP_EXT_TH_20DB_2500_CTRL_REG		0x36A
#define YT8821_UTP_EXT_TH_20DB_2500			GENMASK(15, 0)

#define YT8821_UTP_EXT_MU_COARSE_FR_CTRL_REG		0x4B3
#define YT8821_UTP_EXT_MU_COARSE_FR_F_FFE		GENMASK(14, 12)
#define YT8821_UTP_EXT_MU_COARSE_FR_F_FBE		GENMASK(10, 8)

#define YT8821_UTP_EXT_MU_FINE_FR_CTRL_REG		0x4B5
#define YT8821_UTP_EXT_MU_FINE_FR_F_FFE			GENMASK(14, 12)
#define YT8821_UTP_EXT_MU_FINE_FR_F_FBE			GENMASK(10, 8)

#define YT8821_UTP_EXT_PI_CTRL_REG			0x56
#define YT8821_UTP_EXT_PI_RST_N_FIFO			BIT(5)
#define YT8821_UTP_EXT_PI_TX_CLK_SEL_AFE		BIT(4)
#define YT8821_UTP_EXT_PI_RX_CLK_3_SEL_AFE		BIT(3)
#define YT8821_UTP_EXT_PI_RX_CLK_2_SEL_AFE		BIT(2)
#define YT8821_UTP_EXT_PI_RX_CLK_1_SEL_AFE		BIT(1)
#define YT8821_UTP_EXT_PI_RX_CLK_0_SEL_AFE		BIT(0)

#define YT8821_UTP_EXT_VCT_CFG6_CTRL_REG		0x97
#define YT8821_UTP_EXT_FECHO_AMP_TH_HUGE		GENMASK(15, 8)

#define YT8821_UTP_EXT_TXGE_NFR_FR_THP_CTRL_REG		0x660
#define YT8821_UTP_EXT_NFR_TX_ABILITY			BIT(3)

#define YT8821_UTP_EXT_PLL_CTRL_REG			0x450
#define YT8821_UTP_EXT_PLL_SPARE_CFG			GENMASK(7, 0)

#define YT8821_UTP_EXT_DAC_IMID_CH_2_3_CTRL_REG		0x466
#define YT8821_UTP_EXT_DAC_IMID_CH_3_10_ORG		GENMASK(14, 8)
#define YT8821_UTP_EXT_DAC_IMID_CH_2_10_ORG		GENMASK(6, 0)

#define YT8821_UTP_EXT_DAC_IMID_CH_0_1_CTRL_REG		0x467
#define YT8821_UTP_EXT_DAC_IMID_CH_1_10_ORG		GENMASK(14, 8)
#define YT8821_UTP_EXT_DAC_IMID_CH_0_10_ORG		GENMASK(6, 0)

#define YT8821_UTP_EXT_DAC_IMSB_CH_2_3_CTRL_REG		0x468
#define YT8821_UTP_EXT_DAC_IMSB_CH_3_10_ORG		GENMASK(14, 8)
#define YT8821_UTP_EXT_DAC_IMSB_CH_2_10_ORG		GENMASK(6, 0)

#define YT8821_UTP_EXT_DAC_IMSB_CH_0_1_CTRL_REG		0x469
#define YT8821_UTP_EXT_DAC_IMSB_CH_1_10_ORG		GENMASK(14, 8)
#define YT8821_UTP_EXT_DAC_IMSB_CH_0_10_ORG		GENMASK(6, 0)

>> +/**
>> + * yt8821_probe() - read dts to get chip mode
>> + * @phydev: a pointer to a &struct phy_device
>> + *
>> + * returns 0 or negative errno code
> kerneldoc requires a : after returns.
>
>> + */
>> +static int yt8821_probe(struct phy_device *phydev)
>> +{
>> +	struct device_node *node = phydev->mdio.dev.of_node;
>> +	struct device *dev = &phydev->mdio.dev;
>> +	struct yt8821_priv *priv;
>> +	u8 chip_mode;
>> +
>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>> +	if (!priv)
>> +		return -ENOMEM;
>> +
>> +	phydev->priv = priv;
>> +
>> +	if (of_property_read_u8(node, "motorcomm,chip-mode", &chip_mode))
>> +		chip_mode = YT8821_CHIP_MODE_FORCE_BX2500;
>> +
>> +	switch (chip_mode) {
>> +	case YT8821_CHIP_MODE_AUTO_BX2500_SGMII:
>> +		priv->chip_mode = YT8821_CHIP_MODE_AUTO_BX2500_SGMII;
>> +		break;
>> +	case YT8821_CHIP_MODE_FORCE_BX2500:
>> +		priv->chip_mode = YT8821_CHIP_MODE_FORCE_BX2500;
>> +		break;
>> +	default:
>> +		phydev_warn(phydev, "chip_mode err:%d\n", chip_mode);
>> +		return -EINVAL;
> Didn't the binding say it defaults to forced? Yet here it gives an
> error?
>
>> + * yt8821_get_rate_matching - read register to get phy chip mode
> Why? You have it in priv?
>
>> +/**
>> + * yt8821gen_init_paged() - generic initialization according to page
>> + * @phydev: a pointer to a &struct phy_device
>> + * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to
>> + * operate.
>> + *
>> + * returns 0 or negative errno code
>> + */
>> +static int yt8821gen_init_paged(struct phy_device *phydev, int page)
>> +{
>> +	int old_page;
>> +	int ret = 0;
>> +
>> +	old_page = phy_select_page(phydev, page & YT8521_RSSR_SPACE_MASK);
>> +	if (old_page < 0)
>> +		goto err_restore_page;
>> +
>> +	if (page & YT8521_RSSR_SPACE_MASK) {
>> +		/* sds init */
>> +		ret = __phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0);
>> +		if (ret < 0)
>> +			goto err_restore_page;
>> +
>> +		ret = ytphy_write_ext(phydev, YT8821_SDS_EXT_CSR_CTRL_REG,
>> +				      YT8821_SDS_EXT_CSR_PLL_SETTING);
>> +		if (ret < 0)
>> +			goto err_restore_page;
>> +	} else {
>> +		/* utp init */
>> +		ret = ytphy_write_ext(phydev, YT8821_UTP_EXT_FFE_IPR_CTRL_REG,
>> +				      YT8821_UTP_EXT_FFE_SETTING);
>> +		if (ret < 0)
>> +			goto err_restore_page;
>> +
> ...
>
>> +	}
>> +
>> +err_restore_page:
>> +	return phy_restore_page(phydev, old_page, ret);
>> +}
>> +
>> +/**
>> + * yt8821gen_init() - generic initialization
>> + * @phydev: a pointer to a &struct phy_device
>> + *
>> + * returns 0 or negative errno code
>> + */
>> +static int yt8821gen_init(struct phy_device *phydev)
>> +{
>> +	int ret = 0;
>> +
>> +	ret = yt8821gen_init_paged(phydev, YT8521_RSSR_FIBER_SPACE);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	return yt8821gen_init_paged(phydev, YT8521_RSSR_UTP_SPACE);
> That is odd. Why not have two functions, rather than one with a
> parameter. You get better functions names then, making it clearer what
> each function is doing.

In next commit, yt8821gen_init_paged(phydev, YT8521_RSSR_FIBER_SPACE); will
be replaced with yt8821_serdes_init(phydev);,
yt8821gen_init_paged(phydev, YT8521_RSSR_UTP_SPACE); will be replaced with
yt8821_utp_init(phydev);
please help to confirm it is ok?

>> +}
>> +
>> +/**
>> + * yt8821_auto_sleep_config() - phy auto sleep config
>> + * @phydev: a pointer to a &struct phy_device
>> + * @enable: true enable auto sleep, false disable auto sleep
>> + *
>> + * returns 0 or negative errno code
>> + */
>> +static int yt8821_auto_sleep_config(struct phy_device *phydev, bool enable)
>> +{
>> +	int old_page;
>> +	int ret = 0;
>> +
>> +	old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
>> +	if (old_page < 0)
>> +		goto err_restore_page;
>> +
>> +	ret = ytphy_modify_ext(phydev,
>> +			       YT8521_EXTREG_SLEEP_CONTROL1_REG,
>> +			       YT8521_ESC1R_SLEEP_SW,
>> +			       enable ? 1 : 0);
> So each page has its own extension registers?

Yes

>
>> +	if (ret < 0)
>> +		goto err_restore_page;
>> +
>> +err_restore_page:
>> +	return phy_restore_page(phydev, old_page, ret);
>> +}
>> +
>> +/**
>> + * yt8821_config_init() - phy initializatioin
>> + * @phydev: a pointer to a &struct phy_device
>> + *
>> + * returns 0 or negative errno code
>> + */
>> +static int yt8821_config_init(struct phy_device *phydev)
>> +{
>> +	struct yt8821_priv *priv = phydev->priv;
>> +	int ret, val;
>> +
>> +	phydev->irq = PHY_POLL;
> Why do this?

phydev->irq = PHY_POLL; will be removed in next commit.

>
>> +
>> +	val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);
>> +	if (priv->chip_mode == YT8821_CHIP_MODE_AUTO_BX2500_SGMII) {
>> +		ret = ytphy_modify_ext_with_lock(phydev,
>> +						 YT8521_CHIP_CONFIG_REG,
>> +						 YT8521_CCR_MODE_SEL_MASK,
>> +						 FIELD_PREP(YT8521_CCR_MODE_SEL_MASK, 0));
>> +		if (ret < 0)
>> +			return ret;
>> +
>> +		__assign_bit(PHY_INTERFACE_MODE_2500BASEX,
>> +			     phydev->possible_interfaces,
>> +			     true);
>> +		__assign_bit(PHY_INTERFACE_MODE_SGMII,
>> +			     phydev->possible_interfaces,
>> +			     true);
>> +
>> +		phydev->rate_matching = RATE_MATCH_NONE;
>> +	} else if (priv->chip_mode == YT8821_CHIP_MODE_FORCE_BX2500) {
>> +		ret = ytphy_modify_ext_with_lock(phydev,
>> +						 YT8521_CHIP_CONFIG_REG,
>> +						 YT8521_CCR_MODE_SEL_MASK,
>> +						 FIELD_PREP(YT8521_CCR_MODE_SEL_MASK, 1));
>> +		if (ret < 0)
>> +			return ret;
>> +
>> +		phydev->rate_matching = RATE_MATCH_PAUSE;
>> +	}
> The idea of this phydev->possible_interfaces is to allow the core to
> figure out what mode is most appropriate. So i would drop the mode in
> DT, default to auto, and let the core tell you it wants 2500 BaseX if
> that is all the MAC can do.
>
>> +static int yt8821_read_status(struct phy_device *phydev)
>> +{
>> +	struct yt8821_priv *priv = phydev->priv;
>> +	int old_page;
>> +	int ret = 0;
>> +	int link;
>> +	int val;
>> +
>> +	if (phydev->autoneg == AUTONEG_ENABLE) {
>> +		int lpadv = phy_read_mmd(phydev,
>> +					 MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
>> +
>> +		if (lpadv < 0)
>> +			return lpadv;
>> +
>> +		mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising,
>> +						  lpadv);
>> +	}
>> +
>> +	ret = ytphy_write_ext_with_lock(phydev,
>> +					YT8521_REG_SPACE_SELECT_REG,
>> +					YT8521_RSSR_UTP_SPACE);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	ret = genphy_read_status(phydev);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
>> +	if (old_page < 0)
>> +		goto err_restore_page;
>> +
>> +	val = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG);
>> +	if (val < 0) {
>> +		ret = val;
>> +		goto err_restore_page;
>> +	}
>> +
>> +	link = val & YTPHY_SSR_LINK;
>> +	if (link)
>> +		yt8821_adjust_status(phydev, val);
>> +
>> +	if (link) {
>> +		if (phydev->link == 0)
>> +			phydev_info(phydev,
>> +				    "%s, phy addr: %d, link up, mii reg 0x%x = 0x%x\n",
>> +				    __func__, phydev->mdio.addr,
>> +				    YTPHY_SPECIFIC_STATUS_REG,
>> +				    (unsigned int)val);
> phydev_dbg()?
>
phydev_dbg() instead in next commit.

>> +		phydev->link = 1;
>> +	} else {
>> +		if (phydev->link == 1)
>> +			phydev_info(phydev, "%s, phy addr: %d, link down\n",
>> +				    __func__, phydev->mdio.addr);
> phydev_dbg()?
>
> 	Andrew
Frank Sae Aug. 11, 2024, 1:58 p.m. UTC | #6
On 7/29/24 04:41, Russell King (Oracle) wrote:
> On Sat, Jul 27, 2024 at 02:20:31AM -0700, Frank.Sae wrote:
>> +/**
>> + * yt8821_config_init() - phy initializatioin
>> + * @phydev: a pointer to a &struct phy_device
>> + *
>> + * returns 0 or negative errno code
>> + */
>> +static int yt8821_config_init(struct phy_device *phydev)
>> +{
>> +	struct yt8821_priv *priv = phydev->priv;
>> +	int ret, val;
>> +
>> +	phydev->irq = PHY_POLL;
>> +
>> +	val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);
>> +	if (priv->chip_mode == YT8821_CHIP_MODE_AUTO_BX2500_SGMII) {
>> +		ret = ytphy_modify_ext_with_lock(phydev,
>> +						 YT8521_CHIP_CONFIG_REG,
>> +						 YT8521_CCR_MODE_SEL_MASK,
>> +						 FIELD_PREP(YT8521_CCR_MODE_SEL_MASK, 0));
>> +		if (ret < 0)
>> +			return ret;
>> +
>> +		__assign_bit(PHY_INTERFACE_MODE_2500BASEX,
>> +			     phydev->possible_interfaces,
>> +			     true);
>> +		__assign_bit(PHY_INTERFACE_MODE_SGMII,
>> +			     phydev->possible_interfaces,
>> +			     true);
> Before each and every call to .config_init, phydev->possible_interfaces
> will be cleared. So, please use __set_bit() here.
>
>> +static int yt8821_read_status(struct phy_device *phydev)
>> +{
>> +	struct yt8821_priv *priv = phydev->priv;
>> +	int old_page;
>> +	int ret = 0;
>> +	int link;
>> +	int val;
>> +
>> +	if (phydev->autoneg == AUTONEG_ENABLE) {
>> +		int lpadv = phy_read_mmd(phydev,
>> +					 MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
>> +
>> +		if (lpadv < 0)
>> +			return lpadv;
>> +
>> +		mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising,
>> +						  lpadv);
>> +	}
>> +
>> +	ret = ytphy_write_ext_with_lock(phydev,
>> +					YT8521_REG_SPACE_SELECT_REG,
>> +					YT8521_RSSR_UTP_SPACE);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	ret = genphy_read_status(phydev);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
>> +	if (old_page < 0)
>> +		goto err_restore_page;
>> +
>> +	val = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG);
>> +	if (val < 0) {
>> +		ret = val;
>> +		goto err_restore_page;
>> +	}
>> +
>> +	link = val & YTPHY_SSR_LINK;
> What link status is this reporting? For interface switching to work,
> phydev->link must _only_ indicate whether the _media_ side interface
> is up or down. It must _not_ include the status of the MAC facing
> interface from the PHY.
>
> Why? The interface configuration of the MAC is only performed when
> the _media_ link comes up, denoted by phydev->link becoming true.
> If the MAC interface configuration mismatches the PHY interface
> configuration, then the MAC facing interface of the PHY will
> remain down, and if phydev->link is forced to false, then the link
> will never come up.
>
> So, I hope that this isn't testing the MAC facing interface status
> of the PHY!
>
MAC facing interface will be switched according to phy interface. when phy
media side state is link down, the mac facing interface will not be
configured, this refers to Marvell10g.c(mv3310_update_interface) and
Realtek.c(rtl822xb_update_interface).
diff mbox series

Patch

diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c
index 7a11fdb687cc..a432b27dd849 100644
--- a/drivers/net/phy/motorcomm.c
+++ b/drivers/net/phy/motorcomm.c
@@ -1,6 +1,6 @@ 
 // SPDX-License-Identifier: GPL-2.0+
 /*
- * Motorcomm 8511/8521/8531/8531S PHY driver.
+ * Motorcomm 8511/8521/8531/8531S/8821 PHY driver.
  *
  * Author: Peter Geis <pgwipeout@gmail.com>
  * Author: Frank <Frank.Sae@motor-comm.com>
@@ -16,7 +16,7 @@ 
 #define PHY_ID_YT8521		0x0000011a
 #define PHY_ID_YT8531		0x4f51e91b
 #define PHY_ID_YT8531S		0x4f51e91a
-
+#define PHY_ID_YT8821		0x4f51ea19
 /* YT8521/YT8531S Register Overview
  *	UTP Register space	|	FIBER Register space
  *  ------------------------------------------------------------
@@ -52,6 +52,15 @@ 
 #define YTPHY_SSR_SPEED_10M			0x0
 #define YTPHY_SSR_SPEED_100M			0x1
 #define YTPHY_SSR_SPEED_1000M			0x2
+/* bit9 as speed_mode[2], bit15:14 as Speed_mode[1:0]
+ * Speed_mode[2:0]:
+ * 100 = 2P5G
+ * 011 = 10G
+ * 010 = 1000 Mbps
+ * 001 = 100 Mbps
+ * 000 = 10 Mbps
+ */
+#define YT8821_SSR_SPEED_2500M			0x4
 #define YTPHY_SSR_DUPLEX_OFFSET			13
 #define YTPHY_SSR_DUPLEX			BIT(13)
 #define YTPHY_SSR_PAGE_RECEIVED			BIT(12)
@@ -270,12 +279,59 @@ 
 #define YT8531_SCR_CLK_SRC_REF_25M		4
 #define YT8531_SCR_CLK_SRC_SSC_25M		5
 
+#define YT8821_SDS_EXT_CSR_CTRL_REG			0x23
+#define YT8821_SDS_EXT_CSR_PLL_SETTING			0x8605
+#define YT8821_UTP_EXT_FFE_IPR_CTRL_REG			0x34E
+#define YT8821_UTP_EXT_FFE_SETTING			0x8080
+#define YT8821_UTP_EXT_VGA_LPF1_CAP_CTRL_REG		0x4D2
+#define YT8821_UTP_EXT_VGA_LPF1_CAP_SHT_SETTING		0x5200
+#define YT8821_UTP_EXT_VGA_LPF2_CAP_CTRL_REG		0x4D3
+#define YT8821_UTP_EXT_VGA_LPF2_CAP_SHT_SETTING		0x5200
+#define YT8821_UTP_EXT_TRACE_CTRL_REG			0x372
+#define YT8821_UTP_EXT_TRACE_LNG_MED_GAIN_THR_SETTING	0x5A3C
+#define YT8821_UTP_EXT_IPR_CTRL_REG			0x374
+#define YT8821_UTP_EXT_IPR_ALPHA_IPR_SETTING		0x7C6C
+#define YT8821_UTP_EXT_ECHO_CTRL_REG			0x336
+#define YT8821_UTP_EXT_ECHO_SETTING			0xAA0A
+#define YT8821_UTP_EXT_GAIN_CTRL_REG			0x340
+#define YT8821_UTP_EXT_AGC_MED_GAIN_SETTING		0x3022
+#define YT8821_UTP_EXT_TH_20DB_2500_CTRL_REG		0x36A
+#define YT8821_UTP_EXT_TH_20DB_2500_SETTING		0x8000
+#define YT8821_UTP_EXT_MU_COARSE_FR_CTRL_REG		0x4B3
+#define YT8821_UTP_EXT_MU_COARSE_FR_FFE_GN_DC_SETTING	0x7711
+#define YT8821_UTP_EXT_MU_FINE_FR_CTRL_REG		0x4B5
+#define YT8821_UTP_EXT_MU_FINE_FR_FFE_GN_DC_SETTING	0x2211
+#define YT8821_UTP_EXT_ANALOG_CFG7_CTRL_REG		0x56
+#define YT8821_UTP_EXT_ANALOG_CFG7_RESET		0x20
+#define YT8821_UTP_EXT_ANALOG_CFG7_PI_CLK_SEL_AFE	0x3F
+#define YT8821_UTP_EXT_VCT_CFG6_CTRL_REG		0x97
+#define YT8821_UTP_EXT_VCT_CFG6_FECHO_AMP_TH_SETTING	0x380C
+#define YT8821_UTP_EXT_TXGE_NFR_FR_THP_CTRL_REG		0x660
+#define YT8821_UTP_EXT_TXGE_NFR_FR_SETTING		0x112A
+#define YT8821_UTP_EXT_PLL_CTRL_REG			0x450
+#define YT8821_UTP_EXT_PLL_SPARE_SETTING		0xE9
+#define YT8821_UTP_EXT_DAC_IMID_CHANNEL23_CTRL_REG	0x466
+#define YT8821_UTP_EXT_DAC_IMID_CHANNEL23_SETTING	0x6464
+#define YT8821_UTP_EXT_DAC_IMID_CHANNEL01_CTRL_REG	0x467
+#define YT8821_UTP_EXT_DAC_IMID_CHANNEL01_SETTING	0x6464
+#define YT8821_UTP_EXT_DAC_IMSB_CHANNEL23_CTRL_REG	0x468
+#define YT8821_UTP_EXT_DAC_IMSB_CHANNEL23_SETTING	0x6464
+#define YT8821_UTP_EXT_DAC_IMSB_CHANNEL01_CTRL_REG	0x469
+#define YT8821_UTP_EXT_DAC_IMSB_CHANNEL01_SETTING	0x6464
 /* Extended Register  end */
 
 #define YTPHY_DTS_OUTPUT_CLK_DIS		0
 #define YTPHY_DTS_OUTPUT_CLK_25M		25000000
 #define YTPHY_DTS_OUTPUT_CLK_125M		125000000
 
+#define YT8821_CHIP_MODE_AUTO_BX2500_SGMII	0
+#define YT8821_CHIP_MODE_FORCE_BX2500		1
+
+struct yt8821_priv {
+	/* chip mode: AUTO_BX2500_SGMII/FORCE_BX2500 */
+	u32 chip_mode;
+};
+
 struct yt8521_priv {
 	/* combo_advertising is used for case of YT8521 in combo mode,
 	 * this means that yt8521 may work in utp or fiber mode which depends
@@ -2252,6 +2308,564 @@  static int yt8521_get_features(struct phy_device *phydev)
 	return ret;
 }
 
+/**
+ * yt8821_probe() - read dts to get chip mode
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8821_probe(struct phy_device *phydev)
+{
+	struct device_node *node = phydev->mdio.dev.of_node;
+	struct device *dev = &phydev->mdio.dev;
+	struct yt8821_priv *priv;
+	u8 chip_mode;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
+	if (of_property_read_u8(node, "motorcomm,chip-mode", &chip_mode))
+		chip_mode = YT8821_CHIP_MODE_FORCE_BX2500;
+
+	switch (chip_mode) {
+	case YT8821_CHIP_MODE_AUTO_BX2500_SGMII:
+		priv->chip_mode = YT8821_CHIP_MODE_AUTO_BX2500_SGMII;
+		break;
+	case YT8821_CHIP_MODE_FORCE_BX2500:
+		priv->chip_mode = YT8821_CHIP_MODE_FORCE_BX2500;
+		break;
+	default:
+		phydev_warn(phydev, "chip_mode err:%d\n", chip_mode);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * yt8821_get_features - read mmd register to get 2.5G capability
+ * @phydev: target phy_device struct
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8821_get_features(struct phy_device *phydev)
+{
+	int val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_NG_EXTABLE);
+	if (val < 0)
+		return val;
+
+	linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+			 phydev->supported,
+			 val & MDIO_PMA_NG_EXTABLE_2_5GBT);
+
+	return genphy_read_abilities(phydev);
+}
+
+/**
+ * yt8821_get_rate_matching - read register to get phy chip mode
+ * @phydev: target phy_device struct
+ * @iface: PHY data interface type
+ *
+ * returns rate matching type or negative errno code
+ */
+static int yt8821_get_rate_matching(struct phy_device *phydev,
+				    phy_interface_t iface)
+{
+	int val;
+
+	val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);
+	if (val < 0)
+		return val;
+
+	if ((val & YT8521_CCR_MODE_SEL_MASK) ==
+		YT8821_CHIP_MODE_FORCE_BX2500) {
+		return RATE_MATCH_PAUSE;
+	}
+
+	return RATE_MATCH_NONE;
+}
+
+/**
+ * yt8821_aneg_done() - determines the auto negotiation result
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0(no link)or 1(utp link) or negative errno code
+ */
+static int yt8821_aneg_done(struct phy_device *phydev)
+{
+	int link;
+
+	link = yt8521_aneg_done_paged(phydev, YT8521_RSSR_UTP_SPACE);
+
+	return link;
+}
+
+/**
+ * yt8821gen_init_paged() - generic initialization according to page
+ * @phydev: a pointer to a &struct phy_device
+ * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to
+ * operate.
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8821gen_init_paged(struct phy_device *phydev, int page)
+{
+	int old_page;
+	int ret = 0;
+
+	old_page = phy_select_page(phydev, page & YT8521_RSSR_SPACE_MASK);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	if (page & YT8521_RSSR_SPACE_MASK) {
+		/* sds init */
+		ret = __phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, YT8821_SDS_EXT_CSR_CTRL_REG,
+				      YT8821_SDS_EXT_CSR_PLL_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+	} else {
+		/* utp init */
+		ret = ytphy_write_ext(phydev, YT8821_UTP_EXT_FFE_IPR_CTRL_REG,
+				      YT8821_UTP_EXT_FFE_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_VGA_LPF1_CAP_CTRL_REG,
+				      YT8821_UTP_EXT_VGA_LPF1_CAP_SHT_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_VGA_LPF2_CAP_CTRL_REG,
+				      YT8821_UTP_EXT_VGA_LPF2_CAP_SHT_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, YT8821_UTP_EXT_TRACE_CTRL_REG,
+				      YT8821_UTP_EXT_TRACE_LNG_MED_GAIN_THR_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, YT8821_UTP_EXT_IPR_CTRL_REG,
+				      YT8821_UTP_EXT_IPR_ALPHA_IPR_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, YT8821_UTP_EXT_ECHO_CTRL_REG,
+				      YT8821_UTP_EXT_ECHO_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, YT8821_UTP_EXT_GAIN_CTRL_REG,
+				      YT8821_UTP_EXT_AGC_MED_GAIN_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_TH_20DB_2500_CTRL_REG,
+				      YT8821_UTP_EXT_TH_20DB_2500_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_MU_COARSE_FR_CTRL_REG,
+				      YT8821_UTP_EXT_MU_COARSE_FR_FFE_GN_DC_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_MU_FINE_FR_CTRL_REG,
+				      YT8821_UTP_EXT_MU_FINE_FR_FFE_GN_DC_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_ANALOG_CFG7_CTRL_REG,
+				      YT8821_UTP_EXT_ANALOG_CFG7_RESET);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_ANALOG_CFG7_CTRL_REG,
+				      YT8821_UTP_EXT_ANALOG_CFG7_PI_CLK_SEL_AFE);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_VCT_CFG6_CTRL_REG,
+				      YT8821_UTP_EXT_VCT_CFG6_FECHO_AMP_TH_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_TXGE_NFR_FR_THP_CTRL_REG,
+				      YT8821_UTP_EXT_TXGE_NFR_FR_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev, YT8821_UTP_EXT_PLL_CTRL_REG,
+				      YT8821_UTP_EXT_PLL_SPARE_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_DAC_IMID_CHANNEL23_CTRL_REG,
+				      YT8821_UTP_EXT_DAC_IMID_CHANNEL23_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_DAC_IMID_CHANNEL01_CTRL_REG,
+				      YT8821_UTP_EXT_DAC_IMID_CHANNEL01_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_DAC_IMSB_CHANNEL23_CTRL_REG,
+				      YT8821_UTP_EXT_DAC_IMSB_CHANNEL23_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+
+		ret = ytphy_write_ext(phydev,
+				      YT8821_UTP_EXT_DAC_IMSB_CHANNEL01_CTRL_REG,
+				      YT8821_UTP_EXT_DAC_IMSB_CHANNEL01_SETTING);
+		if (ret < 0)
+			goto err_restore_page;
+	}
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8821gen_init() - generic initialization
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8821gen_init(struct phy_device *phydev)
+{
+	int ret = 0;
+
+	ret = yt8821gen_init_paged(phydev, YT8521_RSSR_FIBER_SPACE);
+	if (ret < 0)
+		return ret;
+
+	return yt8821gen_init_paged(phydev, YT8521_RSSR_UTP_SPACE);
+}
+
+/**
+ * yt8821_auto_sleep_config() - phy auto sleep config
+ * @phydev: a pointer to a &struct phy_device
+ * @enable: true enable auto sleep, false disable auto sleep
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8821_auto_sleep_config(struct phy_device *phydev, bool enable)
+{
+	int old_page;
+	int ret = 0;
+
+	old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	ret = ytphy_modify_ext(phydev,
+			       YT8521_EXTREG_SLEEP_CONTROL1_REG,
+			       YT8521_ESC1R_SLEEP_SW,
+			       enable ? 1 : 0);
+	if (ret < 0)
+		goto err_restore_page;
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8821_soft_reset() - soft reset utp and serdes
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8821_soft_reset(struct phy_device *phydev)
+{
+	return ytphy_modify_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG,
+					  YT8521_CCR_SW_RST, 0);
+}
+
+/**
+ * yt8821_config_init() - phy initializatioin
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8821_config_init(struct phy_device *phydev)
+{
+	struct yt8821_priv *priv = phydev->priv;
+	int ret, val;
+
+	phydev->irq = PHY_POLL;
+
+	val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);
+	if (priv->chip_mode == YT8821_CHIP_MODE_AUTO_BX2500_SGMII) {
+		ret = ytphy_modify_ext_with_lock(phydev,
+						 YT8521_CHIP_CONFIG_REG,
+						 YT8521_CCR_MODE_SEL_MASK,
+						 FIELD_PREP(YT8521_CCR_MODE_SEL_MASK, 0));
+		if (ret < 0)
+			return ret;
+
+		__assign_bit(PHY_INTERFACE_MODE_2500BASEX,
+			     phydev->possible_interfaces,
+			     true);
+		__assign_bit(PHY_INTERFACE_MODE_SGMII,
+			     phydev->possible_interfaces,
+			     true);
+
+		phydev->rate_matching = RATE_MATCH_NONE;
+	} else if (priv->chip_mode == YT8821_CHIP_MODE_FORCE_BX2500) {
+		ret = ytphy_modify_ext_with_lock(phydev,
+						 YT8521_CHIP_CONFIG_REG,
+						 YT8521_CCR_MODE_SEL_MASK,
+						 FIELD_PREP(YT8521_CCR_MODE_SEL_MASK, 1));
+		if (ret < 0)
+			return ret;
+
+		phydev->rate_matching = RATE_MATCH_PAUSE;
+	}
+
+	ret = yt8821gen_init(phydev);
+	if (ret < 0)
+		return ret;
+
+	/* disable auto sleep */
+	ret = yt8821_auto_sleep_config(phydev, false);
+	if (ret < 0)
+		return ret;
+
+	/* soft reset */
+	yt8821_soft_reset(phydev);
+
+	return ret;
+}
+
+/**
+ * yt8821_adjust_status() - update speed and duplex to phydev
+ * @phydev: a pointer to a &struct phy_device
+ * @val: read from YTPHY_SPECIFIC_STATUS_REG
+ */
+static void yt8821_adjust_status(struct phy_device *phydev, int val)
+{
+	int speed_mode, duplex;
+	int speed_mode_low, speed_mode_high;
+	int speed = SPEED_UNKNOWN;
+
+	duplex = FIELD_GET(YTPHY_SSR_DUPLEX, val);
+
+	speed_mode_low = FIELD_GET(GENMASK(15, 14), val);
+	speed_mode_high = FIELD_GET(BIT(9), val);
+	speed_mode = FIELD_PREP(BIT(2), speed_mode_high) |
+			FIELD_PREP(GENMASK(1, 0), speed_mode_low);
+	switch (speed_mode) {
+	case YTPHY_SSR_SPEED_10M:
+		speed = SPEED_10;
+		break;
+	case YTPHY_SSR_SPEED_100M:
+		speed = SPEED_100;
+		break;
+	case YTPHY_SSR_SPEED_1000M:
+		speed = SPEED_1000;
+		break;
+	case YT8821_SSR_SPEED_2500M:
+		speed = SPEED_2500;
+		break;
+	default:
+		speed = SPEED_UNKNOWN;
+		break;
+	}
+
+	phydev->speed = speed;
+	phydev->duplex = duplex;
+}
+
+/**
+ * yt8821_update_interface() - update interface per current speed
+ * @phydev: a pointer to a &struct phy_device
+ */
+static void yt8821_update_interface(struct phy_device *phydev)
+{
+	if (!phydev->link)
+		return;
+
+	switch (phydev->speed) {
+	case SPEED_2500:
+		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+		break;
+	case SPEED_1000:
+	case SPEED_100:
+	case SPEED_10:
+		phydev->interface = PHY_INTERFACE_MODE_SGMII;
+		break;
+	default:
+		phydev_warn(phydev, "phy speed err:%d\n", phydev->speed);
+		break;
+	}
+}
+
+/**
+ * yt8821_read_status() -  determines the negotiated speed and duplex
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8821_read_status(struct phy_device *phydev)
+{
+	struct yt8821_priv *priv = phydev->priv;
+	int old_page;
+	int ret = 0;
+	int link;
+	int val;
+
+	if (phydev->autoneg == AUTONEG_ENABLE) {
+		int lpadv = phy_read_mmd(phydev,
+					 MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+
+		if (lpadv < 0)
+			return lpadv;
+
+		mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising,
+						  lpadv);
+	}
+
+	ret = ytphy_write_ext_with_lock(phydev,
+					YT8521_REG_SPACE_SELECT_REG,
+					YT8521_RSSR_UTP_SPACE);
+	if (ret < 0)
+		return ret;
+
+	ret = genphy_read_status(phydev);
+	if (ret < 0)
+		return ret;
+
+	old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
+	if (old_page < 0)
+		goto err_restore_page;
+
+	val = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG);
+	if (val < 0) {
+		ret = val;
+		goto err_restore_page;
+	}
+
+	link = val & YTPHY_SSR_LINK;
+	if (link)
+		yt8821_adjust_status(phydev, val);
+
+	if (link) {
+		if (phydev->link == 0)
+			phydev_info(phydev,
+				    "%s, phy addr: %d, link up, mii reg 0x%x = 0x%x\n",
+				    __func__, phydev->mdio.addr,
+				    YTPHY_SPECIFIC_STATUS_REG,
+				    (unsigned int)val);
+		phydev->link = 1;
+	} else {
+		if (phydev->link == 1)
+			phydev_info(phydev, "%s, phy addr: %d, link down\n",
+				    __func__, phydev->mdio.addr);
+		phydev->link = 0;
+	}
+
+	if (priv->chip_mode == YT8821_CHIP_MODE_AUTO_BX2500_SGMII)
+		yt8821_update_interface(phydev);
+
+err_restore_page:
+	return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8821_modify_utp_fiber_bmcr - bits modify a PHY's BMCR register
+ * @phydev: the phy_device struct
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * NOTE: Convenience function which allows a PHY's BMCR register to be
+ * modified as new register value = (old register value & ~mask) | set.
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8821_modify_utp_fiber_bmcr(struct phy_device *phydev, u16 mask,
+					u16 set)
+{
+	int ret;
+
+	ret = yt8521_modify_bmcr_paged(phydev, YT8521_RSSR_UTP_SPACE,
+				       mask, set);
+	if (ret < 0)
+		return ret;
+
+	return yt8521_modify_bmcr_paged(phydev, YT8521_RSSR_FIBER_SPACE,
+					mask, set);
+}
+
+/**
+ * yt8821_suspend() - suspend the hardware
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8821_suspend(struct phy_device *phydev)
+{
+	int wol_config;
+
+	wol_config = ytphy_read_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG);
+	if (wol_config < 0)
+		return wol_config;
+
+	/* if wol enable, do nothing */
+	if (wol_config & YTPHY_WCR_ENABLE)
+		return 0;
+
+	return yt8821_modify_utp_fiber_bmcr(phydev, 0, BMCR_PDOWN);
+}
+
+/**
+ * yt8821_resume() - resume the hardware
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8821_resume(struct phy_device *phydev)
+{
+	int wol_config;
+	int ret;
+
+	/* disable auto sleep */
+	ret = yt8821_auto_sleep_config(phydev, false);
+	if (ret < 0)
+		return ret;
+
+	wol_config = ytphy_read_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG);
+	if (wol_config < 0)
+		return wol_config;
+
+	/* if wol enable, do nothing */
+	if (wol_config & YTPHY_WCR_ENABLE)
+		return 0;
+
+	return yt8821_modify_utp_fiber_bmcr(phydev, BMCR_PDOWN, 0);
+}
+
 static struct phy_driver motorcomm_phy_drvs[] = {
 	{
 		PHY_ID_MATCH_EXACT(PHY_ID_YT8511),
@@ -2307,11 +2921,29 @@  static struct phy_driver motorcomm_phy_drvs[] = {
 		.suspend	= yt8521_suspend,
 		.resume		= yt8521_resume,
 	},
+	{
+		PHY_ID_MATCH_EXACT(PHY_ID_YT8821),
+		.name			= "YT8821 2.5Gbps PHY",
+		.get_features		= yt8821_get_features,
+		.probe			= yt8821_probe,
+		.read_page		= yt8521_read_page,
+		.write_page		= yt8521_write_page,
+		.get_wol		= ytphy_get_wol,
+		.set_wol		= ytphy_set_wol,
+		.config_aneg		= genphy_config_aneg,
+		.aneg_done		= yt8821_aneg_done,
+		.config_init		= yt8821_config_init,
+		.get_rate_matching	= yt8821_get_rate_matching,
+		.read_status		= yt8821_read_status,
+		.soft_reset		= yt8821_soft_reset,
+		.suspend		= yt8821_suspend,
+		.resume			= yt8821_resume,
+	},
 };
 
 module_phy_driver(motorcomm_phy_drvs);
 
-MODULE_DESCRIPTION("Motorcomm 8511/8521/8531/8531S PHY driver");
+MODULE_DESCRIPTION("Motorcomm 8511/8521/8531/8531S/8821 PHY driver");
 MODULE_AUTHOR("Peter Geis");
 MODULE_AUTHOR("Frank");
 MODULE_LICENSE("GPL");
@@ -2321,6 +2953,7 @@  static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
 	{ PHY_ID_MATCH_EXACT(PHY_ID_YT8521) },
 	{ PHY_ID_MATCH_EXACT(PHY_ID_YT8531) },
 	{ PHY_ID_MATCH_EXACT(PHY_ID_YT8531S) },
+	{ PHY_ID_MATCH_EXACT(PHY_ID_YT8821) },
 	{ /* sentinel */ }
 };