Devicetree
 help / color / mirror / Atom feed
* [PATCH net-next v2 0/8] net: phy: add X-Powers AC200/AC300 EPHY support
@ 2026-08-05  2:26 James Hilliard
  2026-08-05  2:26 ` [PATCH net-next v2 1/8] dt-bindings: net: x-powers: add AC200 EPHY control James Hilliard
                   ` (7 more replies)
  0 siblings, 8 replies; 26+ messages in thread
From: James Hilliard @ 2026-08-05  2:26 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Andrew Lunn, Heiner Kallweit, Russell King,
	Heiko Stuebner
  Cc: netdev, devicetree, linux-kernel, mfd, linux-arm-kernel,
	linux-rockchip, James Hilliard

This revision deliberately limits the series to the common PHY and the
two package-specific control paths. It does not contain runtime Device
Tree selection, generic MDIO reconfiguration, LEDs, interrupts,
Wake-on-LAN, statistics, EDPD or PHY-autonomous EEE.

The AC200 and AC300 MDIO link endpoints report the same Clause 22 PHY
identifier and use a compatible link-side paged register layout. Neither
endpoint is accessible until its package-specific control path has
initialized it. AC200 uses registers in its I2C MFD regmap, while AC300
exposes a separate non-PHY Clause 22 control device.

Keep those transport-specific resources in separate control drivers and
share only a small private operations interface with the common PHY
driver. A board described by this series references exactly one enabled
control device. The PHY driver creates a managed device link to that
provider, powers the link block before accessing it, and passes the PHY
address and MAC-selected interface mode to the provider.

This split is independent of CONFIG_OF_DYNAMIC. The initial series uses
only fixed descriptions and has no Dynamic MDIO dependency. A separate
development branch can later use bit 0 of the same configuration field
for runtime selection without changing the binding or provider interface
or duplicating the common PHY code.

The series adds:

  - a minimal AC200 MFD core and AC200 EPHY control child;
  - the AC300 non-PHY MDIO control driver; and
  - the shared AC200/AC300 PHY driver with basic link operation, package
    calibration, power sequencing and vendor analog initialization.

The reduced v2 series was built and boot-tested on an H616 system with
an AC300 package. The raw SID bytes at offset 0x2c were 0x06 0x05, making
the packed two-bit configuration field 0b01: AC300 with normal calibration
tuning. The driver validated that value against the fixed AC300 provider,
bound at MDIO address 0, negotiated a 100 Mbps full-duplex link and passed
network traffic.

The H616 EMAC1 support has been split into an independent series. Board
Device Trees and defconfig updates will follow after the driver and
binding interfaces settle. That later H616 integration will use the
Allwinner H616 PWM controller as the companion input clock; this code
series neither applies on nor builds against the PWM series.

The AC200 portions build on earlier work by Jernej Skrabec and Andre
Przywara:

  https://github.com/jernejsk/linux-1/commits/ac200-v4

Public AC200 and AC300 documentation is linked from:

  https://linux-sunxi.org/AC200

The AC200 parent schema references the EPHY-control schema in patch 1.
The AC200 control driver needs the MFD core, so those patches need to be
kept together or placed on an immutable branch. The AC300 control driver
and common PHY driver have no link-time dependency on the AC200 backend.

Assisted-by: OpenAI Codex (gpt-5.6-sol, max)
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v1 -> v2:
  - split H616 EMAC1, board Device Trees and defconfig into independent
    series
  - remove Dynamic MDIO changes and runtime Device Tree provider
    selection; require exactly one enabled provider
  - remove the PHY-autonomous EEE dependency and keep Intelligent EEE
    disabled
  - defer LEDs, interrupts, Wake-on-LAN, statistics and EDPD to separate
    feature patches
  - make the MAC-provided phydev->interface the sole interface-mode
    source
  - retain separate AC200 and AC300 control drivers behind a minimal
    private operations structure
  - retain the two-bit package-configuration cell, validate its
    variant bit against the fixed provider and use its AC300 tuning bit
  - reduce the initial AC200 MFD core to the common regmap, supplies,
    clock, reset and EPHY child
  - leave package LED pads disabled in the basic control drivers
  - Link to v1:
    https://patch.msgid.link/20260802-submit-acx00-of-dynamic-v1-v1-0-0a53cd9e21cc@gmail.com

To: Andrew Lunn <andrew+netdev@lunn.ch>
To: "David S. Miller" <davem@davemloft.net>
To: Eric Dumazet <edumazet@google.com>
To: Jakub Kicinski <kuba@kernel.org>
To: Paolo Abeni <pabeni@redhat.com>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: James Hilliard <james.hilliard1@gmail.com>
To: Lee Jones <lee@kernel.org>
To: Andrew Lunn <andrew@lunn.ch>
To: Heiner Kallweit <hkallweit1@gmail.com>
To: Russell King <linux@armlinux.org.uk>
To: Heiko Stuebner <heiko@sntech.de>
Cc: netdev@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: mfd@lists.linux.dev
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-rockchip@lists.infradead.org

---
James Hilliard (8):
      dt-bindings: net: x-powers: add AC200 EPHY control
      dt-bindings: mfd: x-powers: add AC200
      mfd: add X-Powers AC200 support
      net: phy: add X-Powers AC200 EPHY control driver
      dt-bindings: net: x-powers: add AC300 EPHY control
      net: phy: add X-Powers AC300 EPHY control driver
      dt-bindings: net: x-powers: add AC200/AC300 EPHY
      net: phy: add X-Powers AC200/AC300 EPHY driver

 .../devicetree/bindings/mfd/x-powers,ac200.yaml    |  77 +++
 .../bindings/net/x-powers,ac200-ephy-ctl.yaml      |  39 ++
 .../bindings/net/x-powers,ac300-ephy-ctl.yaml      |  70 +++
 .../bindings/net/x-powers,acx00-ephy.yaml          | 117 +++++
 drivers/mfd/Kconfig                                |  12 +
 drivers/mfd/Makefile                               |   1 +
 drivers/mfd/ac200.c                                | 207 ++++++++
 drivers/net/phy/Kconfig                            |  27 +
 drivers/net/phy/Makefile                           |   3 +
 drivers/net/phy/xpowers-ac200-ctl.c                | 294 +++++++++++
 drivers/net/phy/xpowers-ac300-ctl.c                | 423 ++++++++++++++++
 drivers/net/phy/xpowers-acx00.c                    | 544 +++++++++++++++++++++
 drivers/net/phy/xpowers-acx00.h                    |  17 +
 13 files changed, 1831 insertions(+)
---
base-commit: b16bab325801c7be004222cad0ae296aa5982e79
change-id: 20260802-submit-acx00-of-dynamic-v1-94a0dc15f282

Best regards,
--  
James Hilliard <james.hilliard1@gmail.com>


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

* [PATCH net-next v2 1/8] dt-bindings: net: x-powers: add AC200 EPHY control
  2026-08-05  2:26 [PATCH net-next v2 0/8] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
@ 2026-08-05  2:26 ` James Hilliard
  2026-08-06  2:32   ` sashiko-bot
  2026-08-05  2:27 ` [PATCH net-next v2 2/8] dt-bindings: mfd: x-powers: add AC200 James Hilliard
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 26+ messages in thread
From: James Hilliard @ 2026-08-05  2:26 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Andrew Lunn, Heiner Kallweit, Russell King,
	Heiko Stuebner
  Cc: netdev, devicetree, linux-kernel, mfd, linux-arm-kernel,
	linux-rockchip, James Hilliard

The AC200 contains a Fast Ethernet PHY whose power, calibration and
interface-selection registers are accessed through the parent AC200 I2C
regmap.

Describe this control function and its optional SoC SID calibration
cell. The vendor sequence adds three to the calibration value modulo 16
before writing BPS_EFFUSE. If the cell is omitted, the driver reads the
AC200's internal copy of the calibration eFuse.

This node represents the package control logic, not the Clause 22 PHY.
The latter remains a separate ethernet-phy node on the MDIO bus, which
supplies its address and the MAC-selected interface mode at runtime.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 .../bindings/net/x-powers,ac200-ephy-ctl.yaml      | 39 ++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/Documentation/devicetree/bindings/net/x-powers,ac200-ephy-ctl.yaml b/Documentation/devicetree/bindings/net/x-powers,ac200-ephy-ctl.yaml
new file mode 100644
index 000000000000..5bb60542030f
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/x-powers,ac200-ephy-ctl.yaml
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/x-powers,ac200-ephy-ctl.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: X-Powers AC200 Ethernet PHY control
+
+maintainers:
+  - James Hilliard <james.hilliard1@gmail.com>
+
+description:
+  The AC200 Ethernet PHY control function configures and powers the Fast
+  Ethernet PHY in the AC200. Its registers are accessed through the parent
+  AC200 I2C regmap.
+
+properties:
+  compatible:
+    const: x-powers,ac200-ephy-ctl
+
+  nvmem-cells:
+    maxItems: 1
+    description:
+      Optional SoC SID cell containing the Ethernet PHY calibration value
+      written to the AC200 BPS_EFFUSE field after adding 3 modulo 16, as
+      prescribed by the vendor initialization sequence. If omitted, the
+      driver reads the AC200's internal copy of the calibration eFuse.
+
+  nvmem-cell-names:
+    const: calibration
+
+required:
+  - compatible
+
+dependentRequired:
+  nvmem-cells: [ nvmem-cell-names ]
+
+additionalProperties: false
+...

-- 
2.53.0


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

* [PATCH net-next v2 2/8] dt-bindings: mfd: x-powers: add AC200
  2026-08-05  2:26 [PATCH net-next v2 0/8] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
  2026-08-05  2:26 ` [PATCH net-next v2 1/8] dt-bindings: net: x-powers: add AC200 EPHY control James Hilliard
@ 2026-08-05  2:27 ` James Hilliard
  2026-08-06  2:32   ` sashiko-bot
  2026-08-05  2:27 ` [PATCH net-next v2 3/8] mfd: add X-Powers AC200 support James Hilliard
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 26+ messages in thread
From: James Hilliard @ 2026-08-05  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Andrew Lunn, Heiner Kallweit, Russell King,
	Heiko Stuebner
  Cc: netdev, devicetree, linux-kernel, mfd, linux-arm-kernel,
	linux-rockchip, James Hilliard

The AC200 is an I2C-controlled mixed-signal companion IC containing
audio, video, RTC and Fast Ethernet PHY functions.

Describe the parent device, its input clock, the four package supplies
and its optional Ethernet PHY control child. The input clock must run at
24 or 27 MHz when the Ethernet PHY is used, matching the rates encoded by
the documented EPHY clock selector.

Interrupt-controller and TV-encoder calibration properties can be added
with the child functions which consume them; the initial binding covers
only the common core and Ethernet control child implemented here.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 .../devicetree/bindings/mfd/x-powers,ac200.yaml    | 77 ++++++++++++++++++++++
 1 file changed, 77 insertions(+)

diff --git a/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml b/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml
new file mode 100644
index 000000000000..4ea56e99c370
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml
@@ -0,0 +1,77 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/x-powers,ac200.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: X-Powers AC200 mixed-signal IC
+
+maintainers:
+  - James Hilliard <james.hilliard1@gmail.com>
+
+description:
+  The AC200 is a mixed-signal companion IC containing audio, video, RTC and
+  Fast Ethernet PHY functions. Its control registers are accessed over I2C.
+
+properties:
+  compatible:
+    const: x-powers,ac200
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+    description:
+      AC200 input clock. When using the Ethernet PHY, its configured rate must
+      be 24 or 27 MHz, matching the rates encoded by the documented EPHY clock
+      selector.
+
+  ac-ldoin-supply:
+    description: 3.3 V supply for the audio-codec LDO input
+
+  ephy-vcc-supply:
+    description: 3.3 V supply for the Ethernet PHY analog front end
+
+  rtc-vcc-supply:
+    description: 3.3 V supply for the RTC
+
+  tv-vcc-supply:
+    description: 3.3 V supply for the TV encoder DAC
+
+  ethernet-phy-control:
+    $ref: /schemas/net/x-powers,ac200-ephy-ctl.yaml#
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - ac-ldoin-supply
+  - ephy-vcc-supply
+  - rtc-vcc-supply
+  - tv-vcc-supply
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        mixed-signal@10 {
+            compatible = "x-powers,ac200";
+            reg = <0x10>;
+            clocks = <&pwm 5>;
+            ac-ldoin-supply = <&reg_3v3>;
+            ephy-vcc-supply = <&reg_3v3>;
+            rtc-vcc-supply = <&reg_3v3>;
+            tv-vcc-supply = <&reg_3v3>;
+            ethernet-phy-control {
+                compatible = "x-powers,ac200-ephy-ctl";
+                nvmem-cells = <&ephy_calibration>;
+                nvmem-cell-names = "calibration";
+            };
+        };
+    };
+...

-- 
2.53.0


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

* [PATCH net-next v2 3/8] mfd: add X-Powers AC200 support
  2026-08-05  2:26 [PATCH net-next v2 0/8] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
  2026-08-05  2:26 ` [PATCH net-next v2 1/8] dt-bindings: net: x-powers: add AC200 EPHY control James Hilliard
  2026-08-05  2:27 ` [PATCH net-next v2 2/8] dt-bindings: mfd: x-powers: add AC200 James Hilliard
@ 2026-08-05  2:27 ` James Hilliard
  2026-08-06  2:32   ` sashiko-bot
  2026-08-05  2:27 ` [PATCH net-next v2 4/8] net: phy: add X-Powers AC200 EPHY control driver James Hilliard
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 26+ messages in thread
From: James Hilliard @ 2026-08-05  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Andrew Lunn, Heiner Kallweit, Russell King,
	Heiko Stuebner
  Cc: netdev, devicetree, linux-kernel, mfd, linux-arm-kernel,
	linux-rockchip, James Hilliard

The X-Powers AC200 is a mixed-signal companion IC with a paged register
map accessed over I2C.

Enable the package supplies and input clock, prevent the clock rate from
changing, and apply the vendor settling delays around common reset.
Initialize the paged regmap, report the chip and package revision, and
instantiate the Ethernet PHY control child when firmware describes it.

Cache only the common page selector. Individual function resets can
invalidate other registers without regmap's knowledge, so all functional
registers remain volatile.

The AC200 and its children cannot initiate DMA. Mark the parent as
DMA-incapable before adding the child. Register the common-reset action
before the MFD child so managed teardown removes the child before
resetting its parent, and also reset the chip during system shutdown.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 drivers/mfd/Kconfig  |  12 +++
 drivers/mfd/Makefile |   1 +
 drivers/mfd/ac200.c  | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 220 insertions(+)

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 763ce6a34782..3360c9b86be8 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -205,6 +205,18 @@ config MFD_AC100
 	  This driver include only the core APIs. You have to select individual
 	  components like codecs or RTC under the corresponding menus.
 
+config MFD_AC200
+	tristate "X-Powers AC200"
+	depends on I2C
+	depends on OF
+	select MFD_CORE
+	select REGMAP_I2C
+	help
+	  Support for the X-Powers AC200 mixed-signal companion IC. The AC200
+	  contains audio, video, RTC and Fast Ethernet PHY functions and is
+	  co-packaged with some Allwinner H6 and H616 SoCs. This driver provides
+	  the shared register access used by the individual function drivers.
+
 config MFD_AXP20X
 	tristate
 	select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index dd4bb7e77c33..890e76a9ad00 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -150,6 +150,7 @@ obj-$(CONFIG_MFD_DA9052_SPI)	+= da9052-spi.o
 obj-$(CONFIG_MFD_DA9052_I2C)	+= da9052-i2c.o
 
 obj-$(CONFIG_MFD_AC100)		+= ac100.o
+obj-$(CONFIG_MFD_AC200)		+= ac200.o
 obj-$(CONFIG_MFD_AXP20X)	+= axp20x.o
 obj-$(CONFIG_MFD_AXP20X_I2C)	+= axp20x-i2c.o
 obj-$(CONFIG_MFD_AXP20X_RSB)	+= axp20x-rsb.o
diff --git a/drivers/mfd/ac200.c b/drivers/mfd/ac200.c
new file mode 100644
index 000000000000..dd31205fe698
--- /dev/null
+++ b/drivers/mfd/ac200.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MFD core driver for the X-Powers AC200
+ *
+ * Copyright (C) 2019 Jernej Skrabec <jernej.skrabec@gmail.com>
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ *
+ * Based on the AC100 driver:
+ * Copyright (C) 2016 Chen-Yu Tsai
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define AC200_SYS_VERSION_REG			0x0000
+#define AC200_SYS_VERSION_PACKAGE_MASK		GENMASK(15, 14)
+#define AC200_SYS_VERSION_CHIP_MASK		GENMASK(11, 0)
+
+#define AC200_SYS_CONTROL_REG			0x0002
+#define AC200_SYS_CONTROL_CHIP_RESET_DEASSERT	BIT(0)
+
+/* Interface register accessible from every register page. */
+#define AC200_TWI_REG_ADDR_H	0x00fe
+#define AC200_MAX_REG		0xa1f2
+
+struct ac200 {
+	struct regmap *regmap;
+};
+
+static const char * const ac200_supplies[] = {
+	"ac-ldoin",
+	"ephy-vcc",
+	"rtc-vcc",
+	"tv-vcc",
+};
+
+static const struct regmap_range_cfg ac200_range_cfg[] = {
+	{
+		.range_max = AC200_MAX_REG,
+		.selector_reg = AC200_TWI_REG_ADDR_H,
+		.selector_mask = 0xff,
+		.window_len = 256,
+	},
+};
+
+/*
+ * Each AC200 sub-block can reset independently, invalidating its register
+ * contents without regmap's knowledge. Cache only the common page selector;
+ * this avoids a selector read-modify-write for every access on the same page
+ * without ever returning stale functional-register values.
+ */
+static bool ac200_volatile_reg(struct device *dev, unsigned int reg)
+{
+	return reg != AC200_TWI_REG_ADDR_H;
+}
+
+static const struct regmap_config ac200_regmap_config = {
+	.name = "ac200",
+	.reg_bits = 8,
+	.reg_stride = 2,
+	.val_bits = 16,
+	.ranges = ac200_range_cfg,
+	.num_ranges = ARRAY_SIZE(ac200_range_cfg),
+	.max_register = AC200_MAX_REG,
+	.volatile_reg = ac200_volatile_reg,
+	.cache_type = REGCACHE_MAPLE,
+};
+
+static const struct mfd_cell ac200_cells[] = {
+	{
+		.name = "ac200-ephy-ctl",
+		.of_compatible = "x-powers,ac200-ephy-ctl",
+	},
+};
+
+static int ac200_disable(struct ac200 *ac200)
+{
+	return regmap_write(ac200->regmap, AC200_SYS_CONTROL_REG, 0);
+}
+
+static void ac200_disable_action(void *data)
+{
+	ac200_disable(data);
+}
+
+static int ac200_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct device_node *ephy_node __free(device_node) = NULL;
+	struct ac200 *ac200;
+	struct clk *clk;
+	unsigned int version;
+	int ret;
+
+	ac200 = devm_kzalloc(dev, sizeof(*ac200), GFP_KERNEL);
+	if (!ac200)
+		return -ENOMEM;
+
+	ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ac200_supplies),
+					     ac200_supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to enable supplies\n");
+
+	clk = devm_clk_get_enabled(dev, NULL);
+	if (IS_ERR(clk))
+		return dev_err_probe(dev, PTR_ERR(clk),
+				     "failed to enable input clock\n");
+
+	ret = devm_clk_rate_exclusive_get(dev, clk);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to lock clock rate\n");
+
+	ac200->regmap = devm_regmap_init_i2c(client, &ac200_regmap_config);
+	if (IS_ERR(ac200->regmap))
+		return dev_err_probe(dev, PTR_ERR(ac200->regmap),
+				     "failed to initialize regmap\n");
+
+	i2c_set_clientdata(client, ac200);
+
+	/*
+	 * No minimum delay is documented. Match the vendor driver's 40 ms delay
+	 * before its first AC200 register access after enabling the input clock.
+	 */
+	msleep(40);
+
+	ret = regmap_read(ac200->regmap, AC200_SYS_VERSION_REG, &version);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to read chip version\n");
+
+	dev_info(dev, "AC200 revision %#lx in package %lu\n",
+		 FIELD_GET(AC200_SYS_VERSION_CHIP_MASK, version),
+		 FIELD_GET(AC200_SYS_VERSION_PACKAGE_MASK, version));
+
+	/* Run after the MFD children have been removed. */
+	ret = devm_add_action_or_reset(dev, ac200_disable_action, ac200);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL_REG, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL_REG,
+			   AC200_SYS_CONTROL_CHIP_RESET_DEASSERT);
+	if (ret)
+		return ret;
+
+	/* Match the settling interval used by the vendor initialization. */
+	usleep_range(1000, 2000);
+
+	/* Neither the AC200 nor its child devices can perform DMA. */
+	dev->coherent_dma_mask = 0;
+	dev->dma_mask = &dev->coherent_dma_mask;
+	ephy_node = of_get_compatible_child(dev->of_node,
+					    "x-powers,ac200-ephy-ctl");
+	if (!ephy_node)
+		return 0;
+
+	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, ac200_cells,
+				   ARRAY_SIZE(ac200_cells), NULL, 0, NULL);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to add MFD devices\n");
+
+	return 0;
+}
+
+static void ac200_shutdown(struct i2c_client *client)
+{
+	struct ac200 *ac200 = i2c_get_clientdata(client);
+
+	ac200_disable(ac200);
+}
+
+static const struct of_device_id ac200_of_match[] = {
+	{ .compatible = "x-powers,ac200" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ac200_of_match);
+
+static const struct i2c_device_id ac200_i2c_ids[] = {
+	{ "ac200" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ac200_i2c_ids);
+
+static struct i2c_driver ac200_driver = {
+	.driver = {
+		.name = "ac200",
+		.of_match_table = ac200_of_match,
+	},
+	.probe = ac200_probe,
+	.shutdown = ac200_shutdown,
+	.id_table = ac200_i2c_ids,
+};
+module_i2c_driver(ac200_driver);
+
+MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
+MODULE_DESCRIPTION("X-Powers AC200 MFD core driver");
+MODULE_LICENSE("GPL");

-- 
2.53.0


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

* [PATCH net-next v2 4/8] net: phy: add X-Powers AC200 EPHY control driver
  2026-08-05  2:26 [PATCH net-next v2 0/8] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
                   ` (2 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH net-next v2 3/8] mfd: add X-Powers AC200 support James Hilliard
@ 2026-08-05  2:27 ` James Hilliard
  2026-08-06  2:32   ` sashiko-bot
  2026-08-06  9:19   ` Jagielski, Jedrzej
  2026-08-05  2:27 ` [PATCH net-next v2 5/8] dt-bindings: net: x-powers: add AC300 EPHY control James Hilliard
                   ` (3 subsequent siblings)
  7 siblings, 2 replies; 26+ messages in thread
From: James Hilliard @ 2026-08-05  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Andrew Lunn, Heiner Kallweit, Russell King,
	Heiko Stuebner
  Cc: netdev, devicetree, linux-kernel, mfd, linux-arm-kernel,
	linux-rockchip, James Hilliard

The AC200 Fast Ethernet PHY needs package registers in the parent I2C
regmap to be configured before its Clause 22 endpoint becomes usable.

Add a control driver which obtains the calibration value, applies the
vendor offset, selects the documented 24 or 27 MHz input clock, programs
the link PHY address and performs the required reset, clock and shutdown
sequence. Use the optional SoC SID cell when supplied and otherwise read
the AC200's internal calibration eFuse.

Early PHY creation occurs before a MAC has attached, so use reset-default
MII initially. A separate operation later applies phydev->interface; when
the block is already powered it changes only RMII_SEL before the normal
PHY soft reset.

Expose serialized, idempotent power and interface operations to the
common PHY driver. Enable only the MII I/O pads in this basic driver;
dedicated LED outputs remain disabled until a later LED patch describes
and manages them.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 drivers/net/phy/Kconfig             |   9 ++
 drivers/net/phy/Makefile            |   1 +
 drivers/net/phy/xpowers-ac200-ctl.c | 294 ++++++++++++++++++++++++++++++++++++
 drivers/net/phy/xpowers-acx00.h     |  15 ++
 4 files changed, 319 insertions(+)

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index a29d3fed8a05..6119f4de880d 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -475,6 +475,15 @@ config VITESSE_PHY
 	help
 	  Currently supports the vsc8244
 
+config XPOWERS_AC200_PHY_CTL
+	tristate "X-Powers AC200 Ethernet PHY control"
+	depends on MFD_AC200
+	help
+	  Enable the control driver for the Fast Ethernet PHY function in
+	  the X-Powers AC200 mixed-signal companion IC. It programs the PHY
+	  address, interface mode, calibration and I/O controls through the
+	  parent AC200 I2C regmap.
+
 config XILINX_GMII2RGMII
 	tristate "Xilinx GMII2RGMII converter driver"
 	help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index e23df5e836e9..f81854fc9f12 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -100,4 +100,5 @@ obj-$(CONFIG_SMSC_PHY)		+= smsc.o
 obj-$(CONFIG_STE10XP)		+= ste10Xp.o
 obj-$(CONFIG_TERANETICS_PHY)	+= teranetics.o
 obj-$(CONFIG_VITESSE_PHY)	+= vitesse.o
+obj-$(CONFIG_XPOWERS_AC200_PHY_CTL) += xpowers-ac200-ctl.o
 obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
diff --git a/drivers/net/phy/xpowers-ac200-ctl.c b/drivers/net/phy/xpowers-ac200-ctl.c
new file mode 100644
index 000000000000..c69e6a8c71a4
--- /dev/null
+++ b/drivers/net/phy/xpowers-ac200-ctl.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * X-Powers AC200 Ethernet PHY control driver
+ *
+ * Copyright (c) 2022 Arm Ltd. (Andre Przywara <andre.przywara@arm.com>)
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include "xpowers-acx00.h"
+
+#define AC200_EPHY_BPS_EFFUSE_OFFSET	3
+
+#define AC200_SYS_EPHY_CTL0_REG			0x0014
+#define AC200_EPHY_RESET_DEASSERT		BIT(0)
+#define AC200_EPHY_SYSCLK_ENABLE			BIT(1)
+
+#define AC200_SYS_EPHY_CTL1_REG			0x0016
+#define AC200_EPHY_MII_IO_ENABLE			BIT(0)
+
+/* AC200-internal copy of the Ethernet PHY calibration eFuse. */
+#define AC200_EFUSE_EPHY_REG			0x8004
+
+#define AC200_EPHY_CTL_REG			0x6000
+#define AC200_EPHY_SHUTDOWN			BIT(0)
+#define AC200_EPHY_CLK_SEL_24_MHZ		BIT(2)
+#define AC200_EPHY_PHY_ADDR_MASK			GENMASK(8, 4)
+#define AC200_EPHY_RMII_SEL			BIT(11)
+#define AC200_EPHY_BPS_EFFUSE_MASK		GENMASK(15, 12)
+
+struct ac200_ephy_ctl {
+	struct acx00_ephy_control control;
+	struct regmap *regmap;
+	struct mutex lock; /* Serializes power sequencing and state. */
+	u16 ephy_ctl;
+	unsigned int phy_addr;
+	phy_interface_t interface;
+	bool powered;
+};
+
+static u16 ac200_ephy_ctl_config(const struct ac200_ephy_ctl *priv)
+{
+	return priv->ephy_ctl |
+		(priv->interface == PHY_INTERFACE_MODE_RMII ?
+		 AC200_EPHY_RMII_SEL : 0) |
+		FIELD_PREP(AC200_EPHY_PHY_ADDR_MASK, priv->phy_addr);
+}
+
+static int ac200_ephy_ctl_power_off_locked(struct ac200_ephy_ctl *priv)
+{
+	int err;
+	int ret;
+
+	if (!priv->powered)
+		return 0;
+
+	ret = regmap_write(priv->regmap, AC200_EPHY_CTL_REG,
+			   ac200_ephy_ctl_config(priv) | AC200_EPHY_SHUTDOWN);
+	err = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL1_REG, 0);
+	if (!ret)
+		ret = err;
+	err = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0_REG, 0);
+	if (!ret)
+		ret = err;
+
+	priv->powered = false;
+
+	return ret;
+}
+
+static int ac200_ephy_ctl_power_off(struct acx00_ephy_control *control)
+{
+	struct ac200_ephy_ctl *priv =
+		container_of(control, struct ac200_ephy_ctl, control);
+	int ret;
+
+	mutex_lock(&priv->lock);
+	ret = ac200_ephy_ctl_power_off_locked(priv);
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+static int
+ac200_ephy_ctl_set_interface(struct acx00_ephy_control *control,
+			     phy_interface_t interface)
+{
+	struct ac200_ephy_ctl *priv =
+		container_of(control, struct ac200_ephy_ctl, control);
+	u16 value;
+	int ret = 0;
+
+	switch (interface) {
+	case PHY_INTERFACE_MODE_MII:
+		value = 0;
+		break;
+	case PHY_INTERFACE_MODE_RMII:
+		value = AC200_EPHY_RMII_SEL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&priv->lock);
+	if (priv->interface == interface)
+		goto out_unlock;
+
+	if (priv->powered)
+		ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL_REG,
+					 AC200_EPHY_RMII_SEL, value);
+	if (!ret)
+		priv->interface = interface;
+
+out_unlock:
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+static int ac200_ephy_ctl_power_on(struct acx00_ephy_control *control,
+				   unsigned int phy_addr)
+{
+	struct ac200_ephy_ctl *priv =
+		container_of(control, struct ac200_ephy_ctl, control);
+	u16 ephy_ctl;
+	int ret;
+
+	if (phy_addr > FIELD_MAX(AC200_EPHY_PHY_ADDR_MASK))
+		return -EINVAL;
+
+	mutex_lock(&priv->lock);
+	if (priv->powered && priv->phy_addr == phy_addr) {
+		ret = 0;
+		goto out_unlock;
+	}
+	if (priv->powered) {
+		ret = ac200_ephy_ctl_power_off_locked(priv);
+		if (ret)
+			goto out_unlock;
+	}
+	priv->phy_addr = phy_addr;
+
+	ephy_ctl = ac200_ephy_ctl_config(priv);
+
+	/* Start from a disabled state before applying the configuration. */
+	ret = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0_REG, 0);
+	if (ret)
+		goto err_disable;
+
+	ret = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL1_REG,
+			   AC200_EPHY_MII_IO_ENABLE);
+	if (ret)
+		goto err_disable;
+
+	ret = regmap_write(priv->regmap, AC200_EPHY_CTL_REG,
+			   ephy_ctl | AC200_EPHY_SHUTDOWN);
+	if (ret)
+		goto err_disable;
+
+	ret = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0_REG,
+			   AC200_EPHY_RESET_DEASSERT |
+			   AC200_EPHY_SYSCLK_ENABLE);
+	if (ret)
+		goto err_disable;
+
+	fsleep(10000);
+
+	ret = regmap_write(priv->regmap, AC200_EPHY_CTL_REG, ephy_ctl);
+	if (ret)
+		goto err_disable;
+
+	priv->powered = true;
+	goto out_unlock;
+
+err_disable:
+	/* Attempt every step of the shutdown sequence after a partial start. */
+	priv->powered = true;
+	ac200_ephy_ctl_power_off_locked(priv);
+out_unlock:
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+static int ac200_ephy_ctl_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ac200_ephy_ctl *priv;
+	unsigned long clk_rate;
+	unsigned int calibration;
+	u8 nvmem_calibration;
+	u8 bps_effuse_code;
+	struct clk *clk;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	mutex_init(&priv->lock);
+
+	priv->regmap = dev_get_regmap(dev->parent, NULL);
+	if (!priv->regmap)
+		return dev_err_probe(dev, -EPROBE_DEFER,
+				     "parent regmap is not ready\n");
+
+	if (device_property_present(dev, "nvmem-cells")) {
+		ret = nvmem_cell_read_u8(dev, "calibration",
+					 &nvmem_calibration);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "failed to read calibration data\n");
+		calibration = nvmem_calibration;
+	} else {
+		ret = regmap_read(priv->regmap, AC200_EFUSE_EPHY_REG,
+				  &calibration);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "failed to read on-chip calibration data\n");
+	}
+
+	/* The vendor driver supplies no transfer function beyond this offset. */
+	bps_effuse_code = (calibration + AC200_EPHY_BPS_EFFUSE_OFFSET) &
+			   FIELD_MAX(AC200_EPHY_BPS_EFFUSE_MASK);
+	priv->ephy_ctl =
+		FIELD_PREP(AC200_EPHY_BPS_EFFUSE_MASK, bps_effuse_code);
+	/* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */
+
+	clk = clk_get(dev->parent, NULL);
+	if (IS_ERR(clk))
+		return dev_err_probe(dev, PTR_ERR(clk),
+				     "failed to get input clock\n");
+
+	clk_rate = clk_get_rate(clk);
+	clk_put(clk);
+
+	switch (clk_rate) {
+	case 24000000:
+		priv->ephy_ctl |= AC200_EPHY_CLK_SEL_24_MHZ;
+		break;
+	case 27000000:
+		break;
+	default:
+		return dev_err_probe(dev, -EINVAL,
+				     "unsupported input clock rate %lu Hz\n",
+				     clk_rate);
+	}
+
+	priv->control.power_on = ac200_ephy_ctl_power_on;
+	priv->control.power_off = ac200_ephy_ctl_power_off;
+	priv->control.set_interface = ac200_ephy_ctl_set_interface;
+	/* MII is the reset default used until the MAC supplies its interface. */
+	priv->interface = PHY_INTERFACE_MODE_MII;
+	platform_set_drvdata(pdev, &priv->control);
+
+	return 0;
+}
+
+static void ac200_ephy_ctl_remove(struct platform_device *pdev)
+{
+	struct acx00_ephy_control *control = platform_get_drvdata(pdev);
+
+	control->power_off(control);
+}
+
+static const struct of_device_id ac200_ephy_ctl_of_match[] = {
+	{ .compatible = "x-powers,ac200-ephy-ctl" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ac200_ephy_ctl_of_match);
+
+static struct platform_driver ac200_ephy_ctl_driver = {
+	.probe = ac200_ephy_ctl_probe,
+	.remove = ac200_ephy_ctl_remove,
+	.shutdown = ac200_ephy_ctl_remove,
+	.driver = {
+		.name = "ac200-ephy-ctl",
+		.of_match_table = ac200_ephy_ctl_of_match,
+	},
+};
+module_platform_driver(ac200_ephy_ctl_driver);
+
+MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
+MODULE_DESCRIPTION("X-Powers AC200 Ethernet PHY control driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/xpowers-acx00.h b/drivers/net/phy/xpowers-acx00.h
new file mode 100644
index 000000000000..482ba6faba6e
--- /dev/null
+++ b/drivers/net/phy/xpowers-acx00.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __DRIVERS_NET_PHY_XPOWERS_ACX00_H
+#define __DRIVERS_NET_PHY_XPOWERS_ACX00_H
+
+#include <linux/phy.h>
+
+struct acx00_ephy_control {
+	int (*power_on)(struct acx00_ephy_control *control,
+			unsigned int phy_addr);
+	int (*power_off)(struct acx00_ephy_control *control);
+	int (*set_interface)(struct acx00_ephy_control *control,
+			     phy_interface_t interface);
+};
+
+#endif

-- 
2.53.0


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

* [PATCH net-next v2 5/8] dt-bindings: net: x-powers: add AC300 EPHY control
  2026-08-05  2:26 [PATCH net-next v2 0/8] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
                   ` (3 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH net-next v2 4/8] net: phy: add X-Powers AC200 EPHY control driver James Hilliard
@ 2026-08-05  2:27 ` James Hilliard
  2026-08-05  3:38   ` Rob Herring (Arm)
                     ` (2 more replies)
  2026-08-05  2:27 ` [PATCH net-next v2 6/8] net: phy: add X-Powers AC300 EPHY control driver James Hilliard
                   ` (2 subsequent siblings)
  7 siblings, 3 replies; 26+ messages in thread
From: James Hilliard @ 2026-08-05  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Andrew Lunn, Heiner Kallweit, Russell King,
	Heiko Stuebner
  Cc: netdev, devicetree, linux-kernel, mfd, linux-arm-kernel,
	linux-rockchip, James Hilliard

The AC300 exposes its Fast Ethernet PHY control registers through a
separate non-PHY Clause 22 address. The manual defines that address as
the link PHY address plus 16, giving control addresses 16 through 23 and
link addresses 0 through 7.

Describe the control endpoint, its required VCC1 supply, its 24, 25 or
27 MHz input clock and its SoC SID calibration cell. The actual PHY
remains a separate ethernet-phy node and supplies the MAC-selected
interface mode at runtime.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 .../bindings/net/x-powers,ac300-ephy-ctl.yaml      | 70 ++++++++++++++++++++++
 1 file changed, 70 insertions(+)

diff --git a/Documentation/devicetree/bindings/net/x-powers,ac300-ephy-ctl.yaml b/Documentation/devicetree/bindings/net/x-powers,ac300-ephy-ctl.yaml
new file mode 100644
index 000000000000..8a616ff3e89f
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/x-powers,ac300-ephy-ctl.yaml
@@ -0,0 +1,70 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/x-powers,ac300-ephy-ctl.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: X-Powers AC300 Ethernet PHY control interface
+
+maintainers:
+  - James Hilliard <james.hilliard1@gmail.com>
+
+description:
+  The AC300 Fast Ethernet PHY has a separate control interface accessed as a
+  non-PHY Clause 22 device. Its address is 16 plus the address of the link PHY.
+
+properties:
+  compatible:
+    const: x-powers,ac300-ephy-ctl
+
+  reg:
+    minimum: 16
+    maximum: 23
+
+  clocks:
+    maxItems: 1
+    description:
+      AC300 input clock. Its configured rate must be 24, 25, or 27 MHz and
+      determines the corresponding EPHY_CLK_SEL value.
+
+  vcc1-supply:
+    description:
+      3.3 V supply for the AC300 I/O, bandgap, Ethernet PHY analog front end,
+      and internal digital LDO
+
+  nvmem-cells:
+    maxItems: 1
+    description:
+      SoC SID cell containing the Ethernet PHY calibration value written to
+      the AC300 BGS_EFFUSE field after adding 3 modulo 16, as prescribed by
+      the vendor initialization sequence.
+
+  nvmem-cell-names:
+    const: calibration
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - vcc1-supply
+  - nvmem-cells
+  - nvmem-cell-names
+
+additionalProperties: false
+
+examples:
+  - |
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ethernet-phy-control@10 {
+            compatible = "x-powers,ac300-ephy-ctl";
+            reg = <0x10>;
+            clocks = <&pwm 5>;
+            vcc1-supply = <&reg_3v3>;
+            nvmem-cells = <&ephy_calibration>;
+            nvmem-cell-names = "calibration";
+        };
+    };
+...

-- 
2.53.0


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

* [PATCH net-next v2 6/8] net: phy: add X-Powers AC300 EPHY control driver
  2026-08-05  2:26 [PATCH net-next v2 0/8] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
                   ` (4 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH net-next v2 5/8] dt-bindings: net: x-powers: add AC300 EPHY control James Hilliard
@ 2026-08-05  2:27 ` James Hilliard
  2026-08-05 12:22   ` Andrew Lunn
                     ` (2 more replies)
  2026-08-05  2:27 ` [PATCH net-next v2 7/8] dt-bindings: net: x-powers: add AC200/AC300 EPHY James Hilliard
  2026-08-05  2:27 ` [PATCH net-next v2 8/8] net: phy: add X-Powers AC200/AC300 EPHY driver James Hilliard
  7 siblings, 3 replies; 26+ messages in thread
From: James Hilliard @ 2026-08-05  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Andrew Lunn, Heiner Kallweit, Russell King,
	Heiko Stuebner
  Cc: netdev, devicetree, linux-kernel, mfd, linux-arm-kernel,
	linux-rockchip, James Hilliard

The AC300 Fast Ethernet PHY needs a separate non-PHY Clause 22 endpoint
to complete package setup before the link endpoint becomes usable.

Enable VCC1 and the input clock, select its 24, 25 or 27 MHz rate, apply
the SoC SID calibration value and vendor offset, configure the documented
I/O drive strength, and perform the reset-before-clock power-up sequence.
Validate the control and link address relationship against the package
straps, clear a latched MDIO error, and configure the internal digital
LDO only for the package variant which uses it.

Early PHY creation uses reset-default MII. A separate operation later
applies phydev->interface and, if already powered, changes only RMII_SEL
before the normal PHY soft reset.

Expose serialized, idempotent power and interface operations to the
common PHY driver. Enable only the MDIO, MII and input-clock pads in this
basic driver; dedicated LED outputs remain disabled for a later patch.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 drivers/net/phy/Kconfig             |   9 +
 drivers/net/phy/Makefile            |   1 +
 drivers/net/phy/xpowers-ac300-ctl.c | 423 ++++++++++++++++++++++++++++++++++++
 drivers/net/phy/xpowers-acx00.h     |   2 +
 4 files changed, 435 insertions(+)

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 6119f4de880d..7535db7f3471 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -484,6 +484,15 @@ config XPOWERS_AC200_PHY_CTL
 	  address, interface mode, calibration and I/O controls through the
 	  parent AC200 I2C regmap.
 
+config XPOWERS_AC300_PHY_CTL
+	tristate "X-Powers AC300 Ethernet PHY control"
+	depends on COMMON_CLK && NVMEM && OF_MDIO
+	help
+	  Enable the MDIO control driver for the Fast Ethernet PHY in the
+	  X-Powers AC300 companion IC. It programs the PHY calibration,
+	  interface mode, clock and I/O controls through the separate MDIO
+	  control address.
+
 config XILINX_GMII2RGMII
 	tristate "Xilinx GMII2RGMII converter driver"
 	help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index f81854fc9f12..0c4e40c9a28a 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -101,4 +101,5 @@ obj-$(CONFIG_STE10XP)		+= ste10Xp.o
 obj-$(CONFIG_TERANETICS_PHY)	+= teranetics.o
 obj-$(CONFIG_VITESSE_PHY)	+= vitesse.o
 obj-$(CONFIG_XPOWERS_AC200_PHY_CTL) += xpowers-ac200-ctl.o
+obj-$(CONFIG_XPOWERS_AC300_PHY_CTL) += xpowers-ac300-ctl.o
 obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
diff --git a/drivers/net/phy/xpowers-ac300-ctl.c b/drivers/net/phy/xpowers-ac300-ctl.c
new file mode 100644
index 000000000000..d3ca4df8a9a3
--- /dev/null
+++ b/drivers/net/phy/xpowers-ac300-ctl.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * X-Powers AC300 Ethernet PHY control driver
+ *
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/phy.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+
+#include "xpowers-acx00.h"
+
+#define AC300_EPHY_BGS_EFFUSE_OFFSET	3
+#define AC300_SYS_CONTROL_REG			0x00
+#define AC300_CHIP_VERSION_MASK			GENMASK(15, 12)
+#define AC300_PACKAGE_STATUS_MASK		GENMASK(11, 8)
+#define AC300_EPHY_CLK_SEL_MASK			GENMASK(7, 6)
+#define AC300_EPHY_CLK_SEL_25_MHZ		FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 0)
+#define AC300_EPHY_CLK_SEL_27_MHZ		FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 1)
+#define AC300_EPHY_CLK_SEL_24_MHZ		FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 2)
+#define AC300_EFUSE_CLK_ENABLE			BIT(5)
+#define AC300_EPHY_REG_CLK_ENABLE		BIT(4)
+#define AC300_MDIO_ERROR			BIT(3)
+#define AC300_CLKIN_GATING_ENABLE		BIT(2)
+#define AC300_EPHY_RESET_DEASSERT		BIT(1)
+#define AC300_CHIP_RESET_DEASSERT		BIT(0)
+
+#define AC300_MASK_VERSION_REG			0x04
+#define AC300_MASK_VERSION_MASK			GENMASK(2, 0)
+
+#define AC300_PACKAGE_POR_INTERNAL_DLDO		BIT(3)
+#define AC300_PACKAGE_PHY_ADDR_MASK		GENMASK(2, 0)
+
+#define AC300_SYS_BIAS1_REG			0x02
+#define AC300_INTERNAL_DLDO_ENABLE		BIT(15)
+
+#define AC300_SYS_IO_REG			0x05
+#define AC300_MDIO_DRV_MASK			GENMASK(15, 14)
+#define AC300_MII_DRV_MASK			GENMASK(11, 10)
+#define AC300_IO_DRV_LEVEL_2			2
+#define AC300_CLKIN_PAD_ENABLE			BIT(4)
+#define AC300_EPHY_MII_IO_ENABLE			BIT(0)
+
+#define AC300_EPHY_CONFIG_REG			0x06
+#define AC300_EPHY_BGS_EFFUSE_MASK		GENMASK(15, 12)
+#define AC300_EPHY_RMII_SEL			BIT(11)
+#define AC300_EPHY_SHUTDOWN			BIT(0)
+
+#define AC300_SYS_CONTROL_ENABLE_BITS \
+	(AC300_EFUSE_CLK_ENABLE | AC300_EPHY_REG_CLK_ENABLE | \
+	 AC300_CLKIN_GATING_ENABLE | AC300_EPHY_RESET_DEASSERT | \
+	 AC300_CHIP_RESET_DEASSERT)
+
+#define AC300_SYS_IO_VALUE \
+	(FIELD_PREP(AC300_MDIO_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \
+	 FIELD_PREP(AC300_MII_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \
+	 AC300_CLKIN_PAD_ENABLE | AC300_EPHY_MII_IO_ENABLE)
+
+struct ac300_ephy_ctl {
+	struct acx00_ephy_control control;
+	struct mdio_device *mdiodev;
+	struct clk *clk;
+	struct mutex lock; /* Serializes power sequencing and state. */
+	u16 sys_control;
+	u16 ephy_config;
+	phy_interface_t interface;
+	bool package_known;
+	bool internal_dldo;
+	bool powered;
+};
+
+static unsigned int
+ac300_ephy_ctl_link_addr(const struct ac300_ephy_ctl *priv)
+{
+	return priv->mdiodev->addr - AC300_EPHY_CONTROL_ADDR_OFFSET;
+}
+
+static u16 ac300_ephy_ctl_config(const struct ac300_ephy_ctl *priv)
+{
+	return priv->ephy_config |
+		(priv->interface == PHY_INTERFACE_MODE_RMII ?
+		 AC300_EPHY_RMII_SEL : 0);
+}
+
+static int ac300_ephy_ctl_power_off_locked(struct ac300_ephy_ctl *priv)
+{
+	int err;
+	int ret;
+
+	if (!priv->powered)
+		return 0;
+
+	ret = mdiodev_write(priv->mdiodev, AC300_EPHY_CONFIG_REG,
+			    ac300_ephy_ctl_config(priv) | AC300_EPHY_SHUTDOWN);
+	err = mdiodev_write(priv->mdiodev, AC300_SYS_IO_REG, 0);
+	if (!ret)
+		ret = err;
+	err = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG,
+			    priv->package_known && !priv->internal_dldo ?
+			    AC300_CHIP_RESET_DEASSERT : 0);
+	if (!ret)
+		ret = err;
+
+	clk_disable_unprepare(priv->clk);
+	priv->powered = false;
+
+	return ret;
+}
+
+static int ac300_ephy_ctl_power_off(struct acx00_ephy_control *control)
+{
+	struct ac300_ephy_ctl *priv =
+		container_of(control, struct ac300_ephy_ctl, control);
+	int ret;
+
+	mutex_lock(&priv->lock);
+	ret = ac300_ephy_ctl_power_off_locked(priv);
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+static int
+ac300_ephy_ctl_set_interface(struct acx00_ephy_control *control,
+			     phy_interface_t interface)
+{
+	struct ac300_ephy_ctl *priv =
+		container_of(control, struct ac300_ephy_ctl, control);
+	u16 value;
+	int ret = 0;
+
+	switch (interface) {
+	case PHY_INTERFACE_MODE_MII:
+		value = 0;
+		break;
+	case PHY_INTERFACE_MODE_RMII:
+		value = AC300_EPHY_RMII_SEL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&priv->lock);
+	if (priv->interface == interface)
+		goto out_unlock;
+
+	if (priv->powered)
+		ret = mdiodev_modify(priv->mdiodev, AC300_EPHY_CONFIG_REG,
+				     AC300_EPHY_RMII_SEL, value);
+	if (!ret)
+		priv->interface = interface;
+
+out_unlock:
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+static int ac300_ephy_ctl_power_on(struct acx00_ephy_control *control,
+				   unsigned int phy_addr)
+{
+	struct ac300_ephy_ctl *priv =
+		container_of(control, struct ac300_ephy_ctl, control);
+	u8 package_status;
+	u16 reset_value;
+	int sys_control;
+	int ret;
+
+	if (phy_addr != ac300_ephy_ctl_link_addr(priv))
+		return -EINVAL;
+
+	mutex_lock(&priv->lock);
+	if (priv->powered) {
+		ret = 0;
+		goto out_unlock;
+	}
+
+	ret = clk_prepare_enable(priv->clk);
+	if (ret)
+		goto out_unlock;
+	priv->powered = true;
+
+	/* Keep the external-supply configuration across subsequent resets. */
+	reset_value = priv->package_known && !priv->internal_dldo ?
+		      AC300_CHIP_RESET_DEASSERT : 0;
+	ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG, reset_value);
+	if (ret)
+		goto err_power_off;
+
+	/* The manual requires both resets to be released before the clocks. */
+	ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG,
+			    AC300_EPHY_RESET_DEASSERT |
+			    AC300_CHIP_RESET_DEASSERT);
+	if (ret)
+		goto err_power_off;
+
+	/* Retain the vendor clock-enable defaults, including the eFuse clock. */
+	ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG,
+			    priv->sys_control);
+	if (ret)
+		goto err_power_off;
+
+	sys_control = mdiodev_read(priv->mdiodev, AC300_SYS_CONTROL_REG);
+	if (sys_control < 0) {
+		ret = sys_control;
+		goto err_power_off;
+	}
+	if (sys_control & AC300_MDIO_ERROR) {
+		ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG,
+				    priv->sys_control | AC300_MDIO_ERROR);
+		if (ret)
+			goto err_power_off;
+
+		sys_control = mdiodev_read(priv->mdiodev, AC300_SYS_CONTROL_REG);
+		if (sys_control < 0) {
+			ret = sys_control;
+			goto err_power_off;
+		}
+		if (sys_control & AC300_MDIO_ERROR) {
+			ret = -EIO;
+			goto err_power_off;
+		}
+	}
+
+	package_status = FIELD_GET(AC300_PACKAGE_STATUS_MASK, sys_control);
+	if ((~package_status & AC300_PACKAGE_PHY_ADDR_MASK) !=
+	    ac300_ephy_ctl_link_addr(priv)) {
+		ret = -EINVAL;
+		goto err_power_off;
+	}
+
+	priv->internal_dldo = package_status & AC300_PACKAGE_POR_INTERNAL_DLDO;
+	priv->package_known = true;
+	ret = mdiodev_modify(priv->mdiodev, AC300_SYS_BIAS1_REG,
+			     AC300_INTERNAL_DLDO_ENABLE,
+			     priv->internal_dldo ?
+			     AC300_INTERNAL_DLDO_ENABLE : 0);
+	if (ret)
+		goto err_power_off;
+
+	/* Keep the documented default drive level and leave the IRQ disabled. */
+	ret = mdiodev_write(priv->mdiodev, AC300_SYS_IO_REG,
+			    AC300_SYS_IO_VALUE);
+	if (ret)
+		goto err_power_off;
+
+	fsleep(10000);
+
+	ret = mdiodev_write(priv->mdiodev, AC300_EPHY_CONFIG_REG,
+			    ac300_ephy_ctl_config(priv) | AC300_EPHY_SHUTDOWN);
+	if (ret)
+		goto err_power_off;
+
+	fsleep(10000);
+
+	ret = mdiodev_write(priv->mdiodev, AC300_EPHY_CONFIG_REG,
+			    ac300_ephy_ctl_config(priv));
+	if (ret)
+		goto err_power_off;
+
+	goto out_unlock;
+
+err_power_off:
+	ac300_ephy_ctl_power_off_locked(priv);
+out_unlock:
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+static int ac300_ephy_ctl_probe(struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct ac300_ephy_ctl *priv;
+	unsigned long clk_rate;
+	unsigned int phy_addr;
+	u8 calibration;
+	u8 bgs_effuse_code;
+	u8 package_status;
+	int mask_version;
+	int sys_control;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	if (mdiodev->addr < AC300_EPHY_CONTROL_ADDR_OFFSET ||
+	    mdiodev->addr > AC300_EPHY_CONTROL_ADDR_OFFSET +
+			    FIELD_MAX(AC300_PACKAGE_PHY_ADDR_MASK))
+		return dev_err_probe(dev, -EINVAL,
+				     "control address is outside the package range\n");
+	priv->mdiodev = mdiodev;
+	mutex_init(&priv->lock);
+
+	ret = devm_regulator_get_enable(dev, "vcc1");
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to enable VCC1 supply\n");
+
+	/* Wait for the power-on reset interval specified by the manual. */
+	fsleep(10000);
+
+	priv->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(priv->clk))
+		return dev_err_probe(dev, PTR_ERR(priv->clk),
+				     "failed to get input clock\n");
+
+	ret = devm_clk_rate_exclusive_get(dev, priv->clk);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to lock clock rate\n");
+
+	clk_rate = clk_get_rate(priv->clk);
+	switch (clk_rate) {
+	case 24000000:
+		priv->sys_control = AC300_EPHY_CLK_SEL_24_MHZ;
+		break;
+	case 25000000:
+		priv->sys_control = AC300_EPHY_CLK_SEL_25_MHZ;
+		break;
+	case 27000000:
+		priv->sys_control = AC300_EPHY_CLK_SEL_27_MHZ;
+		break;
+	default:
+		return dev_err_probe(dev, -EINVAL,
+				     "unsupported input clock rate %lu Hz\n",
+				     clk_rate);
+	}
+	priv->sys_control |= AC300_SYS_CONTROL_ENABLE_BITS;
+
+	ret = nvmem_cell_read_u8(dev, "calibration", &calibration);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to read calibration data\n");
+
+	/* The vendor driver supplies no transfer function beyond this offset. */
+	bgs_effuse_code = (calibration + AC300_EPHY_BGS_EFFUSE_OFFSET) &
+			   FIELD_MAX(AC300_EPHY_BGS_EFFUSE_MASK);
+	priv->ephy_config =
+		FIELD_PREP(AC300_EPHY_BGS_EFFUSE_MASK, bgs_effuse_code);
+	/* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */
+
+	priv->control.power_on = ac300_ephy_ctl_power_on;
+	priv->control.power_off = ac300_ephy_ctl_power_off;
+	priv->control.set_interface = ac300_ephy_ctl_set_interface;
+	/* MII is the reset default used until the MAC supplies its interface. */
+	priv->interface = PHY_INTERFACE_MODE_MII;
+	mdiodev_set_drvdata(mdiodev, &priv->control);
+
+	/* Validate the package while the control endpoint is known to respond. */
+	phy_addr = ac300_ephy_ctl_link_addr(priv);
+	ret = ac300_ephy_ctl_power_on(&priv->control, phy_addr);
+	if (ret)
+		return ret;
+
+	sys_control = mdiodev_read(mdiodev, AC300_SYS_CONTROL_REG);
+	if (sys_control < 0) {
+		ret = sys_control;
+		goto err_disable;
+	}
+	package_status = FIELD_GET(AC300_PACKAGE_STATUS_MASK, sys_control);
+
+	mask_version = mdiodev_read(mdiodev, AC300_MASK_VERSION_REG);
+	if (mask_version < 0) {
+		ret = mask_version;
+		goto err_disable;
+	}
+
+	dev_info(dev, "chip version %u, mask version %u, package %#x, %s supplies, PHY %u, %lu Hz clock\n",
+		 (unsigned int)FIELD_GET(AC300_CHIP_VERSION_MASK, sys_control),
+		 (unsigned int)FIELD_GET(AC300_MASK_VERSION_MASK, mask_version),
+		 package_status,
+		 package_status & AC300_PACKAGE_POR_INTERNAL_DLDO ?
+			"POR/internal DLDO" : "reset pin/external VDD",
+		 ac300_ephy_ctl_link_addr(priv), clk_rate);
+
+	ret = ac300_ephy_ctl_power_off(&priv->control);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to quiesce control block\n");
+
+	return 0;
+
+err_disable:
+	ac300_ephy_ctl_power_off(&priv->control);
+	return ret;
+}
+
+static void ac300_ephy_ctl_remove(struct mdio_device *mdiodev)
+{
+	struct acx00_ephy_control *control = mdiodev_get_drvdata(mdiodev);
+
+	control->power_off(control);
+}
+
+static const struct of_device_id ac300_ephy_ctl_of_match[] = {
+	{ .compatible = "x-powers,ac300-ephy-ctl" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ac300_ephy_ctl_of_match);
+
+static struct mdio_driver ac300_ephy_ctl_driver = {
+	.probe = ac300_ephy_ctl_probe,
+	.remove = ac300_ephy_ctl_remove,
+	.shutdown = ac300_ephy_ctl_remove,
+	.mdiodrv.driver = {
+		.name = "ac300-ephy-ctl",
+		.of_match_table = ac300_ephy_ctl_of_match,
+	},
+};
+
+mdio_module_driver(ac300_ephy_ctl_driver);
+
+MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
+MODULE_DESCRIPTION("X-Powers AC300 Ethernet PHY control driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/xpowers-acx00.h b/drivers/net/phy/xpowers-acx00.h
index 482ba6faba6e..0af8895e4cae 100644
--- a/drivers/net/phy/xpowers-acx00.h
+++ b/drivers/net/phy/xpowers-acx00.h
@@ -4,6 +4,8 @@
 
 #include <linux/phy.h>
 
+#define AC300_EPHY_CONTROL_ADDR_OFFSET	16
+
 struct acx00_ephy_control {
 	int (*power_on)(struct acx00_ephy_control *control,
 			unsigned int phy_addr);

-- 
2.53.0


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

* [PATCH net-next v2 7/8] dt-bindings: net: x-powers: add AC200/AC300 EPHY
  2026-08-05  2:26 [PATCH net-next v2 0/8] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
                   ` (5 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH net-next v2 6/8] net: phy: add X-Powers AC300 EPHY control driver James Hilliard
@ 2026-08-05  2:27 ` James Hilliard
  2026-08-06  2:32   ` sashiko-bot
  2026-08-05  2:27 ` [PATCH net-next v2 8/8] net: phy: add X-Powers AC200/AC300 EPHY driver James Hilliard
  7 siblings, 1 reply; 26+ messages in thread
From: James Hilliard @ 2026-08-05  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Andrew Lunn, Heiner Kallweit, Russell King,
	Heiko Stuebner
  Cc: netdev, devicetree, linux-kernel, mfd, linux-arm-kernel,
	linux-rockchip, James Hilliard

The AC200 and AC300 MDIO link interfaces report the same Clause 22
identifier and use a compatible link-side paged register layout, but need
different package control interfaces before that identifier can be read.

Describe the forced-ID PHY and require exactly one reference to an
enabled AC200 or AC300 control device. The PHY node's reg value is
programmed through the AC200 control interface. For AC300, it must match
the address selected by the package straps and is therefore limited to 0
through 7.

Allow a system to provide its optional two-bit SID configuration field.
Bit 0 identifies the AC200 or AC300 package and must agree with the
described control device. Bit 1 selects the AC300 low-calibration tuning
sequence. Omitting the field skips package validation and selects the
normal calibration sequence.

Also describe the optional xMII receive-clock inversion needed by some
integrations. Requiring an X-Powers control reference distinguishes these
integrations from the Rockchip RK630 PHY, which reports the same
identifier.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 .../bindings/net/x-powers,acx00-ephy.yaml          | 117 +++++++++++++++++++++
 1 file changed, 117 insertions(+)

diff --git a/Documentation/devicetree/bindings/net/x-powers,acx00-ephy.yaml b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy.yaml
new file mode 100644
index 000000000000..c280bcf8aa25
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy.yaml
@@ -0,0 +1,117 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/x-powers,acx00-ephy.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: X-Powers AC200 and AC300 Ethernet PHY
+
+maintainers:
+  - James Hilliard <james.hilliard1@gmail.com>
+
+description:
+  The AC200 and AC300 expose Fast Ethernet MDIO link interfaces which report
+  the same PHY ID and use a compatible link-side paged register layout. Each
+  package uses a different control interface to make its MDIO endpoint
+  accessible. A fixed board references its enabled AC200 or AC300 control
+  device. The PHY node's reg value is programmed through the AC200 interface;
+  for AC300 it must match the address selected by the package straps.
+
+select:
+  properties:
+    compatible:
+      contains:
+        const: ethernet-phy-id0044.1400
+  required:
+    - compatible
+  anyOf:
+    - required:
+        - x-powers,ac200-control
+    - required:
+        - x-powers,ac300-control
+
+properties:
+  compatible:
+    const: ethernet-phy-id0044.1400
+
+  nvmem-cells:
+    maxItems: 1
+    description:
+      Optional two-bit SoC SID configuration field. Bit 0 selects the package
+      variant, with zero for AC200 and one for AC300, and must agree with the
+      described control device. Bit 1 selects the AC300 vendor low-calibration
+      tuning sequence. Omitting the field selects the normal tuning sequence
+      without validating the package variant.
+
+  nvmem-cell-names:
+    const: configuration
+
+  x-powers,ac200-control:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description: Reference to the AC200 Ethernet PHY control function
+
+  x-powers,ac300-control:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description: Reference to the AC300 MDIO Ethernet PHY control device
+
+  x-powers,xmii-rx-clock-inverted:
+    type: boolean
+    description:
+      Invert the xMII receive clock inside the PHY. This is required by some
+      SoC integrations, including the Allwinner H6 AC200 connection.
+
+required:
+  - compatible
+
+oneOf:
+  - properties:
+      x-powers,ac300-control: false
+    required:
+      - x-powers,ac200-control
+  - properties:
+      x-powers,ac200-control: false
+    required:
+      - x-powers,ac300-control
+
+dependentRequired:
+  nvmem-cells: [ nvmem-cell-names ]
+
+allOf:
+  - $ref: ethernet-phy.yaml#
+  - if:
+      required:
+        - x-powers,ac300-control
+    then:
+      properties:
+        reg:
+          maximum: 7
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ethernet-phy@0 {
+            compatible = "ethernet-phy-id0044.1400";
+            reg = <0>;
+            nvmem-cells = <&ephy_configuration>;
+            nvmem-cell-names = "configuration";
+            x-powers,ac300-control = <&ac300_ephy_ctl>;
+        };
+    };
+
+  - |
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ethernet-phy@1 {
+            compatible = "ethernet-phy-id0044.1400";
+            reg = <1>;
+            x-powers,ac200-control = <&ac200_ephy_ctl>;
+        };
+    };
+...

-- 
2.53.0


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

* [PATCH net-next v2 8/8] net: phy: add X-Powers AC200/AC300 EPHY driver
  2026-08-05  2:26 [PATCH net-next v2 0/8] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
                   ` (6 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH net-next v2 7/8] dt-bindings: net: x-powers: add AC200/AC300 EPHY James Hilliard
@ 2026-08-05  2:27 ` James Hilliard
  2026-08-05 12:37   ` Andrew Lunn
  2026-08-06  2:32   ` sashiko-bot
  7 siblings, 2 replies; 26+ messages in thread
From: James Hilliard @ 2026-08-05  2:27 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Andrew Lunn, Heiner Kallweit, Russell King,
	Heiko Stuebner
  Cc: netdev, devicetree, linux-kernel, mfd, linux-arm-kernel,
	linux-rockchip, James Hilliard

The AC200 and AC300 MDIO link endpoints report the same Clause 22
identifier and use a compatible link-side paged register layout. Their
identification registers are inaccessible until a package-specific
control path powers and configures the block.

Add a common PHY driver which matches the firmware-provided identifier
only when the node also references one X-Powers control device. Validate
that reference and the AC300 address relationship, defer until the
provider is bound, and create a managed device link to protect its
lifetime and order teardown and power management.

When the optional two-bit SID configuration field is present, verify that
its package-selector bit agrees with the fixed control reference. Use its
second bit to select the AC300 low-calibration tuning sequence. Omitting
the field skips package validation and selects the normal sequence.

Power the control block in reset-default MII mode during PHY probe. Once
the MAC supplies phydev->interface, pass MII or RMII to the provider
before the normal PHY soft reset. Program the AC200 link address through
its provider and let the AC300 provider validate its strapped address.

Apply the common vendor analog initialization, with the package-specific
normal value and the optional AC300 low-calibration sequence. Support the
board-selected xMII receive-clock inversion and leave MDI/MDI-X in its
vendor automatic mode. Disable standard and Intelligent EEE initially,
then restore phylib's standard EEE policy after later resets.

Power the package control block down with the PHY and restore the vendor
configuration on resume. If power-off fails, attempt to restore the PHY
before returning the original suspend error.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 drivers/net/phy/Kconfig         |   9 +
 drivers/net/phy/Makefile        |   1 +
 drivers/net/phy/xpowers-acx00.c | 544 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 554 insertions(+)

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 7535db7f3471..c505ade5955c 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -493,6 +493,15 @@ config XPOWERS_AC300_PHY_CTL
 	  interface mode, clock and I/O controls through the separate MDIO
 	  control address.
 
+config XPOWERS_ACX00_PHY
+	tristate "X-Powers AC200/AC300 Ethernet PHY"
+	depends on OF_MDIO
+	help
+	  Enable the Fast Ethernet PHY driver shared by the X-Powers AC200
+	  and AC300 companion ICs. This driver configures their common MDIO PHY
+	  registers and uses one statically selected package-control provider for
+	  power, calibration and interface setup.
+
 config XILINX_GMII2RGMII
 	tristate "Xilinx GMII2RGMII converter driver"
 	help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 0c4e40c9a28a..db71f1c2b756 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -102,4 +102,5 @@ obj-$(CONFIG_TERANETICS_PHY)	+= teranetics.o
 obj-$(CONFIG_VITESSE_PHY)	+= vitesse.o
 obj-$(CONFIG_XPOWERS_AC200_PHY_CTL) += xpowers-ac200-ctl.o
 obj-$(CONFIG_XPOWERS_AC300_PHY_CTL) += xpowers-ac300-ctl.o
+obj-$(CONFIG_XPOWERS_ACX00_PHY)	+= xpowers-acx00.o
 obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
diff --git a/drivers/net/phy/xpowers-acx00.c b/drivers/net/phy/xpowers-acx00.c
new file mode 100644
index 000000000000..f9e6be751252
--- /dev/null
+++ b/drivers/net/phy/xpowers-acx00.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * X-Powers AC200/AC300 Ethernet PHY driver
+ *
+ * Copyright (C) 2019 Jernej Skrabec <jernej.skrabec@gmail.com>
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+#include "xpowers-acx00.h"
+
+#define ACX00_EPHY_ID				0x00441400
+
+#define ACX00_EPHY_CONFIG_VARIANT_AC300		BIT(0)
+#define ACX00_EPHY_CONFIG_CALIBRATION_LOW	BIT(1)
+
+#define ACX00_PAGE_SELECT_REG			0x1f
+#define ACX00_PAGE_SELECT_MASK			GENMASK(12, 8)
+#define ACX00_PAGE_0				0
+#define ACX00_PAGE_1				1
+#define ACX00_PAGE_2				2
+#define ACX00_PAGE_6				6
+#define ACX00_PAGE_8				8
+
+#define ACX00_PAGE0_GLOBAL_CONFIG_REG		0x13
+#define ACX00_PAGE0_XMII_RX_CLOCK_INVERT	BIT(12)
+#define ACX00_PAGE0_MDI_MODE_MASK		GENMASK(1, 0)
+#define ACX00_PAGE0_MDI_MODE_AUTO		2
+
+#define ACX00_PAGE1_APS_CONTROL_REG		0x12
+#define ACX00_PAGE1_APS_DISABLED_4S_VALUE	0x4824
+#define ACX00_PAGE1_UAPS_CONTROL_REG		0x13
+#define ACX00_PAGE1_UAPS_ENABLE			BIT(15)
+#define ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG	0x17
+#define ACX00_PAGE1_INTELLIGENT_EEE_ENABLE	BIT(3)
+
+#define ACX00_PAGE2_TX_DATA_CONTROL_REG		0x18
+#define ACX00_PAGE2_10BT_FIR_SELECT_MASK		GENMASK(14, 12)
+#define ACX00_PAGE2_10BT_FIR_SELECT_DEFAULT	0
+
+#define ACX00_PAGE6_ADC_CONTROL_REG		0x10
+#define ACX00_PAGE6_ADC_CONTROL_LOW_CAL_VALUE	0x5523
+#define ACX00_PAGE6_AFE_RX_CONTROL_REG		0x13
+#define ACX00_PAGE6_AFE_RX_CONTROL_VALUE		0xf000
+#define ACX00_PAGE6_AFE_EQ_RX_DETECT_CONTROL_REG	0x14
+#define AC200_PAGE6_AFE_EQ_RX_DETECT_VALUE	0x708f
+#define AC300_PAGE6_AFE_EQ_RX_DETECT_VALUE	0x708b
+#define ACX00_PAGE6_AFE_EQ_RX_DETECT_LOW_CAL_VALUE 0x7809
+#define ACX00_PAGE6_TX_LEVEL_REG			0x15
+#define ACX00_PAGE6_TX_LEVEL_100M_MASK		GENMASK(15, 8)
+#define ACX00_PAGE6_TX_LEVEL_10M_MASK		GENMASK(7, 0)
+#define ACX00_PAGE6_TX_LEVEL_VALUE(_100m, _10m) \
+	(FIELD_PREP(ACX00_PAGE6_TX_LEVEL_100M_MASK, (_100m)) | \
+	 FIELD_PREP(ACX00_PAGE6_TX_LEVEL_10M_MASK, (_10m)))
+#define ACX00_PAGE6_TX_LEVEL_DEFAULT_VALUE \
+	ACX00_PAGE6_TX_LEVEL_VALUE(0x15, 0x30)
+#define ACX00_PAGE6_TX_LEVEL_LOW_CAL_VALUE \
+	ACX00_PAGE6_TX_LEVEL_VALUE(0x35, 0x33)
+
+#define ACX00_PAGE8_AFE_CONTROL_REG		0x18
+#define ACX00_PAGE8_AFE_CONTROL_VALUE		0x00bc
+#define ACX00_PAGE8_AUTO_CAL_CONTROL_REG		0x1d
+#define ACX00_PAGE8_AUTO_CAL_TX_LEVEL_ADJUST_BYPASS BIT(11)
+#define ACX00_PAGE8_AUTO_CAL_LOW_CAL_OPAQUE_BITS	0x0044
+#define ACX00_PAGE8_AUTO_CAL_LOW_VALUE \
+	(ACX00_PAGE8_AUTO_CAL_TX_LEVEL_ADJUST_BYPASS | \
+	 ACX00_PAGE8_AUTO_CAL_LOW_CAL_OPAQUE_BITS)
+
+/*
+ * Another integration of this exact-ID PHY documents its digital vendor
+ * register map, which also matches the observed ACx00 reset values. The ACx00
+ * analog-page field encodings remain unpublished, so keep those as opaque
+ * vendor initialization values instead of inventing bit definitions.
+ */
+
+struct acx00_ephy_priv {
+	struct phy_device *phydev;
+	struct acx00_ephy_control *control;
+	bool is_ac300;
+	bool use_low_calibration_tuning;
+	bool xmii_rx_clock_inverted;
+};
+
+static int acx00_ephy_read_page(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = __phy_read(phydev, ACX00_PAGE_SELECT_REG);
+	if (ret < 0)
+		return ret;
+
+	return FIELD_GET(ACX00_PAGE_SELECT_MASK, ret);
+}
+
+static int acx00_ephy_write_page(struct phy_device *phydev, int page)
+{
+	return __phy_write(phydev, ACX00_PAGE_SELECT_REG,
+			   FIELD_PREP(ACX00_PAGE_SELECT_MASK, page));
+}
+
+static int acx00_ephy_control_power_on(struct acx00_ephy_priv *priv)
+{
+	return priv->control->power_on(priv->control,
+				       priv->phydev->mdio.addr);
+}
+
+static int acx00_ephy_control_power_off(struct acx00_ephy_priv *priv)
+{
+	return priv->control->power_off(priv->control);
+}
+
+static int acx00_ephy_set_interface(struct phy_device *phydev)
+{
+	struct acx00_ephy_priv *priv = phydev->priv;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_NA)
+		return 0;
+	if (phydev->interface != PHY_INTERFACE_MODE_MII &&
+	    phydev->interface != PHY_INTERFACE_MODE_RMII)
+		return -EINVAL;
+
+	return priv->control->set_interface(priv->control,
+					    phydev->interface);
+}
+
+static void acx00_ephy_control_release(void *data)
+{
+	struct acx00_ephy_priv *priv = data;
+	int ret;
+
+	ret = acx00_ephy_control_power_off(priv);
+	if (ret)
+		phydev_warn(priv->phydev,
+			    "failed to power off control block: %pe\n",
+			    ERR_PTR(ret));
+}
+
+static struct device *
+acx00_ephy_find_supplier(struct device_node *control, bool is_ac300)
+{
+	struct platform_device *pdev;
+	struct mdio_device *mdiodev;
+
+	if (is_ac300) {
+		mdiodev = of_mdio_find_device(control);
+		return mdiodev ? &mdiodev->dev : NULL;
+	}
+
+	pdev = of_find_device_by_node(control);
+	return pdev ? &pdev->dev : NULL;
+}
+
+static int acx00_ephy_get_control(struct phy_device *phydev,
+				  struct acx00_ephy_priv *priv)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct acx00_ephy_control *control;
+	struct device_node *control_node;
+	struct device *supplier;
+	struct device_link *link;
+	const char *compatible;
+	const char *property;
+	bool has_ac200;
+	bool has_ac300;
+	u32 control_addr;
+	u32 configuration = 0;
+	int ret;
+
+	has_ac200 = of_property_present(dev->of_node,
+					"x-powers,ac200-control");
+	has_ac300 = of_property_present(dev->of_node,
+					"x-powers,ac300-control");
+	if (has_ac200 == has_ac300)
+		return dev_err_probe(dev, -EINVAL,
+				     "exactly one ACx00 control is required\n");
+
+	priv->is_ac300 = has_ac300;
+	property = has_ac300 ? "x-powers,ac300-control" :
+			       "x-powers,ac200-control";
+	compatible = has_ac300 ? "x-powers,ac300-ephy-ctl" :
+				 "x-powers,ac200-ephy-ctl";
+	control_node = of_parse_phandle(dev->of_node, property, 0);
+	if (!control_node)
+		return dev_err_probe(dev, -EINVAL, "missing %s\n", property);
+	if (!of_device_is_compatible(control_node, compatible)) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "%s does not reference a %s device\n",
+				    property, compatible);
+		goto out_put_node;
+	}
+	if (!of_device_is_available(control_node)) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "%s references a disabled device\n",
+				    property);
+		goto out_put_node;
+	}
+
+	if (has_ac300) {
+		ret = of_property_read_u32(control_node, "reg", &control_addr);
+		if (ret || control_addr != phydev->mdio.addr +
+					       AC300_EPHY_CONTROL_ADDR_OFFSET) {
+			ret = dev_err_probe(dev, -EINVAL,
+					    "AC300 control address does not match reg\n");
+			goto out_put_node;
+		}
+	}
+
+	if (device_property_present(dev, "nvmem-cells")) {
+		ret = nvmem_cell_read_variable_le_u32(dev,
+						      "configuration",
+						      &configuration);
+		if (ret) {
+			dev_err_probe(dev, ret,
+				      "failed to read PHY configuration\n");
+			goto out_put_node;
+		}
+
+		if (!!(configuration & ACX00_EPHY_CONFIG_VARIANT_AC300) !=
+		    priv->is_ac300) {
+			ret = dev_err_probe(dev, -EINVAL,
+					    "configuration does not match control device\n");
+			goto out_put_node;
+		}
+	}
+
+	priv->use_low_calibration_tuning =
+		priv->is_ac300 &&
+		!!(configuration & ACX00_EPHY_CONFIG_CALIBRATION_LOW);
+	ret = 0;
+
+	supplier = acx00_ephy_find_supplier(control_node, priv->is_ac300);
+	if (!supplier) {
+		ret = dev_err_probe(dev, -EPROBE_DEFER,
+				    "%s device is not registered\n", property);
+		goto out_put_node;
+	}
+
+	link = device_link_add(dev, supplier, DL_FLAG_AUTOREMOVE_CONSUMER);
+	if (!link) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "failed to link %s device\n", property);
+		goto out_put_supplier;
+	}
+
+	/* The managed link keeps the provider and its operations bound. */
+	device_lock(supplier);
+	if (!device_is_bound(supplier)) {
+		ret = -EPROBE_DEFER;
+	} else {
+		control = dev_get_drvdata(supplier);
+		if (!control || !control->power_on || !control->power_off ||
+		    !control->set_interface)
+			ret = -EINVAL;
+		else
+			priv->control = control;
+	}
+	device_unlock(supplier);
+	if (ret == -EPROBE_DEFER)
+		ret = dev_err_probe(dev, ret, "%s driver is not ready\n",
+				    property);
+	else if (ret)
+		ret = dev_err_probe(dev, ret,
+				    "%s driver has invalid control operations\n",
+				    property);
+
+out_put_supplier:
+	put_device(supplier);
+out_put_node:
+	of_node_put(control_node);
+	return ret;
+}
+
+static int acx00_ephy_disable_eee(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = phy_modify_paged(phydev, ACX00_PAGE_1,
+			       ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG,
+			       ACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0);
+	if (ret)
+		return ret;
+
+	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV,
+				  MDIO_EEE_100TX);
+}
+
+static int acx00_ephy_probe(struct phy_device *phydev)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct acx00_ephy_priv *priv;
+	int ret;
+
+	if (!dev->of_node)
+		return -ENODEV;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->phydev = phydev;
+	ret = acx00_ephy_get_control(phydev, priv);
+	if (ret)
+		return ret;
+
+	priv->xmii_rx_clock_inverted =
+		device_property_read_bool(dev,
+					  "x-powers,xmii-rx-clock-inverted");
+	phydev->priv = priv;
+	ret = devm_add_action_or_reset(dev, acx00_ephy_control_release, priv);
+	if (ret)
+		return ret;
+
+	ret = acx00_ephy_control_power_on(priv);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to power on control block\n");
+
+	/* Set the hardware default before phylib reads its EEE advertisement. */
+	ret = acx00_ephy_disable_eee(phydev);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to disable EEE\n");
+
+	return 0;
+}
+
+static int acx00_ephy_soft_reset(struct phy_device *phydev)
+{
+	struct acx00_ephy_priv *priv = phydev->priv;
+	int ret;
+
+	ret = acx00_ephy_set_interface(phydev);
+	if (ret)
+		return ret;
+
+	ret = acx00_ephy_control_power_on(priv);
+	if (ret)
+		return ret;
+
+	/* ACx00 can acknowledge reset in power-down without restarting. */
+	ret = genphy_resume(phydev);
+	if (ret)
+		return ret;
+
+	return genphy_soft_reset(phydev);
+}
+
+static int acx00_ephy_config_init(struct phy_device *phydev)
+{
+	struct acx00_ephy_priv *priv = phydev->priv;
+	u16 afe_eq_rx_detect = priv->is_ac300 ?
+		AC300_PAGE6_AFE_EQ_RX_DETECT_VALUE :
+		AC200_PAGE6_AFE_EQ_RX_DETECT_VALUE;
+	u16 tx_level_value = ACX00_PAGE6_TX_LEVEL_DEFAULT_VALUE;
+	u16 global_config;
+	int oldpage;
+	int ret;
+
+	global_config = FIELD_PREP(ACX00_PAGE0_MDI_MODE_MASK,
+				   ACX00_PAGE0_MDI_MODE_AUTO);
+	if (priv->xmii_rx_clock_inverted)
+		global_config |= ACX00_PAGE0_XMII_RX_CLOCK_INVERT;
+
+	ret = phy_modify_paged(phydev, ACX00_PAGE_0,
+			       ACX00_PAGE0_GLOBAL_CONFIG_REG,
+			       ACX00_PAGE0_XMII_RX_CLOCK_INVERT |
+			       ACX00_PAGE0_MDI_MODE_MASK, global_config);
+	if (ret)
+		return ret;
+
+	if (priv->is_ac300 && priv->use_low_calibration_tuning) {
+		afe_eq_rx_detect =
+			ACX00_PAGE6_AFE_EQ_RX_DETECT_LOW_CAL_VALUE;
+		tx_level_value = ACX00_PAGE6_TX_LEVEL_LOW_CAL_VALUE;
+	}
+
+	oldpage = phy_select_page(phydev, ACX00_PAGE_1);
+	if (oldpage < 0)
+		return oldpage;
+
+	ret = __phy_write(phydev, ACX00_PAGE1_APS_CONTROL_REG,
+			  ACX00_PAGE1_APS_DISABLED_4S_VALUE);
+	if (ret)
+		goto out_restore_page;
+	ret = __phy_modify(phydev, ACX00_PAGE1_UAPS_CONTROL_REG,
+			   ACX00_PAGE1_UAPS_ENABLE, 0);
+	if (ret)
+		goto out_restore_page;
+	ret = __phy_modify(phydev, ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG,
+			   ACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0);
+	if (ret)
+		goto out_restore_page;
+
+	ret = acx00_ephy_write_page(phydev, ACX00_PAGE_2);
+	if (ret)
+		goto out_restore_page;
+	ret = __phy_modify(phydev, ACX00_PAGE2_TX_DATA_CONTROL_REG,
+			   ACX00_PAGE2_10BT_FIR_SELECT_MASK,
+			   FIELD_PREP(ACX00_PAGE2_10BT_FIR_SELECT_MASK,
+				      ACX00_PAGE2_10BT_FIR_SELECT_DEFAULT));
+	if (ret)
+		goto out_restore_page;
+
+	ret = acx00_ephy_write_page(phydev, ACX00_PAGE_6);
+	if (ret)
+		goto out_restore_page;
+	ret = __phy_write(phydev, ACX00_PAGE6_AFE_EQ_RX_DETECT_CONTROL_REG,
+			  afe_eq_rx_detect);
+	if (ret)
+		goto out_restore_page;
+	ret = __phy_write(phydev, ACX00_PAGE6_AFE_RX_CONTROL_REG,
+			  ACX00_PAGE6_AFE_RX_CONTROL_VALUE);
+	if (ret)
+		goto out_restore_page;
+	if (priv->is_ac300 && priv->use_low_calibration_tuning) {
+		ret = __phy_write(phydev, ACX00_PAGE6_ADC_CONTROL_REG,
+				  ACX00_PAGE6_ADC_CONTROL_LOW_CAL_VALUE);
+		if (ret)
+			goto out_restore_page;
+	}
+	ret = __phy_write(phydev, ACX00_PAGE6_TX_LEVEL_REG, tx_level_value);
+	if (ret)
+		goto out_restore_page;
+
+	ret = acx00_ephy_write_page(phydev, ACX00_PAGE_8);
+	if (ret)
+		goto out_restore_page;
+	if (priv->is_ac300 && priv->use_low_calibration_tuning) {
+		ret = __phy_write(phydev, ACX00_PAGE8_AUTO_CAL_CONTROL_REG,
+				  ACX00_PAGE8_AUTO_CAL_LOW_VALUE);
+		if (ret)
+			goto out_restore_page;
+	}
+	ret = __phy_write(phydev, ACX00_PAGE8_AFE_CONTROL_REG,
+			  ACX00_PAGE8_AFE_CONTROL_VALUE);
+
+out_restore_page:
+	ret = phy_restore_page(phydev, oldpage, ret);
+	if (ret)
+		return ret;
+
+	/* Restore the standard EEE policy retained by phylib across resets. */
+	return genphy_c45_an_config_eee_aneg(phydev);
+}
+
+static int acx00_ephy_power_on_and_resume(struct phy_device *phydev)
+{
+	struct acx00_ephy_priv *priv = phydev->priv;
+	int ret;
+
+	ret = acx00_ephy_set_interface(phydev);
+	if (ret)
+		return ret;
+
+	ret = acx00_ephy_control_power_on(priv);
+	if (ret)
+		return ret;
+
+	ret = genphy_resume(phydev);
+	if (ret) {
+		acx00_ephy_control_power_off(priv);
+		return ret;
+	}
+
+	/* Powering off the control block loses the vendor-page state. */
+	return acx00_ephy_config_init(phydev);
+}
+
+static int acx00_ephy_resume(struct phy_device *phydev)
+{
+	return acx00_ephy_power_on_and_resume(phydev);
+}
+
+static int acx00_ephy_suspend(struct phy_device *phydev)
+{
+	struct acx00_ephy_priv *priv = phydev->priv;
+	int resume_ret;
+	int ret;
+
+	ret = genphy_suspend(phydev);
+	if (ret)
+		return ret;
+
+	ret = acx00_ephy_control_power_off(priv);
+	if (ret) {
+		resume_ret = acx00_ephy_power_on_and_resume(phydev);
+		if (resume_ret)
+			phydev_warn(phydev,
+				    "failed to recover from suspend error: %pe\n",
+				    ERR_PTR(resume_ret));
+	}
+
+	return ret;
+}
+
+static int acx00_ephy_match_phy_device(struct phy_device *phydev,
+				       const struct phy_driver *phydrv)
+{
+	struct device_node *node = phydev->mdio.dev.of_node;
+
+	if (!genphy_match_phy_device(phydev, phydrv) || !node)
+		return 0;
+
+	/* RK630 reports the same PHY ID, so require an X-Powers control link. */
+	return of_property_present(node, "x-powers,ac200-control") ||
+	       of_property_present(node, "x-powers,ac300-control");
+}
+
+static struct phy_driver acx00_ephy_driver[] = {
+	{
+		PHY_ID_MATCH_MODEL(ACX00_EPHY_ID),
+		.name = "X-Powers AC200/AC300 EPHY",
+		.match_phy_device = acx00_ephy_match_phy_device,
+		.probe = acx00_ephy_probe,
+		.read_page = acx00_ephy_read_page,
+		.write_page = acx00_ephy_write_page,
+		.soft_reset = acx00_ephy_soft_reset,
+		.config_init = acx00_ephy_config_init,
+		.suspend = acx00_ephy_suspend,
+		.resume = acx00_ephy_resume,
+	},
+};
+module_phy_driver(acx00_ephy_driver);
+
+static const struct mdio_device_id __maybe_unused acx00_ephy_tbl[] = {
+	{ PHY_ID_MATCH_MODEL(ACX00_EPHY_ID) },
+	{ }
+};
+MODULE_DEVICE_TABLE(mdio, acx00_ephy_tbl);
+
+MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
+MODULE_DESCRIPTION("X-Powers AC200/AC300 Ethernet PHY driver");
+MODULE_LICENSE("GPL");

-- 
2.53.0


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

* Re: [PATCH net-next v2 5/8] dt-bindings: net: x-powers: add AC300 EPHY control
  2026-08-05  2:27 ` [PATCH net-next v2 5/8] dt-bindings: net: x-powers: add AC300 EPHY control James Hilliard
@ 2026-08-05  3:38   ` Rob Herring (Arm)
  2026-08-05 14:17   ` Rob Herring
  2026-08-06  2:32   ` sashiko-bot
  2 siblings, 0 replies; 26+ messages in thread
From: Rob Herring (Arm) @ 2026-08-05  3:38 UTC (permalink / raw)
  To: James Hilliard
  Cc: Eric Dumazet, Jakub Kicinski, devicetree, Lee Jones, mfd,
	Heiner Kallweit, Andrew Lunn, Russell King, Andrew Lunn,
	Paolo Abeni, linux-arm-kernel, linux-rockchip, netdev,
	Heiko Stuebner, Conor Dooley, David S. Miller,
	Krzysztof Kozlowski, linux-kernel


On Tue, 04 Aug 2026 20:27:03 -0600, James Hilliard wrote:
> The AC300 exposes its Fast Ethernet PHY control registers through a
> separate non-PHY Clause 22 address. The manual defines that address as
> the link PHY address plus 16, giving control addresses 16 through 23 and
> link addresses 0 through 7.
> 
> Describe the control endpoint, its required VCC1 supply, its 24, 25 or
> 27 MHz input clock and its SoC SID calibration cell. The actual PHY
> remains a separate ethernet-phy node and supplies the MAC-selected
> interface mode at runtime.
> 
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> ---
>  .../bindings/net/x-powers,ac300-ephy-ctl.yaml      | 70 ++++++++++++++++++++++
>  1 file changed, 70 insertions(+)
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Documentation/devicetree/bindings/display/imx/fsl,imx-lcdc.example.dtb: display0: fsl,pcr: b'\xf0\xc8\x80\x80' is not of type 'object', 'integer', 'array', 'boolean', 'null'
	from schema $id: http://devicetree.org/schemas/dt-core.yaml

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/20260804-submit-acx00-of-dynamic-v1-v2-5-3eef49ff1d8c@gmail.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


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

* Re: [PATCH net-next v2 6/8] net: phy: add X-Powers AC300 EPHY control driver
  2026-08-05  2:27 ` [PATCH net-next v2 6/8] net: phy: add X-Powers AC300 EPHY control driver James Hilliard
@ 2026-08-05 12:22   ` Andrew Lunn
  2026-08-06  2:32   ` sashiko-bot
  2026-08-06  9:20   ` Jagielski, Jedrzej
  2 siblings, 0 replies; 26+ messages in thread
From: Andrew Lunn @ 2026-08-05 12:22 UTC (permalink / raw)
  To: James Hilliard
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Heiner Kallweit, Russell King, Heiko Stuebner, netdev,
	devicetree, linux-kernel, mfd, linux-arm-kernel, linux-rockchip

On Tue, Aug 04, 2026 at 08:27:04PM -0600, James Hilliard wrote:
> The AC300 Fast Ethernet PHY needs a separate non-PHY Clause 22 endpoint
> to complete package setup before the link endpoint becomes usable.

Please use the functions in phy_package.c

    Andrew

---
pw-bot: cr

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

* Re: [PATCH net-next v2 8/8] net: phy: add X-Powers AC200/AC300 EPHY driver
  2026-08-05  2:27 ` [PATCH net-next v2 8/8] net: phy: add X-Powers AC200/AC300 EPHY driver James Hilliard
@ 2026-08-05 12:37   ` Andrew Lunn
  2026-08-06  1:28     ` James Hilliard
  2026-08-06  2:32   ` sashiko-bot
  1 sibling, 1 reply; 26+ messages in thread
From: Andrew Lunn @ 2026-08-05 12:37 UTC (permalink / raw)
  To: James Hilliard
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Heiner Kallweit, Russell King, Heiko Stuebner, netdev,
	devicetree, linux-kernel, mfd, linux-arm-kernel, linux-rockchip

> +static int acx00_ephy_disable_eee(struct phy_device *phydev)
> +{
> +	int ret;
> +
> +	ret = phy_modify_paged(phydev, ACX00_PAGE_1,
> +			       ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG,
> +			       ACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0);
> +	if (ret)
> +		return ret;
> +
> +	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV,
> +				  MDIO_EEE_100TX);
> +}


> +
> +	/* Set the hardware default before phylib reads its EEE advertisement. */
> +	ret = acx00_ephy_disable_eee(phydev);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "failed to disable EEE\n");

If the PHY is capable of doing MAC based EEE, that should be the
default.

Why clear MDIO_AN_EEE_ADV? This does not stop the user from
re-enabling EEE for this link mode. phy_disable_eee_mode().

	Andrew

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

* Re: [PATCH net-next v2 5/8] dt-bindings: net: x-powers: add AC300 EPHY control
  2026-08-05  2:27 ` [PATCH net-next v2 5/8] dt-bindings: net: x-powers: add AC300 EPHY control James Hilliard
  2026-08-05  3:38   ` Rob Herring (Arm)
@ 2026-08-05 14:17   ` Rob Herring
  2026-08-06  1:36     ` James Hilliard
  2026-08-06  2:32   ` sashiko-bot
  2 siblings, 1 reply; 26+ messages in thread
From: Rob Herring @ 2026-08-05 14:17 UTC (permalink / raw)
  To: James Hilliard
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Krzysztof Kozlowski, Conor Dooley, Lee Jones,
	Andrew Lunn, Heiner Kallweit, Russell King, Heiko Stuebner,
	netdev, devicetree, linux-kernel, mfd, linux-arm-kernel,
	linux-rockchip

On Tue, Aug 04, 2026 at 08:27:03PM -0600, James Hilliard wrote:
> The AC300 exposes its Fast Ethernet PHY control registers through a
> separate non-PHY Clause 22 address. The manual defines that address as
> the link PHY address plus 16, giving control addresses 16 through 23 and
> link addresses 0 through 7.

That's not a separate device, but just a second address range. You 
should make 'reg' have 2 entries.

> 
> Describe the control endpoint, its required VCC1 supply, its 24, 25 or
> 27 MHz input clock and its SoC SID calibration cell. The actual PHY
> remains a separate ethernet-phy node and supplies the MAC-selected
> interface mode at runtime.
> 
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> ---
>  .../bindings/net/x-powers,ac300-ephy-ctl.yaml      | 70 ++++++++++++++++++++++
>  1 file changed, 70 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/net/x-powers,ac300-ephy-ctl.yaml b/Documentation/devicetree/bindings/net/x-powers,ac300-ephy-ctl.yaml
> new file mode 100644
> index 000000000000..8a616ff3e89f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/x-powers,ac300-ephy-ctl.yaml
> @@ -0,0 +1,70 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/net/x-powers,ac300-ephy-ctl.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: X-Powers AC300 Ethernet PHY control interface
> +
> +maintainers:
> +  - James Hilliard <james.hilliard1@gmail.com>
> +
> +description:
> +  The AC300 Fast Ethernet PHY has a separate control interface accessed as a
> +  non-PHY Clause 22 device. Its address is 16 plus the address of the link PHY.
> +
> +properties:
> +  compatible:
> +    const: x-powers,ac300-ephy-ctl
> +
> +  reg:
> +    minimum: 16
> +    maximum: 23
> +
> +  clocks:
> +    maxItems: 1
> +    description:
> +      AC300 input clock. Its configured rate must be 24, 25, or 27 MHz and
> +      determines the corresponding EPHY_CLK_SEL value.
> +
> +  vcc1-supply:
> +    description:
> +      3.3 V supply for the AC300 I/O, bandgap, Ethernet PHY analog front end,
> +      and internal digital LDO

Sounds like this supply and clock would need to be enabled before 
accessing the "link PHY address"? If so, then 2 nodes is really a 
problem.

Rob

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

* Re: [PATCH net-next v2 8/8] net: phy: add X-Powers AC200/AC300 EPHY driver
  2026-08-05 12:37   ` Andrew Lunn
@ 2026-08-06  1:28     ` James Hilliard
  2026-08-06  9:21       ` Jagielski, Jedrzej
  0 siblings, 1 reply; 26+ messages in thread
From: James Hilliard @ 2026-08-06  1:28 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Heiner Kallweit, Russell King, Heiko Stuebner, netdev,
	devicetree, linux-kernel, mfd, linux-arm-kernel, linux-rockchip

On Wed, Aug 5, 2026 at 6:38 AM Andrew Lunn <andrew@lunn.ch> wrote:
>
> > +static int acx00_ephy_disable_eee(struct phy_device *phydev)
> > +{
> > +     int ret;
> > +
> > +     ret = phy_modify_paged(phydev, ACX00_PAGE_1,
> > +                            ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG,
> > +                            ACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0);
> > +     if (ret)
> > +             return ret;
> > +
> > +     return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV,
> > +                               MDIO_EEE_100TX);
> > +}
>
>
> > +
> > +     /* Set the hardware default before phylib reads its EEE advertisement. */
> > +     ret = acx00_ephy_disable_eee(phydev);
> > +     if (ret)
> > +             return dev_err_probe(dev, ret,
> > +                                  "failed to disable EEE\n");
>
> If the PHY is capable of doing MAC based EEE, that should be the
> default.

AFAIU it's only capable of PHY based EEE

> Why clear MDIO_AN_EEE_ADV? This does not stop the user from
> re-enabling EEE for this link mode. phy_disable_eee_mode().

I think I was doing this since I was trying to align the driver defaults
with the vendor driver defaults which disable EEE entirely.

>
>         Andrew

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

* Re: [PATCH net-next v2 5/8] dt-bindings: net: x-powers: add AC300 EPHY control
  2026-08-05 14:17   ` Rob Herring
@ 2026-08-06  1:36     ` James Hilliard
  0 siblings, 0 replies; 26+ messages in thread
From: James Hilliard @ 2026-08-06  1:36 UTC (permalink / raw)
  To: Rob Herring
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Krzysztof Kozlowski, Conor Dooley, Lee Jones,
	Andrew Lunn, Heiner Kallweit, Russell King, Heiko Stuebner,
	netdev, devicetree, linux-kernel, mfd, linux-arm-kernel,
	linux-rockchip

On Wed, Aug 5, 2026 at 8:17 AM Rob Herring <robh@kernel.org> wrote:
>
> On Tue, Aug 04, 2026 at 08:27:03PM -0600, James Hilliard wrote:
> > The AC300 exposes its Fast Ethernet PHY control registers through a
> > separate non-PHY Clause 22 address. The manual defines that address as
> > the link PHY address plus 16, giving control addresses 16 through 23 and
> > link addresses 0 through 7.
>
> That's not a separate device, but just a second address range. You
> should make 'reg' have 2 entries.

I tried a different approach in my v3 using ethernet-phy-package to
instead of ctrl drivers with the AC300 control address specified by the
ethernet-phy-package register(internal it's doing a + 16 to get align
with existing conventions).

>
> >
> > Describe the control endpoint, its required VCC1 supply, its 24, 25 or
> > 27 MHz input clock and its SoC SID calibration cell. The actual PHY
> > remains a separate ethernet-phy node and supplies the MAC-selected
> > interface mode at runtime.
> >
> > Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> > ---
> >  .../bindings/net/x-powers,ac300-ephy-ctl.yaml      | 70 ++++++++++++++++++++++
> >  1 file changed, 70 insertions(+)
> >
> > diff --git a/Documentation/devicetree/bindings/net/x-powers,ac300-ephy-ctl.yaml b/Documentation/devicetree/bindings/net/x-powers,ac300-ephy-ctl.yaml
> > new file mode 100644
> > index 000000000000..8a616ff3e89f
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/net/x-powers,ac300-ephy-ctl.yaml
> > @@ -0,0 +1,70 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/net/x-powers,ac300-ephy-ctl.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: X-Powers AC300 Ethernet PHY control interface
> > +
> > +maintainers:
> > +  - James Hilliard <james.hilliard1@gmail.com>
> > +
> > +description:
> > +  The AC300 Fast Ethernet PHY has a separate control interface accessed as a
> > +  non-PHY Clause 22 device. Its address is 16 plus the address of the link PHY.
> > +
> > +properties:
> > +  compatible:
> > +    const: x-powers,ac300-ephy-ctl
> > +
> > +  reg:
> > +    minimum: 16
> > +    maximum: 23
> > +
> > +  clocks:
> > +    maxItems: 1
> > +    description:
> > +      AC300 input clock. Its configured rate must be 24, 25, or 27 MHz and
> > +      determines the corresponding EPHY_CLK_SEL value.
> > +
> > +  vcc1-supply:
> > +    description:
> > +      3.3 V supply for the AC300 I/O, bandgap, Ethernet PHY analog front end,
> > +      and internal digital LDO
>
> Sounds like this supply and clock would need to be enabled before
> accessing the "link PHY address"? If so, then 2 nodes is really a
> problem.

I think having the link node depend on the ctrl node was preventing
issues there AFAIU. My v3 works differently though.

>
> Rob

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

* Re: [PATCH net-next v2 6/8] net: phy: add X-Powers AC300 EPHY control driver
  2026-08-05  2:27 ` [PATCH net-next v2 6/8] net: phy: add X-Powers AC300 EPHY control driver James Hilliard
  2026-08-05 12:22   ` Andrew Lunn
@ 2026-08-06  2:32   ` sashiko-bot
  2026-08-06  9:20   ` Jagielski, Jedrzej
  2 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-08-06  2:32 UTC (permalink / raw)
  To: James Hilliard; +Cc: lee, devicetree, conor+dt, mfd, robh

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Unconditional assertion of CHIP_RESET on first power-on wipes external-supply configuration.
--

--- Patch [6]: [PATCH net-next v2 6/8] net: phy: add X-Powers AC300 EPHY control driver ---
commit 22de676d43131faa2cdf14d10397704b51076320
Author: James Hilliard <james.hilliard1@gmail.com>

net: phy: add X-Powers AC300 EPHY control driver

This commit adds an MDIO control driver for the Fast Ethernet PHY in the
X-Powers AC300 companion IC. It programs the PHY calibration, interface mode,
clock, and I/O controls.

diff --git a/drivers/net/phy/xpowers-ac300-ctl.c b/drivers/net/phy/xpowers-ac300-ctl.c
--- /dev/null
+++ b/drivers/net/phy/xpowers-ac300-ctl.c

[ ... ]

> +static int ac300_ephy_ctl_power_on(struct acx00_ephy_control *control,
> +				   unsigned int phy_addr)
> +{
> +	struct ac300_ephy_ctl *priv =
> +		container_of(control, struct ac300_ephy_ctl, control);

[ ... ]

> +	ret = clk_prepare_enable(priv->clk);
> +	if (ret)
> +		goto out_unlock;
> +	priv->powered = true;
> +
> +	/* Keep the external-supply configuration across subsequent resets. */
> +	reset_value = priv->package_known && !priv->internal_dldo ?
> +		      AC300_CHIP_RESET_DEASSERT : 0;
> +	ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG, reset_value);

[Severity: High]
Does this code inadvertently wipe the external-supply configuration on the
first probe?

When ac300_ephy_ctl_power_on() is called for the first time,
priv->package_known is false because it is zero-initialized by
devm_kzalloc().

This causes reset_value to evaluate to 0, which is then written to
AC300_SYS_CONTROL_REG, unconditionally asserting CHIP_RESET for all package
types before the package status is ever read.

[ ... ]

> +static int ac300_ephy_ctl_probe(struct mdio_device *mdiodev)
> +{

[ ... ]

> +	priv->control.power_on = ac300_ephy_ctl_power_on;
> +	priv->control.power_off = ac300_ephy_ctl_power_off;
> +	priv->control.set_interface = ac300_ephy_ctl_set_interface;
> +	/* MII is the reset default used until the MAC supplies its interface. */
> +	priv->interface = PHY_INTERFACE_MODE_MII;
> +	mdiodev_set_drvdata(mdiodev, &priv->control);
> +
> +	/* Validate the package while the control endpoint is known to respond. */
> +	phy_addr = ac300_ephy_ctl_link_addr(priv);
> +	ret = ac300_ephy_ctl_power_on(&priv->control, phy_addr);

[Severity: High]
Could this first call to ac300_ephy_ctl_power_on() trigger the destructive
reset mentioned above?

Since priv->package_known is still false at this point during the probe,
the driver seems to wipe out the configuration it intends to preserve.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804-submit-acx00-of-dynamic-v1-v2-0-3eef49ff1d8c@gmail.com?part=6

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

* Re: [PATCH net-next v2 2/8] dt-bindings: mfd: x-powers: add AC200
  2026-08-05  2:27 ` [PATCH net-next v2 2/8] dt-bindings: mfd: x-powers: add AC200 James Hilliard
@ 2026-08-06  2:32   ` sashiko-bot
  0 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-08-06  2:32 UTC (permalink / raw)
  To: James Hilliard; +Cc: lee, devicetree, robh, mfd, conor+dt

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The binding omits required properties for the hardware's interrupt controller capability.
- [Low] The commit message description is not capitalized, violating the MFD subsystem guidelines.
--

--- Patch [2]: [PATCH net-next v2 2/8] dt-bindings: mfd: x-powers: add AC200 ---
commit 5fdfa30c4c35094fa0d906393ce55eb7a91506b0
Author: James Hilliard <james.hilliard1@gmail.com>

dt-bindings: mfd: x-powers: add AC200

This commit introduces the device tree binding for the X-Powers AC200
mixed-signal companion IC. It describes the parent device, its input clocks,
supplies, and an optional Ethernet PHY control child.

> dt-bindings: mfd: x-powers: add AC200

[Severity: Low]
This isn't a bug, but should the description after the subsystem prefix be
capitalized? The MFD subsystem conventions generally expect a capitalized
description like "Add AC200".

> diff --git a/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml b/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml
> new file mode 100644
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml

[ ... ]

> +  ethernet-phy-control:
> +    $ref: /schemas/net/x-powers,ac200-ephy-ctl.yaml#
> +
> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - ac-ldoin-supply
> +  - ephy-vcc-supply
> +  - rtc-vcc-supply
> +  - tv-vcc-supply

[Severity: Medium]
The commit message notes that interrupt-controller properties can be added
later with the child functions that consume them. Does omitting these
properties from the required list allow incomplete device tree nodes to
silently pass schema validation?

Since the hardware has interrupt controller capabilities, should
interrupt-controller and #interrupt-cells be defined and added to the
required list now? This would prevent situations where future drivers attempt
to use the interrupts and fail on device trees that passed this initial schema.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804-submit-acx00-of-dynamic-v1-v2-0-3eef49ff1d8c@gmail.com?part=2

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

* Re: [PATCH net-next v2 1/8] dt-bindings: net: x-powers: add AC200 EPHY control
  2026-08-05  2:26 ` [PATCH net-next v2 1/8] dt-bindings: net: x-powers: add AC200 EPHY control James Hilliard
@ 2026-08-06  2:32   ` sashiko-bot
  0 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-08-06  2:32 UTC (permalink / raw)
  To: James Hilliard; +Cc: mfd, lee, robh, devicetree, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The YAML binding schema is missing the mandatory `examples:` section.
--

--- Patch [1]: [PATCH net-next v2 1/8] dt-bindings: net: x-powers: add AC200 EPHY control ---
commit e7e7a5a68f157d374c9eb4fdb6add2b0b9d23284
Author: James Hilliard <james.hilliard1@gmail.com>

dt-bindings: net: x-powers: add AC200 EPHY control

This commit adds the device tree binding for the X-Powers AC200 Fast Ethernet
PHY control function. It describes the control logic and an optional SoC SID
calibration cell used by the AC200 I2C regmap.

> diff --git a/Documentation/devicetree/bindings/net/x-powers,ac200-ephy-ctl.yaml b/Documentation/devicetree/bindings/net/x-powers,ac200-ephy-ctl.yaml
> new file mode 100644
> index 0000000000000..5bb60542030fc
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/x-powers,ac200-ephy-ctl.yaml

[ ... ]

> required:
>   - compatible
> 
> dependentRequired:
>   nvmem-cells: [ nvmem-cell-names ]
> 
> additionalProperties: false
> ...

[Severity: Low]
Is this YAML binding schema missing the examples section? 

Device tree bindings typically require an examples section at the end of the
document. Without it, developers lack documentation on how to construct the
node, and the dt_binding_check tooling has no concrete representation to
validate the schema against to ensure constraints like dependentRequired work
as intended.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804-submit-acx00-of-dynamic-v1-v2-0-3eef49ff1d8c@gmail.com?part=1

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

* Re: [PATCH net-next v2 7/8] dt-bindings: net: x-powers: add AC200/AC300 EPHY
  2026-08-05  2:27 ` [PATCH net-next v2 7/8] dt-bindings: net: x-powers: add AC200/AC300 EPHY James Hilliard
@ 2026-08-06  2:32   ` sashiko-bot
  0 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-08-06  2:32 UTC (permalink / raw)
  To: James Hilliard; +Cc: conor+dt, devicetree, mfd, lee, robh

> The AC200 and AC300 MDIO link interfaces report the same Clause 22
> identifier and use a compatible link-side paged register layout, but need
> different package control interfaces before that identifier can be read.
> 
> Describe the forced-ID PHY and require exactly one reference to an
> enabled AC200 or AC300 control device. The PHY node's reg value is
> programmed through the AC200 control interface. For AC300, it must match
> the address selected by the package straps and is therefore limited to 0
> through 7.
> 
> Allow a system to provide its optional two-bit SID configuration field.
> Bit 0 identifies the AC200 or AC300 package and must agree with the
> described control device. Bit 1 selects the AC300 low-calibration tuning
> sequence. Omitting the field skips package validation and selects the
> normal calibration sequence.
> 
> Also describe the optional xMII receive-clock inversion needed by some
> integrations. Requiring an X-Powers control reference distinguishes these
> integrations from the Rockchip RK630 PHY, which reports the same
> identifier.
> 
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804-submit-acx00-of-dynamic-v1-v2-0-3eef49ff1d8c@gmail.com?part=7


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

* Re: [PATCH net-next v2 3/8] mfd: add X-Powers AC200 support
  2026-08-05  2:27 ` [PATCH net-next v2 3/8] mfd: add X-Powers AC200 support James Hilliard
@ 2026-08-06  2:32   ` sashiko-bot
  0 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-08-06  2:32 UTC (permalink / raw)
  To: James Hilliard; +Cc: conor+dt, robh, devicetree, mfd, lee

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] The driver uses PLATFORM_DEVID_NONE instead of PLATFORM_DEVID_AUTO when registering MFD children, risking sysfs naming collisions.
- [Low] The commit message prefix violates MFD subsystem capitalization and formatting rules.
- [Low] The `i2c_device_id` array uses implicit positional initializers instead of named initializers.
--

--- Patch [3]: [PATCH net-next v2 3/8] mfd: add X-Powers AC200 support ---
commit 98ebae297c54eb1823cd6d77c3a20fc90dde205b
Author: James Hilliard <james.hilliard1@gmail.com>

mfd: add X-Powers AC200 support

This commit introduces the MFD core driver for the X-Powers AC200 mixed-signal
companion IC. It initializes shared regmap access over I2C, applies vendor
resets, and registers Ethernet PHY control children.

> mfd: add X-Powers AC200 support

[Severity: Low]
This isn't a bug, but should the commit message prefix follow the standard
MFD capitalization and naming format? The subsystem guidelines require
capitalizing the description and using the driver name, such as
mfd: ac200: Add X-Powers AC200 support.

> The X-Powers AC200 is a mixed-signal companion IC with a paged register
> map accessed over I2C.

[ ... ]

> --- /dev/null
> +++ b/drivers/mfd/ac200.c
> @@ -0,0 +1,207 @@

[ ... ]

> +	ephy_node = of_get_compatible_child(dev->of_node,
> +					    "x-powers,ac200-ephy-ctl");
> +	if (!ephy_node)
> +		return 0;
> +
> +	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, ac200_cells,
> +				   ARRAY_SIZE(ac200_cells), NULL, 0, NULL);

[Severity: Medium]
Could using PLATFORM_DEVID_NONE here cause sysfs naming collisions if
multiple instances of the AC200 are present on different I2C buses? The MFD
subsystem guidelines suggest preferring PLATFORM_DEVID_AUTO for automatic
cell indexing to prevent driver load failures.

> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to add MFD devices\n");
> +
> +	return 0;
> +}

[ ... ]

> +static const struct of_device_id ac200_of_match[] = {
> +	{ .compatible = "x-powers,ac200" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, ac200_of_match);
> +
> +static const struct i2c_device_id ac200_i2c_ids[] = {
> +	{ "ac200" },

[Severity: Low]
This isn't a bug, but does the I2C subsystem require initialized arrays of
type struct i2c_device_id to use named initializers instead of implicit
positional ones? It looks like this should be { .name = "ac200" }.

> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, ac200_i2c_ids);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804-submit-acx00-of-dynamic-v1-v2-0-3eef49ff1d8c@gmail.com?part=3

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

* Re: [PATCH net-next v2 8/8] net: phy: add X-Powers AC200/AC300 EPHY driver
  2026-08-05  2:27 ` [PATCH net-next v2 8/8] net: phy: add X-Powers AC200/AC300 EPHY driver James Hilliard
  2026-08-05 12:37   ` Andrew Lunn
@ 2026-08-06  2:32   ` sashiko-bot
  1 sibling, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-08-06  2:32 UTC (permalink / raw)
  To: James Hilliard; +Cc: mfd, conor+dt, lee, robh, devicetree

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Premature devres action registration causes unbalanced power_off on probe failure.
- [High] Missing phy_restore_page() on phy_select_page() error causes MDIO bus deadlock.
- [High] Unbalanced power_on in soft_reset causes resource reference count leak.
- [High] Acquiring the supplier's device_lock in probe creates an AB-BA deadlock.
- [Medium] Incorrect error recovery in suspend causes an unbalanced power_on.
--

--- Patch [8]: [PATCH net-next v2 8/8] net: phy: add X-Powers AC200/AC300 EPHY driver ---
commit 5868cfc367391cc28058cf619b83005f8ecd1852
Author: James Hilliard <james.hilliard1@gmail.com>

net: phy: add X-Powers AC200/AC300 EPHY driver

This commit adds a shared PHY driver for the X-Powers AC200 and AC300 companion
ICs. It configures the common MDIO PHY registers and manages package-specific
power, calibration, and interface configurations.

> diff --git a/drivers/net/phy/xpowers-acx00.c b/drivers/net/phy/xpowers-acx00.c
> new file mode 100644
> index 0000000000000..f9e6be751252d
> --- /dev/null
> +++ b/drivers/net/phy/xpowers-acx00.c
[ ... ]
> +static int acx00_ephy_get_control(struct phy_device *phydev,
> +				  struct acx00_ephy_priv *priv)
> +{
[ ... ]
> +	/* The managed link keeps the provider and its operations bound. */
> +	device_lock(supplier);
> +	if (!device_is_bound(supplier)) {
> +		ret = -EPROBE_DEFER;
> +	} else {

[Severity: High]
Does this introduce a regression by creating a deadlock during device unbind?

The driver core's really_probe() holds the consumer's device_lock while
calling acx00_ephy_get_control(), which then acquires device_lock(supplier).

Conversely, the driver core's unbind path holds the supplier's device_lock and
attempts to acquire the consumer's device_lock to tear down the device link.

[ ... ]
> +static int acx00_ephy_probe(struct phy_device *phydev)
> +{
[ ... ]
> +	ret = devm_add_action_or_reset(dev, acx00_ephy_control_release, priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = acx00_ephy_control_power_on(priv);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "failed to power on control block\n");

[Severity: High]
If acx00_ephy_control_power_on() fails, will the devres cleanup execute an
unbalanced power_off on the control block?

Since the devm action is registered before power_on completes, returning an
error here triggers acx00_ephy_control_release() on a device that failed to
power on.

[ ... ]
> +static int acx00_ephy_soft_reset(struct phy_device *phydev)
> +{
[ ... ]
> +	ret = acx00_ephy_set_interface(phydev);
> +	if (ret)
> +		return ret;
> +
> +	ret = acx00_ephy_control_power_on(priv);
> +	if (ret)
> +		return ret;

[Severity: High]
Does this cause a regression by leaking the provider's reference counts
(regulator, clock, etc.) on every soft reset?

acx00_ephy_control_power_on() is called unconditionally, but there is no
corresponding power_off in the reset cycle. Repeated interface up/down cycles
would monotonically increment the reference counts.

[ ... ]
> +static int acx00_ephy_config_init(struct phy_device *phydev)
> +{
[ ... ]
> +	oldpage = phy_select_page(phydev, ACX00_PAGE_1);
> +	if (oldpage < 0)
> +		return oldpage;

[Severity: High]
Does returning directly here cause a regression by leaving the mdio bus
permanently deadlocked?

phy_select_page() acquires the mdio bus lock before attempting to read the
page. If the read fails and returns an error, the caller must still invoke
phy_restore_page() to unlock the bus.

[ ... ]
> +static int acx00_ephy_suspend(struct phy_device *phydev)
> +{
[ ... ]
> +	ret = acx00_ephy_control_power_off(priv);
> +	if (ret) {
> +		resume_ret = acx00_ephy_power_on_and_resume(phydev);
> +		if (resume_ret)
> +			phydev_warn(phydev,
> +				    "failed to recover from suspend error: %pe\n",
> +				    ERR_PTR(resume_ret));
> +	}

[Severity: Medium]
Does this error recovery path cause a regression by forcing an unbalanced power
state?

If acx00_ephy_control_power_off() fails, the resource was never successfully
powered off. Invoking acx00_ephy_power_on_and_resume() here will execute an
additional power_on, resulting in a reference count leak.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804-submit-acx00-of-dynamic-v1-v2-0-3eef49ff1d8c@gmail.com?part=8

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

* Re: [PATCH net-next v2 5/8] dt-bindings: net: x-powers: add AC300 EPHY control
  2026-08-05  2:27 ` [PATCH net-next v2 5/8] dt-bindings: net: x-powers: add AC300 EPHY control James Hilliard
  2026-08-05  3:38   ` Rob Herring (Arm)
  2026-08-05 14:17   ` Rob Herring
@ 2026-08-06  2:32   ` sashiko-bot
  2 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-08-06  2:32 UTC (permalink / raw)
  To: James Hilliard; +Cc: robh, devicetree, conor+dt, mfd, lee

> The AC300 exposes its Fast Ethernet PHY control registers through a
> separate non-PHY Clause 22 address. The manual defines that address as
> the link PHY address plus 16, giving control addresses 16 through 23 and
> link addresses 0 through 7.
> 
> Describe the control endpoint, its required VCC1 supply, its 24, 25 or
> 27 MHz input clock and its SoC SID calibration cell. The actual PHY
> remains a separate ethernet-phy node and supplies the MAC-selected
> interface mode at runtime.
> 
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804-submit-acx00-of-dynamic-v1-v2-0-3eef49ff1d8c@gmail.com?part=5


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

* Re: [PATCH net-next v2 4/8] net: phy: add X-Powers AC200 EPHY control driver
  2026-08-05  2:27 ` [PATCH net-next v2 4/8] net: phy: add X-Powers AC200 EPHY control driver James Hilliard
@ 2026-08-06  2:32   ` sashiko-bot
  2026-08-06  9:19   ` Jagielski, Jedrzej
  1 sibling, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-08-06  2:32 UTC (permalink / raw)
  To: James Hilliard; +Cc: devicetree, mfd, conor+dt, robh, lee

> The AC200 Fast Ethernet PHY needs package registers in the parent I2C
> regmap to be configured before its Clause 22 endpoint becomes usable.
> 
> Add a control driver which obtains the calibration value, applies the
> vendor offset, selects the documented 24 or 27 MHz input clock, programs
> the link PHY address and performs the required reset, clock and shutdown
> sequence. Use the optional SoC SID cell when supplied and otherwise read
> the AC200's internal calibration eFuse.
> 
> Early PHY creation occurs before a MAC has attached, so use reset-default
> MII initially. A separate operation later applies phydev->interface; when
> the block is already powered it changes only RMII_SEL before the normal
> PHY soft reset.
> 
> Expose serialized, idempotent power and interface operations to the
> common PHY driver. Enable only the MII I/O pads in this basic driver;
> dedicated LED outputs remain disabled until a later LED patch describes
> and manages them.
> 
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804-submit-acx00-of-dynamic-v1-v2-0-3eef49ff1d8c@gmail.com?part=4


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

* RE: [PATCH net-next v2 4/8] net: phy: add X-Powers AC200 EPHY control driver
  2026-08-05  2:27 ` [PATCH net-next v2 4/8] net: phy: add X-Powers AC200 EPHY control driver James Hilliard
  2026-08-06  2:32   ` sashiko-bot
@ 2026-08-06  9:19   ` Jagielski, Jedrzej
  1 sibling, 0 replies; 26+ messages in thread
From: Jagielski, Jedrzej @ 2026-08-06  9:19 UTC (permalink / raw)
  To: James Hilliard, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Lee Jones, Andrew Lunn, Heiner Kallweit,
	Russell King, Heiko Stuebner
  Cc: netdev@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, mfd@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org,
	linux-rockchip@lists.infradead.org

From: James Hilliard <james.hilliard1@gmail.com> 
Sent: Wednesday, August 5, 2026 4:27 AM

>The AC200 Fast Ethernet PHY needs package registers in the parent I2C
>regmap to be configured before its Clause 22 endpoint becomes usable.
>
>Add a control driver which obtains the calibration value, applies the
>vendor offset, selects the documented 24 or 27 MHz input clock, programs
>the link PHY address and performs the required reset, clock and shutdown
>sequence. Use the optional SoC SID cell when supplied and otherwise read
>the AC200's internal calibration eFuse.
>
>Early PHY creation occurs before a MAC has attached, so use reset-default
>MII initially. A separate operation later applies phydev->interface; when
>the block is already powered it changes only RMII_SEL before the normal
>PHY soft reset.
>
>Expose serialized, idempotent power and interface operations to the
>common PHY driver. Enable only the MII I/O pads in this basic driver;
>dedicated LED outputs remain disabled until a later LED patch describes
>and manages them.
>
>Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
>---
> drivers/net/phy/Kconfig             |   9 ++
> drivers/net/phy/Makefile            |   1 +
> drivers/net/phy/xpowers-ac200-ctl.c | 294 ++++++++++++++++++++++++++++++++++++
> drivers/net/phy/xpowers-acx00.h     |  15 ++
> 4 files changed, 319 insertions(+)
>
>diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
>index a29d3fed8a05..6119f4de880d 100644
>--- a/drivers/net/phy/Kconfig
>+++ b/drivers/net/phy/Kconfig
>@@ -475,6 +475,15 @@ config VITESSE_PHY
> 	help
> 	  Currently supports the vsc8244
> 
>+config XPOWERS_AC200_PHY_CTL
>+	tristate "X-Powers AC200 Ethernet PHY control"
>+	depends on MFD_AC200
>+	help
>+	  Enable the control driver for the Fast Ethernet PHY function in
>+	  the X-Powers AC200 mixed-signal companion IC. It programs the PHY
>+	  address, interface mode, calibration and I/O controls through the
>+	  parent AC200 I2C regmap.
>+
> config XILINX_GMII2RGMII
> 	tristate "Xilinx GMII2RGMII converter driver"
> 	help
>diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
>index e23df5e836e9..f81854fc9f12 100644
>--- a/drivers/net/phy/Makefile
>+++ b/drivers/net/phy/Makefile
>@@ -100,4 +100,5 @@ obj-$(CONFIG_SMSC_PHY)		+= smsc.o
> obj-$(CONFIG_STE10XP)		+= ste10Xp.o
> obj-$(CONFIG_TERANETICS_PHY)	+= teranetics.o
> obj-$(CONFIG_VITESSE_PHY)	+= vitesse.o
>+obj-$(CONFIG_XPOWERS_AC200_PHY_CTL) += xpowers-ac200-ctl.o
> obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
>diff --git a/drivers/net/phy/xpowers-ac200-ctl.c b/drivers/net/phy/xpowers-ac200-ctl.c
>new file mode 100644
>index 000000000000..c69e6a8c71a4
>--- /dev/null
>+++ b/drivers/net/phy/xpowers-ac200-ctl.c
>@@ -0,0 +1,294 @@
>+// SPDX-License-Identifier: GPL-2.0-only
>+/*
>+ * X-Powers AC200 Ethernet PHY control driver
>+ *
>+ * Copyright (c) 2022 Arm Ltd. (Andre Przywara <andre.przywara@arm.com>)
>+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>

Quite unclear for me what's Andre's role here
if he worked on that shouldn't he be mentioned in the
commit msg anyhow?

>+ */
>+
>+#include <linux/bitfield.h>
>+#include <linux/clk.h>
>+#include <linux/delay.h>
>+#include <linux/module.h>
>+#include <linux/mutex.h>
>+#include <linux/nvmem-consumer.h>
>+#include <linux/phy.h>
>+#include <linux/platform_device.h>
>+#include <linux/property.h>
>+#include <linux/regmap.h>
>+
>+#include "xpowers-acx00.h"
>+
>+#define AC200_EPHY_BPS_EFFUSE_OFFSET	3
>+
>+#define AC200_SYS_EPHY_CTL0_REG			0x0014
>+#define AC200_EPHY_RESET_DEASSERT		BIT(0)
>+#define AC200_EPHY_SYSCLK_ENABLE			BIT(1)
>+
>+#define AC200_SYS_EPHY_CTL1_REG			0x0016
>+#define AC200_EPHY_MII_IO_ENABLE			BIT(0)
>+
>+/* AC200-internal copy of the Ethernet PHY calibration eFuse. */
>+#define AC200_EFUSE_EPHY_REG			0x8004
>+
>+#define AC200_EPHY_CTL_REG			0x6000
>+#define AC200_EPHY_SHUTDOWN			BIT(0)
>+#define AC200_EPHY_CLK_SEL_24_MHZ		BIT(2)
>+#define AC200_EPHY_PHY_ADDR_MASK			GENMASK(8, 4)
>+#define AC200_EPHY_RMII_SEL			BIT(11)
>+#define AC200_EPHY_BPS_EFFUSE_MASK		GENMASK(15, 12)
>+
>+struct ac200_ephy_ctl {
>+	struct acx00_ephy_control control;
>+	struct regmap *regmap;
>+	struct mutex lock; /* Serializes power sequencing and state. */
>+	u16 ephy_ctl;
>+	unsigned int phy_addr;
>+	phy_interface_t interface;
>+	bool powered;
>+};
>+
>+static u16 ac200_ephy_ctl_config(const struct ac200_ephy_ctl *priv)
>+{
>+	return priv->ephy_ctl |
>+		(priv->interface == PHY_INTERFACE_MODE_RMII ?
>+		 AC200_EPHY_RMII_SEL : 0) |
>+		FIELD_PREP(AC200_EPHY_PHY_ADDR_MASK, priv->phy_addr);
>+}
>+
>+static int ac200_ephy_ctl_power_off_locked(struct ac200_ephy_ctl *priv)
>+{
>+	int err;
>+	int ret;
>+
>+	if (!priv->powered)
>+		return 0;
>+
>+	ret = regmap_write(priv->regmap, AC200_EPHY_CTL_REG,
>+			   ac200_ephy_ctl_config(priv) | AC200_EPHY_SHUTDOWN);

how about creating wrapper logging which register write failed?
currently there's no info at the end what's not ideal when debugging
especially as the're called in a sequence

>+	err = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL1_REG, 0);
>+	if (!ret)
>+		ret = err;
>+	err = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0_REG, 0);
>+	if (!ret)
>+		ret = err;
>+
>+	priv->powered = false;
>+
>+	return ret;
>+}
>+
>+static int ac200_ephy_ctl_power_off(struct acx00_ephy_control *control)
>+{
>+	struct ac200_ephy_ctl *priv =
>+		container_of(control, struct ac200_ephy_ctl, control);
>+	int ret;
>+
>+	mutex_lock(&priv->lock);
>+	ret = ac200_ephy_ctl_power_off_locked(priv);
>+	mutex_unlock(&priv->lock);
>+
>+	return ret;
>+}
>+
>+static int
>+ac200_ephy_ctl_set_interface(struct acx00_ephy_control *control,
>+			     phy_interface_t interface)
>+{
>+	struct ac200_ephy_ctl *priv =
>+		container_of(control, struct ac200_ephy_ctl, control);
>+	u16 value;
>+	int ret = 0;
>+
>+	switch (interface) {
>+	case PHY_INTERFACE_MODE_MII:
>+		value = 0;
>+		break;
>+	case PHY_INTERFACE_MODE_RMII:
>+		value = AC200_EPHY_RMII_SEL;
>+		break;
>+	default:
>+		return -EINVAL;
>+	}

'value' can be moved into priv->powered branch since
it's used only there

>+
>+	mutex_lock(&priv->lock);
>+	if (priv->interface == interface)
>+		goto out_unlock;
>+
>+	if (priv->powered)
>+		ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL_REG,
>+					 AC200_EPHY_RMII_SEL, value);
>+	if (!ret)
>+		priv->interface = interface;
>+
>+out_unlock:
>+	mutex_unlock(&priv->lock);
>+
>+	return ret;
>+}
>+
>+static int ac200_ephy_ctl_power_on(struct acx00_ephy_control *control,
>+				   unsigned int phy_addr)
>+{
>+	struct ac200_ephy_ctl *priv =
>+		container_of(control, struct ac200_ephy_ctl, control);
>+	u16 ephy_ctl;
>+	int ret;
>+
>+	if (phy_addr > FIELD_MAX(AC200_EPHY_PHY_ADDR_MASK))
>+		return -EINVAL;
>+
>+	mutex_lock(&priv->lock);
>+	if (priv->powered && priv->phy_addr == phy_addr) {
>+		ret = 0;
>+		goto out_unlock;
>+	}
>+	if (priv->powered) {
>+		ret = ac200_ephy_ctl_power_off_locked(priv);
>+		if (ret)
>+			goto out_unlock;
>+	}
>+	priv->phy_addr = phy_addr;
>+
>+	ephy_ctl = ac200_ephy_ctl_config(priv);
>+
>+	/* Start from a disabled state before applying the configuration. */
>+	ret = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0_REG, 0);
>+	if (ret)
>+		goto err_disable;
>+
>+	ret = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL1_REG,
>+			   AC200_EPHY_MII_IO_ENABLE);
>+	if (ret)
>+		goto err_disable;
>+
>+	ret = regmap_write(priv->regmap, AC200_EPHY_CTL_REG,
>+			   ephy_ctl | AC200_EPHY_SHUTDOWN);
>+	if (ret)
>+		goto err_disable;
>+
>+	ret = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0_REG,
>+			   AC200_EPHY_RESET_DEASSERT |
>+			   AC200_EPHY_SYSCLK_ENABLE);
>+	if (ret)
>+		goto err_disable;
>+

what does the value comes from?

>+	fsleep(10000);
>+
>+	ret = regmap_write(priv->regmap, AC200_EPHY_CTL_REG, ephy_ctl);
>+	if (ret)
>+		goto err_disable;
>+
>+	priv->powered = true;
>+	goto out_unlock;
>+
>+err_disable:
>+	/* Attempt every step of the shutdown sequence after a partial start. */
>+	priv->powered = true;
>+	ac200_ephy_ctl_power_off_locked(priv);
>+out_unlock:
>+	mutex_unlock(&priv->lock);
>+
>+	return ret;
>+}
>+
>+static int ac200_ephy_ctl_probe(struct platform_device *pdev)
>+{
>+	struct device *dev = &pdev->dev;
>+	struct ac200_ephy_ctl *priv;
>+	unsigned long clk_rate;
>+	unsigned int calibration;
>+	u8 nvmem_calibration;
>+	u8 bps_effuse_code;
>+	struct clk *clk;
>+	int ret;
>+
>+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>+	if (!priv)
>+		return -ENOMEM;
>+	mutex_init(&priv->lock);
>+
>+	priv->regmap = dev_get_regmap(dev->parent, NULL);
>+	if (!priv->regmap)
>+		return dev_err_probe(dev, -EPROBE_DEFER,
>+				     "parent regmap is not ready\n");
>+
>+	if (device_property_present(dev, "nvmem-cells")) {
>+		ret = nvmem_cell_read_u8(dev, "calibration",
>+					 &nvmem_calibration);
>+		if (ret)
>+			return dev_err_probe(dev, ret,
>+					     "failed to read calibration data\n");
>+		calibration = nvmem_calibration;
>+	} else {
>+		ret = regmap_read(priv->regmap, AC200_EFUSE_EPHY_REG,
>+				  &calibration);
>+		if (ret)
>+			return dev_err_probe(dev, ret,
>+					     "failed to read on-chip calibration data\n");
>+	}
>+
>+	/* The vendor driver supplies no transfer function beyond this offset. */
>+	bps_effuse_code = (calibration + AC200_EPHY_BPS_EFFUSE_OFFSET) &
>+			   FIELD_MAX(AC200_EPHY_BPS_EFFUSE_MASK);
>+	priv->ephy_ctl =
>+		FIELD_PREP(AC200_EPHY_BPS_EFFUSE_MASK, bps_effuse_code);
>+	/* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */
>+
>+	clk = clk_get(dev->parent, NULL);
>+	if (IS_ERR(clk))
>+		return dev_err_probe(dev, PTR_ERR(clk),
>+				     "failed to get input clock\n");
>+
>+	clk_rate = clk_get_rate(clk);
>+	clk_put(clk);
>+
>+	switch (clk_rate) {
>+	case 24000000:
>+		priv->ephy_ctl |= AC200_EPHY_CLK_SEL_24_MHZ;
>+		break;
>+	case 27000000:
>+		break;
>+	default:
>+		return dev_err_probe(dev, -EINVAL,
>+				     "unsupported input clock rate %lu Hz\n",
>+				     clk_rate);
>+	}
>+
>+	priv->control.power_on = ac200_ephy_ctl_power_on;
>+	priv->control.power_off = ac200_ephy_ctl_power_off;
>+	priv->control.set_interface = ac200_ephy_ctl_set_interface;
>+	/* MII is the reset default used until the MAC supplies its interface. */
>+	priv->interface = PHY_INTERFACE_MODE_MII;
>+	platform_set_drvdata(pdev, &priv->control);
>+
>+	return 0;
>+}
>+
>+static void ac200_ephy_ctl_remove(struct platform_device *pdev)
>+{
>+	struct acx00_ephy_control *control = platform_get_drvdata(pdev);
>+
>+	control->power_off(control);
>+}
>+
>+static const struct of_device_id ac200_ephy_ctl_of_match[] = {
>+	{ .compatible = "x-powers,ac200-ephy-ctl" },
>+	{ }
>+};
>+MODULE_DEVICE_TABLE(of, ac200_ephy_ctl_of_match);
>+
>+static struct platform_driver ac200_ephy_ctl_driver = {
>+	.probe = ac200_ephy_ctl_probe,
>+	.remove = ac200_ephy_ctl_remove,
>+	.shutdown = ac200_ephy_ctl_remove,
>+	.driver = {
>+		.name = "ac200-ephy-ctl",
>+		.of_match_table = ac200_ephy_ctl_of_match,
>+	},
>+};
>+module_platform_driver(ac200_ephy_ctl_driver);
>+
>+MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
>+MODULE_DESCRIPTION("X-Powers AC200 Ethernet PHY control driver");
>+MODULE_LICENSE("GPL");
>diff --git a/drivers/net/phy/xpowers-acx00.h b/drivers/net/phy/xpowers-acx00.h
>new file mode 100644
>index 000000000000..482ba6faba6e
>--- /dev/null
>+++ b/drivers/net/phy/xpowers-acx00.h
>@@ -0,0 +1,15 @@
>+/* SPDX-License-Identifier: GPL-2.0-only */
>+#ifndef __DRIVERS_NET_PHY_XPOWERS_ACX00_H
>+#define __DRIVERS_NET_PHY_XPOWERS_ACX00_H
>+
>+#include <linux/phy.h>
>+
>+struct acx00_ephy_control {
>+	int (*power_on)(struct acx00_ephy_control *control,
>+			unsigned int phy_addr);
>+	int (*power_off)(struct acx00_ephy_control *control);
>+	int (*set_interface)(struct acx00_ephy_control *control,
>+			     phy_interface_t interface);
>+};
>+
>+#endif
>
>-- 
>2.53.0



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

* RE: [PATCH net-next v2 6/8] net: phy: add X-Powers AC300 EPHY control driver
  2026-08-05  2:27 ` [PATCH net-next v2 6/8] net: phy: add X-Powers AC300 EPHY control driver James Hilliard
  2026-08-05 12:22   ` Andrew Lunn
  2026-08-06  2:32   ` sashiko-bot
@ 2026-08-06  9:20   ` Jagielski, Jedrzej
  2 siblings, 0 replies; 26+ messages in thread
From: Jagielski, Jedrzej @ 2026-08-06  9:20 UTC (permalink / raw)
  To: James Hilliard, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Lee Jones, Andrew Lunn, Heiner Kallweit,
	Russell King, Heiko Stuebner
  Cc: netdev@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, mfd@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org,
	linux-rockchip@lists.infradead.org

From: James Hilliard <james.hilliard1@gmail.com> 
Sent: Wednesday, August 5, 2026 4:27 AM

>The AC300 Fast Ethernet PHY needs a separate non-PHY Clause 22 endpoint
>to complete package setup before the link endpoint becomes usable.
>
>Enable VCC1 and the input clock, select its 24, 25 or 27 MHz rate, apply
>the SoC SID calibration value and vendor offset, configure the documented
>I/O drive strength, and perform the reset-before-clock power-up sequence.
>Validate the control and link address relationship against the package
>straps, clear a latched MDIO error, and configure the internal digital
>LDO only for the package variant which uses it.
>
>Early PHY creation uses reset-default MII. A separate operation later
>applies phydev->interface and, if already powered, changes only RMII_SEL
>before the normal PHY soft reset.
>
>Expose serialized, idempotent power and interface operations to the
>common PHY driver. Enable only the MDIO, MII and input-clock pads in this
>basic driver; dedicated LED outputs remain disabled for a later patch.
>
>Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
>---
> drivers/net/phy/Kconfig             |   9 +
> drivers/net/phy/Makefile            |   1 +
> drivers/net/phy/xpowers-ac300-ctl.c | 423 ++++++++++++++++++++++++++++++++++++
> drivers/net/phy/xpowers-acx00.h     |   2 +
> 4 files changed, 435 insertions(+)
>
>diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
>index 6119f4de880d..7535db7f3471 100644
>--- a/drivers/net/phy/Kconfig
>+++ b/drivers/net/phy/Kconfig
>@@ -484,6 +484,15 @@ config XPOWERS_AC200_PHY_CTL
> 	  address, interface mode, calibration and I/O controls through the
> 	  parent AC200 I2C regmap.
> 
>+config XPOWERS_AC300_PHY_CTL
>+	tristate "X-Powers AC300 Ethernet PHY control"
>+	depends on COMMON_CLK && NVMEM && OF_MDIO
>+	help
>+	  Enable the MDIO control driver for the Fast Ethernet PHY in the
>+	  X-Powers AC300 companion IC. It programs the PHY calibration,
>+	  interface mode, clock and I/O controls through the separate MDIO
>+	  control address.
>+
> config XILINX_GMII2RGMII
> 	tristate "Xilinx GMII2RGMII converter driver"
> 	help
>diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
>index f81854fc9f12..0c4e40c9a28a 100644
>--- a/drivers/net/phy/Makefile
>+++ b/drivers/net/phy/Makefile
>@@ -101,4 +101,5 @@ obj-$(CONFIG_STE10XP)		+= ste10Xp.o
> obj-$(CONFIG_TERANETICS_PHY)	+= teranetics.o
> obj-$(CONFIG_VITESSE_PHY)	+= vitesse.o
> obj-$(CONFIG_XPOWERS_AC200_PHY_CTL) += xpowers-ac200-ctl.o
>+obj-$(CONFIG_XPOWERS_AC300_PHY_CTL) += xpowers-ac300-ctl.o
> obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
>diff --git a/drivers/net/phy/xpowers-ac300-ctl.c b/drivers/net/phy/xpowers-ac300-ctl.c
>new file mode 100644
>index 000000000000..d3ca4df8a9a3
>--- /dev/null
>+++ b/drivers/net/phy/xpowers-ac300-ctl.c
>@@ -0,0 +1,423 @@
>+// SPDX-License-Identifier: GPL-2.0-only
>+/*
>+ * X-Powers AC300 Ethernet PHY control driver
>+ *
>+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
>+ */
>+
>+#include <linux/bitfield.h>
>+#include <linux/clk.h>
>+#include <linux/delay.h>
>+#include <linux/mdio.h>
>+#include <linux/module.h>
>+#include <linux/mutex.h>
>+#include <linux/nvmem-consumer.h>
>+#include <linux/phy.h>
>+#include <linux/property.h>
>+#include <linux/regulator/consumer.h>
>+
>+#include "xpowers-acx00.h"
>+
>+#define AC300_EPHY_BGS_EFFUSE_OFFSET	3
>+#define AC300_SYS_CONTROL_REG			0x00
>+#define AC300_CHIP_VERSION_MASK			GENMASK(15, 12)
>+#define AC300_PACKAGE_STATUS_MASK		GENMASK(11, 8)
>+#define AC300_EPHY_CLK_SEL_MASK			GENMASK(7, 6)
>+#define AC300_EPHY_CLK_SEL_25_MHZ		FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 0)
>+#define AC300_EPHY_CLK_SEL_27_MHZ		FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 1)
>+#define AC300_EPHY_CLK_SEL_24_MHZ		FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 2)
>+#define AC300_EFUSE_CLK_ENABLE			BIT(5)
>+#define AC300_EPHY_REG_CLK_ENABLE		BIT(4)
>+#define AC300_MDIO_ERROR			BIT(3)
>+#define AC300_CLKIN_GATING_ENABLE		BIT(2)
>+#define AC300_EPHY_RESET_DEASSERT		BIT(1)
>+#define AC300_CHIP_RESET_DEASSERT		BIT(0)
>+
>+#define AC300_MASK_VERSION_REG			0x04
>+#define AC300_MASK_VERSION_MASK			GENMASK(2, 0)
>+
>+#define AC300_PACKAGE_POR_INTERNAL_DLDO		BIT(3)
>+#define AC300_PACKAGE_PHY_ADDR_MASK		GENMASK(2, 0)
>+
>+#define AC300_SYS_BIAS1_REG			0x02
>+#define AC300_INTERNAL_DLDO_ENABLE		BIT(15)
>+
>+#define AC300_SYS_IO_REG			0x05
>+#define AC300_MDIO_DRV_MASK			GENMASK(15, 14)
>+#define AC300_MII_DRV_MASK			GENMASK(11, 10)
>+#define AC300_IO_DRV_LEVEL_2			2
>+#define AC300_CLKIN_PAD_ENABLE			BIT(4)
>+#define AC300_EPHY_MII_IO_ENABLE			BIT(0)
>+
>+#define AC300_EPHY_CONFIG_REG			0x06
>+#define AC300_EPHY_BGS_EFFUSE_MASK		GENMASK(15, 12)
>+#define AC300_EPHY_RMII_SEL			BIT(11)
>+#define AC300_EPHY_SHUTDOWN			BIT(0)
>+
>+#define AC300_SYS_CONTROL_ENABLE_BITS \
>+	(AC300_EFUSE_CLK_ENABLE | AC300_EPHY_REG_CLK_ENABLE | \
>+	 AC300_CLKIN_GATING_ENABLE | AC300_EPHY_RESET_DEASSERT | \
>+	 AC300_CHIP_RESET_DEASSERT)
>+
>+#define AC300_SYS_IO_VALUE \
>+	(FIELD_PREP(AC300_MDIO_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \
>+	 FIELD_PREP(AC300_MII_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \
>+	 AC300_CLKIN_PAD_ENABLE | AC300_EPHY_MII_IO_ENABLE)
>+
>+struct ac300_ephy_ctl {
>+	struct acx00_ephy_control control;
>+	struct mdio_device *mdiodev;
>+	struct clk *clk;
>+	struct mutex lock; /* Serializes power sequencing and state. */
>+	u16 sys_control;
>+	u16 ephy_config;
>+	phy_interface_t interface;
>+	bool package_known;
>+	bool internal_dldo;
>+	bool powered;
>+};
>+
>+static unsigned int
>+ac300_ephy_ctl_link_addr(const struct ac300_ephy_ctl *priv)
>+{
>+	return priv->mdiodev->addr - AC300_EPHY_CONTROL_ADDR_OFFSET;
>+}
>+
>+static u16 ac300_ephy_ctl_config(const struct ac300_ephy_ctl *priv)
>+{
>+	return priv->ephy_config |
>+		(priv->interface == PHY_INTERFACE_MODE_RMII ?
>+		 AC300_EPHY_RMII_SEL : 0);
>+}
>+
>+static int ac300_ephy_ctl_power_off_locked(struct ac300_ephy_ctl *priv)
>+{
>+	int err;
>+	int ret;
>+
>+	if (!priv->powered)
>+		return 0;
>+
>+	ret = mdiodev_write(priv->mdiodev, AC300_EPHY_CONFIG_REG,
>+			    ac300_ephy_ctl_config(priv) | AC300_EPHY_SHUTDOWN);
>+	err = mdiodev_write(priv->mdiodev, AC300_SYS_IO_REG, 0);
>+	if (!ret)
>+		ret = err;
>+	err = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG,
>+			    priv->package_known && !priv->internal_dldo ?
>+			    AC300_CHIP_RESET_DEASSERT : 0);
>+	if (!ret)
>+		ret = err;
>+
>+	clk_disable_unprepare(priv->clk);
>+	priv->powered = false;
>+
>+	return ret;
>+}
>+
>+static int ac300_ephy_ctl_power_off(struct acx00_ephy_control *control)
>+{
>+	struct ac300_ephy_ctl *priv =
>+		container_of(control, struct ac300_ephy_ctl, control);
>+	int ret;
>+
>+	mutex_lock(&priv->lock);
>+	ret = ac300_ephy_ctl_power_off_locked(priv);
>+	mutex_unlock(&priv->lock);
>+
>+	return ret;
>+}
>+
>+static int
>+ac300_ephy_ctl_set_interface(struct acx00_ephy_control *control,
>+			     phy_interface_t interface)
>+{
>+	struct ac300_ephy_ctl *priv =
>+		container_of(control, struct ac300_ephy_ctl, control);
>+	u16 value;
>+	int ret = 0;

RCT

>+
>+	switch (interface) {
>+	case PHY_INTERFACE_MODE_MII:
>+		value = 0;
>+		break;
>+	case PHY_INTERFACE_MODE_RMII:
>+		value = AC300_EPHY_RMII_SEL;
>+		break;
>+	default:
>+		return -EINVAL;
>+	}
>+
>+	mutex_lock(&priv->lock);
>+	if (priv->interface == interface)
>+		goto out_unlock;
>+
>+	if (priv->powered)
>+		ret = mdiodev_modify(priv->mdiodev, AC300_EPHY_CONFIG_REG,
>+				     AC300_EPHY_RMII_SEL, value);

value can be moved, same like for previous patch

>+	if (!ret)
>+		priv->interface = interface;
>+
>+out_unlock:
>+	mutex_unlock(&priv->lock);
>+
>+	return ret;
>+}
>+
>+static int ac300_ephy_ctl_power_on(struct acx00_ephy_control *control,
>+				   unsigned int phy_addr)
>+{
>+	struct ac300_ephy_ctl *priv =
>+		container_of(control, struct ac300_ephy_ctl, control);
>+	u8 package_status;
>+	u16 reset_value;
>+	int sys_control;
>+	int ret;
>+
>+	if (phy_addr != ac300_ephy_ctl_link_addr(priv))
>+		return -EINVAL;
>+
>+	mutex_lock(&priv->lock);
>+	if (priv->powered) {
>+		ret = 0;
>+		goto out_unlock;
>+	}
>+
>+	ret = clk_prepare_enable(priv->clk);
>+	if (ret)
>+		goto out_unlock;
>+	priv->powered = true;
>+
>+	/* Keep the external-supply configuration across subsequent resets. */
>+	reset_value = priv->package_known && !priv->internal_dldo ?
>+		      AC300_CHIP_RESET_DEASSERT : 0;
>+	ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG, reset_value);
>+	if (ret)
>+		goto err_power_off;
>+
>+	/* The manual requires both resets to be released before the clocks. */
>+	ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG,
>+			    AC300_EPHY_RESET_DEASSERT |
>+			    AC300_CHIP_RESET_DEASSERT);
>+	if (ret)
>+		goto err_power_off;
>+
>+	/* Retain the vendor clock-enable defaults, including the eFuse clock. */
>+	ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG,
>+			    priv->sys_control);
>+	if (ret)
>+		goto err_power_off;
>+
>+	sys_control = mdiodev_read(priv->mdiodev, AC300_SYS_CONTROL_REG);
>+	if (sys_control < 0) {
>+		ret = sys_control;
>+		goto err_power_off;
>+	}
>+	if (sys_control & AC300_MDIO_ERROR) {

sys_control is checked for both and both ifs are contradictory
if else?

>+		ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG,
>+				    priv->sys_control | AC300_MDIO_ERROR);
>+		if (ret)
>+			goto err_power_off;
>+
>+		sys_control = mdiodev_read(priv->mdiodev, AC300_SYS_CONTROL_REG);
>+		if (sys_control < 0) {
>+			ret = sys_control;
>+			goto err_power_off;
>+		}
>+		if (sys_control & AC300_MDIO_ERROR) {
>+			ret = -EIO;
>+			goto err_power_off;
>+		}
>+	}

somehow that doubling instructions with only AC300_MDIO_ERROR changed
doesn't appeal to me, could be done with goto saving some lines
purely cosmetic nothing critical

>+
>+	package_status = FIELD_GET(AC300_PACKAGE_STATUS_MASK, sys_control);
>+	if ((~package_status & AC300_PACKAGE_PHY_ADDR_MASK) !=
>+	    ac300_ephy_ctl_link_addr(priv)) {
>+		ret = -EINVAL;
>+		goto err_power_off;
>+	}
>+
>+	priv->internal_dldo = package_status & AC300_PACKAGE_POR_INTERNAL_DLDO;
>+	priv->package_known = true;
>+	ret = mdiodev_modify(priv->mdiodev, AC300_SYS_BIAS1_REG,
>+			     AC300_INTERNAL_DLDO_ENABLE,
>+			     priv->internal_dldo ?
>+			     AC300_INTERNAL_DLDO_ENABLE : 0);
>+	if (ret)
>+		goto err_power_off;
>+
>+	/* Keep the documented default drive level and leave the IRQ disabled. */
>+	ret = mdiodev_write(priv->mdiodev, AC300_SYS_IO_REG,
>+			    AC300_SYS_IO_VALUE);
>+	if (ret)
>+		goto err_power_off;
>+
>+	fsleep(10000);
>+
>+	ret = mdiodev_write(priv->mdiodev, AC300_EPHY_CONFIG_REG,
>+			    ac300_ephy_ctl_config(priv) | AC300_EPHY_SHUTDOWN);
>+	if (ret)
>+		goto err_power_off;
>+
>+	fsleep(10000);
>+
>+	ret = mdiodev_write(priv->mdiodev, AC300_EPHY_CONFIG_REG,
>+			    ac300_ephy_ctl_config(priv));
>+	if (ret)
>+		goto err_power_off;
>+
>+	goto out_unlock;
>+
>+err_power_off:
>+	ac300_ephy_ctl_power_off_locked(priv);
>+out_unlock:
>+	mutex_unlock(&priv->lock);
>+
>+	return ret;
>+}
>+
>+static int ac300_ephy_ctl_probe(struct mdio_device *mdiodev)
>+{
>+	struct device *dev = &mdiodev->dev;
>+	struct ac300_ephy_ctl *priv;
>+	unsigned long clk_rate;
>+	unsigned int phy_addr;
>+	u8 calibration;
>+	u8 bgs_effuse_code;
>+	u8 package_status;
>+	int mask_version;
>+	int sys_control;
>+	int ret;

RCT

>+
>+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>+	if (!priv)
>+		return -ENOMEM;
>+	if (mdiodev->addr < AC300_EPHY_CONTROL_ADDR_OFFSET ||
>+	    mdiodev->addr > AC300_EPHY_CONTROL_ADDR_OFFSET +
>+			    FIELD_MAX(AC300_PACKAGE_PHY_ADDR_MASK))
>+		return dev_err_probe(dev, -EINVAL,
>+				     "control address is outside the package range\n");
>+	priv->mdiodev = mdiodev;
>+	mutex_init(&priv->lock);
>+
>+	ret = devm_regulator_get_enable(dev, "vcc1");
>+	if (ret)
>+		return dev_err_probe(dev, ret,
>+				     "failed to enable VCC1 supply\n");
>+
>+	/* Wait for the power-on reset interval specified by the manual. */
>+	fsleep(10000);
>+
>+	priv->clk = devm_clk_get(dev, NULL);
>+	if (IS_ERR(priv->clk))
>+		return dev_err_probe(dev, PTR_ERR(priv->clk),
>+				     "failed to get input clock\n");
>+
>+	ret = devm_clk_rate_exclusive_get(dev, priv->clk);
>+	if (ret)
>+		return dev_err_probe(dev, ret, "failed to lock clock rate\n");
>+
>+	clk_rate = clk_get_rate(priv->clk);
>+	switch (clk_rate) {
>+	case 24000000:

why do not provide defines also for these?

>+		priv->sys_control = AC300_EPHY_CLK_SEL_24_MHZ;
>+		break;
>+	case 25000000:
>+		priv->sys_control = AC300_EPHY_CLK_SEL_25_MHZ;
>+		break;
>+	case 27000000:
>+		priv->sys_control = AC300_EPHY_CLK_SEL_27_MHZ;
>+		break;
>+	default:
>+		return dev_err_probe(dev, -EINVAL,
>+				     "unsupported input clock rate %lu Hz\n",
>+				     clk_rate);
>+	}
>+	priv->sys_control |= AC300_SYS_CONTROL_ENABLE_BITS;
>+
>+	ret = nvmem_cell_read_u8(dev, "calibration", &calibration);
>+	if (ret)
>+		return dev_err_probe(dev, ret,
>+				     "failed to read calibration data\n");
>+
>+	/* The vendor driver supplies no transfer function beyond this offset. */
>+	bgs_effuse_code = (calibration + AC300_EPHY_BGS_EFFUSE_OFFSET) &
>+			   FIELD_MAX(AC300_EPHY_BGS_EFFUSE_MASK);
>+	priv->ephy_config =
>+		FIELD_PREP(AC300_EPHY_BGS_EFFUSE_MASK, bgs_effuse_code);
>+	/* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */
>+
>+	priv->control.power_on = ac300_ephy_ctl_power_on;
>+	priv->control.power_off = ac300_ephy_ctl_power_off;
>+	priv->control.set_interface = ac300_ephy_ctl_set_interface;
>+	/* MII is the reset default used until the MAC supplies its interface. */
>+	priv->interface = PHY_INTERFACE_MODE_MII;
>+	mdiodev_set_drvdata(mdiodev, &priv->control);
>+
>+	/* Validate the package while the control endpoint is known to respond. */
>+	phy_addr = ac300_ephy_ctl_link_addr(priv);
>+	ret = ac300_ephy_ctl_power_on(&priv->control, phy_addr);
>+	if (ret)
>+		return ret;
>+
>+	sys_control = mdiodev_read(mdiodev, AC300_SYS_CONTROL_REG);
>+	if (sys_control < 0) {
>+		ret = sys_control;
>+		goto err_disable;
>+	}
>+	package_status = FIELD_GET(AC300_PACKAGE_STATUS_MASK, sys_control);
>+
>+	mask_version = mdiodev_read(mdiodev, AC300_MASK_VERSION_REG);
>+	if (mask_version < 0) {
>+		ret = mask_version;
>+		goto err_disable;
>+	}
>+
>+	dev_info(dev, "chip version %u, mask version %u, package %#x, %s supplies, PHY %u, %lu Hz clock\n",
>+		 (unsigned int)FIELD_GET(AC300_CHIP_VERSION_MASK, sys_control),
>+		 (unsigned int)FIELD_GET(AC300_MASK_VERSION_MASK, mask_version),
>+		 package_status,
>+		 package_status & AC300_PACKAGE_POR_INTERNAL_DLDO ?
>+			"POR/internal DLDO" : "reset pin/external VDD",
>+		 ac300_ephy_ctl_link_addr(priv), clk_rate);
>+
>+	ret = ac300_ephy_ctl_power_off(&priv->control);
>+	if (ret)
>+		return dev_err_probe(dev, ret,
>+				     "failed to quiesce control block\n");
>+
>+	return 0;
>+
>+err_disable:
>+	ac300_ephy_ctl_power_off(&priv->control);
>+	return ret;
>+}
>+
>+static void ac300_ephy_ctl_remove(struct mdio_device *mdiodev)
>+{
>+	struct acx00_ephy_control *control = mdiodev_get_drvdata(mdiodev);
>+
>+	control->power_off(control);
>+}
>+
>+static const struct of_device_id ac300_ephy_ctl_of_match[] = {
>+	{ .compatible = "x-powers,ac300-ephy-ctl" },
>+	{ }
>+};
>+MODULE_DEVICE_TABLE(of, ac300_ephy_ctl_of_match);
>+
>+static struct mdio_driver ac300_ephy_ctl_driver = {
>+	.probe = ac300_ephy_ctl_probe,
>+	.remove = ac300_ephy_ctl_remove,
>+	.shutdown = ac300_ephy_ctl_remove,
>+	.mdiodrv.driver = {
>+		.name = "ac300-ephy-ctl",
>+		.of_match_table = ac300_ephy_ctl_of_match,
>+	},
>+};
>+
>+mdio_module_driver(ac300_ephy_ctl_driver);
>+
>+MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
>+MODULE_DESCRIPTION("X-Powers AC300 Ethernet PHY control driver");
>+MODULE_LICENSE("GPL");
>diff --git a/drivers/net/phy/xpowers-acx00.h b/drivers/net/phy/xpowers-acx00.h
>index 482ba6faba6e..0af8895e4cae 100644
>--- a/drivers/net/phy/xpowers-acx00.h
>+++ b/drivers/net/phy/xpowers-acx00.h
>@@ -4,6 +4,8 @@
> 
> #include <linux/phy.h>
> 
>+#define AC300_EPHY_CONTROL_ADDR_OFFSET	16
>+
> struct acx00_ephy_control {
> 	int (*power_on)(struct acx00_ephy_control *control,
> 			unsigned int phy_addr);
>
>-- 
>2.53.0



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

* RE: [PATCH net-next v2 8/8] net: phy: add X-Powers AC200/AC300 EPHY driver
  2026-08-06  1:28     ` James Hilliard
@ 2026-08-06  9:21       ` Jagielski, Jedrzej
  0 siblings, 0 replies; 26+ messages in thread
From: Jagielski, Jedrzej @ 2026-08-06  9:21 UTC (permalink / raw)
  To: James Hilliard, Andrew Lunn
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lee Jones, Heiner Kallweit, Russell King, Heiko Stuebner,
	netdev@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, mfd@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org,
	linux-rockchip@lists.infradead.org

From: James Hilliard <james.hilliard1@gmail.com> 
Sent: Wednesday, August 5, 2026 4:27 AM

>The AC200 and AC300 MDIO link endpoints report the same Clause 22
>identifier and use a compatible link-side paged register layout. Their
>identification registers are inaccessible until a package-specific
>control path powers and configures the block.
>
>Add a common PHY driver which matches the firmware-provided identifier
>only when the node also references one X-Powers control device. Validate
>that reference and the AC300 address relationship, defer until the
>provider is bound, and create a managed device link to protect its
>lifetime and order teardown and power management.
>
>When the optional two-bit SID configuration field is present, verify that
>its package-selector bit agrees with the fixed control reference. Use its
>second bit to select the AC300 low-calibration tuning sequence. Omitting
>the field skips package validation and selects the normal sequence.
>
>Power the control block in reset-default MII mode during PHY probe. Once
>the MAC supplies phydev->interface, pass MII or RMII to the provider
>before the normal PHY soft reset. Program the AC200 link address through
>its provider and let the AC300 provider validate its strapped address.
>
>Apply the common vendor analog initialization, with the package-specific
>normal value and the optional AC300 low-calibration sequence. Support the
>board-selected xMII receive-clock inversion and leave MDI/MDI-X in its
>vendor automatic mode. Disable standard and Intelligent EEE initially,
>then restore phylib's standard EEE policy after later resets.
>
>Power the package control block down with the PHY and restore the vendor
>configuration on resume. If power-off fails, attempt to restore the PHY
>before returning the original suspend error.
>
>Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
>---
> drivers/net/phy/Kconfig         |   9 +
> drivers/net/phy/Makefile        |   1 +
> drivers/net/phy/xpowers-acx00.c | 544 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 554 insertions(+)
>

...

>+static struct device *
>+acx00_ephy_find_supplier(struct device_node *control, bool is_ac300)
>+{
>+	struct platform_device *pdev;
>+	struct mdio_device *mdiodev;
>+
>+	if (is_ac300) {

two approaches are mixed now - specific callbacks and ac200/300 checks,
why not to fully stick to callbacks


>+		mdiodev = of_mdio_find_device(control);
>+		return mdiodev ? &mdiodev->dev : NULL;
>+	}
>+
>+	pdev = of_find_device_by_node(control);
>+	return pdev ? &pdev->dev : NULL;
>+}
>+
>+static int acx00_ephy_get_control(struct phy_device *phydev,
>+				  struct acx00_ephy_priv *priv)
>+{
>+	struct device *dev = &phydev->mdio.dev;
>+	struct acx00_ephy_control *control;
>+	struct device_node *control_node;
>+	struct device *supplier;
>+	struct device_link *link;
>+	const char *compatible;
>+	const char *property;
>+	bool has_ac200;
>+	bool has_ac300;
>+	u32 control_addr;
>+	u32 configuration = 0;
>+	int ret;
>+
>+	has_ac200 = of_property_present(dev->of_node,
>+					"x-powers,ac200-control");
>+	has_ac300 = of_property_present(dev->of_node,
>+					"x-powers,ac300-control");
>+	if (has_ac200 == has_ac300)
>+		return dev_err_probe(dev, -EINVAL,
>+				     "exactly one ACx00 control is required\n");
>+
>+	priv->is_ac300 = has_ac300;

wouldn't enum be a better choice here? easier extending for future
possible ac400,500 etc 
dunno whether planned just thinking

>+	property = has_ac300 ? "x-powers,ac300-control" :
>+			       "x-powers,ac200-control";
>+	compatible = has_ac300 ? "x-powers,ac300-ephy-ctl" :
>+				 "x-powers,ac200-ephy-ctl";
>+	control_node = of_parse_phandle(dev->of_node, property, 0);
>+	if (!control_node)
>+		return dev_err_probe(dev, -EINVAL, "missing %s\n", property);
>+	if (!of_device_is_compatible(control_node, compatible)) {
>+		ret = dev_err_probe(dev, -EINVAL,
>+				    "%s does not reference a %s device\n",
>+				    property, compatible);
>+		goto out_put_node;
>+	}
>+	if (!of_device_is_available(control_node)) {
>+		ret = dev_err_probe(dev, -EINVAL,
>+				    "%s references a disabled device\n",
>+				    property);
>+		goto out_put_node;
>+	}
>+
>+	if (has_ac300) {
>+		ret = of_property_read_u32(control_node, "reg", &control_addr);
>+		if (ret || control_addr != phydev->mdio.addr +
>+					       AC300_EPHY_CONTROL_ADDR_OFFSET) {
>+			ret = dev_err_probe(dev, -EINVAL,
>+					    "AC300 control address does not match reg\n");
>+			goto out_put_node;
>+		}
>+	}



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

end of thread, other threads:[~2026-08-06  9:21 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-05  2:26 [PATCH net-next v2 0/8] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
2026-08-05  2:26 ` [PATCH net-next v2 1/8] dt-bindings: net: x-powers: add AC200 EPHY control James Hilliard
2026-08-06  2:32   ` sashiko-bot
2026-08-05  2:27 ` [PATCH net-next v2 2/8] dt-bindings: mfd: x-powers: add AC200 James Hilliard
2026-08-06  2:32   ` sashiko-bot
2026-08-05  2:27 ` [PATCH net-next v2 3/8] mfd: add X-Powers AC200 support James Hilliard
2026-08-06  2:32   ` sashiko-bot
2026-08-05  2:27 ` [PATCH net-next v2 4/8] net: phy: add X-Powers AC200 EPHY control driver James Hilliard
2026-08-06  2:32   ` sashiko-bot
2026-08-06  9:19   ` Jagielski, Jedrzej
2026-08-05  2:27 ` [PATCH net-next v2 5/8] dt-bindings: net: x-powers: add AC300 EPHY control James Hilliard
2026-08-05  3:38   ` Rob Herring (Arm)
2026-08-05 14:17   ` Rob Herring
2026-08-06  1:36     ` James Hilliard
2026-08-06  2:32   ` sashiko-bot
2026-08-05  2:27 ` [PATCH net-next v2 6/8] net: phy: add X-Powers AC300 EPHY control driver James Hilliard
2026-08-05 12:22   ` Andrew Lunn
2026-08-06  2:32   ` sashiko-bot
2026-08-06  9:20   ` Jagielski, Jedrzej
2026-08-05  2:27 ` [PATCH net-next v2 7/8] dt-bindings: net: x-powers: add AC200/AC300 EPHY James Hilliard
2026-08-06  2:32   ` sashiko-bot
2026-08-05  2:27 ` [PATCH net-next v2 8/8] net: phy: add X-Powers AC200/AC300 EPHY driver James Hilliard
2026-08-05 12:37   ` Andrew Lunn
2026-08-06  1:28     ` James Hilliard
2026-08-06  9:21       ` Jagielski, Jedrzej
2026-08-06  2:32   ` sashiko-bot

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