Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 net-next 0/2] net: phy: mediatek: add driver for EcoNet Fast Ethernet SoC PHYs
@ 2026-08-27  6:59 Caleb James DeLisle
  2026-08-27  6:59 ` [PATCH v2 net-next 1/2] net: phy: mediatek: differentiate MT7530 GE PHY from EcoNet FE PHY Caleb James DeLisle
  2026-08-27  6:59 ` [PATCH v2 net-next 2/2] net: phy: mediatek: add driver for EcoNet Fast Ethernet SoC PHYs Caleb James DeLisle
  0 siblings, 2 replies; 4+ messages in thread
From: Caleb James DeLisle @ 2026-08-27  6:59 UTC (permalink / raw)
  To: netdev
  Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	matthias.bgg, angelogioacchino.delregno, daniel, naseefkm, joey,
	linux-kernel, linux-arm-kernel, linux-mediatek,
	Caleb James DeLisle

Add support for 10/100 builtin SoC PHYs identified as 03a2.9414 or
03a2.9412.

Changes since v1:
* Include 03a2.9412 and differentiate from MTK_GPHY_ID_MT7530 by checking
gigabit support.
* Switch to for loops that are easier to read
* Add ECONET_RETRIES and explanation of calibration re-tries
* Better explain the "master" PHY which is needed for calibration
* Fix incorrect reverse christmas trees
* v1: https://lore.kernel.org/netdev/20260825180421.1804729-1-cjd@cjdns.fr/

Caleb James DeLisle (2):
  net: phy: mediatek: differentiate MT7530 GE PHY from EcoNet FE PHY
  net: phy: mediatek: add driver for EcoNet Fast Ethernet SoC PHYs

 MAINTAINERS                              |   6 +
 drivers/net/phy/mediatek/Kconfig         |  18 +
 drivers/net/phy/mediatek/Makefile        |   1 +
 drivers/net/phy/mediatek/econet-fe-soc.c | 672 +++++++++++++++++++++++
 drivers/net/phy/mediatek/mtk-ge.c        |  14 +
 5 files changed, 711 insertions(+)
 create mode 100644 drivers/net/phy/mediatek/econet-fe-soc.c

-- 
2.39.5



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

* [PATCH v2 net-next 1/2] net: phy: mediatek: differentiate MT7530 GE PHY from EcoNet FE PHY
  2026-08-27  6:59 [PATCH v2 net-next 0/2] net: phy: mediatek: add driver for EcoNet Fast Ethernet SoC PHYs Caleb James DeLisle
@ 2026-08-27  6:59 ` Caleb James DeLisle
  2026-08-27  8:23   ` Paolo Abeni
  2026-08-27  6:59 ` [PATCH v2 net-next 2/2] net: phy: mediatek: add driver for EcoNet Fast Ethernet SoC PHYs Caleb James DeLisle
  1 sibling, 1 reply; 4+ messages in thread
From: Caleb James DeLisle @ 2026-08-27  6:59 UTC (permalink / raw)
  To: netdev
  Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	matthias.bgg, angelogioacchino.delregno, daniel, naseefkm, joey,
	linux-kernel, linux-arm-kernel, linux-mediatek,
	Caleb James DeLisle

MT7530 gigabit PHY has the same ID as an EcoNet EN751221 10/100
PHY. Differentiate by checking whether the PHY supports 1000BASE-T
which is always supported by MT7530.

Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
 drivers/net/phy/mediatek/mtk-ge.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/drivers/net/phy/mediatek/mtk-ge.c b/drivers/net/phy/mediatek/mtk-ge.c
index 73d9b72f9d9e..3b4d774057c2 100644
--- a/drivers/net/phy/mediatek/mtk-ge.c
+++ b/drivers/net/phy/mediatek/mtk-ge.c
@@ -96,6 +96,19 @@ static int mt7531_phy_config_init(struct phy_device *phydev)
 	return 0;
 }
 
+/*
+ * MTK_GPHY_ID_MT7530 collides with ECONET_FEPHY_ID_LEGACY
+ * but this is a gigabit PHY so ESTATUS lists 1000BASE-T.
+ */
+static int mt7530_phy_match(struct phy_device *phydev,
+			    const struct phy_driver *phydrv)
+{
+	if (phydev->phy_id != MTK_GPHY_ID_MT7530)
+		return false;
+
+	return (phy_read(phydev, MII_ESTATUS) & ESTATUS_1000_TFULL) != 0;
+}
+
 static struct phy_driver mtk_gephy_driver[] = {
 	{
 		PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7530),
@@ -106,6 +119,7 @@ static struct phy_driver mtk_gephy_driver[] = {
 		 */
 		.config_intr	= genphy_no_config_intr,
 		.handle_interrupt = genphy_handle_interrupt_no_ack,
+		.match_phy_device = mt7530_phy_match,
 		.suspend	= genphy_suspend,
 		.resume		= genphy_resume,
 		.read_page	= mtk_phy_read_page,
-- 
2.39.5



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

* [PATCH v2 net-next 2/2] net: phy: mediatek: add driver for EcoNet Fast Ethernet SoC PHYs
  2026-08-27  6:59 [PATCH v2 net-next 0/2] net: phy: mediatek: add driver for EcoNet Fast Ethernet SoC PHYs Caleb James DeLisle
  2026-08-27  6:59 ` [PATCH v2 net-next 1/2] net: phy: mediatek: differentiate MT7530 GE PHY from EcoNet FE PHY Caleb James DeLisle
@ 2026-08-27  6:59 ` Caleb James DeLisle
  1 sibling, 0 replies; 4+ messages in thread
From: Caleb James DeLisle @ 2026-08-27  6:59 UTC (permalink / raw)
  To: netdev
  Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	matthias.bgg, angelogioacchino.delregno, daniel, naseefkm, joey,
	linux-kernel, linux-arm-kernel, linux-mediatek,
	Caleb James DeLisle

Add support for the Fast Ethernet PHYs found in EcoNet SoCs based on
the EN7526C variant. This includes the reasonably popular EN7526F SoC.
This PHY bears the ID 03a2.9414 is sometimes referred to as "7522",
though that is not a published product name.

Also provide support for the older EN7512 Fast Ethernet SoC PHYs found
in EN751221 chips which do not have the MCM switch. That is chips which
do not have a "G" in the name. These PHYs bear the ID 03a2.9412 which
collides with MTK_GPHY_ID_MT7530 gigabit PHY, but they are
differentiated by the fact that 1000BASE-T is always supported by
MT7530 and never by this.

Tested on EN7526C but not EN7512.

Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
 MAINTAINERS                              |   6 +
 drivers/net/phy/mediatek/Kconfig         |  18 +
 drivers/net/phy/mediatek/Makefile        |   1 +
 drivers/net/phy/mediatek/econet-fe-soc.c | 672 +++++++++++++++++++++++
 4 files changed, 697 insertions(+)
 create mode 100644 drivers/net/phy/mediatek/econet-fe-soc.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 460cb7268845..7ca3d8d08c69 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9253,6 +9253,12 @@ W:	https://linuxtv.org
 Q:	http://patchwork.linuxtv.org/project/linux-media/list/
 F:	drivers/media/dvb-frontends/ec100*
 
+ECONET ETHERNET PHY DRIVER
+M:	Caleb James DeLisle <cjd@cjdns.fr>
+L:	netdev@vger.kernel.org
+S:	Maintained
+F:	drivers/net/phy/mediatek/econet-fe-soc.c
+
 ECONET MIPS PLATFORM
 M:	Caleb James DeLisle <cjd@cjdns.fr>
 L:	linux-mips@vger.kernel.org
diff --git a/drivers/net/phy/mediatek/Kconfig b/drivers/net/phy/mediatek/Kconfig
index 3b9cf82c0cb2..9c57b22540aa 100644
--- a/drivers/net/phy/mediatek/Kconfig
+++ b/drivers/net/phy/mediatek/Kconfig
@@ -1,4 +1,22 @@
 # SPDX-License-Identifier: GPL-2.0-only
+
+config ECONET_FE_SOC_PHY
+	tristate "EcoNet 10/100 SoC Ethernet PHYs"
+	depends on ECONET || COMPILE_TEST
+	select MTK_NET_PHYLIB
+	select PHY_PACKAGE
+	help
+	  Supports EcoNet SoC built-in 10/100 Ethernet PHYs.
+
+	  Include support for built-in Ethernet PHYs which are present in
+	  EN751221 SoCs and bear the ID 03a2.9414 or 03a2.9412. These PHYs
+	  exist in groups of three or four depending on SoC variant.
+	  Their MMIO IDs are always 11, 10, 9, and if ID is 9412, 8. The lowest
+	  numbered PHY of the group is necessary for certain common calibration
+	  for the group, so PHY 9 (or 8) must be configured in the device tree
+	  in order for any other phy in the group to calibrate.
+	  Calibration is automatic and no e-fuse is required.
+
 config MEDIATEK_2P5GE_PHY
 	tristate "MediaTek 2.5Gb Ethernet PHYs"
 	depends on (ARM64 && ARCH_MEDIATEK) || COMPILE_TEST
diff --git a/drivers/net/phy/mediatek/Makefile b/drivers/net/phy/mediatek/Makefile
index 6f9cacf7f906..71239e92aa58 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_ECONET_FE_SOC_PHY)		+= econet-fe-soc.o
 obj-$(CONFIG_MEDIATEK_2P5GE_PHY)	+= mtk-2p5ge.o
 obj-$(CONFIG_MEDIATEK_FE_SOC_PHY)	+= mtk-fe-soc.o
 obj-$(CONFIG_MEDIATEK_GE_PHY)		+= mtk-ge.o
diff --git a/drivers/net/phy/mediatek/econet-fe-soc.c b/drivers/net/phy/mediatek/econet-fe-soc.c
new file mode 100644
index 000000000000..d2caf70d5b95
--- /dev/null
+++ b/drivers/net/phy/mediatek/econet-fe-soc.c
@@ -0,0 +1,672 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/bits.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/types.h>
+
+#include "../phylib.h"
+#include "mtk.h"
+
+#define ECONET_FEPHY_ID_EN7526C		0x03a29414
+#define ECONET_FEPHY_ID_LEGACY		0x03a29412
+
+#define ECONET_PG_G1			0x1000
+#define ECONET_PG_G2			0x2000
+#define ECONET_PG_G3			0x3000
+#define ECONET_PG_G4			0x4000
+#define ECONET_PG_G5			0x5000
+#define ECONET_PG_G6			0x6000
+#define ECONET_PG_G7			0x7000
+
+#define ECONET_PG_L0			0x8000
+#define ECONET_PG_L1			0x9000
+#define ECONET_PG_L2			0xa000
+#define ECONET_PG_L3			0xb000
+#define ECONET_PG_L4			0xc000
+
+/*
+ * These PHYs are always hard wired to specific MMIO addresses, in the legacy
+ * case: 8,9,10,11 and in the EN7526C (3 port) case: 9,10,11.
+ *
+ * The lowest numbered ("master") PHY is special and is used for certain
+ * configuration that is common to the entire block of PHYs.
+ */
+#define ECONET_LEGACY_MASTER_ADDR	8
+#define ECONET_EN7526C_MASTER_ADDR	9
+
+/*
+ * Calibration polling does not complete until ECONET_CAL_MIN_CYCLES
+ * consecutive "done" results are received. See: poll_cal_complete
+ */
+#define ECONET_CAL_MIN_CYCLES		10
+
+/*
+ * Microseconds per calibration poll cycle, calibration takes approximately
+ * ECONET_CAL_CYCLE_US * ECONET_CAL_MIN_CYCLES. Total setup time is usually
+ * less than 150x this number.
+ */
+#define ECONET_CAL_CYCLE_US		10
+
+/* Total time allowed for calibration polling before error is returned. */
+#define ECONET_CAL_TIMEOUT_US		100000
+
+/* On calibration error, re-attempt this number of times before giving up. */
+#define ECONET_RETRIES			5
+
+/*
+ * Note: These register definitions were written without the benefit of the
+ *       hardware specification, so names are given only where the meaning is
+ *       fairly obvious.
+ */
+
+/* G7R24 */
+#define ECONET_CAL_TYPE_MASK		GENMASK(14, 12)
+#define   ECONET_CAL_TYPE_R50		FIELD_PREP(ECONET_CAL_TYPE_MASK, 0x6)
+#define   ECONET_CAL_TYPE_TXOS		FIELD_PREP(ECONET_CAL_TYPE_MASK, 0x3)
+#define   ECONET_CAL_TYPE_TXAMP		FIELD_PREP(ECONET_CAL_TYPE_MASK, 0x7)
+#define ECONET_R50_ZCAL_MASK		GENMASK(11, 6)
+#define ECONET_CALIN_EN7526C		BIT(4)
+#define ECONET_CAL_DONE_EN7526C		BIT(1)
+#define ECONET_CAL_OUT_EN7526C		BIT(0)
+
+/* L4R23 */
+#define ECONET_CALIN_LEGACY		BIT(2)
+#define ECONET_CAL_DONE_LEGACY		BIT(6)
+#define ECONET_CAL_OUT_LEGACY		BIT(4)
+
+#define ECONET_DAC_IN_2V		0x0f0
+
+/* L4R17 */
+#define ECONET_TXOS_SIGN		BIT(13)
+#define ECONET_TXOS_MAG			GENMASK(12, 8)
+
+#define ECONET_TXOS_DEFAULT		0
+#define ECONET_TXOS_MAX			31
+#define ECONET_TXOS_MIN			-31
+
+#define ECONET_R50_ZCAL_DEFAULT		0x20
+#define ECONET_R50_ZCAL_MAX		0x3f
+
+/* L4R22 */
+#define ECONET_R50_TXCAL_MASK		GENMASK(15, 8)
+#define ECONET_R50_RXCAL_MASK		GENMASK(7, 0)
+
+#define ECONET_TXAMP_DEFAULT		0x12
+#define ECONET_TXAMP_MAX		0x3f
+
+/* ZCAL compensation table per chip, per phy, RX + TX */
+struct compensation {
+	s8 zcal_tx;
+	s8 zcal_rx;
+	s8 txos;
+	s8 amp;
+};
+
+static const struct compensation en751221_comp[2][4] = {
+	{
+		/* Legacy */
+		{ .zcal_tx = 2,  .zcal_rx = -4, .txos = 0, .amp = 1 }, /*  8 */
+		{ .zcal_tx = 2,  .zcal_rx = -4, .txos = 1, .amp = 2 }, /*  9 */
+		{ .zcal_tx = 2,  .zcal_rx = -4, .txos = 1, .amp = 1 }, /* 10 */
+		{ .zcal_tx = -2, .zcal_rx = -8, .txos = 1, .amp = 1 }, /* 11 */
+	},
+	{
+		/* EN7526C */
+		{ .zcal_tx = 3,  .zcal_rx = 6, .txos = 1, .amp = 0 }, /*  9 */
+		{ .zcal_tx = 2,  .zcal_rx = 5, .txos = 1, .amp = -1 }, /* 10 */
+		{ .zcal_tx = 6,  .zcal_rx = 6, .txos = 1, .amp = -1 }, /* 11 */
+		{ }, /* Unused */
+	}
+};
+
+static const u8 zcal_to_r50ohm[64] = {
+	127, 127, 127, 127, 127, 127, 126, 123, 120, 117, 114, 112, 110, 107, 105, 103,
+	101,  99,  97,  79,  77,  75,  74,  72,  70,  69,  67,  66,  65,  47,  46,  45,
+	 43,  42,  41,  40,  39,  38,  37,  36,  34,  34,  33,  32,  15,  14,  13,  12,
+	 11,  10,  10,   9,   8,   7,   7,   6,   5,   4,   4,   3,   2,   2,   1,   1
+};
+
+struct econet_socphy_shared {
+	struct phy_device *master_phy;
+};
+
+static struct compensation get_ctab(struct phy_device *phydev)
+{
+	struct econet_socphy_shared *shared = phy_package_get_priv(phydev);
+	u8 phy_offset;
+
+	phy_offset = phydev->mdio.addr - shared->master_phy->mdio.addr;
+
+	if (WARN_ON_ONCE(phy_offset >= ARRAY_SIZE(en751221_comp[0])))
+		return (struct compensation) {};
+
+	return en751221_comp[phydev->phy_id == ECONET_FEPHY_ID_EN7526C][phy_offset];
+}
+
+static void set_calin_flag(struct phy_device *phydev, bool enabled)
+{
+	struct econet_socphy_shared *shared = phy_package_get_priv(phydev);
+	u16 set;
+
+	if (phydev->phy_id == ECONET_FEPHY_ID_EN7526C) {
+		/* G7R24 is shared so phy_modify_paged is required */
+		set = enabled ? ECONET_CALIN_EN7526C : 0;
+		phy_modify_paged(phydev, ECONET_PG_G7, 24,
+				 ECONET_CALIN_EN7526C, set);
+	} else {
+		set = enabled ? ECONET_CALIN_LEGACY : 0;
+		phy_write_paged(shared->master_phy, ECONET_PG_L4, 23, set);
+	}
+}
+
+struct cal_complete_ctx {
+	u16 consecutive_trues	: 15;
+	bool started		: 1;
+	bool observed_false	: 1;
+	u16 aborted_tries	: 7;
+};
+
+/**
+ * poll_cal_complete - poll for calibration completion.
+ *
+ * Because some legacy chips are known to be unreliable (false positives),
+ * reference code adds 10,000us of fixed delay per calibration cycle.
+ * Practically, there are about 10-50 calibration cycles for each of the
+ * three tunables, so adding a significant fixed delay is costly.
+ *
+ * This implementation instead starts with CALIN disabled, polls for
+ * done status false, then enables CALIN and polls until a true done
+ * status is recorded ECONET_CAL_MIN_CYCLES times consecutively.
+ *
+ * @param phydev The PHY
+ * @param ctx    A state machine for polling
+ * @returns:
+ *   < 0 on error
+ *   0 not yet complete
+ *   1 complete false
+ *   2 complete true
+ */
+static int poll_cal_complete(struct phy_device *phydev,
+			     struct cal_complete_ctx *ctx)
+{
+	struct econet_socphy_shared *shared = phy_package_get_priv(phydev);
+	int done_mask;
+	int out_mask;
+	int ret;
+
+	if (phydev->phy_id == ECONET_FEPHY_ID_EN7526C) {
+		ret = phy_read_paged(phydev, ECONET_PG_G7, 24);
+		done_mask = ECONET_CAL_DONE_EN7526C;
+		out_mask = ECONET_CAL_OUT_EN7526C;
+	} else {
+		ret = phy_read_paged(shared->master_phy, ECONET_PG_L4, 23);
+		done_mask = ECONET_CAL_DONE_LEGACY;
+		out_mask = ECONET_CAL_OUT_LEGACY;
+	}
+	if (ret < 0)
+		return ret;
+
+	if ((ret & done_mask) == 0) {
+		ctx->observed_false = true;
+		ctx->aborted_tries += (ctx->consecutive_trues > 0);
+		ctx->consecutive_trues = 0;
+
+		if (!ctx->started) {
+			set_calin_flag(phydev, true);
+			ctx->started = true;
+			return poll_cal_complete(phydev, ctx);
+		}
+
+		return 0;
+	}
+
+	ctx->consecutive_trues++;
+
+	if (ctx->consecutive_trues < ECONET_CAL_MIN_CYCLES || !ctx->observed_false)
+		return 0;
+
+	if (ctx->aborted_tries)
+		phydev_warn(phydev, "Calibration cycle %d false positives\n",
+			    ctx->aborted_tries);
+
+	return ((ret & out_mask) != 0) ? 2 : 1;
+}
+
+/**
+ * poll_cal_complete - check for calibration register
+ *
+ * @param phydev the PHY
+ * @returns: -error or 1 if calibration result is true.
+ */
+static int en751221_fephy_cal_cycle(struct phy_device *phydev)
+{
+	struct cal_complete_ctx cctx = {};
+	int out;
+	int ret;
+
+	set_calin_flag(phydev, false);
+
+	ret = read_poll_timeout(poll_cal_complete, out, out != 0,
+				ECONET_CAL_CYCLE_US, ECONET_CAL_TIMEOUT_US,
+				false, phydev, &cctx);
+
+	if (ret < 0) {
+		phydev_err(phydev, "Calibration timeout %d (%d / %d / %d / %d)\n",
+			   ret, cctx.consecutive_trues, cctx.observed_false,
+			   cctx.aborted_tries, cctx.started);
+	} else if (out < 0) {
+		phydev_err(phydev, "Calibration error %d\n", out);
+		ret = out;
+	} else {
+		ret = out > 1;
+	}
+
+	set_calin_flag(phydev, false);
+	return ret;
+}
+
+static int en751221_fephy_r50(struct phy_device *phydev)
+{
+	struct econet_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct compensation ctab = get_ctab(phydev);
+	int zcal_sz = ARRAY_SIZE(zcal_to_r50ohm);
+	int initial_comp_out;
+	int polarity = 0;
+	int ret = -EIO;
+	int comp_out;
+	u16 rxcal;
+	u16 txcal;
+	u8 zcal;
+
+	if (phydev->phy_id == ECONET_FEPHY_ID_EN7526C)
+		phy_write_paged(phydev, ECONET_PG_G7, 24, ECONET_CAL_TYPE_R50);
+	else
+		phy_write_paged(shared->master_phy, ECONET_PG_L3, 25, 0xc000);
+
+	phy_modify_paged(phydev, ECONET_PG_L3, 25, 0x1000, 0x1000);
+
+	/*
+	 * priority is unconditionally set to 1 or -1 in the first cycle and
+	 * zcal is unsigned so this always exits.
+	 */
+	for (zcal = ECONET_R50_ZCAL_DEFAULT;
+	     zcal <= ECONET_R50_ZCAL_MAX;
+	     zcal += polarity
+	) {
+		if (phydev->phy_id == ECONET_FEPHY_ID_EN7526C)
+			phy_modify_paged(phydev, ECONET_PG_G7, 24,
+					 ECONET_R50_ZCAL_MASK,
+					 FIELD_PREP(ECONET_R50_ZCAL_MASK,
+						    zcal));
+		else
+			phy_write_paged(shared->master_phy, ECONET_PG_L3, 26,
+					zcal);
+
+		comp_out = en751221_fephy_cal_cycle(phydev);
+		if (comp_out < 0) {
+			/* Error */
+			ret = comp_out;
+			goto out;
+		}
+
+		if (polarity == 0) {
+			/* First cycle */
+			initial_comp_out = comp_out;
+			polarity = comp_out ? -1 : 1;
+		} else if (initial_comp_out != comp_out) {
+			/* Found */
+			ret = 0;
+			break;
+		}
+	}
+
+	if (ret)
+		goto out;
+
+	rxcal = max(0, min(zcal_sz - 1, ctab.zcal_rx + zcal));
+	txcal = max(0, min(zcal_sz - 1, ctab.zcal_tx + zcal));
+
+	phy_write_paged(phydev, ECONET_PG_L4, 22,
+			FIELD_PREP(ECONET_R50_TXCAL_MASK, txcal) |
+			FIELD_PREP(ECONET_R50_RXCAL_MASK, rxcal));
+
+out:
+	/* Zero out registers. */
+	phy_write_paged(phydev, ECONET_PG_L3, 25, 0x0000);
+
+	if (phydev->phy_id == ECONET_FEPHY_ID_EN7526C)
+		phy_write_paged(phydev, ECONET_PG_G7, 24, 0x0000);
+	else
+		phy_write_paged(shared->master_phy, ECONET_PG_L3, 25, 0xc000);
+
+	phy_write_paged(phydev, ECONET_PG_L3, 25, 0x0000);
+
+	return ret;
+}
+
+static int en751221_fephy_tx_offset(struct phy_device *phydev)
+{
+	struct econet_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct compensation ctab = get_ctab(phydev);
+	int initial_comp_out;
+	int polarity = 0;
+	int ret = -EIO;
+	u16 offset_bin;
+	int comp_out;
+	int offset;
+
+	phy_write_paged(phydev, ECONET_PG_L0, 0, 0x2100);
+	phy_write_paged(phydev, ECONET_PG_L0, 26, 0x5200); /* fix MDI */
+
+	if (phydev->phy_id == ECONET_FEPHY_ID_EN7526C) {
+		phy_write_paged(phydev, ECONET_PG_G7, 24, ECONET_CAL_TYPE_TXOS);
+		phy_write_paged(phydev, ECONET_PG_L3, 25, 0x0400);
+	} else {
+		phy_write_paged(shared->master_phy, ECONET_PG_L3, 25, 0x4800);
+		phy_write_paged(phydev, ECONET_PG_L3, 25, 0x4c00);
+	}
+
+	phy_modify_paged(phydev, ECONET_PG_G4, 21, 0x0800, 0x0800);
+
+	phy_modify_paged(phydev, ECONET_PG_L0, 30, 0x02c0, 0x02c0);
+
+	phy_write_paged(phydev, ECONET_PG_G1, 26, 0x8000);
+
+	for (offset = ECONET_TXOS_DEFAULT;
+	     offset <= ECONET_TXOS_MAX && offset >= ECONET_TXOS_MIN;
+	     offset += polarity
+	) {
+		offset_bin = FIELD_PREP(ECONET_TXOS_MAG, abs(offset));
+		if (offset < 0)
+			offset_bin |= ECONET_TXOS_SIGN;
+
+		phy_write_paged(phydev, ECONET_PG_L4, 17, offset_bin);
+
+		comp_out = en751221_fephy_cal_cycle(phydev);
+		if (comp_out < 0) {
+			ret = comp_out;
+			goto out;
+		}
+
+		if (polarity == 0) {
+			/* First cycle */
+			initial_comp_out = comp_out;
+			polarity = comp_out ? -1 : 1;
+		} else if (initial_comp_out != comp_out) {
+			/* Found */
+			ret = 0;
+			break;
+		}
+	}
+
+	if (ret) {
+		offset = ECONET_TXOS_DEFAULT;
+	} else {
+		offset += ctab.txos * polarity;
+
+		if (offset > ECONET_TXOS_MAX || offset < ECONET_TXOS_MIN)
+			offset -= ctab.txos * polarity;
+	}
+
+	offset_bin = FIELD_PREP(ECONET_TXOS_MAG, abs(offset));
+	if (offset < 0)
+		offset_bin |= ECONET_TXOS_SIGN;
+	phy_write_paged(phydev, ECONET_PG_L4, 17, offset_bin);
+
+out:
+	if (phydev->phy_id == ECONET_FEPHY_ID_EN7526C)
+		phy_write_paged(phydev, ECONET_PG_G7, 24, 0x0000);
+	else
+		phy_write_paged(shared->master_phy, ECONET_PG_L3, 25, 0x0000);
+
+	phy_write_paged(phydev, ECONET_PG_L3, 25, 0x0000);
+
+	return ret;
+}
+
+static int en751221_fephy_tx_amp(struct phy_device *phydev)
+{
+	struct econet_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct compensation ctab = get_ctab(phydev);
+	int initial_comp_out;
+	int polarity = 0;
+	int ret = -EIO;
+	int comp_out;
+	u8 tx_amp;
+
+	phy_write_paged(phydev, ECONET_PG_L0, 0, 0x2100);
+	phy_write_paged(phydev, ECONET_PG_L0, 26, 0x5203);
+	phy_write_paged(phydev, ECONET_PG_G2, 25, 0x10c0);
+
+	phy_write_paged(phydev, ECONET_PG_G1, 26, 0x8000 | ECONET_DAC_IN_2V);
+	phy_write_paged(phydev, ECONET_PG_G4, 21, 0x0800);
+	phy_write_paged(phydev, ECONET_PG_L0, 30, 0x02c0);
+	phy_write_paged(phydev, ECONET_PG_L4, 21, 0x0000);
+
+	if (phydev->phy_id == ECONET_FEPHY_ID_EN7526C) {
+		phy_write_paged(phydev, ECONET_PG_G7, 24, ECONET_CAL_TYPE_TXAMP);
+		phy_write_paged(phydev, ECONET_PG_L3, 25, 0x0600);
+	} else {
+		phy_write_paged(shared->master_phy, ECONET_PG_L3, 25, 0xca00);
+		phy_write_paged(phydev, ECONET_PG_L3, 25, 0xca00 | 0x0400);
+	}
+
+	for (tx_amp = ECONET_TXAMP_DEFAULT;
+	     tx_amp <= ECONET_TXAMP_MAX;
+	     tx_amp += polarity
+	) {
+		phy_write_paged(phydev, ECONET_PG_L2, 23, tx_amp);
+
+		comp_out = en751221_fephy_cal_cycle(phydev);
+		if (comp_out < 0) {
+			/* Error */
+			ret = comp_out;
+			goto out;
+		}
+
+		if (polarity == 0) {
+			/* First cycle */
+			initial_comp_out = comp_out;
+			polarity = comp_out ? -1 : 1;
+		} else if (initial_comp_out != comp_out) {
+			/* Found */
+			ret = 0;
+			break;
+		}
+	}
+
+	if (ret)
+		tx_amp = ECONET_TXAMP_DEFAULT;
+	else if (tx_amp + ctab.amp < ECONET_TXAMP_MAX)
+		tx_amp += ctab.amp;
+
+	phy_write_paged(phydev, ECONET_PG_L2, 23, tx_amp);
+
+out:
+	if (phydev->phy_id == ECONET_FEPHY_ID_EN7526C) {
+		phy_write_paged(phydev, ECONET_PG_G7, 24, 0x0000);
+		phy_write_paged(phydev, ECONET_PG_L0, 30, 0x0000);
+	} else {
+		phy_write_paged(shared->master_phy, ECONET_PG_L3, 25, 0x0000);
+	}
+
+	phy_write_paged(phydev, ECONET_PG_L3, 25, 0x0000);
+
+	return ret;
+}
+
+static int en751221_fephy_config_init(struct phy_device *phydev)
+{
+	struct econet_socphy_shared *shared = phy_package_get_priv(phydev);
+	u16 l0r26_temp;
+	int ret;
+	int i;
+
+	/*
+	 * These PHYs are all part of the same overall hardware device, and
+	 * some calibration registers are shared between all PHYs in the group.
+	 *
+	 * There is a "master" PHY of the group which contains certain shared
+	 * calibration registers that are not present on the other PHYs.
+	 *
+	 * master_phy is set in en751221_fephy_probe when the master PHY is
+	 * probed. But if the master PHY is not included in the devicetree then
+	 * calibration cannot proceed for any PHY of the group.
+	 */
+	if (!shared->master_phy) {
+		phydev_err(phydev,
+			   "master PHY (lowest number) must be configured\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (phydev->mdio.addr < shared->master_phy->mdio.addr)
+		return -EOPNOTSUPP;
+
+	/* Global registers */
+	phy_write_paged(phydev, ECONET_PG_G4, 26, 0x8044);
+	phy_write_paged(phydev, ECONET_PG_G5, 21, 0x00ea);
+	phy_write_paged(phydev, ECONET_PG_G5, 27, 0x02f0);
+
+	/* Local registers */
+	phy_write_paged(phydev, ECONET_PG_L0, 30, 0xa000);
+	phy_write_paged(phydev, ECONET_PG_L1, 22, 0xf000);
+	phy_write_paged(phydev, ECONET_PG_L2, 22, 0x4444);
+	phy_write_paged(phydev, ECONET_PG_L2, 24, 0x0c0c);
+	phy_write_paged(phydev, ECONET_PG_L2, 28, 0x7c44);
+	phy_write_paged(phydev, ECONET_PG_L2, 30, 0x0005);
+	phy_write_paged(phydev, ECONET_PG_L3, 17, 0x0000);
+
+	/* For E1/E2, E3 requires 0x2220, but no known EN751221 E3 chips exist */
+	phy_write_paged(phydev, ECONET_PG_L0, 30, 0x2200);
+
+	if (phydev->phy_id == ECONET_FEPHY_ID_EN7526C) {
+		 /* 100Mb tx p2z_mid, z2n_mid, z2n_ovs_post, n2z_mid */
+		phy_write_paged(phydev, ECONET_PG_G5, 22, 0x0030);
+		phy_write_paged(phydev, ECONET_PG_G5, 25, 0x0248);
+		phy_write_paged(phydev, ECONET_PG_G5, 27, 0x02f0);
+		phy_write_paged(phydev, ECONET_PG_G5, 28, 0x0230);
+	}
+
+	ret = phy_read_paged(phydev, ECONET_PG_L0, 26);
+	if (ret < 0)
+		return ret;
+
+	l0r26_temp = ret;
+
+	/* BG voltage */
+	phy_write_paged(phydev, ECONET_PG_G2, 25, 0x10c0);
+
+	/* MDI */
+	phy_write_paged(phydev, ECONET_PG_L0, 26, 0x5603);
+
+	/* disable tx slew control */
+	phy_write_paged(phydev, ECONET_PG_L4, 21, 0x0000);
+
+	phy_write_paged(phydev, ECONET_PG_L0, 0, 0x2100);
+
+	for (i = 0; i < ECONET_RETRIES; i++) {
+		ret = en751221_fephy_r50(phydev);
+		if (!ret)
+			break;
+	}
+	if (ret)
+		return ret;
+
+	for (i = 0; i < ECONET_RETRIES; i++) {
+		ret = en751221_fephy_tx_offset(phydev);
+		if (!ret)
+			break;
+	}
+	if (ret)
+		return ret;
+
+	for (i = 0; i < ECONET_RETRIES; i++) {
+		ret = en751221_fephy_tx_amp(phydev);
+		if (!ret)
+			break;
+	}
+	if (ret)
+		return ret;
+
+	phy_write_paged(phydev, ECONET_PG_G1, 26, 0x0000);
+	phy_write_paged(phydev, ECONET_PG_L0, 26, l0r26_temp);
+	phy_write_paged(phydev, ECONET_PG_G1, 26, 0x0000);
+
+	phy_write_paged(phydev, ECONET_PG_L2, 22, 0x4444);
+	phy_write_paged(phydev, ECONET_PG_L0, 0, 0x3100);
+
+	return 0;
+}
+
+static int en751221_fephy_probe(struct phy_device *phydev)
+{
+	int master = ECONET_EN7526C_MASTER_ADDR;
+	struct econet_socphy_shared *shared;
+	struct mtk_socphy_priv *priv;
+	int ret;
+
+	if (phydev->phy_id != ECONET_FEPHY_ID_EN7526C)
+		master = ECONET_LEGACY_MASTER_ADDR;
+
+	ret = devm_phy_package_join(&phydev->mdio.dev, phydev, master,
+				    sizeof(struct econet_socphy_shared));
+	if (ret)
+		return ret;
+
+	shared = phy_package_get_priv(phydev);
+
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	if (phydev->mdio.addr == master)
+		shared->master_phy = phydev;
+
+	phydev->priv = priv;
+
+	return 0;
+}
+
+/*
+ * ECONET_FEPHY_ID_LEGACY collides with MTK_GPHY_ID_MT7530
+ * but this is a 10/100 PHY so ESTATUS does not list 1000BASE-T.
+ */
+static int en751221_fephy_match(struct phy_device *phydev,
+				const struct phy_driver *phydrv)
+{
+	if (phydev->phy_id == ECONET_FEPHY_ID_EN7526C)
+		return true;
+
+	if (phydev->phy_id != ECONET_FEPHY_ID_LEGACY)
+		return false;
+
+	return (phy_read(phydev, MII_ESTATUS) & ESTATUS_1000_TFULL) == 0;
+}
+
+static struct phy_driver en751221_fephy_driver[] = {
+	{
+		PHY_ID_MATCH_EXACT(ECONET_FEPHY_ID_EN7526C),
+		.name		= "EcoNet EN751221 FEPHY",
+		.config_init	= en751221_fephy_config_init,
+		.config_intr	= genphy_no_config_intr,
+		.handle_interrupt = genphy_handle_interrupt_no_ack,
+		.match_phy_device = en751221_fephy_match,
+		.probe		= en751221_fephy_probe,
+		.read_page	= mtk_phy_read_page,
+		.write_page	= mtk_phy_write_page,
+	}
+};
+
+module_phy_driver(en751221_fephy_driver);
+
+static const struct mdio_device_id __maybe_unused en751221_fephy_tbl[] = {
+	{ PHY_ID_MATCH_EXACT(ECONET_FEPHY_ID_EN7526C) },
+	{ PHY_ID_MATCH_EXACT(ECONET_FEPHY_ID_LEGACY) },
+	{ }
+};
+
+MODULE_DESCRIPTION("EcoNet SoC 10/100 Ethernet PHY driver");
+MODULE_AUTHOR("Caleb James DeLisle <cjd@cjdns.fr>");
+MODULE_LICENSE("GPL");
+
+MODULE_DEVICE_TABLE(mdio, en751221_fephy_tbl);
-- 
2.39.5



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

* Re: [PATCH v2 net-next 1/2] net: phy: mediatek: differentiate MT7530 GE PHY from EcoNet FE PHY
  2026-08-27  6:59 ` [PATCH v2 net-next 1/2] net: phy: mediatek: differentiate MT7530 GE PHY from EcoNet FE PHY Caleb James DeLisle
@ 2026-08-27  8:23   ` Paolo Abeni
  0 siblings, 0 replies; 4+ messages in thread
From: Paolo Abeni @ 2026-08-27  8:23 UTC (permalink / raw)
  To: Caleb James DeLisle, netdev
  Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, matthias.bgg,
	angelogioacchino.delregno, daniel, naseefkm, joey, linux-kernel,
	linux-arm-kernel, linux-mediatek

On 8/27/26 8:59 AM, Caleb James DeLisle wrote:
> MT7530 gigabit PHY has the same ID as an EcoNet EN751221 10/100
> PHY. Differentiate by checking whether the PHY supports 1000BASE-T
> which is always supported by MT7530.
> 
> Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
## Form letter - net-next-closed

net-next pull request for v7.3 has already been merged, and therefore
the net-next tree is closed for new drivers, features, code refactoring
and optimizations. We are currently accepting bug fixes only.

Please repost when net-next reopens after Aug 31st.

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

See:
https://www.kernel.org/doc/html/next/process/maintainer-netdev.html#development-cycle
-- 
pw-bot: defer
pv-bot: closed



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

end of thread, other threads:[~2026-08-27  8:24 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-27  6:59 [PATCH v2 net-next 0/2] net: phy: mediatek: add driver for EcoNet Fast Ethernet SoC PHYs Caleb James DeLisle
2026-08-27  6:59 ` [PATCH v2 net-next 1/2] net: phy: mediatek: differentiate MT7530 GE PHY from EcoNet FE PHY Caleb James DeLisle
2026-08-27  8:23   ` Paolo Abeni
2026-08-27  6:59 ` [PATCH v2 net-next 2/2] net: phy: mediatek: add driver for EcoNet Fast Ethernet SoC PHYs Caleb James DeLisle

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