Linux-mediatek Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next 0/5] net: phy: mediatek: calibration for AN7581/3
@ 2026-07-08 10:23 Christian Marangi
  2026-07-08 10:23 ` [PATCH net-next 1/5] net: phy: mediatek: export __mtk_tr_write Christian Marangi
                   ` (4 more replies)
  0 siblings, 5 replies; 11+ messages in thread
From: Christian Marangi @ 2026-07-08 10:23 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	Daniel Golle, Qingfang Deng, SkyLake Huang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-kernel, netdev,
	linux-arm-kernel, linux-mediatek

This small series adds calibration support for Airoha AN7581 and
AN7583 integrated PHYs.

The calibration logic is quite big so I decided to split
the driver from the mediatek one to keep things organized.

Minor change were needed and just additional symbol to use what
is common between mediatek and airoha PHYs.

I followed the naming pattern of mtk by calling the new source
airoha-ge-soc and related config.

The calibration use lots of static table and software calibration
at runtime instead of using efuse to calibrate.

There is one pecularity where some register and calibration needs
to be done on port0 first and then with the related port.

This is O.K. as port0 presence is mandatory for the functionality
of the integrated switch and it's assumed to be always present
on the MDIO bus.

MAINTAINERS is updated to reflect the changed code.

(this was tested from a long time and also verified by Airoha for
correct order of calibration)

Christian Marangi (5):
  net: phy: mediatek: export __mtk_tr_write
  net: phy: mediatek: move MTK GE SoC registers define to dedicated
    header
  net: phy: mediatek: split Airoha code to dedicated source
  net: phy: mediatek: add calibration logic for AN7581
  net: phy: mediatek: add calibration logic for AN7583

 MAINTAINERS                              |    9 +
 drivers/net/phy/mediatek/Kconfig         |   16 +-
 drivers/net/phy/mediatek/Makefile        |    1 +
 drivers/net/phy/mediatek/airoha-ge-soc.c | 1812 ++++++++++++++++++++++
 drivers/net/phy/mediatek/mtk-ge-soc.c    |  517 +-----
 drivers/net/phy/mediatek/mtk-ge-soc.h    |  364 +++++
 drivers/net/phy/mediatek/mtk-phy-lib.c   |   77 +-
 drivers/net/phy/mediatek/mtk.h           |   15 +
 8 files changed, 2311 insertions(+), 500 deletions(-)
 create mode 100644 drivers/net/phy/mediatek/airoha-ge-soc.c
 create mode 100644 drivers/net/phy/mediatek/mtk-ge-soc.h

-- 
2.53.0



^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH net-next 1/5] net: phy: mediatek: export __mtk_tr_write
  2026-07-08 10:23 [PATCH net-next 0/5] net: phy: mediatek: calibration for AN7581/3 Christian Marangi
@ 2026-07-08 10:23 ` Christian Marangi
  2026-07-08 10:23 ` [PATCH net-next 2/5] net: phy: mediatek: move MTK GE SoC registers define to dedicated header Christian Marangi
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 11+ messages in thread
From: Christian Marangi @ 2026-07-08 10:23 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	Daniel Golle, Qingfang Deng, SkyLake Huang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-kernel, netdev,
	linux-arm-kernel, linux-mediatek

Export __mtk_tr_write to directly write to debug register.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 drivers/net/phy/mediatek/mtk-phy-lib.c | 5 +++--
 drivers/net/phy/mediatek/mtk.h         | 2 ++
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/phy/mediatek/mtk-phy-lib.c b/drivers/net/phy/mediatek/mtk-phy-lib.c
index dfd0f4e439a2..78b998af5238 100644
--- a/drivers/net/phy/mediatek/mtk-phy-lib.c
+++ b/drivers/net/phy/mediatek/mtk-phy-lib.c
@@ -36,8 +36,8 @@ static void __mtk_tr_read(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
 		*tr_high, *tr_low);
 }
 
-static void __mtk_tr_write(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
-			   u8 data_addr, u32 tr_data)
+void __mtk_tr_write(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+		    u8 data_addr, u32 tr_data)
 {
 	__phy_write(phydev, 0x11, tr_data & 0xffff);
 	__phy_write(phydev, 0x12, tr_data >> 16);
@@ -45,6 +45,7 @@ static void __mtk_tr_write(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
 		tr_data >> 16, tr_data & 0xffff);
 	__mtk_tr_access(phydev, false, ch_addr, node_addr, data_addr);
 }
+EXPORT_SYMBOL_GPL(__mtk_tr_write);
 
 void __mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
 		     u8 data_addr, u32 mask, u32 set)
diff --git a/drivers/net/phy/mediatek/mtk.h b/drivers/net/phy/mediatek/mtk.h
index 320f76ffa81f..f172f7d671c9 100644
--- a/drivers/net/phy/mediatek/mtk.h
+++ b/drivers/net/phy/mediatek/mtk.h
@@ -72,6 +72,8 @@ struct mtk_socphy_priv {
 	unsigned long		led_state;
 };
 
+void __mtk_tr_write(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+		    u8 data_addr, u32 tr_data);
 void __mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
 		     u8 data_addr, u32 mask, u32 set);
 void mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next 2/5] net: phy: mediatek: move MTK GE SoC registers define to dedicated header
  2026-07-08 10:23 [PATCH net-next 0/5] net: phy: mediatek: calibration for AN7581/3 Christian Marangi
  2026-07-08 10:23 ` [PATCH net-next 1/5] net: phy: mediatek: export __mtk_tr_write Christian Marangi
@ 2026-07-08 10:23 ` Christian Marangi
  2026-07-08 23:44   ` Andrew Lunn
  2026-07-08 10:23 ` [PATCH net-next 3/5] net: phy: mediatek: split Airoha code to dedicated source Christian Marangi
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 11+ messages in thread
From: Christian Marangi @ 2026-07-08 10:23 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	Daniel Golle, Qingfang Deng, SkyLake Huang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-kernel, netdev,
	linux-arm-kernel, linux-mediatek

In preparation for support of special Software Calibration for Airoha
PHY, move the MTK GE SoC registers define to a dedicated header.

It's also needed to generalize the cal_cycle function as Airoha needs
only part of its logic (the wait logic) to complete a calibration cycle.

This is to keep cleaner code and permit sharing these define in multiple
source code.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 MAINTAINERS                           |   1 +
 drivers/net/phy/mediatek/mtk-ge-soc.c | 371 ++------------------------
 drivers/net/phy/mediatek/mtk-ge-soc.h | 351 ++++++++++++++++++++++++
 3 files changed, 373 insertions(+), 350 deletions(-)
 create mode 100644 drivers/net/phy/mediatek/mtk-ge-soc.h

diff --git a/MAINTAINERS b/MAINTAINERS
index c3f72058a2f2..16e47aea84c3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16512,6 +16512,7 @@ L:	netdev@vger.kernel.org
 S:	Maintained
 F:	drivers/net/phy/mediatek/mtk-2p5ge.c
 F:	drivers/net/phy/mediatek/mtk-ge-soc.c
+F:	drivers/net/phy/mediatek/mtk-ge-soc.h
 F:	drivers/net/phy/mediatek/mtk-phy-lib.c
 F:	drivers/net/phy/mediatek/mtk-ge.c
 F:	drivers/net/phy/mediatek/mtk.h
diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.c b/drivers/net/phy/mediatek/mtk-ge-soc.c
index 9a54949644d5..46bae11ad740 100644
--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
+++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
@@ -11,322 +11,10 @@
 
 #include "../phylib.h"
 #include "mtk.h"
+#include "mtk-ge-soc.h"
 
 #define MTK_PHY_MAX_LEDS			2
 
-#define MTK_GPHY_ID_MT7981			0x03a29461
-#define MTK_GPHY_ID_MT7988			0x03a29481
-#define MTK_GPHY_ID_AN7581			0x03a294c1
-#define MTK_GPHY_ID_AN7583			0xc0ff0420
-
-#define MTK_EXT_PAGE_ACCESS			0x1f
-#define MTK_PHY_PAGE_STANDARD			0x0000
-#define MTK_PHY_PAGE_EXTENDED_3			0x0003
-
-#define MTK_PHY_LPI_REG_14			0x14
-#define MTK_PHY_LPI_WAKE_TIMER_1000_MASK	GENMASK(8, 0)
-
-#define MTK_PHY_LPI_REG_1c			0x1c
-#define MTK_PHY_SMI_DET_ON_THRESH_MASK		GENMASK(13, 8)
-
-#define MTK_PHY_PAGE_EXTENDED_2A30		0x2a30
-
-/* Registers on Token Ring debug nodes */
-/* ch_addr = 0x0, node_addr = 0x7, data_addr = 0x15 */
-/* NormMseLoThresh */
-#define NORMAL_MSE_LO_THRESH_MASK		GENMASK(15, 8)
-
-/* ch_addr = 0x0, node_addr = 0xf, data_addr = 0x3c */
-/* RemAckCntLimitCtrl */
-#define REMOTE_ACK_COUNT_LIMIT_CTRL_MASK	GENMASK(2, 1)
-
-/* ch_addr = 0x1, node_addr = 0xd, data_addr = 0x20 */
-/* VcoSlicerThreshBitsHigh */
-#define VCO_SLICER_THRESH_HIGH_MASK		GENMASK(23, 0)
-
-/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x0 */
-/* DfeTailEnableVgaThresh1000 */
-#define DFE_TAIL_EANBLE_VGA_TRHESH_1000		GENMASK(5, 1)
-
-/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x1 */
-/* MrvlTrFix100Kp */
-#define MRVL_TR_FIX_100KP_MASK			GENMASK(22, 20)
-/* MrvlTrFix100Kf */
-#define MRVL_TR_FIX_100KF_MASK			GENMASK(19, 17)
-/* MrvlTrFix1000Kp */
-#define MRVL_TR_FIX_1000KP_MASK			GENMASK(16, 14)
-/* MrvlTrFix1000Kf */
-#define MRVL_TR_FIX_1000KF_MASK			GENMASK(13, 11)
-
-/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x12 */
-/* VgaDecRate */
-#define VGA_DECIMATION_RATE_MASK		GENMASK(8, 5)
-
-/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x17 */
-/* SlvDSPreadyTime */
-#define SLAVE_DSP_READY_TIME_MASK		GENMASK(22, 15)
-/* MasDSPreadyTime */
-#define MASTER_DSP_READY_TIME_MASK		GENMASK(14, 7)
-
-/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x18 */
-/* EnabRandUpdTrig */
-#define ENABLE_RANDOM_UPDOWN_COUNTER_TRIGGER	BIT(8)
-
-/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x20 */
-/* ResetSyncOffset */
-#define RESET_SYNC_OFFSET_MASK			GENMASK(11, 8)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x0 */
-/* FfeUpdGainForceVal */
-#define FFE_UPDATE_GAIN_FORCE_VAL_MASK		GENMASK(9, 7)
-/* FfeUpdGainForce */
-#define FFE_UPDATE_GAIN_FORCE			BIT(6)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x3 */
-/* TrFreeze */
-#define TR_FREEZE_MASK				GENMASK(11, 0)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x6 */
-/* SS: Steady-state, KP: Proportional Gain */
-/* SSTrKp100 */
-#define SS_TR_KP100_MASK			GENMASK(21, 19)
-/* SSTrKf100 */
-#define SS_TR_KF100_MASK			GENMASK(18, 16)
-/* SSTrKp1000Mas */
-#define SS_TR_KP1000_MASTER_MASK		GENMASK(15, 13)
-/* SSTrKf1000Mas */
-#define SS_TR_KF1000_MASTER_MASK		GENMASK(12, 10)
-/* SSTrKp1000Slv */
-#define SS_TR_KP1000_SLAVE_MASK			GENMASK(9, 7)
-/* SSTrKf1000Slv */
-#define SS_TR_KF1000_SLAVE_MASK			GENMASK(6, 4)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x8 */
-/* clear this bit if wanna select from AFE */
-/* Regsigdet_sel_1000 */
-#define EEE1000_SELECT_SIGNAL_DETECTION_FROM_DFE	BIT(4)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xd */
-/* RegEEE_st2TrKf1000 */
-#define EEE1000_STAGE2_TR_KF_MASK		GENMASK(13, 11)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xf */
-/* RegEEE_slv_waketr_timer_tar */
-#define SLAVE_WAKETR_TIMER_MASK			GENMASK(20, 11)
-/* RegEEE_slv_remtx_timer_tar */
-#define SLAVE_REMTX_TIMER_MASK			GENMASK(10, 1)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x10 */
-/* RegEEE_slv_wake_int_timer_tar */
-#define SLAVE_WAKEINT_TIMER_MASK		GENMASK(10, 1)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x14 */
-/* RegEEE_trfreeze_timer2 */
-#define TR_FREEZE_TIMER2_MASK			GENMASK(9, 0)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x1c */
-/* RegEEE100Stg1_tar */
-#define EEE100_LPSYNC_STAGE1_UPDATE_TIMER_MASK	GENMASK(8, 0)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x25 */
-/* REGEEE_wake_slv_tr_wait_dfesigdet_en */
-#define WAKE_SLAVE_TR_WAIT_DFE_DETECTION_EN	BIT(11)
-
-#define ANALOG_INTERNAL_OPERATION_MAX_US	20
-#define TXRESERVE_MIN				0
-#define TXRESERVE_MAX				7
-
-#define MTK_PHY_ANARG_RG			0x10
-#define   MTK_PHY_TCLKOFFSET_MASK		GENMASK(12, 8)
-
-/* Registers on MDIO_MMD_VEND1 */
-#define MTK_PHY_TXVLD_DA_RG			0x12
-#define   MTK_PHY_DA_TX_I2MPB_A_GBE_MASK	GENMASK(15, 10)
-#define   MTK_PHY_DA_TX_I2MPB_A_TBT_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_A2		0x16
-#define   MTK_PHY_DA_TX_I2MPB_A_HBT_MASK	GENMASK(15, 10)
-#define   MTK_PHY_DA_TX_I2MPB_A_TST_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_B1		0x17
-#define   MTK_PHY_DA_TX_I2MPB_B_GBE_MASK	GENMASK(13, 8)
-#define   MTK_PHY_DA_TX_I2MPB_B_TBT_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_B2		0x18
-#define   MTK_PHY_DA_TX_I2MPB_B_HBT_MASK	GENMASK(13, 8)
-#define   MTK_PHY_DA_TX_I2MPB_B_TST_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_C1		0x19
-#define   MTK_PHY_DA_TX_I2MPB_C_GBE_MASK	GENMASK(13, 8)
-#define   MTK_PHY_DA_TX_I2MPB_C_TBT_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_C2		0x20
-#define   MTK_PHY_DA_TX_I2MPB_C_HBT_MASK	GENMASK(13, 8)
-#define   MTK_PHY_DA_TX_I2MPB_C_TST_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_D1		0x21
-#define   MTK_PHY_DA_TX_I2MPB_D_GBE_MASK	GENMASK(13, 8)
-#define   MTK_PHY_DA_TX_I2MPB_D_TBT_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_D2		0x22
-#define   MTK_PHY_DA_TX_I2MPB_D_HBT_MASK	GENMASK(13, 8)
-#define   MTK_PHY_DA_TX_I2MPB_D_TST_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_RXADC_CTRL_RG7			0xc6
-#define   MTK_PHY_DA_AD_BUF_BIAS_LP_MASK	GENMASK(9, 8)
-
-#define MTK_PHY_RXADC_CTRL_RG9			0xc8
-#define   MTK_PHY_DA_RX_PSBN_TBT_MASK		GENMASK(14, 12)
-#define   MTK_PHY_DA_RX_PSBN_HBT_MASK		GENMASK(10, 8)
-#define   MTK_PHY_DA_RX_PSBN_GBE_MASK		GENMASK(6, 4)
-#define   MTK_PHY_DA_RX_PSBN_LP_MASK		GENMASK(2, 0)
-
-#define MTK_PHY_LDO_OUTPUT_V			0xd7
-
-#define MTK_PHY_RG_ANA_CAL_RG0			0xdb
-#define   MTK_PHY_RG_CAL_CKINV			BIT(12)
-#define   MTK_PHY_RG_ANA_CALEN			BIT(8)
-#define   MTK_PHY_RG_ZCALEN_A			BIT(0)
-
-#define MTK_PHY_RG_ANA_CAL_RG1			0xdc
-#define   MTK_PHY_RG_ZCALEN_B			BIT(12)
-#define   MTK_PHY_RG_ZCALEN_C			BIT(8)
-#define   MTK_PHY_RG_ZCALEN_D			BIT(4)
-#define   MTK_PHY_RG_TXVOS_CALEN		BIT(0)
-
-#define MTK_PHY_RG_ANA_CAL_RG5			0xe0
-#define   MTK_PHY_RG_REXT_TRIM_MASK		GENMASK(13, 8)
-
-#define MTK_PHY_RG_TX_FILTER			0xfe
-
-#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG120	0x120
-#define   MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK	GENMASK(12, 8)
-#define   MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK	GENMASK(4, 0)
-
-#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122	0x122
-#define   MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK	GENMASK(7, 0)
-
-#define MTK_PHY_RG_TESTMUX_ADC_CTRL		0x144
-#define   MTK_PHY_RG_TXEN_DIG_MASK		GENMASK(5, 5)
-
-#define MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B		0x172
-#define   MTK_PHY_CR_TX_AMP_OFFSET_A_MASK	GENMASK(13, 8)
-#define   MTK_PHY_CR_TX_AMP_OFFSET_B_MASK	GENMASK(6, 0)
-
-#define MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D		0x173
-#define   MTK_PHY_CR_TX_AMP_OFFSET_C_MASK	GENMASK(13, 8)
-#define   MTK_PHY_CR_TX_AMP_OFFSET_D_MASK	GENMASK(6, 0)
-
-#define MTK_PHY_RG_AD_CAL_COMP			0x17a
-#define   MTK_PHY_AD_CAL_COMP_OUT_MASK		GENMASK(8, 8)
-
-#define MTK_PHY_RG_AD_CAL_CLK			0x17b
-#define   MTK_PHY_DA_CAL_CLK			BIT(0)
-
-#define MTK_PHY_RG_AD_CALIN			0x17c
-#define   MTK_PHY_DA_CALIN_FLAG			BIT(0)
-
-#define MTK_PHY_RG_DASN_DAC_IN0_A		0x17d
-#define   MTK_PHY_DASN_DAC_IN0_A_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN0_B		0x17e
-#define   MTK_PHY_DASN_DAC_IN0_B_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN0_C		0x17f
-#define   MTK_PHY_DASN_DAC_IN0_C_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN0_D		0x180
-#define   MTK_PHY_DASN_DAC_IN0_D_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN1_A		0x181
-#define   MTK_PHY_DASN_DAC_IN1_A_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN1_B		0x182
-#define   MTK_PHY_DASN_DAC_IN1_B_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN1_C		0x183
-#define   MTK_PHY_DASN_DAC_IN1_C_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN1_D		0x184
-#define   MTK_PHY_DASN_DAC_IN1_D_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DEV1E_REG19b			0x19b
-#define   MTK_PHY_BYPASS_DSP_LPI_READY		BIT(8)
-
-#define MTK_PHY_RG_LP_IIR2_K1_L			0x22a
-#define MTK_PHY_RG_LP_IIR2_K1_U			0x22b
-#define MTK_PHY_RG_LP_IIR2_K2_L			0x22c
-#define MTK_PHY_RG_LP_IIR2_K2_U			0x22d
-#define MTK_PHY_RG_LP_IIR2_K3_L			0x22e
-#define MTK_PHY_RG_LP_IIR2_K3_U			0x22f
-#define MTK_PHY_RG_LP_IIR2_K4_L			0x230
-#define MTK_PHY_RG_LP_IIR2_K4_U			0x231
-#define MTK_PHY_RG_LP_IIR2_K5_L			0x232
-#define MTK_PHY_RG_LP_IIR2_K5_U			0x233
-
-#define MTK_PHY_RG_DEV1E_REG234			0x234
-#define   MTK_PHY_TR_OPEN_LOOP_EN_MASK		GENMASK(0, 0)
-#define   MTK_PHY_LPF_X_AVERAGE_MASK		GENMASK(7, 4)
-#define   MTK_PHY_TR_LP_IIR_EEE_EN		BIT(12)
-
-#define MTK_PHY_RG_LPF_CNT_VAL			0x235
-
-#define MTK_PHY_RG_DEV1E_REG238			0x238
-#define   MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK	GENMASK(8, 0)
-#define   MTK_PHY_LPI_SLV_SEND_TX_EN		BIT(12)
-
-#define MTK_PHY_RG_DEV1E_REG239			0x239
-#define   MTK_PHY_LPI_SEND_LOC_TIMER_MASK	GENMASK(8, 0)
-#define   MTK_PHY_LPI_TXPCS_LOC_RCV		BIT(12)
-
-#define MTK_PHY_RG_DEV1E_REG27C			0x27c
-#define   MTK_PHY_VGASTATE_FFE_THR_ST1_MASK	GENMASK(12, 8)
-#define MTK_PHY_RG_DEV1E_REG27D			0x27d
-#define   MTK_PHY_VGASTATE_FFE_THR_ST2_MASK	GENMASK(4, 0)
-
-#define MTK_PHY_RG_DEV1E_REG2C7			0x2c7
-#define   MTK_PHY_MAX_GAIN_MASK			GENMASK(4, 0)
-#define   MTK_PHY_MIN_GAIN_MASK			GENMASK(12, 8)
-
-#define MTK_PHY_RG_DEV1E_REG2D1			0x2d1
-#define   MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK	GENMASK(7, 0)
-#define   MTK_PHY_LPI_SKIP_SD_SLV_TR		BIT(8)
-#define   MTK_PHY_LPI_TR_READY			BIT(9)
-#define   MTK_PHY_LPI_VCO_EEE_STG0_EN		BIT(10)
-
-#define MTK_PHY_RG_DEV1E_REG323			0x323
-#define   MTK_PHY_EEE_WAKE_MAS_INT_DC		BIT(0)
-#define   MTK_PHY_EEE_WAKE_SLV_INT_DC		BIT(4)
-
-#define MTK_PHY_RG_DEV1E_REG324			0x324
-#define   MTK_PHY_SMI_DETCNT_MAX_MASK		GENMASK(5, 0)
-#define   MTK_PHY_SMI_DET_MAX_EN		BIT(8)
-
-#define MTK_PHY_RG_DEV1E_REG326			0x326
-#define   MTK_PHY_LPI_MODE_SD_ON		BIT(0)
-#define   MTK_PHY_RESET_RANDUPD_CNT		BIT(1)
-#define   MTK_PHY_TREC_UPDATE_ENAB_CLR		BIT(2)
-#define   MTK_PHY_LPI_QUIT_WAIT_DFE_SIG_DET_OFF	BIT(4)
-#define   MTK_PHY_TR_READY_SKIP_AFE_WAKEUP	BIT(5)
-
-#define MTK_PHY_LDO_PUMP_EN_PAIRAB		0x502
-#define MTK_PHY_LDO_PUMP_EN_PAIRCD		0x503
-
-#define MTK_PHY_DA_TX_R50_PAIR_A		0x53d
-#define MTK_PHY_DA_TX_R50_PAIR_B		0x53e
-#define MTK_PHY_DA_TX_R50_PAIR_C		0x53f
-#define MTK_PHY_DA_TX_R50_PAIR_D		0x540
-
-/* Registers on MDIO_MMD_VEND2 */
-#define MTK_PHY_LED1_DEFAULT_POLARITIES		BIT(1)
-
-#define MTK_PHY_RG_BG_RASEL			0x115
-#define   MTK_PHY_RG_BG_RASEL_MASK		GENMASK(2, 0)
-
-/* 'boottrap' register reflecting the configuration of the 4 PHY LEDs */
-#define RG_GPIO_MISC_TPBANK0			0x6f0
-#define   RG_GPIO_MISC_TPBANK0_BOOTMODE		GENMASK(11, 8)
-
 /* These macro privides efuse parsing for internal phy. */
 #define EFS_DA_TX_I2MPB_A(x)			(((x) >> 0) & GENMASK(5, 0))
 #define EFS_DA_TX_I2MPB_B(x)			(((x) >> 6) & GENMASK(5, 0))
@@ -346,51 +34,16 @@
 #define EFS_RG_BG_RASEL(x)			(((x) >> 4) & GENMASK(2, 0))
 #define EFS_RG_REXT_TRIM(x)			(((x) >> 7) & GENMASK(5, 0))
 
-enum {
-	NO_PAIR,
-	PAIR_A,
-	PAIR_B,
-	PAIR_C,
-	PAIR_D,
-};
-
-enum calibration_mode {
-	EFUSE_K,
-	SW_K
-};
-
-enum CAL_ITEM {
-	REXT,
-	TX_OFFSET,
-	TX_AMP,
-	TX_R50,
-	TX_VCM
-};
-
-enum CAL_MODE {
-	EFUSE_M,
-	SW_M
-};
-
 struct mtk_socphy_shared {
 	u32			boottrap;
 	struct mtk_socphy_priv	priv[4];
 };
 
-/* One calibration cycle consists of:
- * 1.Set DA_CALIN_FLAG high to start calibration. Keep it high
- *   until AD_CAL_COMP is ready to output calibration result.
- * 2.Wait until DA_CAL_CLK is available.
- * 3.Fetch AD_CAL_COMP_OUT.
- */
-static int cal_cycle(struct phy_device *phydev, int devad,
-		     u32 regnum, u16 mask, u16 cal_val)
+int mtk_cal_cycle_wait(struct phy_device *phydev)
 {
 	int reg_val;
 	int ret;
 
-	phy_modify_mmd(phydev, devad, regnum,
-		       mask, cal_val);
 	phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CALIN,
 			 MTK_PHY_DA_CALIN_FLAG);
 
@@ -409,7 +62,25 @@ static int cal_cycle(struct phy_device *phydev, int devad,
 	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CAL_COMP);
 	if (ret < 0)
 		return ret;
-	ret = FIELD_GET(MTK_PHY_AD_CAL_COMP_OUT_MASK, ret);
+
+	return FIELD_GET(MTK_PHY_AD_CAL_COMP_OUT_MASK, ret);
+}
+
+/* One calibration cycle consists of:
+ * 1.Set DA_CALIN_FLAG high to start calibration. Keep it high
+ *   until AD_CAL_COMP is ready to output calibration result.
+ * 2.Wait until DA_CAL_CLK is available.
+ * 3.Fetch AD_CAL_COMP_OUT.
+ */
+static int cal_cycle(struct phy_device *phydev, int devad,
+		     u32 regnum, u16 mask, u16 cal_val)
+{
+	int ret;
+
+	phy_modify_mmd(phydev, devad, regnum,
+		       mask, cal_val);
+
+	ret = mtk_cal_cycle_wait(phydev);
 	phydev_dbg(phydev, "cal_val: 0x%x, ret: %d\n", cal_val, ret);
 
 	return ret;
diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.h b/drivers/net/phy/mediatek/mtk-ge-soc.h
new file mode 100644
index 000000000000..9aaa7e3caa41
--- /dev/null
+++ b/drivers/net/phy/mediatek/mtk-ge-soc.h
@@ -0,0 +1,351 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _MTK_GE_SOC_H_
+#define _MTK_GE_SOC_H_
+
+#include <linux/bitfield.h>
+
+#define MTK_GPHY_ID_MT7981			0x03a29461
+#define MTK_GPHY_ID_MT7988			0x03a29481
+#define MTK_GPHY_ID_AN7581			0x03a294c1
+#define MTK_GPHY_ID_AN7583			0xc0ff0420
+
+#define MTK_EXT_PAGE_ACCESS			0x1f
+#define MTK_PHY_PAGE_STANDARD			0x0000
+#define MTK_PHY_PAGE_EXTENDED_3			0x0003
+
+#define MTK_PHY_LPI_REG_14			0x14
+#define MTK_PHY_LPI_WAKE_TIMER_1000_MASK	GENMASK(8, 0)
+
+#define MTK_PHY_LPI_REG_1c			0x1c
+#define MTK_PHY_SMI_DET_ON_THRESH_MASK		GENMASK(13, 8)
+
+#define MTK_PHY_PAGE_EXTENDED_2A30		0x2a30
+
+/* Registers on Token Ring debug nodes */
+/* ch_addr = 0x0, node_addr = 0x7, data_addr = 0x15 */
+/* NormMseLoThresh */
+#define NORMAL_MSE_LO_THRESH_MASK		GENMASK(15, 8)
+
+/* ch_addr = 0x0, node_addr = 0xf, data_addr = 0x3c */
+/* RemAckCntLimitCtrl */
+#define REMOTE_ACK_COUNT_LIMIT_CTRL_MASK	GENMASK(2, 1)
+
+/* ch_addr = 0x1, node_addr = 0xd, data_addr = 0x20 */
+/* VcoSlicerThreshBitsHigh */
+#define VCO_SLICER_THRESH_HIGH_MASK		GENMASK(23, 0)
+
+/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x0 */
+/* DfeTailEnableVgaThresh1000 */
+#define DFE_TAIL_EANBLE_VGA_TRHESH_1000		GENMASK(5, 1)
+
+/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x1 */
+/* MrvlTrFix100Kp */
+#define MRVL_TR_FIX_100KP_MASK			GENMASK(22, 20)
+/* MrvlTrFix100Kf */
+#define MRVL_TR_FIX_100KF_MASK			GENMASK(19, 17)
+/* MrvlTrFix1000Kp */
+#define MRVL_TR_FIX_1000KP_MASK			GENMASK(16, 14)
+/* MrvlTrFix1000Kf */
+#define MRVL_TR_FIX_1000KF_MASK			GENMASK(13, 11)
+
+/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x12 */
+/* VgaDecRate */
+#define VGA_DECIMATION_RATE_MASK		GENMASK(8, 5)
+
+/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x17 */
+/* SlvDSPreadyTime */
+#define SLAVE_DSP_READY_TIME_MASK		GENMASK(22, 15)
+/* MasDSPreadyTime */
+#define MASTER_DSP_READY_TIME_MASK		GENMASK(14, 7)
+
+/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x18 */
+/* EnabRandUpdTrig */
+#define ENABLE_RANDOM_UPDOWN_COUNTER_TRIGGER	BIT(8)
+
+/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x20 */
+/* ResetSyncOffset */
+#define RESET_SYNC_OFFSET_MASK			GENMASK(11, 8)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x0 */
+/* FfeUpdGainForceVal */
+#define FFE_UPDATE_GAIN_FORCE_VAL_MASK		GENMASK(9, 7)
+/* FfeUpdGainForce */
+#define FFE_UPDATE_GAIN_FORCE			BIT(6)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x3 */
+/* TrFreeze */
+#define TR_FREEZE_MASK				GENMASK(11, 0)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x6 */
+/* SS: Steady-state, KP: Proportional Gain */
+/* SSTrKp100 */
+#define SS_TR_KP100_MASK			GENMASK(21, 19)
+/* SSTrKf100 */
+#define SS_TR_KF100_MASK			GENMASK(18, 16)
+/* SSTrKp1000Mas */
+#define SS_TR_KP1000_MASTER_MASK		GENMASK(15, 13)
+/* SSTrKf1000Mas */
+#define SS_TR_KF1000_MASTER_MASK		GENMASK(12, 10)
+/* SSTrKp1000Slv */
+#define SS_TR_KP1000_SLAVE_MASK			GENMASK(9, 7)
+/* SSTrKf1000Slv */
+#define SS_TR_KF1000_SLAVE_MASK			GENMASK(6, 4)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x8 */
+/* clear this bit if wanna select from AFE */
+/* Regsigdet_sel_1000 */
+#define EEE1000_SELECT_SIGNAL_DETECTION_FROM_DFE	BIT(4)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xd */
+/* RegEEE_st2TrKf1000 */
+#define EEE1000_STAGE2_TR_KF_MASK		GENMASK(13, 11)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xf */
+/* RegEEE_slv_waketr_timer_tar */
+#define SLAVE_WAKETR_TIMER_MASK			GENMASK(20, 11)
+/* RegEEE_slv_remtx_timer_tar */
+#define SLAVE_REMTX_TIMER_MASK			GENMASK(10, 1)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x10 */
+/* RegEEE_slv_wake_int_timer_tar */
+#define SLAVE_WAKEINT_TIMER_MASK		GENMASK(10, 1)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x14 */
+/* RegEEE_trfreeze_timer2 */
+#define TR_FREEZE_TIMER2_MASK			GENMASK(9, 0)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x1c */
+/* RegEEE100Stg1_tar */
+#define EEE100_LPSYNC_STAGE1_UPDATE_TIMER_MASK	GENMASK(8, 0)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x25 */
+/* REGEEE_wake_slv_tr_wait_dfesigdet_en */
+#define WAKE_SLAVE_TR_WAIT_DFE_DETECTION_EN	BIT(11)
+
+#define ANALOG_INTERNAL_OPERATION_MAX_US	20
+#define TXRESERVE_MIN				0
+#define TXRESERVE_MAX				7
+
+#define MTK_PHY_ANARG_RG			0x10
+#define   MTK_PHY_TCLKOFFSET_MASK		GENMASK(12, 8)
+
+/* Registers on MDIO_MMD_VEND1 */
+#define MTK_PHY_TXVLD_DA_RG			0x12
+#define   MTK_PHY_DA_TX_I2MPB_A_GBE_MASK	GENMASK(15, 10)
+#define   MTK_PHY_DA_TX_I2MPB_A_TBT_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_A2		0x16
+#define   MTK_PHY_DA_TX_I2MPB_A_HBT_MASK	GENMASK(15, 10)
+#define   MTK_PHY_DA_TX_I2MPB_A_TST_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_B1		0x17
+#define   MTK_PHY_DA_TX_I2MPB_B_GBE_MASK	GENMASK(13, 8)
+#define   MTK_PHY_DA_TX_I2MPB_B_TBT_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_B2		0x18
+#define   MTK_PHY_DA_TX_I2MPB_B_HBT_MASK	GENMASK(13, 8)
+#define   MTK_PHY_DA_TX_I2MPB_B_TST_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_C1		0x19
+#define   MTK_PHY_DA_TX_I2MPB_C_GBE_MASK	GENMASK(13, 8)
+#define   MTK_PHY_DA_TX_I2MPB_C_TBT_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_C2		0x20
+#define   MTK_PHY_DA_TX_I2MPB_C_HBT_MASK	GENMASK(13, 8)
+#define   MTK_PHY_DA_TX_I2MPB_C_TST_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_D1		0x21
+#define   MTK_PHY_DA_TX_I2MPB_D_GBE_MASK	GENMASK(13, 8)
+#define   MTK_PHY_DA_TX_I2MPB_D_TBT_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_D2		0x22
+#define   MTK_PHY_DA_TX_I2MPB_D_HBT_MASK	GENMASK(13, 8)
+#define   MTK_PHY_DA_TX_I2MPB_D_TST_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_RXADC_CTRL_RG7			0xc6
+#define   MTK_PHY_DA_AD_BUF_BIAS_LP_MASK	GENMASK(9, 8)
+
+#define MTK_PHY_RXADC_CTRL_RG9			0xc8
+#define   MTK_PHY_DA_RX_PSBN_TBT_MASK		GENMASK(14, 12)
+#define   MTK_PHY_DA_RX_PSBN_HBT_MASK		GENMASK(10, 8)
+#define   MTK_PHY_DA_RX_PSBN_GBE_MASK		GENMASK(6, 4)
+#define   MTK_PHY_DA_RX_PSBN_LP_MASK		GENMASK(2, 0)
+
+#define MTK_PHY_LDO_OUTPUT_V			0xd7
+
+#define MTK_PHY_RG_ANA_CAL_RG0			0xdb
+#define   MTK_PHY_RG_CAL_CKINV			BIT(12)
+#define   MTK_PHY_RG_ANA_CALEN			BIT(8)
+#define   MTK_PHY_RG_ZCALEN_A			BIT(0)
+
+#define MTK_PHY_RG_ANA_CAL_RG1			0xdc
+#define   MTK_PHY_RG_ZCALEN_B			BIT(12)
+#define   MTK_PHY_RG_ZCALEN_C			BIT(8)
+#define   MTK_PHY_RG_ZCALEN_D			BIT(4)
+#define   MTK_PHY_RG_TXVOS_CALEN		BIT(0)
+
+#define MTK_PHY_RG_ANA_CAL_RG5			0xe0
+#define   MTK_PHY_RG_REXT_TRIM_MASK		GENMASK(13, 8)
+
+#define MTK_PHY_RG_TX_FILTER			0xfe
+
+#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG120	0x120
+#define   MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK	GENMASK(12, 8)
+#define   MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK	GENMASK(4, 0)
+
+#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122	0x122
+#define   MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK	GENMASK(7, 0)
+
+#define MTK_PHY_RG_TESTMUX_ADC_CTRL		0x144
+#define   MTK_PHY_RG_TXEN_DIG_MASK		GENMASK(5, 5)
+
+#define MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B		0x172
+#define   MTK_PHY_CR_TX_AMP_OFFSET_A_MASK	GENMASK(13, 8)
+#define   MTK_PHY_CR_TX_AMP_OFFSET_B_MASK	GENMASK(6, 0)
+
+#define MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D		0x173
+#define   MTK_PHY_CR_TX_AMP_OFFSET_C_MASK	GENMASK(13, 8)
+#define   MTK_PHY_CR_TX_AMP_OFFSET_D_MASK	GENMASK(6, 0)
+
+#define MTK_PHY_RG_AD_CAL_COMP			0x17a
+#define   MTK_PHY_AD_CAL_COMP_OUT_MASK		GENMASK(8, 8)
+
+#define MTK_PHY_RG_AD_CAL_CLK			0x17b
+#define   MTK_PHY_DA_CAL_CLK			BIT(0)
+
+#define MTK_PHY_RG_AD_CALIN			0x17c
+#define   MTK_PHY_DA_CALIN_FLAG			BIT(0)
+
+#define MTK_PHY_RG_DASN_DAC_IN0_A		0x17d
+#define   MTK_PHY_DASN_DAC_IN0_A_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN0_B		0x17e
+#define   MTK_PHY_DASN_DAC_IN0_B_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN0_C		0x17f
+#define   MTK_PHY_DASN_DAC_IN0_C_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN0_D		0x180
+#define   MTK_PHY_DASN_DAC_IN0_D_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN1_A		0x181
+#define   MTK_PHY_DASN_DAC_IN1_A_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN1_B		0x182
+#define   MTK_PHY_DASN_DAC_IN1_B_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN1_C		0x183
+#define   MTK_PHY_DASN_DAC_IN1_C_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN1_D		0x184
+#define   MTK_PHY_DASN_DAC_IN1_D_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DEV1E_REG19b			0x19b
+#define   MTK_PHY_BYPASS_DSP_LPI_READY		BIT(8)
+
+#define MTK_PHY_RG_LP_IIR2_K1_L			0x22a
+#define MTK_PHY_RG_LP_IIR2_K1_U			0x22b
+#define MTK_PHY_RG_LP_IIR2_K2_L			0x22c
+#define MTK_PHY_RG_LP_IIR2_K2_U			0x22d
+#define MTK_PHY_RG_LP_IIR2_K3_L			0x22e
+#define MTK_PHY_RG_LP_IIR2_K3_U			0x22f
+#define MTK_PHY_RG_LP_IIR2_K4_L			0x230
+#define MTK_PHY_RG_LP_IIR2_K4_U			0x231
+#define MTK_PHY_RG_LP_IIR2_K5_L			0x232
+#define MTK_PHY_RG_LP_IIR2_K5_U			0x233
+
+#define MTK_PHY_RG_DEV1E_REG234			0x234
+#define   MTK_PHY_TR_OPEN_LOOP_EN_MASK		GENMASK(0, 0)
+#define   MTK_PHY_LPF_X_AVERAGE_MASK		GENMASK(7, 4)
+#define   MTK_PHY_TR_LP_IIR_EEE_EN		BIT(12)
+
+#define MTK_PHY_RG_LPF_CNT_VAL			0x235
+
+#define MTK_PHY_RG_DEV1E_REG238			0x238
+#define   MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK	GENMASK(8, 0)
+#define   MTK_PHY_LPI_SLV_SEND_TX_EN		BIT(12)
+
+#define MTK_PHY_RG_DEV1E_REG239			0x239
+#define   MTK_PHY_LPI_SEND_LOC_TIMER_MASK	GENMASK(8, 0)
+#define   MTK_PHY_LPI_TXPCS_LOC_RCV		BIT(12)
+
+#define MTK_PHY_RG_DEV1E_REG27C			0x27c
+#define   MTK_PHY_VGASTATE_FFE_THR_ST1_MASK	GENMASK(12, 8)
+#define MTK_PHY_RG_DEV1E_REG27D			0x27d
+#define   MTK_PHY_VGASTATE_FFE_THR_ST2_MASK	GENMASK(4, 0)
+
+#define MTK_PHY_RG_DEV1E_REG2C7			0x2c7
+#define   MTK_PHY_MAX_GAIN_MASK			GENMASK(4, 0)
+#define   MTK_PHY_MIN_GAIN_MASK			GENMASK(12, 8)
+
+#define MTK_PHY_RG_DEV1E_REG2D1			0x2d1
+#define   MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK	GENMASK(7, 0)
+#define   MTK_PHY_LPI_SKIP_SD_SLV_TR		BIT(8)
+#define   MTK_PHY_LPI_TR_READY			BIT(9)
+#define   MTK_PHY_LPI_VCO_EEE_STG0_EN		BIT(10)
+
+#define MTK_PHY_RG_DEV1E_REG323			0x323
+#define   MTK_PHY_EEE_WAKE_MAS_INT_DC		BIT(0)
+#define   MTK_PHY_EEE_WAKE_SLV_INT_DC		BIT(4)
+
+#define MTK_PHY_RG_DEV1E_REG324			0x324
+#define   MTK_PHY_SMI_DETCNT_MAX_MASK		GENMASK(5, 0)
+#define   MTK_PHY_SMI_DET_MAX_EN		BIT(8)
+
+#define MTK_PHY_RG_DEV1E_REG326			0x326
+#define   MTK_PHY_LPI_MODE_SD_ON		BIT(0)
+#define   MTK_PHY_RESET_RANDUPD_CNT		BIT(1)
+#define   MTK_PHY_TREC_UPDATE_ENAB_CLR		BIT(2)
+#define   MTK_PHY_LPI_QUIT_WAIT_DFE_SIG_DET_OFF	BIT(4)
+#define   MTK_PHY_TR_READY_SKIP_AFE_WAKEUP	BIT(5)
+
+#define MTK_PHY_LDO_PUMP_EN_PAIRAB		0x502
+#define MTK_PHY_LDO_PUMP_EN_PAIRCD		0x503
+
+#define MTK_PHY_DA_TX_R50_PAIR_A		0x53d
+#define MTK_PHY_DA_TX_R50_PAIR_B		0x53e
+#define MTK_PHY_DA_TX_R50_PAIR_C		0x53f
+#define MTK_PHY_DA_TX_R50_PAIR_D		0x540
+
+/* Registers on MDIO_MMD_VEND2 */
+#define MTK_PHY_LED1_DEFAULT_POLARITIES		BIT(1)
+
+#define MTK_PHY_RG_BG_RASEL			0x115
+#define   MTK_PHY_RG_BG_RASEL_MASK		GENMASK(2, 0)
+
+/* 'boottrap' register reflecting the configuration of the 4 PHY LEDs */
+#define RG_GPIO_MISC_TPBANK0			0x6f0
+#define   RG_GPIO_MISC_TPBANK0_BOOTMODE		GENMASK(11, 8)
+
+enum CAL_ITEM {
+	REXT,
+	TX_OFFSET,
+	TX_AMP,
+	TX_R50,
+	TX_VCM,
+	RX_OFFSET,
+};
+
+enum {
+	NO_PAIR,
+	PAIR_A,
+	PAIR_B,
+	PAIR_C,
+	PAIR_D,
+};
+
+enum calibration_mode {
+	EFUSE_K,
+	SW_K
+};
+
+enum CAL_MODE {
+	EFUSE_M,
+	SW_M
+};
+
+/* MTK GE SoC common functions */
+int mtk_cal_cycle_wait(struct phy_device *phydev);
+
+#endif /* _MTK_GE_SOC_H_ */
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next 3/5] net: phy: mediatek: split Airoha code to dedicated source
  2026-07-08 10:23 [PATCH net-next 0/5] net: phy: mediatek: calibration for AN7581/3 Christian Marangi
  2026-07-08 10:23 ` [PATCH net-next 1/5] net: phy: mediatek: export __mtk_tr_write Christian Marangi
  2026-07-08 10:23 ` [PATCH net-next 2/5] net: phy: mediatek: move MTK GE SoC registers define to dedicated header Christian Marangi
@ 2026-07-08 10:23 ` Christian Marangi
  2026-07-08 16:32   ` Wayen Yan
  2026-07-08 10:23 ` [PATCH net-next 4/5] net: phy: mediatek: add calibration logic for AN7581 Christian Marangi
  2026-07-08 10:23 ` [PATCH net-next 5/5] net: phy: mediatek: add calibration logic for AN7583 Christian Marangi
  4 siblings, 1 reply; 11+ messages in thread
From: Christian Marangi @ 2026-07-08 10:23 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	Daniel Golle, Qingfang Deng, SkyLake Huang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-kernel, netdev,
	linux-arm-kernel, linux-mediatek

In preparation for Airoha PHYs software calibration support, split the
Airoha code to dedicated source following Mediatek naming pattern of
"ge-soc".

This is to tidy things up and not overload the mediatek driver of specific
Airoha code and ship a bigger PHY kernel module with dead code.

Move the shared LEDs function to mtk-phy-lib to permit the split instead of
duplicating the code.

Also add myself as MAINTAINER of the now new airoha-ge-soc driver.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 MAINTAINERS                              |   8 ++
 drivers/net/phy/mediatek/Kconfig         |  16 ++-
 drivers/net/phy/mediatek/Makefile        |   1 +
 drivers/net/phy/mediatek/airoha-ge-soc.c | 102 ++++++++++++++++
 drivers/net/phy/mediatek/mtk-ge-soc.c    | 146 -----------------------
 drivers/net/phy/mediatek/mtk-ge-soc.h    |   4 +-
 drivers/net/phy/mediatek/mtk-phy-lib.c   |  72 +++++++++++
 drivers/net/phy/mediatek/mtk.h           |  12 ++
 8 files changed, 211 insertions(+), 150 deletions(-)
 create mode 100644 drivers/net/phy/mediatek/airoha-ge-soc.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 16e47aea84c3..e914bccf5cbb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -760,6 +760,14 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/net/airoha,en7581-eth.yaml
 F:	drivers/net/ethernet/airoha/
 
+AIROHA ETHERNET PHY DRIVER
+M:	Christian Marangi <ansuelsmth@gmail.com>
+L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+L:	linux-mediatek@lists.infradead.org (moderated for non-subscribers)
+L:	netdev@vger.kernel.org
+S:	Maintained
+F:	drivers/net/phy/mediatek/airoha-ge-soc.c
+
 AIROHA PCIE PHY DRIVER
 M:	Lorenzo Bianconi <lorenzo@kernel.org>
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
diff --git a/drivers/net/phy/mediatek/Kconfig b/drivers/net/phy/mediatek/Kconfig
index bb7dc876271e..fe51f4616c54 100644
--- a/drivers/net/phy/mediatek/Kconfig
+++ b/drivers/net/phy/mediatek/Kconfig
@@ -1,4 +1,17 @@
 # SPDX-License-Identifier: GPL-2.0-only
+config AIROHA_GE_SOC_PHY
+	tristate "Airoha SoC Ethernet PHYs"
+	depends on ARM64 || COMPILE_TEST
+	depends on ARCH_AIROHA || COMPILE_TEST
+	select MTK_NET_PHYLIB
+	select PHY_PACKAGE
+	help
+	  Supports Airoha SoC built-in Gigabit Ethernet PHYs.
+
+	  Include support for built-in Ethernet PHYs which are present in
+	  the AN7581 and AN7583 SoCs. These PHYs d will dynamically
+	  calibrate during startup.
+
 config MEDIATEK_2P5GE_PHY
 	tristate "MediaTek 2.5Gb Ethernet PHYs"
 	depends on (ARM64 && ARCH_MEDIATEK) || COMPILE_TEST
@@ -24,8 +37,7 @@ config MEDIATEK_GE_PHY
 config MEDIATEK_GE_SOC_PHY
 	tristate "MediaTek SoC Ethernet PHYs"
 	depends on ARM64 || COMPILE_TEST
-	depends on ARCH_AIROHA || (ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || \
-		   COMPILE_TEST
+	depends on (ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || COMPILE_TEST
 	select MTK_NET_PHYLIB
 	select PHY_PACKAGE
 	help
diff --git a/drivers/net/phy/mediatek/Makefile b/drivers/net/phy/mediatek/Makefile
index ac57ecc799fc..eb430f008169 100644
--- a/drivers/net/phy/mediatek/Makefile
+++ b/drivers/net/phy/mediatek/Makefile
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_AIROHA_GE_SOC_PHY)		+= airoha-ge-soc.o
 obj-$(CONFIG_MEDIATEK_2P5GE_PHY)	+= mtk-2p5ge.o
 obj-$(CONFIG_MEDIATEK_GE_PHY)		+= mtk-ge.o
 obj-$(CONFIG_MEDIATEK_GE_SOC_PHY)	+= mtk-ge-soc.o
diff --git a/drivers/net/phy/mediatek/airoha-ge-soc.c b/drivers/net/phy/mediatek/airoha-ge-soc.c
new file mode 100644
index 000000000000..429594f27792
--- /dev/null
+++ b/drivers/net/phy/mediatek/airoha-ge-soc.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/pinctrl/consumer.h>
+#include <linux/phy.h>
+
+#include "mtk.h"
+#include "mtk-ge-soc.h"
+
+#define AIROHA_PHY_MAX_LEDS			2
+
+static int an7581_phy_probe(struct phy_device *phydev)
+{
+	struct mtk_socphy_priv *priv;
+	struct pinctrl *pinctrl;
+
+	/* Toggle pinctrl to enable PHY LED */
+	pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "gbe-led");
+	if (IS_ERR(pinctrl))
+		dev_err(&phydev->mdio.bus->dev,
+			"Failed to setup PHY LED pinctrl\n");
+
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
+	return 0;
+}
+
+static int an7581_phy_led_polarity_set(struct phy_device *phydev, int index,
+				       unsigned long modes)
+{
+	u16 val = 0;
+	u32 mode;
+
+	if (index >= AIROHA_PHY_MAX_LEDS)
+		return -EINVAL;
+
+	for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
+		switch (mode) {
+		case PHY_LED_ACTIVE_LOW:
+			val = MTK_PHY_LED_ON_POLARITY;
+			break;
+		case PHY_LED_ACTIVE_HIGH:
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	return phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
+			      MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL,
+			      MTK_PHY_LED_ON_POLARITY, val);
+}
+
+static int an7583_phy_config_init(struct phy_device *phydev)
+{
+	/* BMCR_PDOWN is enabled by default */
+	return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
+}
+
+static struct phy_driver mtk_socphy_driver[] = {
+	{
+		PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7581),
+		.name		= "Airoha AN7581 PHY",
+		.config_intr	= genphy_no_config_intr,
+		.handle_interrupt = genphy_handle_interrupt_no_ack,
+		.probe		= an7581_phy_probe,
+		.led_blink_set	= mt798x_phy_led_blink_set,
+		.led_brightness_set = mt798x_phy_led_brightness_set,
+		.led_hw_is_supported = mt798x_phy_led_hw_is_supported,
+		.led_hw_control_set = mt798x_phy_led_hw_control_set,
+		.led_hw_control_get = mt798x_phy_led_hw_control_get,
+		.led_polarity_set = an7581_phy_led_polarity_set,
+	},
+	{
+		PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7583),
+		.name		= "Airoha AN7583 PHY",
+		.config_init	= an7583_phy_config_init,
+		.probe		= an7581_phy_probe,
+		.led_blink_set	= mt798x_phy_led_blink_set,
+		.led_brightness_set = mt798x_phy_led_brightness_set,
+		.led_hw_is_supported = mt798x_phy_led_hw_is_supported,
+		.led_hw_control_set = mt798x_phy_led_hw_control_set,
+		.led_hw_control_get = mt798x_phy_led_hw_control_get,
+		.led_polarity_set = an7581_phy_led_polarity_set,
+	},
+};
+
+module_phy_driver(mtk_socphy_driver);
+
+static const struct mdio_device_id __maybe_unused airoha_socphy_tbl[] = {
+	{ PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7581) },
+	{ PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7583) },
+	{ }
+};
+
+MODULE_DESCRIPTION("Airoha SoC Gigabit Ethernet PHY driver");
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_LICENSE("GPL");
+
+MODULE_DEVICE_TABLE(mdio, airoha_socphy_tbl);
diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.c b/drivers/net/phy/mediatek/mtk-ge-soc.c
index 46bae11ad740..868ae089013d 100644
--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
+++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
@@ -886,73 +886,6 @@ static int mt798x_phy_config_init(struct phy_device *phydev)
 	return mt798x_phy_calibration(phydev);
 }
 
-static int mt798x_phy_led_blink_set(struct phy_device *phydev, u8 index,
-				    unsigned long *delay_on,
-				    unsigned long *delay_off)
-{
-	bool blinking = false;
-	int err;
-
-	err = mtk_phy_led_num_dly_cfg(index, delay_on, delay_off, &blinking);
-	if (err < 0)
-		return err;
-
-	err = mtk_phy_hw_led_blink_set(phydev, index, blinking);
-	if (err)
-		return err;
-
-	return mtk_phy_hw_led_on_set(phydev, index, MTK_GPHY_LED_ON_MASK,
-				     false);
-}
-
-static int mt798x_phy_led_brightness_set(struct phy_device *phydev,
-					 u8 index, enum led_brightness value)
-{
-	int err;
-
-	err = mtk_phy_hw_led_blink_set(phydev, index, false);
-	if (err)
-		return err;
-
-	return mtk_phy_hw_led_on_set(phydev, index, MTK_GPHY_LED_ON_MASK,
-				     (value != LED_OFF));
-}
-
-static const unsigned long supported_triggers =
-	BIT(TRIGGER_NETDEV_FULL_DUPLEX) |
-	BIT(TRIGGER_NETDEV_HALF_DUPLEX) |
-	BIT(TRIGGER_NETDEV_LINK)        |
-	BIT(TRIGGER_NETDEV_LINK_10)     |
-	BIT(TRIGGER_NETDEV_LINK_100)    |
-	BIT(TRIGGER_NETDEV_LINK_1000)   |
-	BIT(TRIGGER_NETDEV_RX)          |
-	BIT(TRIGGER_NETDEV_TX);
-
-static int mt798x_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
-					  unsigned long rules)
-{
-	return mtk_phy_led_hw_is_supported(phydev, index, rules,
-					   supported_triggers);
-}
-
-static int mt798x_phy_led_hw_control_get(struct phy_device *phydev, u8 index,
-					 unsigned long *rules)
-{
-	return mtk_phy_led_hw_ctrl_get(phydev, index, rules,
-				       MTK_GPHY_LED_ON_SET,
-				       MTK_GPHY_LED_RX_BLINK_SET,
-				       MTK_GPHY_LED_TX_BLINK_SET);
-};
-
-static int mt798x_phy_led_hw_control_set(struct phy_device *phydev, u8 index,
-					 unsigned long rules)
-{
-	return mtk_phy_led_hw_ctrl_set(phydev, index, rules,
-				       MTK_GPHY_LED_ON_SET,
-				       MTK_GPHY_LED_RX_BLINK_SET,
-				       MTK_GPHY_LED_TX_BLINK_SET);
-};
-
 static bool mt7988_phy_led_get_polarity(struct phy_device *phydev, int led_num)
 {
 	struct mtk_socphy_shared *priv = phy_package_get_priv(phydev);
@@ -1089,58 +1022,6 @@ static int mt7981_phy_probe(struct phy_device *phydev)
 	return mt798x_phy_calibration(phydev);
 }
 
-static int an7581_phy_probe(struct phy_device *phydev)
-{
-	struct mtk_socphy_priv *priv;
-	struct pinctrl *pinctrl;
-
-	/* Toggle pinctrl to enable PHY LED */
-	pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "gbe-led");
-	if (IS_ERR(pinctrl))
-		dev_err(&phydev->mdio.bus->dev,
-			"Failed to setup PHY LED pinctrl\n");
-
-	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
-	if (!priv)
-		return -ENOMEM;
-
-	phydev->priv = priv;
-
-	return 0;
-}
-
-static int an7581_phy_led_polarity_set(struct phy_device *phydev, int index,
-				       unsigned long modes)
-{
-	u16 val = 0;
-	u32 mode;
-
-	if (index >= MTK_PHY_MAX_LEDS)
-		return -EINVAL;
-
-	for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
-		switch (mode) {
-		case PHY_LED_ACTIVE_LOW:
-			val = MTK_PHY_LED_ON_POLARITY;
-			break;
-		case PHY_LED_ACTIVE_HIGH:
-			break;
-		default:
-			return -EINVAL;
-		}
-	}
-
-	return phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
-			      MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL,
-			      MTK_PHY_LED_ON_POLARITY, val);
-}
-
-static int an7583_phy_config_init(struct phy_device *phydev)
-{
-	/* BMCR_PDOWN is enabled by default */
-	return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
-}
-
 static struct phy_driver mtk_socphy_driver[] = {
 	{
 		PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981),
@@ -1176,31 +1057,6 @@ static struct phy_driver mtk_socphy_driver[] = {
 		.led_hw_control_set = mt798x_phy_led_hw_control_set,
 		.led_hw_control_get = mt798x_phy_led_hw_control_get,
 	},
-	{
-		PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581),
-		.name		= "Airoha AN7581 PHY",
-		.config_intr	= genphy_no_config_intr,
-		.handle_interrupt = genphy_handle_interrupt_no_ack,
-		.probe		= an7581_phy_probe,
-		.led_blink_set	= mt798x_phy_led_blink_set,
-		.led_brightness_set = mt798x_phy_led_brightness_set,
-		.led_hw_is_supported = mt798x_phy_led_hw_is_supported,
-		.led_hw_control_set = mt798x_phy_led_hw_control_set,
-		.led_hw_control_get = mt798x_phy_led_hw_control_get,
-		.led_polarity_set = an7581_phy_led_polarity_set,
-	},
-	{
-		PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7583),
-		.name		= "Airoha AN7583 PHY",
-		.config_init	= an7583_phy_config_init,
-		.probe		= an7581_phy_probe,
-		.led_blink_set	= mt798x_phy_led_blink_set,
-		.led_brightness_set = mt798x_phy_led_brightness_set,
-		.led_hw_is_supported = mt798x_phy_led_hw_is_supported,
-		.led_hw_control_set = mt798x_phy_led_hw_control_set,
-		.led_hw_control_get = mt798x_phy_led_hw_control_get,
-		.led_polarity_set = an7581_phy_led_polarity_set,
-	},
 };
 
 module_phy_driver(mtk_socphy_driver);
@@ -1208,8 +1064,6 @@ module_phy_driver(mtk_socphy_driver);
 static const struct mdio_device_id __maybe_unused mtk_socphy_tbl[] = {
 	{ PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981) },
 	{ PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988) },
-	{ PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581) },
-	{ PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7583) },
 	{ }
 };
 
diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.h b/drivers/net/phy/mediatek/mtk-ge-soc.h
index 9aaa7e3caa41..507e68343576 100644
--- a/drivers/net/phy/mediatek/mtk-ge-soc.h
+++ b/drivers/net/phy/mediatek/mtk-ge-soc.h
@@ -7,8 +7,8 @@
 
 #define MTK_GPHY_ID_MT7981			0x03a29461
 #define MTK_GPHY_ID_MT7988			0x03a29481
-#define MTK_GPHY_ID_AN7581			0x03a294c1
-#define MTK_GPHY_ID_AN7583			0xc0ff0420
+#define AIROHA_GPHY_ID_AN7581			0x03a294c1
+#define AIROHA_GPHY_ID_AN7583			0xc0ff0420
 
 #define MTK_EXT_PAGE_ACCESS			0x1f
 #define MTK_PHY_PAGE_STANDARD			0x0000
diff --git a/drivers/net/phy/mediatek/mtk-phy-lib.c b/drivers/net/phy/mediatek/mtk-phy-lib.c
index 78b998af5238..8c2b26f9b840 100644
--- a/drivers/net/phy/mediatek/mtk-phy-lib.c
+++ b/drivers/net/phy/mediatek/mtk-phy-lib.c
@@ -342,6 +342,78 @@ void mtk_phy_leds_state_init(struct phy_device *phydev)
 }
 EXPORT_SYMBOL_GPL(mtk_phy_leds_state_init);
 
+int mt798x_phy_led_blink_set(struct phy_device *phydev, u8 index,
+			     unsigned long *delay_on,
+			     unsigned long *delay_off)
+{
+	bool blinking = false;
+	int err;
+
+	err = mtk_phy_led_num_dly_cfg(index, delay_on, delay_off, &blinking);
+	if (err < 0)
+		return err;
+
+	err = mtk_phy_hw_led_blink_set(phydev, index, blinking);
+	if (err)
+		return err;
+
+	return mtk_phy_hw_led_on_set(phydev, index, MTK_GPHY_LED_ON_MASK,
+				     false);
+}
+EXPORT_SYMBOL_GPL(mt798x_phy_led_blink_set);
+
+int mt798x_phy_led_brightness_set(struct phy_device *phydev,
+				  u8 index, enum led_brightness value)
+{
+	int err;
+
+	err = mtk_phy_hw_led_blink_set(phydev, index, false);
+	if (err)
+		return err;
+
+	return mtk_phy_hw_led_on_set(phydev, index, MTK_GPHY_LED_ON_MASK,
+				     (value != LED_OFF));
+}
+EXPORT_SYMBOL_GPL(mt798x_phy_led_brightness_set);
+
+static const unsigned long supported_triggers =
+	BIT(TRIGGER_NETDEV_FULL_DUPLEX) |
+	BIT(TRIGGER_NETDEV_HALF_DUPLEX) |
+	BIT(TRIGGER_NETDEV_LINK)        |
+	BIT(TRIGGER_NETDEV_LINK_10)     |
+	BIT(TRIGGER_NETDEV_LINK_100)    |
+	BIT(TRIGGER_NETDEV_LINK_1000)   |
+	BIT(TRIGGER_NETDEV_RX)          |
+	BIT(TRIGGER_NETDEV_TX);
+
+int mt798x_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+				   unsigned long rules)
+{
+	return mtk_phy_led_hw_is_supported(phydev, index, rules,
+					   supported_triggers);
+}
+EXPORT_SYMBOL_GPL(mt798x_phy_led_hw_is_supported);
+
+int mt798x_phy_led_hw_control_get(struct phy_device *phydev, u8 index,
+				  unsigned long *rules)
+{
+	return mtk_phy_led_hw_ctrl_get(phydev, index, rules,
+				       MTK_GPHY_LED_ON_SET,
+				       MTK_GPHY_LED_RX_BLINK_SET,
+				       MTK_GPHY_LED_TX_BLINK_SET);
+};
+EXPORT_SYMBOL_GPL(mt798x_phy_led_hw_control_get);
+
+int mt798x_phy_led_hw_control_set(struct phy_device *phydev, u8 index,
+				  unsigned long rules)
+{
+	return mtk_phy_led_hw_ctrl_set(phydev, index, rules,
+				       MTK_GPHY_LED_ON_SET,
+				       MTK_GPHY_LED_RX_BLINK_SET,
+				       MTK_GPHY_LED_TX_BLINK_SET);
+};
+EXPORT_SYMBOL_GPL(mt798x_phy_led_hw_control_set);
+
 MODULE_DESCRIPTION("MediaTek Ethernet PHY driver common");
 MODULE_AUTHOR("Sky Huang <SkyLake.Huang@mediatek.com>");
 MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
diff --git a/drivers/net/phy/mediatek/mtk.h b/drivers/net/phy/mediatek/mtk.h
index f172f7d671c9..c690aaf38124 100644
--- a/drivers/net/phy/mediatek/mtk.h
+++ b/drivers/net/phy/mediatek/mtk.h
@@ -103,4 +103,16 @@ int mtk_phy_hw_led_blink_set(struct phy_device *phydev, u8 index,
 			     bool blinking);
 void mtk_phy_leds_state_init(struct phy_device *phydev);
 
+int mt798x_phy_led_blink_set(struct phy_device *phydev, u8 index,
+			     unsigned long *delay_on,
+			     unsigned long *delay_off);
+int mt798x_phy_led_brightness_set(struct phy_device *phydev,
+				  u8 index, enum led_brightness value);
+int mt798x_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+				   unsigned long rules);
+int mt798x_phy_led_hw_control_get(struct phy_device *phydev, u8 index,
+				  unsigned long *rules);
+int mt798x_phy_led_hw_control_set(struct phy_device *phydev, u8 index,
+				  unsigned long rules);
+
 #endif /* _MTK_EPHY_H_ */
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next 4/5] net: phy: mediatek: add calibration logic for AN7581
  2026-07-08 10:23 [PATCH net-next 0/5] net: phy: mediatek: calibration for AN7581/3 Christian Marangi
                   ` (2 preceding siblings ...)
  2026-07-08 10:23 ` [PATCH net-next 3/5] net: phy: mediatek: split Airoha code to dedicated source Christian Marangi
@ 2026-07-08 10:23 ` Christian Marangi
  2026-07-08 16:30   ` Wayen Yan
  2026-07-08 10:23 ` [PATCH net-next 5/5] net: phy: mediatek: add calibration logic for AN7583 Christian Marangi
  4 siblings, 1 reply; 11+ messages in thread
From: Christian Marangi @ 2026-07-08 10:23 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	Daniel Golle, Qingfang Deng, SkyLake Huang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-kernel, netdev,
	linux-arm-kernel, linux-mediatek

Airoha AN7581 require dedicated calibration and specific tuneup to make
the Ethernet port work on specific HW PCB layout.

The calibration process is similar to Mediatek PHY but Airoha PHY use
Software calibration process instead of making use of EFUSE.

It does also require some complex table for TX Amp compensation and
multiple register settings for EEE and 10/100M to correctly work.
Also the PHY apply a different TX Amp compensation table based on the
SoC PDIDR version. Such version info is stored in the SCU SoC registers.

Add module version as suggested by Airoha to identify the calibration logic
with a specific version.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 drivers/net/phy/mediatek/airoha-ge-soc.c | 1489 ++++++++++++++++++++++
 drivers/net/phy/mediatek/mtk-ge-soc.h    |   13 +
 2 files changed, 1502 insertions(+)

diff --git a/drivers/net/phy/mediatek/airoha-ge-soc.c b/drivers/net/phy/mediatek/airoha-ge-soc.c
index 429594f27792..bc487b093cfa 100644
--- a/drivers/net/phy/mediatek/airoha-ge-soc.c
+++ b/drivers/net/phy/mediatek/airoha-ge-soc.c
@@ -1,16 +1,1493 @@
 // SPDX-License-Identifier: GPL-2.0+
+#include <linux/bitfield.h>
 #include <linux/pinctrl/consumer.h>
 #include <linux/phy.h>
 
+#include "../phylib.h"
 #include "mtk.h"
 #include "mtk-ge-soc.h"
 
+#define AIROHA_DEFAULT_PORT0_ADDR		0x9
 #define AIROHA_PHY_MAX_LEDS			2
 
+enum airoha_mdi_resister_type {
+	MDI_0R,
+	MDI_5R,
+
+	MDI_TYPE_MAX,
+};
+
+enum airoha_transformer_type {
+	TXMR,
+	DISCRETE,
+
+	TRANSFORMER_TYPE_MAX,
+};
+
+enum airoha_calib_const_type {
+	TX_AMP_TEST_A,
+	TX_AMP_TEST_B,
+	TX_AMP_TEST_C,
+	TX_AMP_TEST_D,
+	TX_AMP_1G_A,
+	TX_AMP_1G_B,
+	TX_AMP_1G_C,
+	TX_AMP_1G_D,
+	TX_AMP_100M_A,
+	TX_AMP_100M_B,
+	TX_AMP_10M_A,
+	TX_AMP_10M_B,
+	R50_A,
+	R50_B,
+	R50_C,
+	R50_D,
+
+	CALIB_CONST_TYPE_MAX,
+};
+
+struct airoha_socphy_shared {
+	struct phy_device *phydev_p0;
+	enum airoha_transformer_type transformer_type[4];
+	enum airoha_mdi_resister_type mdi_resister_type;
+	bool rext_sw_calib_done;
+	int (*tx_amp_compensation_tbl)[TRANSFORMER_TYPE_MAX][MDI_TYPE_MAX][CALIB_CONST_TYPE_MAX][4];
+	u8 *r50_cal_tbl;
+};
+
+/*
+ * 2 chip revision
+ * TXMR or discrete
+ * 2 MDI type
+ * TX AMP test/TX AMP 1G/TX AMP 100M/TX AMP 10M/R50
+ * 4 PHY
+ */
+static int an7581_tx_amp_compensation_tbl[2][TRANSFORMER_TYPE_MAX][MDI_TYPE_MAX][CALIB_CONST_TYPE_MAX][4] = {
+	{ /* IC version 1 */
+		[TXMR] = {
+			{ },
+			{
+				[TX_AMP_TEST_A] = { -12, -11, -9, -6 },
+				[TX_AMP_TEST_B] = { -9, -13, -9, -5 },
+				[TX_AMP_TEST_C] = { -5, -4, -3, 1 },
+				[TX_AMP_TEST_D] = { -9, -8, -7, -5 },
+				[TX_AMP_1G_A] = { -12, -11, -9, -6 },
+				[TX_AMP_1G_B] = { -9, -13, -9, -5 },
+				[TX_AMP_1G_C] = { -5, -4, -3, 1 },
+				[TX_AMP_1G_D] = { -9, -8, -7, -5 },
+				[TX_AMP_100M_A] = { -2, -2, 0, 2 },
+				[TX_AMP_100M_B] = { -2, -2, 1, 1 },
+				[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+				[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+				[R50_A] = { 15, 15, 15, 15 },
+				[R50_B] = { 15, 15, 18, 15 },
+				[R50_C] = { -8, -8, -8, -8 },
+				[R50_D] = { -8, -8, -8, -8 },
+			},
+		},
+		[DISCRETE] = {
+			{ },
+			{
+				[TX_AMP_TEST_A] = { -9, -7, -7, -5 },
+				[TX_AMP_TEST_B] = { -9, -9, -7, -4 },
+				[TX_AMP_TEST_C] = { -9, -7, -9, -6 },
+				[TX_AMP_TEST_D] = { -11, -9, -10, -8 },
+				[TX_AMP_1G_A] = { -9, -7, -7, -5 },
+				[TX_AMP_1G_B] = { -9, -8, -7, -4 },
+				[TX_AMP_1G_C] = { -9, -7, -9, -6 },
+				[TX_AMP_1G_D] = { -11, -8, -10, -8 },
+				[TX_AMP_100M_A] = { 4, 3, 4, 4 },
+				[TX_AMP_100M_B] = { 4, 4, 5, 4 },
+				[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+				[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+				[R50_A] = { 0, 0, 0, 0 },
+				[R50_B] = { 0, 0, 0, 0 },
+				[R50_C] = { 0, 0, 0, 0 },
+				[R50_D] = { 0, 0, 0, 0 },
+			},
+		},
+	},
+	{ /* IC version 2 */
+		[TXMR] = {
+			{ },
+			{
+				[TX_AMP_TEST_A] = { -10, -12, -12, -11 },
+				[TX_AMP_TEST_B] = { -10, -11, -11, -10 },
+				[TX_AMP_TEST_C] = { -5, -7, -9, -3 },
+				[TX_AMP_TEST_D] = { -8, -10, -7, -8 },
+				[TX_AMP_1G_A] = { -12, -6, -6, 4 },
+				[TX_AMP_1G_B] = { -10, -5, -5, 1 },
+				[TX_AMP_1G_C] = { -5, -3, -4, 11 },
+				[TX_AMP_1G_D] = { -8, -3, -3, 4 },
+				[TX_AMP_100M_A] = { 2, 1, 0, 3 },
+				[TX_AMP_100M_B] = { 0, 0, 0, 3 },
+				[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+				[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+				[R50_A] = { 15, 15, 15, 15 },
+				[R50_B] = { 15, 15, 18, 15 },
+				[R50_C] = { 4, 4, 4, 4 },
+				[R50_D] = { 4, 4, 4, 4 },
+			},
+		},
+		[DISCRETE] = {
+			{ },
+			{
+				[TX_AMP_TEST_A] = { -11, 11, -12, -7 },
+				[TX_AMP_TEST_B] = { -9, -10, -9, -8 },
+				[TX_AMP_TEST_C] = { -6, -3, -7, -4 },
+				[TX_AMP_TEST_D] = { -7, -8, -10, -7 },
+				[TX_AMP_1G_A] = { -11, -11, -12, -7 },
+				[TX_AMP_1G_B] = { -9, -10, -9, -8 },
+				[TX_AMP_1G_C] = { -6, -3, -7, -4 },
+				[TX_AMP_1G_D] = { -7, -8, -10, -7 },
+				[TX_AMP_100M_A] = { 2, 2, 1, 2 },
+				[TX_AMP_100M_B] = { 3, 3, 3, 3 },
+				[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+				[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+				[R50_A] = { 12, 12, 12, 12 },
+				[R50_B] = { 7, 7, 7, 7 },
+				[R50_C] = { 2, 2, 0, 0 },
+				[R50_D] = { 0, 0, 2, 2 },
+			},
+		},
+	},
+};
+
+static u8 an7581_zcal_to_r45ohm[64] = {
+	127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+	127, 127, 127, 127, 127, 127, 127, 127, 123, 119, 115, 112, 108, 104, 100,  96,
+	 94,  92,  88,  85,  82,  80,  76,  74,  72,  68,  66,  64,  62,  60,  56,  55,
+	 52,  50,  48,  46,  44,  42,  40,  39,  36,  35,  32,  32,  30,  28,  27,  25
+};
+
+static int airoha_cal_cycle(struct phy_device *phydev, int devad,
+			    u32 regnum, u16 mask, u16 cal_val)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	int ret;
+
+	phydev_p0 = shared->phydev_p0;
+
+	phy_modify_mmd(phydev, devad, regnum, mask, cal_val);
+
+	ret = mtk_cal_cycle_wait(phydev_p0);
+	phydev_dbg(phydev, "cal_val: 0x%x, ret: %d\n", cal_val, ret);
+
+	return ret;
+}
+
+static int airoha_rext_cal_sw(struct phy_device *phydev)
+{
+	int calibration_polarity;
+	u8 zcal_ctrl = 32;
+	int first_calib;
+	int ret;
+
+	/* BG voltage output */
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x100, 0xc000);
+	/* tst_mode2 */
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0xff, 0x2);
+
+	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, 0xff,
+			   GENMASK(15, 4) | GENMASK(1, 0));
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+		      MTK_PHY_RG_CAL_CKINV | MTK_PHY_RG_ANA_CALEN |
+		      MTK_PHY_RG_REXT_CALEN);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0);
+
+	phydev_dbg(phydev, "Start REXT SW cal.\n");
+	first_calib = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
+				       MTK_PHY_RG_REXT_ZCAL_CTRL_MASK, zcal_ctrl);
+
+	if (first_calib < 0) {
+		phydev_err(phydev, "REXT SW calibration failed.\n");
+		return -EINVAL;
+	}
+
+	/* If REXT calibration failed:
+	 * - increase dB until calibration succeed.
+	 * If REXT calibration succeeded:
+	 * - decrease dB until calibration fail to fine tune it.
+	 */
+	if (first_calib == 1)
+		calibration_polarity = -1;
+	else
+		calibration_polarity = 1;
+
+	while (zcal_ctrl > 0 &&
+	       zcal_ctrl < FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) {
+		zcal_ctrl += calibration_polarity;
+
+		ret = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
+				       MTK_PHY_RG_REXT_ZCAL_CTRL_MASK, zcal_ctrl);
+		/* Exit if we either failed or succeeded compared to the
+		 * first calibration result. (aka we finished fine tuning or
+		 * we succeeded with calibration)
+		 */
+		if (ret != first_calib)
+			break;
+	}
+
+	if (ret < 0) {
+		phydev_err(phydev, "REXT SW calibration failed.\n");
+		return -EINVAL;
+	}
+
+	if (zcal_ctrl == 0 ||
+	    zcal_ctrl == FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) {
+		zcal_ctrl = 32;
+		phydev_err(phydev, "REXT SW calibration saturation. Defaulting to %x.\n",
+			   zcal_ctrl);
+	}
+
+	phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
+		       MTK_PHY_RG_REXT_TRIM_MASK,
+		       FIELD_PREP(MTK_PHY_RG_REXT_TRIM_MASK, zcal_ctrl));
+	phy_modify_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_RG_BG_RASEL,
+		       MTK_PHY_RG_BG_RASEL_MASK,
+		       FIELD_PREP(MTK_PHY_RG_BG_RASEL_MASK, zcal_ctrl >> 3));
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+
+	return 0;
+}
+
+static int airoha_tx_offset_cal_sw(struct phy_device *phydev, u8 txg_calen_x)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	u16 dev1e_145_tmp, bmcr_tmp;
+	int calibration_polarity;
+	u16 reg_dac1, reg_dac2;
+	int zcal_ctrl = 0;
+	int first_calib;
+	u16 reg, mask;
+	int ret;
+
+	phydev_p0 = shared->phydev_p0;
+
+	/* BG voltage output */
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0xc000);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+		      MTK_PHY_RG_ANA_CALEN);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
+		      MTK_PHY_RG_TXVOS_CALEN);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+		      MTK_PHY_RG_ANA_CALEN);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
+		      MTK_PHY_RG_TXVOS_CALEN);
+
+	/* Force 1G full duplex for calibration */
+	bmcr_tmp = phy_read(phydev, MII_BMCR);
+	phy_write(phydev, MII_BMCR, BMCR_FULLDPLX | BMCR_SPEED1000);
+
+	/* Force MDI */
+	dev1e_145_tmp = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x0145);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0145, 0x1010);
+
+	/* 1e_96[15]:bypass_tx_offset_cal, Hw bypass, Fw cal */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x96,
+		      0x8000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e,
+		      0xf808);
+
+	switch (txg_calen_x) {
+	case PAIR_A:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(12));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_A;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_A;
+		reg = MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B;
+		mask = MTK_PHY_CR_TX_AMP_OFFSET_A_MASK;
+		break;
+	case PAIR_B:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(8));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_B;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_B;
+		reg = MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B;
+		mask = MTK_PHY_CR_TX_AMP_OFFSET_B_MASK;
+		break;
+	case PAIR_C:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(4));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_C;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_C;
+		reg = MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D;
+		mask = MTK_PHY_CR_TX_AMP_OFFSET_C_MASK;
+		break;
+	case PAIR_D:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(0));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_D;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_D;
+		reg = MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D;
+		mask = MTK_PHY_CR_TX_AMP_OFFSET_D_MASK;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, reg_dac1,
+		      0x8000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, reg_dac2,
+		      0x8000);
+
+	phydev_dbg(phydev, "Start TX Offset SW cal.\n");
+
+	first_calib = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, reg, mask,
+				       zcal_ctrl << __ffs(mask));
+
+	/* If TX Offset calibration failed:
+	 * - increase dB until calibration succeed.
+	 * If TX Offset calibration succeeded:
+	 * - decrease dB until calibration fail to fine tune it.
+	 */
+	if (first_calib == 1)
+		calibration_polarity = -1;
+	else
+		calibration_polarity = 1;
+
+	while (zcal_ctrl > -32 && zcal_ctrl < 32) {
+		u32 val;
+
+		zcal_ctrl += calibration_polarity;
+		if (zcal_ctrl >= 0)
+			val = zcal_ctrl;
+		else
+			/* BIT(5) signal negative number for TX Offset */
+			val = BIT(5) | abs(zcal_ctrl);
+
+		ret = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, reg, mask,
+				       val << __ffs(mask));
+		/* Exit if we either failed or succeeded compared to the
+		 * first calibration result. (aka we finished fine tuning or
+		 * we succeeded with calibration)
+		 */
+		if (ret != first_calib)
+			break;
+	}
+
+	if (ret < 0) {
+		phydev_err(phydev, "TX Offset calibration failed.\n");
+		return -EINVAL;
+	}
+
+	if (zcal_ctrl == -32 ||
+	    zcal_ctrl == 32) {
+		zcal_ctrl = 0;
+		phydev_err(phydev, "TX Offset SW calibration saturation. Defaulting to %x.\n",
+			   zcal_ctrl);
+
+		phy_modify_mmd(phydev, MDIO_MMD_VEND1, reg, mask,
+			       zcal_ctrl << __ffs(mask));
+	}
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x100, 0x0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D, 0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x96, 0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xc000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, 0);
+
+	/* Restore BMCR */
+	phy_write(phydev, MII_BMCR, bmcr_tmp);
+
+	/* Restore MDI */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0145, dev1e_145_tmp);
+
+	return 0;
+}
+
+static int airoha_tx_amp_cal_sw(struct phy_device *phydev, u8 txg_calen_x)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	u16 mask_gbe, mask_tbt, mask_tst, mask_hbt;
+	u16 reg, reg_100, reg_dac1, reg_dac2;
+	struct phy_device *phydev_p0;
+	int calibration_polarity;
+	u8 zcal_ctrl = 32;
+	int first_calib;
+	int ret;
+
+	phydev_p0 = shared->phydev_p0;
+
+	/* BG voltage output */
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0xc000);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x145, 0x1010);
+
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+		      MTK_PHY_RG_CAL_CKINV | MTK_PHY_RG_ANA_CALEN);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
+		      MTK_PHY_RG_TXVOS_CALEN);
+
+	/* select 1V */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x10);
+
+	/* enable Tx VLD*/
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xf808);
+
+	/* Force 1G full duplex for calibration */
+	phy_write(phydev, MII_BMCR, BMCR_FULLDPLX | BMCR_SPEED1000);
+
+	switch (txg_calen_x) {
+	case PAIR_A:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(12));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_A;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_A;
+		reg = MTK_PHY_TXVLD_DA_RG;
+		mask_gbe = MTK_PHY_DA_TX_I2MPB_A_GBE_MASK;
+		mask_tbt = MTK_PHY_DA_TX_I2MPB_A_TBT_MASK;
+		reg_100 = MTK_PHY_TX_I2MPB_TEST_MODE_A2;
+		mask_hbt = MTK_PHY_DA_TX_I2MPB_A_HBT_MASK;
+		mask_tst = MTK_PHY_DA_TX_I2MPB_A_TST_MASK;
+		break;
+	case PAIR_B:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(8));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_B;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_B;
+		reg = MTK_PHY_TX_I2MPB_TEST_MODE_B1;
+		mask_gbe = MTK_PHY_DA_TX_I2MPB_B_GBE_MASK;
+		mask_tbt = MTK_PHY_DA_TX_I2MPB_B_TBT_MASK;
+		reg_100 = MTK_PHY_TX_I2MPB_TEST_MODE_B2;
+		mask_hbt = MTK_PHY_DA_TX_I2MPB_B_HBT_MASK;
+		mask_tst = MTK_PHY_DA_TX_I2MPB_B_TST_MASK;
+		break;
+	case PAIR_C:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(4));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_C;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_C;
+		reg = MTK_PHY_TX_I2MPB_TEST_MODE_C1;
+		mask_gbe = MTK_PHY_DA_TX_I2MPB_C_GBE_MASK;
+		mask_tbt = MTK_PHY_DA_TX_I2MPB_C_TBT_MASK;
+		reg_100 = MTK_PHY_TX_I2MPB_TEST_MODE_C2;
+		mask_hbt = MTK_PHY_DA_TX_I2MPB_C_HBT_MASK;
+		mask_tst = MTK_PHY_DA_TX_I2MPB_C_TST_MASK;
+		break;
+	case PAIR_D:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(0));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_D;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_D;
+		reg = MTK_PHY_TX_I2MPB_TEST_MODE_D1;
+		mask_gbe = MTK_PHY_DA_TX_I2MPB_D_GBE_MASK;
+		mask_tbt = MTK_PHY_DA_TX_I2MPB_D_TBT_MASK;
+		reg_100 = MTK_PHY_TX_I2MPB_TEST_MODE_D2;
+		mask_hbt = MTK_PHY_DA_TX_I2MPB_D_HBT_MASK;
+		mask_tst = MTK_PHY_DA_TX_I2MPB_D_TST_MASK;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, reg_dac1,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, reg_dac2,
+		      0x8000 | 0xf0);
+
+	phydev_dbg(phydev, "Start TX Amp SW cal.\n");
+	first_calib = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, reg,
+				       mask_gbe, zcal_ctrl << __ffs(mask_gbe));
+
+	/* If TX Amp calibration failed:
+	 * - increase dB until calibration succeed.
+	 * If TX Amp calibration succeeded:
+	 * - decrease dB until calibration fail to fine tune it.
+	 */
+	if (first_calib == 1)
+		calibration_polarity = -1;
+	else
+		calibration_polarity = 1;
+
+	while (zcal_ctrl > 0 &&
+	       zcal_ctrl < FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) {
+		zcal_ctrl += calibration_polarity;
+
+		ret = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, reg,
+				       mask_gbe, zcal_ctrl << __ffs(mask_gbe));
+		/* Exit if we either failed or succeeded compared to the
+		 * first calibration result. (aka we finished fine tuning or
+		 * we succeeded with calibration)
+		 */
+		if (ret != first_calib)
+			break;
+	}
+
+	if (ret < 0) {
+		phydev_err(phydev, "TX AMP calibration failed.\n");
+		phy_modify_mmd(phydev, MDIO_MMD_VEND1, reg,
+			       mask_gbe | mask_tbt,
+			       (zcal_ctrl << __ffs(mask_gbe)) |
+			       (zcal_ctrl << __ffs(mask_tbt)));
+
+		return -EINVAL;
+	}
+
+	if (zcal_ctrl == 0 ||
+	    zcal_ctrl == FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK))
+		phydev_warn(phydev, "TX AMP SW calibration saturation.\n");
+
+	phy_modify_mmd(phydev, MDIO_MMD_VEND1, reg,
+		       mask_gbe | mask_tbt,
+		       (zcal_ctrl << __ffs(mask_gbe)) |
+		       (zcal_ctrl << __ffs(mask_tbt)));
+
+	phy_modify_mmd(phydev, MDIO_MMD_VEND1, reg_100,
+		       mask_hbt | mask_tst,
+		       (zcal_ctrl << __ffs(mask_hbt)) |
+		       (zcal_ctrl << __ffs(mask_tst)));
+
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0x0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, 0x145, 0x1000);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xc000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, 0);
+
+	/* Restore AN enable */
+	phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
+
+	return 0;
+}
+
+static int airoha_tx_r50_cal_sw(struct phy_device *phydev, u8 txg_calen_x)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	u16 dev1e_145_tmp, bmcr_tmp;
+	int calibration_polarity;
+	u8 zcal_ctrl = 32;
+	int first_calib;
+	u16 reg;
+	int ret;
+
+	phydev_p0 = shared->phydev_p0;
+
+	/* BG voltage output */
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0xc000);
+
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+		      MTK_PHY_RG_CAL_CKINV | MTK_PHY_RG_ANA_CALEN);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	/* select 1V */
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x10);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+		      MTK_PHY_RG_CAL_CKINV | MTK_PHY_RG_ANA_CALEN);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	/* select 1V */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x10);
+
+	/* Force 1G full duplex for calibration */
+	bmcr_tmp = phy_read(phydev, MII_BMCR);
+	phy_write(phydev, MII_BMCR, BMCR_FULLDPLX | BMCR_SPEED1000);
+
+	/* Force MDI */
+	dev1e_145_tmp = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x0145);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0145, 0x1010);
+
+	/* disable tx slew control */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0185, 0x0000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00fb, 0x0100);
+
+	switch (txg_calen_x) {
+	case PAIR_A:
+		phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+				 MTK_PHY_RG_ANA_CAL_RG0,
+				 MTK_PHY_RG_ZCALEN_A);
+		break;
+	case PAIR_B:
+		phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+				 MTK_PHY_RG_ANA_CAL_RG1,
+				 MTK_PHY_RG_ZCALEN_B);
+		break;
+	case PAIR_C:
+		phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+				 MTK_PHY_RG_ANA_CAL_RG1,
+				 MTK_PHY_RG_ZCALEN_C);
+		break;
+	case PAIR_D:
+		phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+				 MTK_PHY_RG_ANA_CAL_RG1,
+				 MTK_PHY_RG_ZCALEN_D);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	phydev_dbg(phydev, "Start TX r50 SW cal.\n");
+	first_calib = airoha_cal_cycle(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
+				       MTK_PHY_RG_REXT_ZCAL_CTRL_MASK, zcal_ctrl);
+
+	if (first_calib < 0) {
+		phydev_err(phydev, "TX r50 SW calibration failed.\n");
+		return -EINVAL;
+	}
+
+	/* If TX r50 calibration failed:
+	 * - increase dB until calibration succeed.
+	 * If TX r50 calibration succeeded:
+	 * - decrease dB until calibration fail to fine tune it.
+	 */
+	if (first_calib == 1)
+		calibration_polarity = -1;
+	else
+		calibration_polarity = 1;
+
+	while (zcal_ctrl > 0 &&
+	       zcal_ctrl < FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) {
+		zcal_ctrl += calibration_polarity;
+
+		ret = airoha_cal_cycle(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
+				       MTK_PHY_RG_REXT_ZCAL_CTRL_MASK, zcal_ctrl);
+		/* Exit if we either failed or succeeded compared to the
+		 * first calibration result. (aka we finished fine tuning or
+		 * we succeeded with calibration)
+		 */
+		if (ret != first_calib)
+			break;
+	}
+
+	if (ret < 0) {
+		phydev_err(phydev, "TX r50 SW calibration failed.\n");
+		return -EINVAL;
+	}
+
+	if (zcal_ctrl == 0 ||
+	    zcal_ctrl == FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK))
+		phydev_warn(phydev, "TX r50 SW calibration saturation.\n");
+
+	zcal_ctrl = shared->r50_cal_tbl[zcal_ctrl];
+
+	switch (txg_calen_x) {
+	case PAIR_A:
+		reg = BIT(7) | FIELD_PREP(MTK_PHY_CR_TX_R50_OFFSET_A_MASK, zcal_ctrl);
+		phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+			       MTK_PHY_RG_CR_TX_R50_OFFSET_A_B,
+			       BIT(7) | MTK_PHY_CR_TX_R50_OFFSET_A_MASK, reg);
+		break;
+	case PAIR_B:
+		reg = BIT(7) | FIELD_PREP(MTK_PHY_CR_TX_R50_OFFSET_B_MASK, zcal_ctrl);
+		phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+			       MTK_PHY_RG_CR_TX_R50_OFFSET_A_B,
+			       BIT(7) | MTK_PHY_CR_TX_R50_OFFSET_B_MASK, reg);
+		break;
+	case PAIR_C:
+		reg = BIT(7) | FIELD_PREP(MTK_PHY_CR_TX_R50_OFFSET_C_MASK, zcal_ctrl);
+		phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+			       MTK_PHY_RG_CR_TX_R50_OFFSET_C_D,
+			       BIT(7) | MTK_PHY_CR_TX_R50_OFFSET_C_MASK, reg);
+		break;
+	case PAIR_D:
+		reg = BIT(7) | FIELD_PREP(MTK_PHY_CR_TX_R50_OFFSET_D_MASK, zcal_ctrl);
+		phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+			       MTK_PHY_RG_CR_TX_R50_OFFSET_C_D,
+			       BIT(7) | MTK_PHY_CR_TX_R50_OFFSET_D_MASK, reg);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+
+	/* Enable tx slew control */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0185, 0x0001);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00fb, 0x0000);
+
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0x0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x0);
+
+	/* Restore BMCR */
+	phy_write(phydev, MII_BMCR, bmcr_tmp);
+
+	/* Restore MDI */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0145, dev1e_145_tmp);
+
+	return 0;
+}
+
+static int airoha_rx_offset_cal_sw(struct phy_device *phydev)
+{
+	/* Hw bypass, Fw cal */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x96, 0x8000);
+	/* tx/rx_cal_criteria_value */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x37, 0x0033);
+
+	/* rx offset cal by Hw setup */
+	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0x39,
+			   BIT(14) | BIT(11));
+
+	/* disable rtune calibration */
+	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, 0x107,
+			   BIT(12));
+
+	/* bypass tx/rx dc offset cancellation process */
+	phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, 0x171,
+			 BIT(8) | BIT(7));
+
+	/* rx offset calibration start */
+	phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, 0x39,
+			 BIT(13));
+
+	/* rx offset calibration stop */
+	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0x39,
+			   BIT(13));
+
+	mdelay(10);
+
+	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0x171,
+			   BIT(8) | BIT(7));
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6,
+		      0x1);
+
+	return 0;
+}
+
+static int airoha_cal_sw(struct phy_device *phydev, enum CAL_ITEM cal_item,
+			 u8 start_pair, u8 end_pair)
+{
+	u8 pair_n;
+	int ret;
+
+	for (pair_n = start_pair; pair_n <= end_pair; pair_n++) {
+		switch (cal_item) {
+		case REXT:
+			ret = airoha_rext_cal_sw(phydev);
+			break;
+		case TX_OFFSET:
+			ret = airoha_tx_offset_cal_sw(phydev, pair_n);
+			break;
+		case TX_AMP:
+			ret = airoha_tx_amp_cal_sw(phydev, pair_n);
+			break;
+		case TX_R50:
+			ret = airoha_tx_r50_cal_sw(phydev, pair_n);
+			break;
+		case RX_OFFSET:
+			ret = airoha_rx_offset_cal_sw(phydev);
+			break;
+		default:
+			return -EINVAL;
+		}
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int airoha_start_cal(struct phy_device *phydev, enum CAL_ITEM cal_item,
+			    enum CAL_MODE cal_mode, u8 start_pair,
+			    u8 end_pair, u32 *buf)
+{
+	int ret;
+
+	switch (cal_mode) {
+	case SW_M:
+		ret = airoha_cal_sw(phydev, cal_item, start_pair, end_pair);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (ret) {
+		phydev_err(phydev, "cal %d failed\n", cal_item);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int airoha_phy_calib(struct phy_device *phydev)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	int ret;
+
+	phydev_p0 = shared->phydev_p0;
+
+	/* PreCalibrate Set */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x5c, 0x6666);
+
+	if (!shared->rext_sw_calib_done) {
+		ret = airoha_start_cal(phydev, REXT, SW_M, NO_PAIR, NO_PAIR, NULL);
+		if (ret)
+			return ret;
+
+		shared->rext_sw_calib_done = true;
+	}
+
+	ret = airoha_start_cal(phydev, TX_R50, SW_M, PAIR_A, PAIR_D, NULL);
+	if (ret)
+		return ret;
+
+	ret = airoha_start_cal(phydev, TX_OFFSET, SW_M, PAIR_A, PAIR_D, NULL);
+	if (ret)
+		return ret;
+
+	ret = airoha_start_cal(phydev, TX_AMP, SW_M, PAIR_A, PAIR_D, NULL);
+	if (ret)
+		return ret;
+
+	ret = airoha_start_cal(phydev, RX_OFFSET, SW_M, NO_PAIR, NO_PAIR, NULL);
+	if (ret)
+		return ret;
+
+	/* Gating, short with other pair */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x15, 0x0);
+
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0x0);
+
+	return 0;
+}
+
+static int airoha_phy_auto_select_transformer(struct phy_device *phydev)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	u8 phy_offset;
+	u16 bmcr_tmp;
+	u16 val;
+	int i;
+
+	phydev_p0 = shared->phydev_p0;
+	phy_offset = phydev->mdio.addr - phydev_p0->mdio.addr;
+
+	/* Force 1G full duplex for calibration */
+	bmcr_tmp = phy_read(phydev, MII_BMCR);
+	phy_write(phydev, MII_BMCR, BMCR_FULLDPLX | BMCR_SPEED1000);
+
+	phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0x271, GENMASK(4, 0),
+		       FIELD_PREP(GENMASK(4, 0), 0x3));
+
+	phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0x269, GENMASK(15, 12),
+		       FIELD_PREP(GENMASK(15, 12), 0x2));
+
+	phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0x26f, GENMASK(14, 12),
+		       FIELD_PREP(GENMASK(14, 12), 0x0));
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(12));
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D,
+		      0x8000 | 0xf0);
+
+	/* Force pGA gain */
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+	__mtk_tr_modify(phydev, 0x1, 0xf, 0x10,
+			GENMASK(20, 15),
+			FIELD_PREP(GENMASK(20, 15), 0x1f));
+
+	__mtk_tr_modify(phydev, 0x1, 0xf, 0x11,
+			GENMASK(23, 18) | GENMASK(15, 10) | GENMASK(7, 2),
+			FIELD_PREP(GENMASK(23, 18), 0x1f) |
+			FIELD_PREP(GENMASK(15, 10), 0x1f) |
+			FIELD_PREP(GENMASK(7, 2), 0x1f));
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xc9, 0xffff);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x151, 0x12);
+
+	switch (phy_offset) {
+	case 0:
+		val = 0x1;
+		break;
+	case 1:
+		val = 0x3;
+		break;
+	case 2:
+		val = 0x5;
+		break;
+	case 3:
+		val = 0x7;
+		break;
+	default:
+		return -EINVAL;
+	}
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x15,
+		      FIELD_PREP(GENMASK(14, 12), val) | 0x161);
+	mdelay(500);
+
+	for (i = 0; i < 3; i++)
+		val = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0x1a);
+
+	/* Check the value and report transformer type for later */
+	shared->transformer_type[phy_offset] = val < 0xe0;
+
+	/* Switch to Default */
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x15, 0x1000);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, 0x0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D,
+		      0x0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xc9, 0xfff);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x151, 0x0);
+
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+	__mtk_tr_modify(phydev, 0x1, 0xf, 0x10,
+			GENMASK(20, 15),
+			FIELD_PREP(GENMASK(20, 15), 0x4));
+
+	__mtk_tr_modify(phydev, 0x1, 0xf, 0x11,
+			GENMASK(23, 18) | GENMASK(15, 10) | GENMASK(7, 2),
+			FIELD_PREP(GENMASK(23, 18), 0x0) |
+			FIELD_PREP(GENMASK(15, 10), 0x0) |
+			FIELD_PREP(GENMASK(7, 2), 0x0));
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	/* Restore BMCR */
+	phy_write(phydev, MII_BMCR, bmcr_tmp);
+
+	return 0;
+}
+
+static int airoha_phy_tx_amp_compensation(struct phy_device *phydev)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+
+	u16 reg_1e_12_read;
+	u16 reg_1e_16_read;
+	u16 reg_1e_17_read;
+	u16 reg_1e_18_read;
+	u16 reg_1e_19_read;
+	u16 reg_1e_20_read;
+	u16 reg_1e_21_read;
+	u16 reg_1e_22_read;
+	u16 reg_1e_174_read;
+	u16 reg_1e_175_read;
+
+	int reg_1e_12_header;
+	int reg_1e_16_header;
+	int reg_1e_17_header;
+	int reg_1e_18_header;
+	int reg_1e_19_header;
+	int reg_1e_21_header;
+	int reg_1e_174_header;
+	int reg_1e_175_header;
+
+	int reg_1e_12_end;
+	int reg_1e_16_end;
+	int reg_1e_17_end;
+	int reg_1e_18_end;
+	int reg_1e_20_end;
+	int reg_1e_22_end;
+	int reg_1e_174_end;
+	int reg_1e_175_end;
+
+	struct phy_device *phydev_p0;
+	u8 phy_offset;
+
+	int txamp_low_limit = -3;
+	int txamp_high_limit = 0x42;
+
+	int r50_low_limit = -8;
+	int r50_high_limit = 0x88;
+
+	bool overflow;
+	int (*tx_amp_table)[CALIB_CONST_TYPE_MAX][4];
+	int transformer_type, mdi_resister_type;
+
+	phydev_p0 = shared->phydev_p0;
+	phy_offset = phydev->mdio.addr - phydev_p0->mdio.addr;
+
+	transformer_type = shared->transformer_type[phy_offset];
+	mdi_resister_type = shared->mdi_resister_type;
+	tx_amp_table = shared->tx_amp_compensation_tbl[transformer_type][mdi_resister_type];
+
+	reg_1e_12_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x12);
+	reg_1e_16_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x16);
+	reg_1e_17_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x17);
+	reg_1e_18_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x18);
+	reg_1e_19_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x19);
+	reg_1e_20_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x20);
+	reg_1e_21_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x21);
+	reg_1e_22_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x22);
+	reg_1e_174_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x174);
+	reg_1e_175_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x175);
+
+	reg_1e_12_header = ((reg_1e_12_read & 0xfc00) / 1024) + *tx_amp_table[TX_AMP_1G_A][phy_offset];
+	reg_1e_17_header = ((reg_1e_17_read & 0x3f00) / 256) + *tx_amp_table[TX_AMP_1G_B][phy_offset];
+	reg_1e_19_header = ((reg_1e_19_read & 0x3f00) / 256) + *tx_amp_table[TX_AMP_1G_C][phy_offset];
+	reg_1e_21_header = ((reg_1e_21_read & 0x3f00) / 256) + *tx_amp_table[TX_AMP_1G_D][phy_offset];
+
+	overflow = false;
+	if (reg_1e_12_header < txamp_low_limit ||
+	    reg_1e_12_header > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_17_header < txamp_low_limit ||
+	    reg_1e_17_header > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_19_header < txamp_low_limit ||
+	    reg_1e_19_header > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_21_header < txamp_low_limit ||
+	    reg_1e_21_header > txamp_high_limit)
+		overflow = true;
+
+	if (overflow) {
+		reg_1e_12_header = 0x20;
+		reg_1e_17_header = 0x20;
+		reg_1e_19_header = 0x20;
+		reg_1e_21_header = 0x20;
+	} else {
+		if (reg_1e_12_header < 0)
+			reg_1e_12_header = 0;
+		else if (reg_1e_12_header > 0x3f)
+			reg_1e_12_header = 0x3f;
+		if (reg_1e_17_header < 0)
+			reg_1e_17_header = 0;
+		else if (reg_1e_17_header > 0x3f)
+			reg_1e_17_header = 0x3f;
+		if (reg_1e_19_header < 0)
+			reg_1e_19_header = 0;
+		else if (reg_1e_19_header > 0x3f)
+			reg_1e_19_header = 0x3f;
+		if (reg_1e_21_header < 0)
+			reg_1e_21_header = 0;
+		else if (reg_1e_21_header > 0x3f)
+			reg_1e_21_header = 0x3f;
+	}
+
+	reg_1e_16_end = (reg_1e_16_read & 0x003f) + *tx_amp_table[TX_AMP_TEST_A][phy_offset];
+	reg_1e_18_end = (reg_1e_18_read & 0x003f) + *tx_amp_table[TX_AMP_TEST_B][phy_offset];
+	reg_1e_20_end = (reg_1e_20_read & 0x003f) + *tx_amp_table[TX_AMP_TEST_C][phy_offset];
+	reg_1e_22_end = (reg_1e_22_read & 0x003f) + *tx_amp_table[TX_AMP_TEST_D][phy_offset];
+
+	overflow = false;
+	if (reg_1e_16_end < txamp_low_limit ||
+	    reg_1e_16_end > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_18_end < txamp_low_limit ||
+	    reg_1e_18_end > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_20_end < txamp_low_limit ||
+	    reg_1e_20_end > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_22_end < txamp_low_limit ||
+	    reg_1e_22_end > txamp_high_limit)
+		overflow = true;
+
+	if (overflow) {
+		reg_1e_16_end = 0x20;
+		reg_1e_18_end = 0x20;
+		reg_1e_20_end = 0x20;
+		reg_1e_22_end = 0x20;
+	} else {
+		if (reg_1e_16_end < 0)
+			reg_1e_16_end = 0;
+		else if (reg_1e_16_end > 0x3f)
+			reg_1e_16_end = 0x3f;
+		if (reg_1e_18_end < 0)
+			reg_1e_18_end = 0;
+		else if (reg_1e_18_end > 0x3f)
+			reg_1e_18_end = 0x3f;
+		if (reg_1e_20_end < 0)
+			reg_1e_20_end = 0;
+		else if (reg_1e_20_end > 0x3f)
+			reg_1e_20_end = 0x3f;
+		if (reg_1e_22_end < 0)
+			reg_1e_22_end = 0;
+		else if (reg_1e_22_end > 0x3f)
+			reg_1e_22_end = 0x3f;
+	}
+
+	reg_1e_16_header = ((reg_1e_16_read & 0xfc00) / 1024) + *tx_amp_table[TX_AMP_100M_A][phy_offset];
+	reg_1e_18_header = ((reg_1e_18_read & 0x3f00) / 256) + *tx_amp_table[TX_AMP_100M_B][phy_offset];
+
+	overflow = false;
+	if (reg_1e_16_header < txamp_low_limit ||
+	    reg_1e_16_header > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_18_header < txamp_low_limit ||
+	    reg_1e_18_header > txamp_high_limit)
+		overflow = true;
+
+	if (overflow) {
+		reg_1e_16_header = 0x20;
+		reg_1e_18_header = 0x20;
+	} else {
+		if (reg_1e_16_header < 0)
+			reg_1e_16_header = 0;
+		else if (reg_1e_16_header > 0x3f)
+			reg_1e_16_header = 0x3f;
+		if (reg_1e_18_header < 0)
+			reg_1e_18_header = 0;
+		else if (reg_1e_18_header > 0x3f)
+			reg_1e_18_header = 0x3f;
+	}
+
+	reg_1e_12_end = (reg_1e_12_read & 0x003f) + *tx_amp_table[TX_AMP_10M_A][phy_offset];
+	reg_1e_17_end = (reg_1e_17_read & 0x003f) + *tx_amp_table[TX_AMP_10M_B][phy_offset];
+
+	overflow = false;
+	if (reg_1e_12_end < txamp_low_limit ||
+	    reg_1e_12_end > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_17_end < txamp_low_limit ||
+	    reg_1e_17_end > txamp_high_limit)
+		overflow = true;
+
+	if (overflow) {
+		reg_1e_12_end = 0x20;
+		reg_1e_17_end = 0x20;
+	} else {
+		if (reg_1e_12_end < 0)
+			reg_1e_12_end = 0;
+		else if (reg_1e_12_end > 0x3f)
+			reg_1e_12_end = 0x3f;
+		if (reg_1e_17_end < 0)
+			reg_1e_17_end = 0;
+		else if (reg_1e_17_end > 0x3f)
+			reg_1e_17_end = 0x3f;
+	}
+
+	reg_1e_174_header = ((reg_1e_174_read & 0x7f00) / 256) + *tx_amp_table[R50_A][phy_offset];
+	reg_1e_174_end = (reg_1e_174_read & 0x007f) + *tx_amp_table[R50_B][phy_offset];
+	reg_1e_175_header = ((reg_1e_175_read & 0x7f00) / 256) + *tx_amp_table[R50_C][phy_offset];
+	reg_1e_175_end = (reg_1e_175_read & 0x007f) + *tx_amp_table[R50_D][phy_offset];
+
+	overflow = false;
+	if (reg_1e_174_header < r50_low_limit ||
+	    reg_1e_174_header > r50_high_limit)
+		overflow = true;
+	if (reg_1e_174_end < r50_low_limit ||
+	    reg_1e_174_end > r50_high_limit)
+		overflow = true;
+	if (reg_1e_175_header < r50_low_limit ||
+	    reg_1e_175_header > r50_high_limit)
+		overflow = true;
+	if (reg_1e_175_end < r50_low_limit ||
+	    reg_1e_175_end > r50_high_limit)
+		overflow = true;
+
+	if (overflow) {
+		reg_1e_174_header = 0x34;
+		reg_1e_174_end = 0x34;
+		reg_1e_175_header = 0x34;
+		reg_1e_175_end = 0x34;
+	} else {
+		if (reg_1e_174_header < 0)
+			reg_1e_174_header = 0;
+		else if (reg_1e_174_header > 0x7f)
+			reg_1e_174_header = 0x7f;
+		if (reg_1e_174_end < 0)
+			reg_1e_174_end = 0;
+		else if (reg_1e_174_end > 0x7f)
+			reg_1e_174_end = 0x7f;
+		if (reg_1e_175_header < 0)
+			reg_1e_175_header = 0;
+		else if (reg_1e_175_header > 0x7f)
+			reg_1e_175_header = 0x7f;
+		if (reg_1e_175_end < 0)
+			reg_1e_175_end = 0;
+		else if (reg_1e_175_end > 0x7f)
+			reg_1e_175_end = 0x7f;
+	}
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x12,
+		      (reg_1e_12_header * 1024) + reg_1e_12_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x16,
+		      (reg_1e_16_header * 1024) + reg_1e_16_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x17,
+		      (reg_1e_17_header * 256) + reg_1e_17_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x18,
+		      (reg_1e_18_header * 256) + reg_1e_18_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x19,
+		      (reg_1e_19_read & 0x00ff) + (reg_1e_19_header * 256));
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x20,
+		      (reg_1e_20_read & 0xff00) + reg_1e_20_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x21,
+		      (reg_1e_21_read & 0x00ff) + (reg_1e_21_header * 256));
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x22,
+		      (reg_1e_22_read & 0xff00) + reg_1e_22_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x174,
+		      0x8080 + (reg_1e_174_header * 256) + reg_1e_174_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x175,
+		      0x8080 + (reg_1e_175_header * 256) + reg_1e_175_end);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x176, 0x4400);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x177, 0x0044);
+
+	return 0;
+}
+
+static int an7581_phy_config_init(struct phy_device *phydev)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	u32 soc_pdidr;
+	u8 phy_offset;
+	int ret;
+
+	phydev_p0 = shared->phydev_p0;
+	phy_offset = phydev->mdio.addr - phydev_p0->mdio.addr;
+
+	/* FIXME: Read SoC PDIDR if available or default to 1 */
+	soc_pdidr = 1;
+
+	shared->mdi_resister_type = MDI_5R;
+	if (soc_pdidr == 1)
+		shared->tx_amp_compensation_tbl = &an7581_tx_amp_compensation_tbl[0];
+	else
+		shared->tx_amp_compensation_tbl = &an7581_tx_amp_compensation_tbl[1];
+	shared->r50_cal_tbl = an7581_zcal_to_r45ohm;
+
+	ret = airoha_phy_calib(phydev);
+	if (ret)
+		return ret;
+
+	ret = airoha_phy_auto_select_transformer(phydev);
+	if (ret)
+		return ret;
+
+	ret = airoha_phy_tx_amp_compensation(phydev);
+	if (ret)
+		return ret;
+
+	/* RX setting for 5R_TXMR before AN setting */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe6, 0x1111);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe7, 0x5555);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe9, 0x0001);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xfe, 0x0000);
+
+	/* Long Loop Reach setting */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x11, 0xff00);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x202, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x203, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x204, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x205, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x206, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x207, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x208, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x209, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x20a, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x20b, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x20e, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x20f, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x210, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x211, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x212, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x213, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x214, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x215, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x216, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x217, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21a, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21b, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21c, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21d, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21e, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21f, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x220, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x221, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x222, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x223, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x226, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x227, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x228, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x229, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22a, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22b, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22c, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22d, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22e, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22f, 0x0025);
+
+	/* RX Setting */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3c, 0xc000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3d, 0x0000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xc000);
+
+	/* EEE setting */
+	phy_write_mmd(phydev, MDIO_MMD_AN, 0x3c, 0x0000);
+
+	/* RX */
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x41, 0x3333);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x44, 0x00c0);
+
+	/* 10M settings */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x1a3, 0x00d2);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x1a4, 0x010e);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x27b, 0x1177);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x27c, 0x0808);
+
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_1);
+	__phy_write(phydev, 0x14, 0x3a18);
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	phy_write(phydev, 0x9, 0x1e00);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x269, 0x2114);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x26a, 0x1113);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x26f, 0x0000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x271, 0x2c63);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x272, 0x0c2b);
+
+	/* EEE set because power down can't set tokenring */
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+	__mtk_tr_write(phydev, 0x0, 0x7, 0x15, 0x0055a0);
+	__mtk_tr_write(phydev, 0x0, 0x7, 0x17, 0x07ff3f);
+	__mtk_tr_write(phydev, 0x1, 0xf, 0x0, 0x00001e);
+	__mtk_tr_write(phydev, 0x1, 0xf, 0x1, 0x6fb90a);
+	__mtk_tr_write(phydev, 0x1, 0xf, 0x18, 0x0e2f00);
+	__mtk_tr_write(phydev, 0x1, 0xd, 0x26, 0x444444);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x3, 0x000004);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x6, 0x2ebaef);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x8, 0x00000b);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0xc, 0x00504d);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0xd, 0x02314f);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0xf, 0x003028);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x10, 0x00000a);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x11, 0x040001);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x14, 0x00024a);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x1c, 0x003210);
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x120, 0x8014);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x122, 0xffff);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x144, 0x0200);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x14a, 0xee20);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x19b, 0x0111);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x234, 0x1181);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x238, 0x0120);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x239, 0x0117);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x2d1, 0x0733);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x323, 0x0011);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x324, 0x013f);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x326, 0x0037);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x268, 0x07f4);
+
+	if (shared->transformer_type[phy_offset] == TXMR) {
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00, 0x0187);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01cc);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c2);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0109);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x020b);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0202);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0387);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03c5);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c2);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x09, 0x0309);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0a, 0x000e);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0002);
+
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0880);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0880);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0880);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0880);
+	} else {
+		if (soc_pdidr == 2) {
+			phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x269, 0x1114);
+			phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x26a, 0x1113);
+			phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x26f, 0x2000);
+			phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x271, 0x4c88);
+			phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x272, 0x0c2b);
+
+			/* 100M sharp */
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00, 0x0190);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01c8);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c0);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0030);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x0208);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0000);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0318);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03c8);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c0);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x09, 0x0230);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0a, 0x0008);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0200);
+
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0885);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0885);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0885);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0885);
+		} else {
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00, 0x0190);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01cf);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c0);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0030);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x020f);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0000);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0318);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03cf);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c0);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x09, 0x0230);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0a, 0x000f);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0200);
+
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0003);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0003);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0003);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0003);
+		}
+	}
+
+	return 0;
+}
+
 static int an7581_phy_probe(struct phy_device *phydev)
 {
+	struct airoha_socphy_shared *shared;
 	struct mtk_socphy_priv *priv;
 	struct pinctrl *pinctrl;
+	int ret;
 
 	/* Toggle pinctrl to enable PHY LED */
 	pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "gbe-led");
@@ -18,10 +1495,18 @@ static int an7581_phy_probe(struct phy_device *phydev)
 		dev_err(&phydev->mdio.bus->dev,
 			"Failed to setup PHY LED pinctrl\n");
 
+	ret = devm_phy_package_join(&phydev->mdio.dev, phydev, 0,
+				    sizeof(struct airoha_socphy_shared));
+	if (ret)
+		return ret;
+
 	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
 		return -ENOMEM;
 
+	if (phydev->mdio.addr == AIROHA_DEFAULT_PORT0_ADDR)
+		shared->phydev_p0 = phydev;
+
 	phydev->priv = priv;
 
 	return 0;
@@ -63,6 +1548,7 @@ static struct phy_driver mtk_socphy_driver[] = {
 	{
 		PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7581),
 		.name		= "Airoha AN7581 PHY",
+		.config_init	= an7581_phy_config_init,
 		.config_intr	= genphy_no_config_intr,
 		.handle_interrupt = genphy_handle_interrupt_no_ack,
 		.probe		= an7581_phy_probe,
@@ -72,6 +1558,8 @@ static struct phy_driver mtk_socphy_driver[] = {
 		.led_hw_control_set = mt798x_phy_led_hw_control_set,
 		.led_hw_control_get = mt798x_phy_led_hw_control_get,
 		.led_polarity_set = an7581_phy_led_polarity_set,
+		.read_page	= mtk_phy_read_page,
+		.write_page	= mtk_phy_write_page,
 	},
 	{
 		PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7583),
@@ -98,5 +1586,6 @@ static const struct mdio_device_id __maybe_unused airoha_socphy_tbl[] = {
 MODULE_DESCRIPTION("Airoha SoC Gigabit Ethernet PHY driver");
 MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
 MODULE_LICENSE("GPL");
+MODULE_VERSION("01.01.20260306");
 
 MODULE_DEVICE_TABLE(mdio, airoha_socphy_tbl);
diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.h b/drivers/net/phy/mediatek/mtk-ge-soc.h
index 507e68343576..5e9d24a9833c 100644
--- a/drivers/net/phy/mediatek/mtk-ge-soc.h
+++ b/drivers/net/phy/mediatek/mtk-ge-soc.h
@@ -12,6 +12,7 @@
 
 #define MTK_EXT_PAGE_ACCESS			0x1f
 #define MTK_PHY_PAGE_STANDARD			0x0000
+#define MTK_PHY_PAGE_EXTENDED_1			0x0001
 #define MTK_PHY_PAGE_EXTENDED_3			0x0003
 
 #define MTK_PHY_LPI_REG_14			0x14
@@ -177,6 +178,7 @@
 #define MTK_PHY_RG_ANA_CAL_RG0			0xdb
 #define   MTK_PHY_RG_CAL_CKINV			BIT(12)
 #define   MTK_PHY_RG_ANA_CALEN			BIT(8)
+#define   MTK_PHY_RG_REXT_CALEN			BIT(4)
 #define   MTK_PHY_RG_ZCALEN_A			BIT(0)
 
 #define MTK_PHY_RG_ANA_CAL_RG1			0xdc
@@ -187,6 +189,9 @@
 
 #define MTK_PHY_RG_ANA_CAL_RG5			0xe0
 #define   MTK_PHY_RG_REXT_TRIM_MASK		GENMASK(13, 8)
+#define   MTK_PHY_RG_REXT_ZCAL_CTRL_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_RG_ANA_CAL_RG6			0xe1
 
 #define MTK_PHY_RG_TX_FILTER			0xfe
 
@@ -208,6 +213,14 @@
 #define   MTK_PHY_CR_TX_AMP_OFFSET_C_MASK	GENMASK(13, 8)
 #define   MTK_PHY_CR_TX_AMP_OFFSET_D_MASK	GENMASK(6, 0)
 
+#define MTK_PHY_RG_CR_TX_R50_OFFSET_A_B		0x174
+#define   MTK_PHY_CR_TX_R50_OFFSET_A_MASK	GENMASK(13, 8)
+#define   MTK_PHY_CR_TX_R50_OFFSET_B_MASK	GENMASK(6, 0)
+
+#define MTK_PHY_RG_CR_TX_R50_OFFSET_C_D		0x175
+#define   MTK_PHY_CR_TX_R50_OFFSET_C_MASK	GENMASK(13, 8)
+#define   MTK_PHY_CR_TX_R50_OFFSET_D_MASK	GENMASK(6, 0)
+
 #define MTK_PHY_RG_AD_CAL_COMP			0x17a
 #define   MTK_PHY_AD_CAL_COMP_OUT_MASK		GENMASK(8, 8)
 
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next 5/5] net: phy: mediatek: add calibration logic for AN7583
  2026-07-08 10:23 [PATCH net-next 0/5] net: phy: mediatek: calibration for AN7581/3 Christian Marangi
                   ` (3 preceding siblings ...)
  2026-07-08 10:23 ` [PATCH net-next 4/5] net: phy: mediatek: add calibration logic for AN7581 Christian Marangi
@ 2026-07-08 10:23 ` Christian Marangi
  2026-07-08 16:31   ` Wayen Yan
  4 siblings, 1 reply; 11+ messages in thread
From: Christian Marangi @ 2026-07-08 10:23 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	Daniel Golle, Qingfang Deng, SkyLake Huang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-kernel, netdev,
	linux-arm-kernel, linux-mediatek

Similar to Airoha AN7581 Switch PHY also the Airoha AN7583 SoC
requires dedicated calibration and tuneup to make EEE correctly work.

Airoha AN7583 differ from AN7581 by having a dedicated table for
calibration and additional EEE logic tuneup.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 drivers/net/phy/mediatek/airoha-ge-soc.c | 227 ++++++++++++++++++++++-
 drivers/net/phy/mediatek/mtk.h           |   1 +
 2 files changed, 225 insertions(+), 3 deletions(-)

diff --git a/drivers/net/phy/mediatek/airoha-ge-soc.c b/drivers/net/phy/mediatek/airoha-ge-soc.c
index bc487b093cfa..601a64d83365 100644
--- a/drivers/net/phy/mediatek/airoha-ge-soc.c
+++ b/drivers/net/phy/mediatek/airoha-ge-soc.c
@@ -159,6 +159,106 @@ static u8 an7581_zcal_to_r45ohm[64] = {
 	 52,  50,  48,  46,  44,  42,  40,  39,  36,  35,  32,  32,  30,  28,  27,  25
 };
 
+/*
+ * 2 chip revision
+ * TXMR or discrete
+ * 2 MDI type
+ * TX AMP test/TX AMP 1G/TX AMP 100M/TX AMP 10M/R50
+ * 4 PHY
+ */
+static int an7583_tx_amp_compensation_tbl[TRANSFORMER_TYPE_MAX][MDI_TYPE_MAX][CALIB_CONST_TYPE_MAX][4] = {
+	[TXMR] = {
+		[MDI_0R] = {
+			[TX_AMP_TEST_A] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_B] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_C] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_D] = { -3, -3, -3, -3 },
+			[TX_AMP_1G_A] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_B] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_C] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_D] = { 0, 0, 0, 0 },
+			[TX_AMP_100M_A] = { 4, 4, 4, 4 },
+			[TX_AMP_100M_B] = { 4, 4, 4, 4 },
+			[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+			[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+			[R50_A] = { 4, 4, 0, 0 },
+			[R50_B] = { 4, 4, 0, 0 },
+			[R50_C] = { 5, 5, 3, 4 },
+			[R50_D] = { 4, 4, 4, 0 },
+		},
+		[MDI_5R] = {
+			[TX_AMP_TEST_A] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_B] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_C] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_D] = { -3, -3, -3, -3 },
+			[TX_AMP_1G_A] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_B] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_C] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_D] = { 0, 0, 0, 0 },
+			[TX_AMP_100M_A] = { 4, 4, 4, 4 },
+			[TX_AMP_100M_B] = { 4, 4, 4, 4 },
+			[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+			[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+			[R50_A] = { -4, -4, -4, -4 },
+			[R50_B] = { -4, -4, -4, -4 },
+			[R50_C] = { -2, -2, -2, -2 },
+			[R50_D] = { -3, -3, -2, -2 },
+		},
+	},
+	[DISCRETE] = {
+		[MDI_0R] = {
+			[TX_AMP_TEST_A] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_B] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_C] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_D] = { -3, -3, -3, -3 },
+			[TX_AMP_1G_A] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_B] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_C] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_D] = { 0, 0, 0, 0 },
+			[TX_AMP_100M_A] = { 4, 4, 4, 4 },
+			[TX_AMP_100M_B] = { 4, 4, 4, 4 },
+			[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+			[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+			[R50_A] = { 4, 4, 4, 4 },
+			[R50_B] = { 4, 4, 4, 4 },
+			[R50_C] = { 6, 6, 6, 6 },
+			[R50_D] = { 4, 4, 4, 4 },
+		},
+		[MDI_5R] = {
+			[TX_AMP_TEST_A] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_B] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_C] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_D] = { -3, -3, -3, -3 },
+			[TX_AMP_1G_A] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_B] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_C] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_D] = { 0, 0, 0, 0 },
+			[TX_AMP_100M_A] = { 4, 4, 4, 4 },
+			[TX_AMP_100M_B] = { 4, 4, 4, 4 },
+			[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+			[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+			[R50_A] = { 0, 0, 0, 0 },
+			[R50_B] = { 0, 0, 0, 0 },
+			[R50_C] = { 0, 0, 0, 0 },
+			[R50_D] = { 0, 0, 0, 0 },
+		},
+	},
+};
+
+static u8 an7583_zcal_to_r50ohm_0R[64] = {
+	127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+	127, 127, 127, 122, 118, 114, 110, 106, 102,  98,  96,  92,  88,  85,  82,  80,
+	 76,  72,  70,  68,  64,  62,  60,  57,  55,  52,  50,  48,  46,  44,  41,  40,
+	 38,  36,  33,  32,  30,  28,  26,  24,  24,  22,  20,  18,  16,  16,  14,  12,
+};
+
+static u8 an7583_zcal_to_r50ohm_5R[64] = {
+	127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+	127, 127, 127, 127, 127, 127, 127, 127, 127, 124, 120, 116, 112, 110, 106, 102,
+	 99,  96,  93,  90,  88,  84,  81,  79,  76,  73,  71,  68,  66,  64,  61,  59,
+	 56,  54,  52,  50,  48,  46,  44,  42,  40,  39,  37,  36,  33,  32,  31,  29,
+};
+
 static int airoha_cal_cycle(struct phy_device *phydev, int devad,
 			    u32 regnum, u16 mask, u16 cal_val)
 {
@@ -766,8 +866,9 @@ static int airoha_rx_offset_cal_sw(struct phy_device *phydev)
 	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0x171,
 			   BIT(8) | BIT(7));
 
-	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6,
-		      0x1);
+	if (phydev->drv->phy_id == AIROHA_GPHY_ID_AN7581)
+		phy_write_mmd(phydev, MDIO_MMD_VEND1,
+			      MTK_PHY_RG_ANA_CAL_RG6, 0x1);
 
 	return 0;
 }
@@ -1540,8 +1641,126 @@ static int an7581_phy_led_polarity_set(struct phy_device *phydev, int index,
 
 static int an7583_phy_config_init(struct phy_device *phydev)
 {
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	u8 phy_offset;
+	int ret;
+
 	/* BMCR_PDOWN is enabled by default */
-	return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
+	phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
+
+	phydev_p0 = shared->phydev_p0;
+	phy_offset = phydev->mdio.addr - phydev_p0->mdio.addr;
+
+	/* FIXME: Read SoC MDI Resister Type if available or default to 5R */
+	shared->mdi_resister_type = MDI_5R;
+
+	shared->mdi_resister_type = MDI_5R;
+	if (shared->mdi_resister_type == MDI_0R)
+		shared->r50_cal_tbl = an7583_zcal_to_r50ohm_0R;
+	if (shared->mdi_resister_type == MDI_5R)
+		shared->r50_cal_tbl = an7583_zcal_to_r50ohm_5R;
+	shared->tx_amp_compensation_tbl = &an7583_tx_amp_compensation_tbl;
+
+	ret = airoha_phy_calib(phydev);
+	if (ret)
+		return ret;
+
+	ret = airoha_phy_auto_select_transformer(phydev);
+	if (ret)
+		return ret;
+
+	ret = airoha_phy_tx_amp_compensation(phydev);
+	if (ret)
+		return ret;
+
+	/* Enable Idle Mode Power Setting */
+	if (phy_offset == 0) {
+		phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x268, 0x07F1);
+		phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x703, 0x3111);
+		phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x271, 0x3C24);
+		phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x701, 0x1023);
+	}
+
+	if (shared->transformer_type[phy_offset] == TXMR) {
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0881);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0881);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0881);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0881);
+
+		if (shared->mdi_resister_type == MDI_0R)
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0205);
+		else
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0200);
+	} else {
+		if (shared->mdi_resister_type == MDI_0R) {
+			/* RX setting for 5R_TXMR before AN setting */
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe7, 0x6666);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe9, 0x0003);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xfe, 0x0006);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe6, 0x1111);
+			/* 100M sharp */
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0c86);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0c86);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0c86);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0c86);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01cb);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c2);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0108);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x0211);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0205);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0387);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03ce);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c8);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0005);
+		} else {
+			/* 100M sharp */
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0886);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0886);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0886);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0886);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00, 0x0195);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01cb);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c2);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0108);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x0211);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0205);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0387);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03ce);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c3);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0a, 0x0010);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0005);
+		}
+	}
+
+	/* RX Setting */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3c, 0xc000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3d, 0x0000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xc000);
+
+	/* EEE setting */
+	phy_write_mmd(phydev, MDIO_MMD_AN, 0x3c, 0x0000);
+
+	/* 10M settings */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x1a3, 0x00d2);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x1a4, 0x010e);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x27b, 0x1177);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x27c, 0x0808);
+
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_3);
+	__phy_write(phydev, 0x14, 0x190);
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_1);
+	__phy_write(phydev, 0x14, 0x3a18);
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	phy_write(phydev, 0x9, 0x0600);
+
+	/* EEE keep only Pair A ON */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x630, 0x006E);
+
+	return 0;
 }
 
 static struct phy_driver mtk_socphy_driver[] = {
@@ -1572,6 +1791,8 @@ static struct phy_driver mtk_socphy_driver[] = {
 		.led_hw_control_set = mt798x_phy_led_hw_control_set,
 		.led_hw_control_get = mt798x_phy_led_hw_control_get,
 		.led_polarity_set = an7581_phy_led_polarity_set,
+		.read_page	= mtk_phy_read_page,
+		.write_page	= mtk_phy_write_page,
 	},
 };
 
diff --git a/drivers/net/phy/mediatek/mtk.h b/drivers/net/phy/mediatek/mtk.h
index c690aaf38124..cd9d4c5550c3 100644
--- a/drivers/net/phy/mediatek/mtk.h
+++ b/drivers/net/phy/mediatek/mtk.h
@@ -13,6 +13,7 @@
 
 #define MTK_EXT_PAGE_ACCESS			0x1f
 #define MTK_PHY_PAGE_EXTENDED_1			0x0001
+#define MTK_PHY_PAGE_EXTENDED_3			0x0003
 #define MTK_PHY_PAGE_STANDARD			0x0000
 #define MTK_PHY_PAGE_EXTENDED_52B5		0x52b5
 
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next 4/5] net: phy: mediatek: add calibration logic for AN7581
  2026-07-08 10:23 ` [PATCH net-next 4/5] net: phy: mediatek: add calibration logic for AN7581 Christian Marangi
@ 2026-07-08 16:30   ` Wayen Yan
  0 siblings, 0 replies; 11+ messages in thread
From: Wayen Yan @ 2026-07-08 16:30 UTC (permalink / raw)
  To: netdev
  Cc: lorenzo, horms, pabeni, kuba, edumazet, andrew+netdev,
	angelogioacchino.delregno, matthias.bgg, linux-arm-kernel,
	linux-mediatek, Andrew Lunn, Heiner Kallweit, Russell King,
	David S. Miller, Daniel Golle, Qingfang Deng, SkyLake Huang,
	linux-kernel

Hi Christian,

Thanks for working on this. One critical bug found that will crash
on probe, plus a couple of minor issues.

1) Uninitialized shared pointer in an7581_phy_probe()

The local variable `shared` is declared but never assigned before
use:

    static int an7581_phy_probe(struct phy_device *phydev)
    {
        struct airoha_socphy_shared *shared;   /* not initialized */
        ...
        ret = devm_phy_package_join(&phydev->mdio.dev, phydev, 0,
                                    sizeof(struct airoha_socphy_shared));
        if (ret)
            return ret;

        ...
        if (phydev->mdio.addr == AIROHA_DEFAULT_PORT0_ADDR)
            shared->phydev_p0 = phydev;   /* writing to uninitialized pointer */

devm_phy_package_join() allocates the shared priv data internally
(accessible via phydev->shared->priv), but the local variable
`shared` itself is never populated. You need to call
phy_package_get_priv(phydev) after the join succeeds before
accessing any shared fields.

Fix:

    shared = phy_package_get_priv(phydev);

should be added right after devm_phy_package_join() succeeds.

Without this fix, the first PHY to probe (addr == 0x9) will crash,
and all subsequent PHYs' config_init will dereference an
uninitialized phydev_p0 in every calibration function.

2) Typo: mdi_resister_type -> mdi_resistor_type

The field name "resister" appears in multiple places (enum, struct
field, config_init, FIXME comments). It should be "resistor".
This will be baked into the ABI once merged so worth fixing now:

- enum airoha_mdi_resister_type -> airoha_mdi_resistor_type
- shared->mdi_resister_type -> shared->mdi_resistor_type
- FIXME comment: "MDI Resister Type" -> "MDI Resistor Type"

3) Observation: mdi_resister_type is always MDI_5R but tables have
   MDI_0R data

Currently the code hardcodes mdi_resister_type = MDI_5R, so the
MDI_0R entries in an7581_tx_amp_compensation_tbl[] are dead data.
The FIXME suggests this should be read from SCU registers
eventually. Consider adding the MDI_0R branch now (or at minimum
an else) so the code structure is ready when the SCU read is
implemented, and avoid shipping dead table data.

Best,
Wayen



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next 5/5] net: phy: mediatek: add calibration logic for AN7583
  2026-07-08 10:23 ` [PATCH net-next 5/5] net: phy: mediatek: add calibration logic for AN7583 Christian Marangi
@ 2026-07-08 16:31   ` Wayen Yan
  0 siblings, 0 replies; 11+ messages in thread
From: Wayen Yan @ 2026-07-08 16:31 UTC (permalink / raw)
  To: netdev
  Cc: lorenzo, horms, pabeni, kuba, edumazet, andrew+netdev,
	angelogioacchino.delregno, matthias.bgg, linux-arm-kernel,
	linux-mediatek, Andrew Lunn, Heiner Kallweit, Russell King,
	David S. Miller, Daniel Golle, Qingfang Deng, SkyLake Huang,
	linux-kernel

Hi Christian,

A few issues in an7583_phy_config_init():

1) Redundant double assignment of mdi_resister_type

    shared->mdi_resister_type = MDI_5R;

    shared->mdi_resister_type = MDI_5R;   /* duplicate */
    if (shared->mdi_resister_type == MDI_0R)
        shared->r50_cal_tbl = an7583_zcal_to_r50ohm_0R;
    if (shared->mdi_resister_type == MDI_5R)
        shared->r50_cal_tbl = an7583_zcal_to_r50ohm_5R;

The first assignment is immediately overwritten by the second
identical one. Also the two `if` blocks should be `else if` --
currently both conditions are checked independently and the
MDI_0R branch is dead code (since the value is hardcoded to
MDI_5R).

If the intent is to default to MDI_5R and later read from SoC,
the structure should be:

    shared->mdi_resister_type = MDI_5R;  /* FIXME: read from SCU */
    if (shared->mdi_resister_type == MDI_0R)
        shared->r50_cal_tbl = an7583_zcal_to_r50ohm_0R;
    else
        shared->r50_cal_tbl = an7583_zcal_to_r50ohm_5R;

2) Typo: mdi_resister_type -> mdi_resistor_type (same as patch 4/5)

Also the FIXME comment says "MDI Resister Type" -- should be
"Resistor Type".

3) Dependency on patch 4/5 probe fix

an7583_phy_config_init() uses shared->phydev_p0 which is set in
an7581_phy_probe(). If the probe function from patch 4/5 is not
fixed (missing shared = phy_package_get_priv(phydev)), this will
deref NULL/garbage:

    phydev_p0 = shared->phydev_p0;
    phy_offset = phydev->mdio.addr - phydev_p0->mdio.addr;

This highlights that patch 4/5's probe fix is a blocker for both
AN7581 and AN7583.

Best,
Wayen



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next 3/5] net: phy: mediatek: split Airoha code to dedicated source
  2026-07-08 10:23 ` [PATCH net-next 3/5] net: phy: mediatek: split Airoha code to dedicated source Christian Marangi
@ 2026-07-08 16:32   ` Wayen Yan
  0 siblings, 0 replies; 11+ messages in thread
From: Wayen Yan @ 2026-07-08 16:32 UTC (permalink / raw)
  To: netdev
  Cc: lorenzo, horms, pabeni, kuba, edumazet, andrew+netdev,
	angelogioacchino.delregno, matthias.bgg, linux-arm-kernel,
	linux-mediatek, Andrew Lunn, Heiner Kallweit, Russell King,
	David S. Miller, Daniel Golle, Qingfang Deng, SkyLake Huang,
	linux-kernel

Hi Christian,

One minor typo in the new Kconfig help text:

In drivers/net/phy/mediatek/Kconfig, the help text for
AIROHA_GE_SOC_PHY has a leftover "d":

    Include support for built-in Ethernet PHYs which are present in
    the AN7581 and AN7583 SoCs. These PHYs d will dynamically
                                            ^
    calibrate during startup.

Should be: "These PHYs will dynamically calibrate during startup."

Otherwise the code split looks clean.

Best,
Wayen



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next 2/5] net: phy: mediatek: move MTK GE SoC registers define to dedicated header
  2026-07-08 10:23 ` [PATCH net-next 2/5] net: phy: mediatek: move MTK GE SoC registers define to dedicated header Christian Marangi
@ 2026-07-08 23:44   ` Andrew Lunn
  2026-07-09 13:46     ` Christian Marangi
  0 siblings, 1 reply; 11+ messages in thread
From: Andrew Lunn @ 2026-07-08 23:44 UTC (permalink / raw)
  To: Christian Marangi
  Cc: Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Daniel Golle, Qingfang Deng,
	SkyLake Huang, Matthias Brugger, AngeloGioacchino Del Regno,
	linux-kernel, netdev, linux-arm-kernel, linux-mediatek

On Wed, Jul 08, 2026 at 12:23:28PM +0200, Christian Marangi wrote:
> In preparation for support of special Software Calibration for Airoha
> PHY, move the MTK GE SoC registers define to a dedicated header.
> 
> It's also needed to generalize the cal_cycle function as Airoha needs
> only part of its logic (the wait logic) to complete a calibration cycle.

Can this be two patches?

	Andrew


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next 2/5] net: phy: mediatek: move MTK GE SoC registers define to dedicated header
  2026-07-08 23:44   ` Andrew Lunn
@ 2026-07-09 13:46     ` Christian Marangi
  0 siblings, 0 replies; 11+ messages in thread
From: Christian Marangi @ 2026-07-09 13:46 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Daniel Golle, Qingfang Deng,
	SkyLake Huang, Matthias Brugger, AngeloGioacchino Del Regno,
	linux-kernel, netdev, linux-arm-kernel, linux-mediatek

On Thu, Jul 09, 2026 at 01:44:37AM +0200, Andrew Lunn wrote:
> On Wed, Jul 08, 2026 at 12:23:28PM +0200, Christian Marangi wrote:
> > In preparation for support of special Software Calibration for Airoha
> > PHY, move the MTK GE SoC registers define to a dedicated header.
> > 
> > It's also needed to generalize the cal_cycle function as Airoha needs
> > only part of its logic (the wait logic) to complete a calibration cycle.
> 
> Can this be two patches?
> 

Sure, will split.

-- 
	Ansuel


^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-07-09 13:46 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-08 10:23 [PATCH net-next 0/5] net: phy: mediatek: calibration for AN7581/3 Christian Marangi
2026-07-08 10:23 ` [PATCH net-next 1/5] net: phy: mediatek: export __mtk_tr_write Christian Marangi
2026-07-08 10:23 ` [PATCH net-next 2/5] net: phy: mediatek: move MTK GE SoC registers define to dedicated header Christian Marangi
2026-07-08 23:44   ` Andrew Lunn
2026-07-09 13:46     ` Christian Marangi
2026-07-08 10:23 ` [PATCH net-next 3/5] net: phy: mediatek: split Airoha code to dedicated source Christian Marangi
2026-07-08 16:32   ` Wayen Yan
2026-07-08 10:23 ` [PATCH net-next 4/5] net: phy: mediatek: add calibration logic for AN7581 Christian Marangi
2026-07-08 16:30   ` Wayen Yan
2026-07-08 10:23 ` [PATCH net-next 5/5] net: phy: mediatek: add calibration logic for AN7583 Christian Marangi
2026-07-08 16:31   ` Wayen Yan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox