net: phy: Add driver for Motorcomm yt8821 2.5G ethernet phy
authorFrank Sae <Frank.Sae@motor-comm.com>
Sun, 1 Sep 2024 08:35:26 +0000 (01:35 -0700)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 5 Sep 2024 13:21:12 +0000 (15:21 +0200)
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..

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).

Signed-off-by: Frank Sae <Frank.Sae@motor-comm.com>
Reviewed-by: Sai Krishna <saikrishnag@marvell.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
drivers/net/phy/motorcomm.c

index fe0aabe..0e91f5d 100644 (file)
@@ -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,8 +16,8 @@
 #define PHY_ID_YT8521          0x0000011a
 #define PHY_ID_YT8531          0x4f51e91b
 #define PHY_ID_YT8531S         0x4f51e91a
-
-/* YT8521/YT8531S Register Overview
+#define PHY_ID_YT8821          0x4f51ea19
+/* YT8521/YT8531S/YT8821 Register Overview
  *     UTP Register space      |       FIBER Register space
  *  ------------------------------------------------------------
  * |   UTP MII                 |       FIBER MII               |
@@ -50,6 +50,8 @@
 #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 YTPHY_SSR_SPEED_2500M                  ((0x0 << 14) | BIT(9))
 #define YTPHY_SSR_DUPLEX_OFFSET                        13
 #define YTPHY_SSR_DUPLEX                       BIT(13)
 #define YTPHY_SSR_PAGE_RECEIVED                        BIT(12)
 #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_VCO_LDO_EN                  BIT(15)
+#define YT8821_SDS_EXT_CSR_VCO_BIAS_LPF_EN             BIT(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_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_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_TH_20DB_2500_CTRL_REG           0x36A
+#define YT8821_UTP_EXT_TH_20DB_2500                    GENMASK(15, 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_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)
+
+#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_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_TXGE_NFR_FR_THP_CTRL_REG                0x660
+#define YT8821_UTP_EXT_NFR_TX_ABILITY                  BIT(3)
 /* 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 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
@@ -2249,6 +2328,572 @@ static int yt8521_get_features(struct phy_device *phydev)
        return ret;
 }
 
+/**
+ * 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 ret;
+
+       ret = genphy_c45_pma_read_ext_abilities(phydev);
+       if (ret < 0)
+               return ret;
+
+       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 (FIELD_GET(YT8521_CCR_MODE_SEL_MASK, val) ==
+           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)
+{
+       return yt8521_aneg_done_paged(phydev, YT8521_RSSR_UTP_SPACE);
+}
+
+/**
+ * yt8821_serdes_init() - serdes init
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * Returns: 0 or negative errno code
+ */
+static int yt8821_serdes_init(struct phy_device *phydev)
+{
+       int old_page;
+       int ret = 0;
+       u16 mask;
+       u16 set;
+
+       old_page = phy_select_page(phydev, YT8521_RSSR_FIBER_SPACE);
+       if (old_page < 0) {
+               phydev_err(phydev, "Failed to select page: %d\n",
+                          old_page);
+               goto err_restore_page;
+       }
+
+       ret = __phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_SDS_EXT_CSR_VCO_LDO_EN |
+               YT8821_SDS_EXT_CSR_VCO_BIAS_LPF_EN;
+       set = YT8821_SDS_EXT_CSR_VCO_LDO_EN;
+       ret = ytphy_modify_ext(phydev, YT8821_SDS_EXT_CSR_CTRL_REG, mask,
+                              set);
+
+err_restore_page:
+       return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8821_utp_init() - utp init
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * Returns: 0 or negative errno code
+ */
+static int yt8821_utp_init(struct phy_device *phydev)
+{
+       int old_page;
+       int ret = 0;
+       u16 mask;
+       u16 save;
+       u16 set;
+
+       old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE);
+       if (old_page < 0) {
+               phydev_err(phydev, "Failed to select page: %d\n",
+                          old_page);
+               goto err_restore_page;
+       }
+
+       mask = YT8821_UTP_EXT_RPDN_BP_FFE_LNG_2500 |
+               YT8821_UTP_EXT_RPDN_BP_FFE_SHT_2500 |
+               YT8821_UTP_EXT_RPDN_IPR_SHT_2500;
+       set = YT8821_UTP_EXT_RPDN_BP_FFE_LNG_2500 |
+               YT8821_UTP_EXT_RPDN_BP_FFE_SHT_2500;
+       ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_RPDN_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_VGA_LPF1_CAP_OTHER |
+               YT8821_UTP_EXT_VGA_LPF1_CAP_2500;
+       ret = ytphy_modify_ext(phydev,
+                              YT8821_UTP_EXT_VGA_LPF1_CAP_CTRL_REG,
+                              mask, 0);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_VGA_LPF2_CAP_OTHER |
+               YT8821_UTP_EXT_VGA_LPF2_CAP_2500;
+       ret = ytphy_modify_ext(phydev,
+                              YT8821_UTP_EXT_VGA_LPF2_CAP_CTRL_REG,
+                              mask, 0);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_TRACE_LNG_GAIN_THE_2500 |
+               YT8821_UTP_EXT_TRACE_MED_GAIN_THE_2500;
+       set = FIELD_PREP(YT8821_UTP_EXT_TRACE_LNG_GAIN_THE_2500, 0x5a) |
+               FIELD_PREP(YT8821_UTP_EXT_TRACE_MED_GAIN_THE_2500, 0x3c);
+       ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_TRACE_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_IPR_LNG_2500;
+       set = FIELD_PREP(YT8821_UTP_EXT_IPR_LNG_2500, 0x6c);
+       ret = ytphy_modify_ext(phydev,
+                              YT8821_UTP_EXT_ALPHA_IPR_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_TRACE_LNG_GAIN_THR_1000;
+       set = FIELD_PREP(YT8821_UTP_EXT_TRACE_LNG_GAIN_THR_1000, 0x2a);
+       ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_ECHO_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_TRACE_MED_GAIN_THR_1000;
+       set = FIELD_PREP(YT8821_UTP_EXT_TRACE_MED_GAIN_THR_1000, 0x22);
+       ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_GAIN_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_TH_20DB_2500;
+       set = FIELD_PREP(YT8821_UTP_EXT_TH_20DB_2500, 0x8000);
+       ret = ytphy_modify_ext(phydev,
+                              YT8821_UTP_EXT_TH_20DB_2500_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_MU_COARSE_FR_F_FFE |
+               YT8821_UTP_EXT_MU_COARSE_FR_F_FBE;
+       set = FIELD_PREP(YT8821_UTP_EXT_MU_COARSE_FR_F_FFE, 0x7) |
+               FIELD_PREP(YT8821_UTP_EXT_MU_COARSE_FR_F_FBE, 0x7);
+       ret = ytphy_modify_ext(phydev,
+                              YT8821_UTP_EXT_MU_COARSE_FR_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_MU_FINE_FR_F_FFE |
+               YT8821_UTP_EXT_MU_FINE_FR_F_FBE;
+       set = FIELD_PREP(YT8821_UTP_EXT_MU_FINE_FR_F_FFE, 0x2) |
+               FIELD_PREP(YT8821_UTP_EXT_MU_FINE_FR_F_FBE, 0x2);
+       ret = ytphy_modify_ext(phydev,
+                              YT8821_UTP_EXT_MU_FINE_FR_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       /* save YT8821_UTP_EXT_PI_CTRL_REG's val for use later */
+       ret = ytphy_read_ext(phydev, YT8821_UTP_EXT_PI_CTRL_REG);
+       if (ret < 0)
+               goto err_restore_page;
+
+       save = ret;
+
+       mask = YT8821_UTP_EXT_PI_TX_CLK_SEL_AFE |
+               YT8821_UTP_EXT_PI_RX_CLK_3_SEL_AFE |
+               YT8821_UTP_EXT_PI_RX_CLK_2_SEL_AFE |
+               YT8821_UTP_EXT_PI_RX_CLK_1_SEL_AFE |
+               YT8821_UTP_EXT_PI_RX_CLK_0_SEL_AFE;
+       ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_PI_CTRL_REG,
+                              mask, 0);
+       if (ret < 0)
+               goto err_restore_page;
+
+       /* restore YT8821_UTP_EXT_PI_CTRL_REG's val */
+       ret = ytphy_write_ext(phydev, YT8821_UTP_EXT_PI_CTRL_REG, save);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_FECHO_AMP_TH_HUGE;
+       set = FIELD_PREP(YT8821_UTP_EXT_FECHO_AMP_TH_HUGE, 0x38);
+       ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_VCT_CFG6_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_NFR_TX_ABILITY;
+       set = YT8821_UTP_EXT_NFR_TX_ABILITY;
+       ret = ytphy_modify_ext(phydev,
+                              YT8821_UTP_EXT_TXGE_NFR_FR_THP_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_PLL_SPARE_CFG;
+       set = FIELD_PREP(YT8821_UTP_EXT_PLL_SPARE_CFG, 0xe9);
+       ret = ytphy_modify_ext(phydev, YT8821_UTP_EXT_PLL_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_DAC_IMID_CH_3_10_ORG |
+               YT8821_UTP_EXT_DAC_IMID_CH_2_10_ORG;
+       set = FIELD_PREP(YT8821_UTP_EXT_DAC_IMID_CH_3_10_ORG, 0x64) |
+               FIELD_PREP(YT8821_UTP_EXT_DAC_IMID_CH_2_10_ORG, 0x64);
+       ret = ytphy_modify_ext(phydev,
+                              YT8821_UTP_EXT_DAC_IMID_CH_2_3_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_DAC_IMID_CH_1_10_ORG |
+               YT8821_UTP_EXT_DAC_IMID_CH_0_10_ORG;
+       set = FIELD_PREP(YT8821_UTP_EXT_DAC_IMID_CH_1_10_ORG, 0x64) |
+               FIELD_PREP(YT8821_UTP_EXT_DAC_IMID_CH_0_10_ORG, 0x64);
+       ret = ytphy_modify_ext(phydev,
+                              YT8821_UTP_EXT_DAC_IMID_CH_0_1_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_DAC_IMSB_CH_3_10_ORG |
+               YT8821_UTP_EXT_DAC_IMSB_CH_2_10_ORG;
+       set = FIELD_PREP(YT8821_UTP_EXT_DAC_IMSB_CH_3_10_ORG, 0x64) |
+               FIELD_PREP(YT8821_UTP_EXT_DAC_IMSB_CH_2_10_ORG, 0x64);
+       ret = ytphy_modify_ext(phydev,
+                              YT8821_UTP_EXT_DAC_IMSB_CH_2_3_CTRL_REG,
+                              mask, set);
+       if (ret < 0)
+               goto err_restore_page;
+
+       mask = YT8821_UTP_EXT_DAC_IMSB_CH_1_10_ORG |
+               YT8821_UTP_EXT_DAC_IMSB_CH_0_10_ORG;
+       set = FIELD_PREP(YT8821_UTP_EXT_DAC_IMSB_CH_1_10_ORG, 0x64) |
+               FIELD_PREP(YT8821_UTP_EXT_DAC_IMSB_CH_0_10_ORG, 0x64);
+       ret = ytphy_modify_ext(phydev,
+                              YT8821_UTP_EXT_DAC_IMSB_CH_0_1_CTRL_REG,
+                              mask, set);
+
+err_restore_page:
+       return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * 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) {
+               phydev_err(phydev, "Failed to select page: %d\n",
+                          old_page);
+               goto err_restore_page;
+       }
+
+       ret = ytphy_modify_ext(phydev,
+                              YT8521_EXTREG_SLEEP_CONTROL1_REG,
+                              YT8521_ESC1R_SLEEP_SW,
+                              enable ? 1 : 0);
+
+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)
+{
+       u8 mode = YT8821_CHIP_MODE_AUTO_BX2500_SGMII;
+       int ret;
+       u16 set;
+
+       if (phydev->interface == PHY_INTERFACE_MODE_2500BASEX)
+               mode = YT8821_CHIP_MODE_FORCE_BX2500;
+
+       set = FIELD_PREP(YT8521_CCR_MODE_SEL_MASK, mode);
+       ret = ytphy_modify_ext_with_lock(phydev,
+                                        YT8521_CHIP_CONFIG_REG,
+                                        YT8521_CCR_MODE_SEL_MASK,
+                                        set);
+       if (ret < 0)
+               return ret;
+
+       __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+                 phydev->possible_interfaces);
+
+       if (mode == YT8821_CHIP_MODE_AUTO_BX2500_SGMII) {
+               __set_bit(PHY_INTERFACE_MODE_SGMII,
+                         phydev->possible_interfaces);
+
+               phydev->rate_matching = RATE_MATCH_NONE;
+       } else if (mode == YT8821_CHIP_MODE_FORCE_BX2500) {
+               phydev->rate_matching = RATE_MATCH_PAUSE;
+       }
+
+       ret = yt8821_serdes_init(phydev);
+       if (ret < 0)
+               return ret;
+
+       ret = yt8821_utp_init(phydev);
+       if (ret < 0)
+               return ret;
+
+       /* disable auto sleep */
+       ret = yt8821_auto_sleep_config(phydev, false);
+       if (ret < 0)
+               return ret;
+
+       /* soft reset */
+       return yt8821_soft_reset(phydev);
+}
+
+/**
+ * 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, duplex;
+       int speed_mode;
+
+       duplex = FIELD_GET(YTPHY_SSR_DUPLEX, val);
+       speed_mode = val & YTPHY_SSR_SPEED_MASK;
+       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 YTPHY_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)
+{
+       int link;
+       int ret;
+       int val;
+
+       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;
+
+       if (phydev->autoneg_complete) {
+               ret = genphy_c45_read_lpa(phydev);
+               if (ret < 0)
+                       return ret;
+       }
+
+       ret = phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG);
+       if (ret < 0)
+               return ret;
+
+       val = ret;
+
+       link = val & YTPHY_SSR_LINK;
+       if (link)
+               yt8821_adjust_status(phydev, val);
+
+       if (link) {
+               if (phydev->link == 0)
+                       phydev_dbg(phydev,
+                                  "%s, phy addr: %d, link up\n",
+                                  __func__, phydev->mdio.addr);
+               phydev->link = 1;
+       } else {
+               if (phydev->link == 1)
+                       phydev_dbg(phydev,
+                                  "%s, phy addr: %d, link down\n",
+                                  __func__, phydev->mdio.addr);
+               phydev->link = 0;
+       }
+
+       val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);
+       if (val < 0)
+               return val;
+
+       if (FIELD_GET(YT8521_CCR_MODE_SEL_MASK, val) ==
+           YT8821_CHIP_MODE_AUTO_BX2500_SGMII)
+               yt8821_update_interface(phydev);
+
+       return 0;
+}
+
+/**
+ * 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),
@@ -2304,11 +2949,28 @@ 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,
+               .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");
@@ -2318,6 +2980,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 */ }
 };