Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Caleb James DeLisle <cjd@cjdns.fr>
To: netdev@vger.kernel.org, ansuelsmth@gmail.com
Cc: andrew@lunn.ch, hkallweit1@gmail.com, linux@armlinux.org.uk,
	davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
	pabeni@redhat.com, daniel@makrotopia.org, dqfext@gmail.com,
	SkyLake.Huang@mediatek.com, matthias.bgg@gmail.com,
	angelogioacchino.delregno@collabora.com, cjd@cjdns.fr,
	linux-kernel@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	linux-mediatek@lists.infradead.org
Subject: [PATCH v2 net-next 3/3] net: phy: mediatek: support EcoNet EN751221 gbit SoC PHY
Date: Wed, 26 Aug 2026 20:21:03 +0000	[thread overview]
Message-ID: <20260826202103.2280645-4-cjd@cjdns.fr> (raw)
In-Reply-To: <20260826202103.2280645-1-cjd@cjdns.fr>

The EcoNet EN751221 gigabit SoC PHY is identified by product IDs 9451
and 9471. These devices have one such PHY which occupies MMIO index
12. This PHY is present in 3FE+1GE chips such as EN7526C, and also
the "G" chips which have an MCM gigabit switch. In the "G" chips,
this provides a 5th ethernet port.

Reference code disables EEE and sets MII_BMCR to BMCR_ANRESTART |
BMCR_ANENABLE prior to calibration. Testing has shown this seems
unnecessary and it has been omitted, but it is noted here in case
of future issues.

Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
 drivers/net/phy/mediatek/Kconfig         |   9 +-
 drivers/net/phy/mediatek/airoha-ge-soc.c | 214 ++++++++++++++++++++++-
 drivers/net/phy/mediatek/mtk-ge-soc.h    |   2 +
 3 files changed, 217 insertions(+), 8 deletions(-)

diff --git a/drivers/net/phy/mediatek/Kconfig b/drivers/net/phy/mediatek/Kconfig
index b8df1a8016c6..2d73ea6d8398 100644
--- a/drivers/net/phy/mediatek/Kconfig
+++ b/drivers/net/phy/mediatek/Kconfig
@@ -1,15 +1,14 @@
 # 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
+	tristate "Airoha/EcoNet SoC Ethernet PHYs"
+	depends on ARCH_AIROHA || ECONET || COMPILE_TEST
 	select MTK_NET_PHYLIB
 	select PHY_PACKAGE
 	help
-	  Supports Airoha SoC built-in Gigabit Ethernet PHYs.
+	  Supports Airoha and EcoNet SoC built-in Gigabit Ethernet PHYs.
 
 	  Include support for built-in Ethernet PHYs which are present in
-	  the AN7581 and AN7583 SoCs. These PHYs will dynamically
+	  the AN7581, AN7583, and EN751221 SoCs. These PHYs will dynamically
 	  calibrate during startup.
 
 config MEDIATEK_2P5GE_PHY
diff --git a/drivers/net/phy/mediatek/airoha-ge-soc.c b/drivers/net/phy/mediatek/airoha-ge-soc.c
index 5a8838a47184..1fa258dc22f4 100644
--- a/drivers/net/phy/mediatek/airoha-ge-soc.c
+++ b/drivers/net/phy/mediatek/airoha-ge-soc.c
@@ -1,7 +1,9 @@
 // SPDX-License-Identifier: GPL-2.0+
 #include <linux/bitfield.h>
+#include <linux/mfd/syscon.h>
 #include <linux/pinctrl/consumer.h>
 #include <linux/phy.h>
+#include <linux/regmap.h>
 
 #include "../phylib.h"
 #include "mtk.h"
@@ -9,6 +11,8 @@
 
 #define AIROHA_DEFAULT_PORT0_ADDR		0x9
 #define AIROHA_PHY_MAX_LEDS			2
+#define ECONET_GPHY_CHIPSCU_RASEL_MASK		GENMASK(15, 13)
+#define ECONET_GPHY_PORT0_ADDR			12
 
 enum airoha_mdi_resister_type {
 	MDI_0R,
@@ -259,6 +263,69 @@ static const u8 an7583_zcal_to_r50ohm_5R[64] = {
 	 56,  54,  52,  50,  48,  46,  44,  42,  40,  39,  37,  36,  33,  32,  31,  29,
 };
 
+/*
+ * 2 chip revisions
+ * TXMR (no evidence that these chips were ever discrete)
+ * MDI 0R (5R not known to exist)
+ * TX AMP test/TX AMP 1G/TX AMP 100M/TX AMP 10M/R50
+ * 1 PHY
+ */
+static const int en751221_tx_amp_compensation_tbl[2][TRANSFORMER_TYPE_MAX][MDI_TYPE_MAX][CALIB_CONST_TYPE_MAX][4] = {
+	{
+		/* EN751221 */
+		[TXMR] = {
+			[MDI_0R] = {
+				[TX_AMP_TEST_A] = { 14 },
+				[TX_AMP_TEST_B] = { 14 },
+				[TX_AMP_TEST_C] = { 14 },
+				[TX_AMP_TEST_D] = { 14 },
+				[TX_AMP_1G_A] = { 2 },
+				[TX_AMP_1G_B] = { 2 },
+				[TX_AMP_1G_C] = { 2 },
+				[TX_AMP_1G_D] = { 2 },
+				[TX_AMP_100M_A] = { 4 },
+				[TX_AMP_100M_B] = { 4 },
+				[TX_AMP_10M_A] = { 0 },
+				[TX_AMP_10M_B] = { 0 },
+				[R50_A] = { 0 },
+				[R50_B] = { 0 },
+				[R50_C] = { 0 },
+				[R50_D] = { 0 },
+			},
+		},
+	},
+	{
+		/* EN7526C */
+		[TXMR] = {
+			[MDI_0R] = {
+				[TX_AMP_TEST_A] = { 14 },
+				[TX_AMP_TEST_B] = { 14 },
+				[TX_AMP_TEST_C] = { 14 },
+				[TX_AMP_TEST_D] = { 14 },
+				[TX_AMP_1G_A] = { 12 },
+				[TX_AMP_1G_B] = { 13 },
+				[TX_AMP_1G_C] = { 9 },
+				[TX_AMP_1G_D] = { 9 },
+				[TX_AMP_100M_A] = { 4 },
+				[TX_AMP_100M_B] = { 4 },
+				[TX_AMP_10M_A] = { 0 },
+				[TX_AMP_10M_B] = { 0 },
+				[R50_A] = { 0 },
+				[R50_B] = { 0 },
+				[R50_C] = { -4 },
+				[R50_D] = { -4 },
+			},
+		},
+	}
+};
+
+static const u8 en751221_zcal_to_r50ohm[64] = {
+	125, 125, 125, 125, 125, 125, 124, 121, 118, 115, 112, 110, 108, 105, 103, 101,
+	 99,  97,  95,  77,  75,  73,  72,  70,  68,  67,  65,  64,  63,  45,  44,  43,
+	 41,  40,  39,  38,  37,  36,  35,  34,  32,  32,  31,  30,  13,  12,  11,  10,
+	  9,   8,   8,   7,   6,   5,   5,   4,   3,   2,   2,   1,   0,   0,   0,   0
+};
+
 static int airoha_cal_cycle(struct phy_device *phydev, int devad,
 			    u32 regnum, u16 mask, u16 cal_val)
 {
@@ -276,6 +343,21 @@ static int airoha_cal_cycle(struct phy_device *phydev, int devad,
 	return ret;
 }
 
+/* EN751221 also mirrors RG_BG_RASEL into CHIP SCU register 0x16c. */
+static void en751221_gphy_mirror_rext(struct phy_device *phydev, u8 zcal)
+{
+	struct regmap *chip_scu;
+
+	chip_scu = syscon_regmap_lookup_by_compatible("econet,en751221-chip-scu");
+	if (IS_ERR(chip_scu)) {
+		phydev_err(phydev, "CHIP SCU unavailable for REXT mirror: %ld\n",
+			   PTR_ERR(chip_scu));
+		return;
+	}
+	regmap_update_bits(chip_scu, 0x16c, ECONET_GPHY_CHIPSCU_RASEL_MASK,
+			   FIELD_PREP(ECONET_GPHY_CHIPSCU_RASEL_MASK, zcal >> 3));
+}
+
 static int airoha_rext_cal_sw(struct phy_device *phydev)
 {
 	int calibration_polarity;
@@ -349,6 +431,10 @@ static int airoha_rext_cal_sw(struct phy_device *phydev)
 		       MTK_PHY_RG_BG_RASEL_MASK,
 		       FIELD_PREP(MTK_PHY_RG_BG_RASEL_MASK, zcal_ctrl >> 3));
 
+	if (phydev->phy_id == ECONET_GPHY_ID_EN7526C ||
+	    phydev->phy_id == ECONET_GPHY_ID_EN751221)
+		en751221_gphy_mirror_rext(phydev, zcal_ctrl);
+
 	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
 
 	return 0;
@@ -1369,6 +1455,106 @@ static int airoha_phy_tx_amp_compensation(struct phy_device *phydev)
 	return 0;
 }
 
+static int en751221_gphy_config_init(struct phy_device *phydev)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	int ret;
+
+	shared->mdi_resister_type = MDI_0R;
+
+	if (phydev->phy_id == ECONET_GPHY_ID_EN751221)
+		shared->tx_amp_compensation_tbl = &en751221_tx_amp_compensation_tbl[0];
+	else
+		shared->tx_amp_compensation_tbl = &en751221_tx_amp_compensation_tbl[1];
+
+	shared->r50_cal_tbl = en751221_zcal_to_r50ohm;
+
+	phy_set_bits(phydev, MII_BMCR, BMCR_RESET);
+
+	/* Global MMD (devad 0x1f) settings. */
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x273, 0x1000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x272, 0x3cff);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x269, 0x4344);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x044, 0x0030);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x27c, 0x0808);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x27b, 0x1177);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x268, 0x0728);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x269, 0x244f);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x271, 0x4e13);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x417, 0x7775);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x024, 0xc007);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x025, 0x003f);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x021, 0x800a);
+
+	/* Local MMD (devad 0x1e) settings. */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x000, 0x01b0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x001, 0x01be);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x002, 0x01be);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x003, 0x0090);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x004, 0x0205);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x005, 0x0204);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x006, 0x039c);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x007, 0x03c0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x008, 0x03bc);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x009, 0x0297);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00a, 0x0005);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00b, 0x0006);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x014, 0x0040);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x044, 0x0060);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x176, 0x5500);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x177, 0x0055);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x041, 0x3333);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0a6, 0x0350);
+
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+	__mtk_tr_write(phydev, 0x1, 0xf, 0x00, 0x00002b);
+	__mtk_tr_write(phydev, 0x1, 0xf, 0x03, 0x082422);
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	/*
+	 * 1000BASE-T master/slave resolution fails about once in ten
+	 * reconnects, so force this PHY to master mode.
+	 */
+	ret = phy_write(phydev, MII_CTRL1000,
+			ADVERTISE_1000FULL | CTL1000_PREFER_MASTER |
+			CTL1000_AS_MASTER | CTL1000_ENABLE_MASTER);
+	if (ret)
+		return ret;
+
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_1);
+	__phy_write(phydev, 0x14, 0x3a14);
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	if (phydev->phy_id == ECONET_GPHY_ID_EN7526C) {
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x201, 0x4002);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03d, 0x0c00);
+	} else {
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x012, 0x2c10);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x017, 0x0410);
+	}
+
+	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;
+
+	return 0;
+}
+
+static int en751221_gphy_match(struct phy_device *phydev,
+			       const struct phy_driver *phydrv)
+{
+	return phydev->phy_id == ECONET_GPHY_ID_EN751221 ||
+	       phydev->phy_id == ECONET_GPHY_ID_EN7526C;
+}
+
 static int an7581_phy_config_init(struct phy_device *phydev)
 {
 	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
@@ -1585,6 +1771,7 @@ static int an7581_phy_config_init(struct phy_device *phydev)
 
 static int an7581_phy_probe(struct phy_device *phydev)
 {
+	int port0 = AIROHA_DEFAULT_PORT0_ADDR;
 	struct airoha_socphy_shared *shared;
 	struct mtk_socphy_priv *priv;
 	struct pinctrl *pinctrl;
@@ -1596,8 +1783,11 @@ 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,
-				    AIROHA_DEFAULT_PORT0_ADDR,
+	if (phydev->phy_id == ECONET_GPHY_ID_EN7526C ||
+	    phydev->phy_id == ECONET_GPHY_ID_EN751221)
+		port0 = ECONET_GPHY_PORT0_ADDR;
+
+	ret = devm_phy_package_join(&phydev->mdio.dev, phydev, port0,
 				    sizeof(struct airoha_socphy_shared));
 	if (ret)
 		return ret;
@@ -1608,7 +1798,7 @@ static int an7581_phy_probe(struct phy_device *phydev)
 	if (!priv)
 		return -ENOMEM;
 
-	if (phydev->mdio.addr == AIROHA_DEFAULT_PORT0_ADDR)
+	if (phydev->mdio.addr == port0)
 		shared->phydev_p0 = phydev;
 
 	phydev->priv = priv;
@@ -1797,6 +1987,22 @@ static struct phy_driver mtk_socphy_driver[] = {
 		.read_page	= mtk_phy_read_page,
 		.write_page	= mtk_phy_write_page,
 	},
+	{
+		.name		= "EcoNet EN751221 GPHY",
+		.config_init	= en751221_gphy_config_init,
+		.config_intr	= genphy_no_config_intr,
+		.handle_interrupt = genphy_handle_interrupt_no_ack,
+		.match_phy_device = en751221_gphy_match,
+		.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,
+		.read_page	= mtk_phy_read_page,
+		.write_page	= mtk_phy_write_page,
+	}
 };
 
 module_phy_driver(mtk_socphy_driver);
@@ -1804,6 +2010,8 @@ 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) },
+	{ PHY_ID_MATCH_EXACT(ECONET_GPHY_ID_EN751221) },
+	{ PHY_ID_MATCH_EXACT(ECONET_GPHY_ID_EN7526C) },
 	{ }
 };
 
diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.h b/drivers/net/phy/mediatek/mtk-ge-soc.h
index 834b61dbc690..1f38b4c81860 100644
--- a/drivers/net/phy/mediatek/mtk-ge-soc.h
+++ b/drivers/net/phy/mediatek/mtk-ge-soc.h
@@ -9,6 +9,8 @@
 #define MTK_GPHY_ID_MT7988			0x03a29481
 #define AIROHA_GPHY_ID_AN7581			0x03a294c1
 #define AIROHA_GPHY_ID_AN7583			0xc0ff0420
+#define ECONET_GPHY_ID_EN751221			0x03a29451
+#define ECONET_GPHY_ID_EN7526C			0x03a29471
 
 #define MTK_EXT_PAGE_ACCESS			0x1f
 #define MTK_PHY_PAGE_STANDARD			0x0000
-- 
2.39.5



  parent reply	other threads:[~2026-08-26 20:21 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-26 20:21 [PATCH v2 net-next 0/3] net: phy: mediatek: support EcoNet EN751221 gbit SoC PHY Caleb James DeLisle
2026-08-26 20:21 ` [PATCH v2 net-next 1/3] net: phy: mediatek: move mtk_cal_cycle_wait to mtk-phy-lib Caleb James DeLisle
2026-08-26 20:21 ` [PATCH v2 net-next 2/3] net: phy: mediatek: bug fixes to airoha-ge-soc.c Caleb James DeLisle
2026-08-26 20:21 ` Caleb James DeLisle [this message]
2026-08-27  8:19 ` [PATCH v2 net-next 0/3] net: phy: mediatek: support EcoNet EN751221 gbit SoC PHY Paolo Abeni

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260826202103.2280645-4-cjd@cjdns.fr \
    --to=cjd@cjdns.fr \
    --cc=SkyLake.Huang@mediatek.com \
    --cc=andrew@lunn.ch \
    --cc=angelogioacchino.delregno@collabora.com \
    --cc=ansuelsmth@gmail.com \
    --cc=daniel@makrotopia.org \
    --cc=davem@davemloft.net \
    --cc=dqfext@gmail.com \
    --cc=edumazet@google.com \
    --cc=hkallweit1@gmail.com \
    --cc=kuba@kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mediatek@lists.infradead.org \
    --cc=linux@armlinux.org.uk \
    --cc=matthias.bgg@gmail.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox