Devicetree
 help / color / mirror / Atom feed
* [PATCH net-next v5 0/5] net: phy: add X-Powers AC200/AC300 EPHY support
@ 2026-08-09 20:28 James Hilliard
  2026-08-09 20:28 ` [PATCH net-next v5 1/5] dt-bindings: mfd: x-powers: Add AC200 James Hilliard
                   ` (4 more replies)
  0 siblings, 5 replies; 11+ messages in thread
From: James Hilliard @ 2026-08-09 20:28 UTC (permalink / raw)
  To: Lee Jones, Arnd Bergmann, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, James Hilliard, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
	Heiner Kallweit, Russell King
  Cc: mfd, devicetree, linux-kernel, netdev

The AC200 and AC300 contain compatible Fast Ethernet link PHYs which
report the same Clause 22 identifier and use the same link-side register
layout. The link endpoint is inaccessible until package-specific control
registers have powered and configured it.

Version 2 represented those control ranges as separate devices. Version 3
instead modeled each variant as a standard Ethernet PHY package with one
forced-ID link-PHY child. The package reg value is the link address.
AC300's control range is a fixed package-relative offset of 16 and is
accessed with the PHY package helpers; it has no separate DT node or MDIO
driver. AC200 references its I2C MFD because the corresponding control
registers reside in that multi-function device.

This revision retains that package model and publishes the AC200 regmap
through syscon. It adds managed registration for externally created
syscon regmaps so a removable provider can withdraw its entry before the
regmap is released. The PHY establishes a managed device link to the AC200
I2C device before looking up the regmap through its firmware reference,
which keeps the supplier bound until its PHY consumer has unbound.

Fixed hardware uses an AC200- or AC300-specific package compatible. Systems
which can contain either package use the ACx00 package compatible and one
packed SID configuration field. Bits 3 through 0 carry the analog
calibration, bit 8 selects AC300, and bit 9 selects its low-calibration
tuning. The driver chooses the backend before acquiring any
backend-specific resource, so an AC300 system does not instantiate or
access the AC200 I2C device.

One xpowers-acx00 PHY module binds the link child, joins the parent package
and runs the selected AC200 or AC300 backend. Only the link PHY registers a
driver. The common implementation and the private backends live in an
X-Powers subdirectory and are linked into the same module. Thus the PHY
driver owns the complete Ethernet PHY while the AC200 MFD owns the shared
mixed-signal chip and its regmap.

The package node owns a common vcc-supply, connected to EPHY_VCC on AC200
and VCC1 on AC300. The PHY driver manages that function supply and can turn
it off while the PHY is suspended. The AC200 MFD keeps ownership of the
chip-wide system input clock.

The series contains no CONFIG_OF_DYNAMIC code and no generic MDIO
reconfiguration. Firmware must make the selected AC200 provider path
available before the PHY probes.

The common link implementation performs the vendor analog initialization,
supports MII and RMII, preserves automatic MDI/MDI-X, and restores package
state across suspend and resume. It preserves standard MAC-managed EEE
advertisement while disabling only the vendor PHY-autonomous Intelligent
EEE mode.

The five patches add the minimal AC200 MFD binding, managed registration
for external syscon regmaps, the AC200 regmap provider, the AC200/AC300
PHY package binding and the combined PHY driver. Board Device Trees and
optional PHY features remain outside this initial 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

Validation completed for v5:

  - arm64 defconfig vmlinux and module builds with W=1;
  - x86_64 allmodconfig object builds with W=1;
  - a built-in AC300-only configuration with I2C and MFD_SYSCON disabled;
  - dt_binding_check for both new schemas;
  - strict checkpatch checks for all five patches, with only the generic
    FILE_PATH_CHANGES prompt ignored; and
  - managed syscon registration, lookup, supplier removal and re-registration
    on the running kernel.

The final AC200 path was hardware-tested on an H616 board. The packed
configuration was 0x001, selecting AC200. The MFD reported revision 0x101
in package 1, the PHY obtained its regmap through syscon after establishing
the supplier device link, and the link PHY bound at address 0. It applied
RMII mode, negotiated a 100 Mbps full-duplex link and passed eight MiB of
traffic in each direction and 100 consecutive pings without loss.

An administrative link down/up cycle dropped the package regulator
consumer count from one to zero, restored it and renegotiated the same link
mode. With the link administratively down, unbinding the AC200 supplier
unbound the PHY consumer before removing the syscon regmap. Rebinding the
supplier re-registered the regmap; rebinding the PHY then restored the
device link and the 100 Mbps full-duplex link.

The final AC300 path was hardware-tested on the connected H616 board. The
driver read the packed SID field as 0x106, selected AC300 without enabling
the AC200 I2C path, accessed the control range at package base plus 16,
bound the link PHY at address 0, applied RMII mode and negotiated a
100 Mbps full-duplex link after a cold boot. Eight MiB of traffic passed
in each direction. An administrative link down/up cycle disabled and
restored the package regulator and renegotiated the same link mode.

Assisted-by: OpenAI Codex (gpt-5.6-sol, max)
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v2 -> v3:
  - model AC200 and AC300 as standard Ethernet PHY packages
  - remove the standalone AC200 and AC300 control bindings and drivers
  - put the common link implementation and both private backends in one
    xpowers-acx00 module
  - access the AC300 control range at package base plus 16 with PHY package
    helpers
  - access AC200 package registers through its referenced MFD regmap and a
    managed device link
  - add an ACx00 package compatible which selects the backend from one
    packed SID configuration field
  - combine calibration, package selection and AC300 tuning into that field
  - optionally activate only a selected fail-needs-probe AC200 path when
    CONFIG_OF_DYNAMIC is available
  - preserve standard MAC-managed EEE advertisement and disable only the
    PHY-autonomous Intelligent EEE mode
  - reduce the series from eight patches to four
  - Link to v2:
    https://patch.msgid.link/20260804-submit-acx00-of-dynamic-v1-v2-0-3eef49ff1d8c@gmail.com

Changes v3 -> v4:
  - rebase onto current net-next
  - always restore the PHY page and release the MDIO bus lock after a
    page-selection error
  - preserve AC300 external-VDD configuration until package status
    identifies the supply arrangement
  - remove the unsupported AC300 MDIO-error clear/readback sequence,
    which is absent from the BSP initialization
  - remove AC200 post-reset waits not required by its documentation or
    hardware testing
  - report the failing AC200 control register
  - follow MFD subject and driver-data naming conventions
  - use the AC200 teardown callback directly, follow reverse Christmas-tree
    declaration ordering and use a named I2C ID initializer
  - use named input-clock rates and simplify MII/RMII selection
  - avoid blocking on the AC200 supplier lock while checking driver
    readiness
  - Link to v3:
    https://patch.msgid.link/20260805-submit-acx00-of-dynamic-v1-v3-0-32c3210a6f25@gmail.com

Changes v4 -> v5:
  - remove CONFIG_OF_DYNAMIC path activation from the submitted series
  - publish the AC200 regmap through syscon and add managed registration
    for externally created regmaps
  - resolve the AC200 regmap through its phandle after establishing a
    managed device link to the I2C supplier
  - move EPHY_VCC and VCC1 ownership to a common PHY-package vcc-supply and
    manage it across suspend and resume
  - move the four PHY source files into an X-Powers subdirectory
  - remove private backend mutexes because phylib serializes these
    lifecycle callbacks and neither backend has an independent worker or
    interrupt path
  - rebase onto current net-next
  - Link to v4:
    https://patch.msgid.link/20260806-submit-acx00-of-dynamic-v1-v4-0-9bf2f7441632@gmail.com

To: Lee Jones <lee@kernel.org>
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: Arnd Bergmann <arnd@arndb.de>
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: Andrew Lunn <andrew@lunn.ch>
To: Heiner Kallweit <hkallweit1@gmail.com>
To: Russell King <linux@armlinux.org.uk>
Cc: mfd@lists.linux.dev
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: netdev@vger.kernel.org

---
James Hilliard (5):
      dt-bindings: mfd: x-powers: Add AC200
      mfd: syscon: Add managed registration for external regmaps
      mfd: ac200: Add X-Powers AC200 support
      dt-bindings: net: x-powers: add AC200/AC300 EPHY packages
      net: phy: add X-Powers AC200/AC300 EPHY driver

 .../devicetree/bindings/mfd/x-powers,ac200.yaml    |  49 ++
 .../bindings/net/x-powers,acx00-ephy-package.yaml  | 207 ++++++++
 drivers/mfd/Kconfig                                |  12 +
 drivers/mfd/Makefile                               |   1 +
 drivers/mfd/ac200.c                                | 170 +++++++
 drivers/mfd/syscon.c                               |  87 +++-
 drivers/net/phy/Kconfig                            |  11 +
 drivers/net/phy/Makefile                           |   1 +
 drivers/net/phy/xpowers/Makefile                   |   3 +
 drivers/net/phy/xpowers/ac200.c                    | 315 ++++++++++++
 drivers/net/phy/xpowers/ac300.c                    | 387 +++++++++++++++
 drivers/net/phy/xpowers/acx00.c                    | 536 +++++++++++++++++++++
 drivers/net/phy/xpowers/acx00.h                    |  28 ++
 include/linux/mfd/syscon.h                         |  12 +
 14 files changed, 1797 insertions(+), 22 deletions(-)
---
base-commit: 001b5d347d8ba39b2dccaefcc57967b18caec8fe
change-id: 20260802-submit-acx00-of-dynamic-v1-94a0dc15f282

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


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

* [PATCH net-next v5 1/5] dt-bindings: mfd: x-powers: Add AC200
  2026-08-09 20:28 [PATCH net-next v5 0/5] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
@ 2026-08-09 20:28 ` James Hilliard
  2026-08-11  7:07   ` Krzysztof Kozlowski
  2026-08-09 20:28 ` [PATCH net-next v5 2/5] mfd: syscon: Add managed registration for external regmaps James Hilliard
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 11+ messages in thread
From: James Hilliard @ 2026-08-09 20:28 UTC (permalink / raw)
  To: Lee Jones, Arnd Bergmann, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, James Hilliard, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
	Heiner Kallweit, Russell King
  Cc: mfd, devicetree, linux-kernel, netdev

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

Describe the parent device and its shared input clock. The Ethernet PHY
is represented by a PHY package on its MDIO bus, with a phandle back to
this device for access to the package control registers, so it does not
require an MFD child node. Function-specific supplies belong to their
consumer bindings rather than the shared parent.

The input clock must run at 24 or 27 MHz when the Ethernet PHY is used,
matching the rates encoded by its documented clock selector.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 .../devicetree/bindings/mfd/x-powers,ac200.yaml    | 49 ++++++++++++++++++++++
 1 file changed, 49 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..609831b15c80
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml
@@ -0,0 +1,49 @@
+# 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.
+
+required:
+  - compatible
+  - reg
+  - clocks
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        mixed-signal@10 {
+            compatible = "x-powers,ac200";
+            reg = <0x10>;
+            clocks = <&pwm 5>;
+        };
+    };
+...

-- 
2.53.0


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

* [PATCH net-next v5 2/5] mfd: syscon: Add managed registration for external regmaps
  2026-08-09 20:28 [PATCH net-next v5 0/5] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
  2026-08-09 20:28 ` [PATCH net-next v5 1/5] dt-bindings: mfd: x-powers: Add AC200 James Hilliard
@ 2026-08-09 20:28 ` James Hilliard
  2026-08-09 20:28 ` [PATCH net-next v5 3/5] mfd: ac200: Add X-Powers AC200 support James Hilliard
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 11+ messages in thread
From: James Hilliard @ 2026-08-09 20:28 UTC (permalink / raw)
  To: Lee Jones, Arnd Bergmann, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, James Hilliard, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
	Heiner Kallweit, Russell King
  Cc: mfd, devicetree, linux-kernel, netdev

of_syscon_register_regmap() publishes an externally owned regmap in the
global syscon list, but provides no way for a removable driver to
withdraw it. Registering a devm-managed regmap from such a driver would
therefore leave a stale pointer after unbind.

Factor external registration through an internal helper and add
devm_of_syscon_register_regmap(). The managed action removes the entry
under the syscon list lock before later devres actions release the
regmap. Hold a device-node reference for the lifetime of every
externally registered entry so its lookup key also remains valid.

Consumers of a removable provider remain responsible for ordering
teardown, for example with a managed device link.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 drivers/mfd/syscon.c       | 87 ++++++++++++++++++++++++++++++++++------------
 include/linux/mfd/syscon.h | 12 +++++++
 2 files changed, 77 insertions(+), 22 deletions(-)

diff --git a/drivers/mfd/syscon.c b/drivers/mfd/syscon.c
index 21a7fcdd2737..448422fe4f21 100644
--- a/drivers/mfd/syscon.c
+++ b/drivers/mfd/syscon.c
@@ -10,6 +10,7 @@
 
 #include <linux/cleanup.h>
 #include <linux/clk.h>
+#include <linux/device.h>
 #include <linux/err.h>
 #include <linux/hwspinlock.h>
 #include <linux/list.h>
@@ -193,53 +194,95 @@ static struct regmap *device_node_get_regmap(struct device_node *np,
 	return syscon->regmap;
 }
 
-/**
- * of_syscon_register_regmap() - Register regmap for specified device node
- * @np: Device tree node
- * @regmap: Pointer to regmap object
- *
- * Register an externally created regmap object with syscon for the specified
- * device tree node. This regmap will then be returned to client drivers using
- * the syscon_regmap_lookup_by_phandle() API.
- *
- * Return: 0 on success, negative error code on failure.
- */
-int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)
+static struct syscon *
+of_syscon_register_regmap_internal(struct device_node *np,
+				   struct regmap *regmap)
 {
 	struct syscon *entry, *syscon = NULL;
-	int ret;
 
 	if (!np || !regmap)
-		return -EINVAL;
+		return ERR_PTR(-EINVAL);
 
 	syscon = kzalloc_obj(*syscon);
 	if (!syscon)
-		return -ENOMEM;
+		return ERR_PTR(-ENOMEM);
 
 	/* check if syscon entry already exists */
 	mutex_lock(&syscon_list_lock);
 
 	list_for_each_entry(entry, &syscon_list, list)
 		if (entry->np == np) {
-			ret = -EEXIST;
-			goto err_unlock;
+			mutex_unlock(&syscon_list_lock);
+			kfree(syscon);
+			return ERR_PTR(-EEXIST);
 		}
 
 	syscon->regmap = regmap;
-	syscon->np = np;
+	syscon->np = of_node_get(np);
 
 	/* register the regmap in syscon list */
 	list_add_tail(&syscon->list, &syscon_list);
 	mutex_unlock(&syscon_list_lock);
 
-	return 0;
+	return syscon;
+}
+
+/**
+ * of_syscon_register_regmap() - Register regmap for specified device node
+ * @np: Device tree node
+ * @regmap: Pointer to regmap object
+ *
+ * Register an externally created regmap object with syscon for the specified
+ * device tree node. This regmap will then be returned to client drivers using
+ * the syscon_regmap_lookup_by_phandle() API.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)
+{
+	return PTR_ERR_OR_ZERO(of_syscon_register_regmap_internal(np, regmap));
+}
+EXPORT_SYMBOL_GPL(of_syscon_register_regmap);
+
+static void devm_of_syscon_register_regmap_release(void *data)
+{
+	struct syscon *syscon = data;
 
-err_unlock:
+	mutex_lock(&syscon_list_lock);
+	list_del(&syscon->list);
 	mutex_unlock(&syscon_list_lock);
+
+	of_node_put(syscon->np);
 	kfree(syscon);
-	return ret;
 }
-EXPORT_SYMBOL_GPL(of_syscon_register_regmap);
+
+/**
+ * devm_of_syscon_register_regmap() - Register a managed external syscon regmap
+ * @dev: Device that owns the regmap
+ * @np: Device tree node associated with the regmap
+ * @regmap: Pointer to the externally created regmap
+ *
+ * Register an externally created regmap object with syscon and remove it when
+ * @dev is unbound. Consumers must stop using the regmap before the provider is
+ * unbound, for example by establishing a managed device link to @dev.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int devm_of_syscon_register_regmap(struct device *dev,
+				   struct device_node *np,
+				   struct regmap *regmap)
+{
+	struct syscon *syscon;
+
+	syscon = of_syscon_register_regmap_internal(np, regmap);
+	if (IS_ERR(syscon))
+		return PTR_ERR(syscon);
+
+	return devm_add_action_or_reset(dev,
+					devm_of_syscon_register_regmap_release,
+					syscon);
+}
+EXPORT_SYMBOL_GPL(devm_of_syscon_register_regmap);
 
 /**
  * device_node_to_regmap() - Get or create a regmap for specified device node
diff --git a/include/linux/mfd/syscon.h b/include/linux/mfd/syscon.h
index aad9c6b50463..3e804fc1b03e 100644
--- a/include/linux/mfd/syscon.h
+++ b/include/linux/mfd/syscon.h
@@ -15,6 +15,7 @@
 #include <linux/errno.h>
 
 struct device_node;
+struct device;
 
 #ifdef CONFIG_MFD_SYSCON
 struct regmap *device_node_to_regmap(struct device_node *np);
@@ -30,6 +31,9 @@ struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np,
 							const char *property);
 int of_syscon_register_regmap(struct device_node *np,
 			      struct regmap *regmap);
+int devm_of_syscon_register_regmap(struct device *dev,
+				   struct device_node *np,
+				   struct regmap *regmap);
 #else
 static inline struct regmap *device_node_to_regmap(struct device_node *np)
 {
@@ -75,6 +79,14 @@ static inline int of_syscon_register_regmap(struct device_node *np,
 	return -EOPNOTSUPP;
 }
 
+static inline int
+devm_of_syscon_register_regmap(struct device *dev,
+			       struct device_node *np,
+			       struct regmap *regmap)
+{
+	return -EOPNOTSUPP;
+}
+
 #endif
 
 #endif /* __LINUX_MFD_SYSCON_H__ */

-- 
2.53.0


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

* [PATCH net-next v5 3/5] mfd: ac200: Add X-Powers AC200 support
  2026-08-09 20:28 [PATCH net-next v5 0/5] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
  2026-08-09 20:28 ` [PATCH net-next v5 1/5] dt-bindings: mfd: x-powers: Add AC200 James Hilliard
  2026-08-09 20:28 ` [PATCH net-next v5 2/5] mfd: syscon: Add managed registration for external regmaps James Hilliard
@ 2026-08-09 20:28 ` James Hilliard
  2026-08-09 20:28 ` [PATCH net-next v5 4/5] dt-bindings: net: x-powers: add AC200/AC300 EPHY packages James Hilliard
  2026-08-09 20:28 ` [PATCH net-next v5 5/5] net: phy: add X-Powers AC200/AC300 EPHY driver James Hilliard
  4 siblings, 0 replies; 11+ messages in thread
From: James Hilliard @ 2026-08-09 20:28 UTC (permalink / raw)
  To: Lee Jones, Arnd Bergmann, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, James Hilliard, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
	Heiner Kallweit, Russell King
  Cc: mfd, devicetree, linux-kernel, netdev

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

Enable the shared input clock and prevent its rate from changing. Match
the vendor driver's 40 ms wait before the first register access,
initialize the paged regmap, report the chip and package revision, and
apply common reset.

The Ethernet PHY link endpoint is independently enumerated on its MDIO
bus, so publish the regmap through managed syscon registration for lookup
through its AC200 firmware reference rather than creating an artificial
MFD platform child. The managed entry is withdrawn before the regmap is
released, while the function driver separately manages the PHY analog
supply.

Cache only the common page selector. Individual functions can reset
independently and invalidate their other registers without regmap's
knowledge, so leave all functional registers volatile.

Reset the chip during managed teardown and system shutdown. Consumers
can use device links to ensure that they unbind before the shared AC200
resources are released.

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

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 763ce6a34782..95e3c62742bb 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_SYSCON
+	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..25066bb36ac7
--- /dev/null
+++ b/drivers/mfd/ac200.c
@@ -0,0 +1,170 @@
+// 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/syscon.h>
+#include <linux/module.h>
+#include <linux/regmap.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 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 void ac200_disable(void *data)
+{
+	struct ac200 *ddata = data;
+
+	regmap_write(ddata->regmap, AC200_SYS_CONTROL_REG, 0);
+}
+
+static int ac200_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	unsigned int version;
+	struct ac200 *ddata;
+	struct clk *clk;
+	int ret;
+
+	ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+	if (!ddata)
+		return -ENOMEM;
+
+	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");
+
+	ddata->regmap = devm_regmap_init_i2c(client, &ac200_regmap_config);
+	if (IS_ERR(ddata->regmap))
+		return dev_err_probe(dev, PTR_ERR(ddata->regmap),
+				     "failed to initialize regmap\n");
+
+	i2c_set_clientdata(client, ddata);
+
+	/*
+	 * 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(ddata->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));
+
+	/* Reset the chip after dependent function drivers have unbound. */
+	ret = devm_add_action_or_reset(dev, ac200_disable, ddata);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(ddata->regmap, AC200_SYS_CONTROL_REG, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(ddata->regmap, AC200_SYS_CONTROL_REG,
+			   AC200_SYS_CONTROL_CHIP_RESET_DEASSERT);
+	if (ret)
+		return ret;
+
+	ret = devm_of_syscon_register_regmap(dev, dev->of_node,
+					     ddata->regmap);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to register regmap\n");
+
+	return 0;
+}
+
+static void ac200_shutdown(struct i2c_client *client)
+{
+	ac200_disable(i2c_get_clientdata(client));
+}
+
+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[] = {
+	{ .name = "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] 11+ messages in thread

* [PATCH net-next v5 4/5] dt-bindings: net: x-powers: add AC200/AC300 EPHY packages
  2026-08-09 20:28 [PATCH net-next v5 0/5] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
                   ` (2 preceding siblings ...)
  2026-08-09 20:28 ` [PATCH net-next v5 3/5] mfd: ac200: Add X-Powers AC200 support James Hilliard
@ 2026-08-09 20:28 ` James Hilliard
  2026-08-11  7:15   ` Krzysztof Kozlowski
  2026-08-09 20:28 ` [PATCH net-next v5 5/5] net: phy: add X-Powers AC200/AC300 EPHY driver James Hilliard
  4 siblings, 1 reply; 11+ messages in thread
From: James Hilliard @ 2026-08-09 20:28 UTC (permalink / raw)
  To: Lee Jones, Arnd Bergmann, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, James Hilliard, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
	Heiner Kallweit, Russell King
  Cc: mfd, devicetree, linux-kernel, netdev

The AC200 and AC300 contain compatible Fast Ethernet link PHYs which
report the same Clause 22 identifier and use the same link-side register
layout. Their package control paths differ: AC200 uses registers in its
I2C MFD, while the AC300 control range is at a second Clause 22 address
fixed at 16 plus the link address.

Describe both variants as Ethernet PHY packages. The package reg value is
the link PHY base address and the child supplies the documented PHY ID,
because the identification registers cannot be read until package setup
has completed. The AC300 driver reaches its control range with the PHY
package offset helpers. The AC200 package instead references its MFD.

Give fixed hardware variant-specific compatibles. Also provide an ACx00
compatible for systems which can contain either package, with one packed
NVMEM field containing calibration in bits 3 through 0, the AC300 selector
in bit 8 and its low-calibration selector in bit 9. Such a description
contains the potential resources for both backends; the driver consumes
only those selected by the configuration field.

Describe the package clock, supply and optional xMII receive-clock
inversion needed by some integrations. A fixed AC200 package may omit the
NVMEM field and use its internal calibration copy. Limit AC300-capable link
addresses to 0 through 7 so the control address remains in the documented
range 16 through 23.

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

diff --git a/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml
new file mode 100644
index 000000000000..ae0c5e980c39
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml
@@ -0,0 +1,207 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/x-powers,acx00-ephy-package.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: X-Powers AC200 and AC300 Ethernet PHY packages
+
+maintainers:
+  - James Hilliard <james.hilliard1@gmail.com>
+
+description:
+  The AC200 and AC300 contain compatible Fast Ethernet PHY blocks which report
+  the same Clause 22 PHY ID and use the same link-side register layout. The
+  package control paths differ. AC200 control registers are part of an I2C MFD,
+  while AC300 control registers occupy a second Clause 22 address fixed at 16
+  plus the link PHY address.
+
+  Systems with a known package use the corresponding AC200 or AC300 compatible.
+  Systems which can contain either package use the ACx00 compatible and a
+  packed NVMEM configuration field to select the backend at runtime.
+
+  The link PHY cannot report its ID until the package control sequence has
+  completed, so its child node supplies the documented PHY ID. The package and
+  child use the same base address. The AC200 driver programs that address into
+  the package, while the AC300 driver derives its control address from it.
+
+select:
+  properties:
+    compatible:
+      enum:
+        - x-powers,ac200-ephy-package
+        - x-powers,ac300-ephy-package
+        - x-powers,acx00-ephy-package
+  required:
+    - compatible
+
+properties:
+  compatible:
+    enum:
+      - x-powers,ac200-ephy-package
+      - x-powers,ac300-ephy-package
+      - x-powers,acx00-ephy-package
+
+  reg:
+    maximum: 31
+
+  clocks:
+    maxItems: 1
+    description:
+      AC300 input clock, running at 24, 25, or 27 MHz. The AC200 input clock
+      belongs to the referenced AC200 MFD instead.
+
+  vcc-supply:
+    description:
+      3.3 V package supply. This is connected to EPHY_VCC on AC200 and VCC1
+      on AC300.
+
+  nvmem-cells:
+    maxItems: 1
+    description:
+      Packed EPHY configuration field. Bits 3 through 0 contain the analog
+      calibration code, bits 7 through 4 are reserved, bit 8 selects AC300
+      when set and AC200 when clear, and bit 9 requests the AC300
+      low-calibration tuning sequence. A fixed AC200 package may provide only
+      the low calibration bits or omit the cell and use its internal eFuse
+      copy instead.
+
+  nvmem-cell-names:
+    const: configuration
+
+  x-powers,ac200:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description: Reference to the AC200 MFD containing this Ethernet PHY
+
+  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.
+
+patternProperties:
+  ^ethernet-phy@[a-f0-9]+$:
+    $ref: ethernet-phy.yaml#
+    properties:
+      compatible:
+        const: ethernet-phy-id0044.1400
+    required:
+      - compatible
+    unevaluatedProperties: false
+
+required:
+  - compatible
+  - vcc-supply
+
+dependentRequired:
+  nvmem-cells: [ nvmem-cell-names ]
+  nvmem-cell-names: [ nvmem-cells ]
+
+allOf:
+  - $ref: ethernet-phy-package.yaml#
+  - if:
+      properties:
+        compatible:
+          const: x-powers,ac200-ephy-package
+    then:
+      properties:
+        clocks: false
+      required:
+        - x-powers,ac200
+  - if:
+      properties:
+        compatible:
+          const: x-powers,ac300-ephy-package
+    then:
+      properties:
+        reg:
+          maximum: 7
+        x-powers,ac200: false
+      required:
+        - clocks
+        - nvmem-cells
+        - nvmem-cell-names
+  - if:
+      properties:
+        compatible:
+          const: x-powers,acx00-ephy-package
+    then:
+      properties:
+        reg:
+          maximum: 7
+      required:
+        - clocks
+        - nvmem-cells
+        - nvmem-cell-names
+        - x-powers,ac200
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ethernet-phy-package@0 {
+            compatible = "x-powers,acx00-ephy-package";
+            reg = <0>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+            clocks = <&pwm 5>;
+            vcc-supply = <&reg_3v3>;
+            nvmem-cells = <&ephy_configuration>;
+            nvmem-cell-names = "configuration";
+            x-powers,ac200 = <&ac200>;
+
+            ethernet-phy@0 {
+                compatible = "ethernet-phy-id0044.1400";
+                reg = <0>;
+            };
+        };
+    };
+
+  - |
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ethernet-phy-package@0 {
+            compatible = "x-powers,ac300-ephy-package";
+            reg = <0>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+            clocks = <&pwm 5>;
+            vcc-supply = <&reg_3v3>;
+            nvmem-cells = <&ephy_configuration>;
+            nvmem-cell-names = "configuration";
+
+            ethernet-phy@0 {
+                compatible = "ethernet-phy-id0044.1400";
+                reg = <0>;
+            };
+        };
+    };
+
+  - |
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        ethernet-phy-package@1 {
+            compatible = "x-powers,ac200-ephy-package";
+            reg = <1>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+            vcc-supply = <&reg_3v3>;
+            x-powers,ac200 = <&ac200>;
+            nvmem-cells = <&ephy_configuration>;
+            nvmem-cell-names = "configuration";
+
+            ethernet-phy@1 {
+                compatible = "ethernet-phy-id0044.1400";
+                reg = <1>;
+            };
+        };
+    };
+...

-- 
2.53.0


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

* [PATCH net-next v5 5/5] net: phy: add X-Powers AC200/AC300 EPHY driver
  2026-08-09 20:28 [PATCH net-next v5 0/5] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
                   ` (3 preceding siblings ...)
  2026-08-09 20:28 ` [PATCH net-next v5 4/5] dt-bindings: net: x-powers: add AC200/AC300 EPHY packages James Hilliard
@ 2026-08-09 20:28 ` James Hilliard
  4 siblings, 0 replies; 11+ messages in thread
From: James Hilliard @ 2026-08-09 20:28 UTC (permalink / raw)
  To: Lee Jones, Arnd Bergmann, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, James Hilliard, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
	Heiner Kallweit, Russell King
  Cc: mfd, devicetree, linux-kernel, netdev

The AC200 and AC300 contain compatible Fast Ethernet link PHYs, but the
link endpoint is inaccessible until package-specific control registers
have powered and configured it.

Add one PHY driver which binds the link child and joins its parent
Ethernet PHY package. Fixed package compatibles select the corresponding
backend. The generic ACx00 compatible reads one packed configuration field
and selects the backend before touching backend-specific resources. The
AC300 path therefore does not resolve or access the candidate AC200 I2C
device.

AC200 locates the I2C device referenced by the package and establishes a
managed device link before obtaining its regmap through syscon. The link
keeps the supplier bound until the PHY consumer has unbound, while the
syscon lookup defers probing until the AC200 driver has published its
managed regmap. Firmware must make the selected AC200 provider path
available before the PHY probes.

AC300 uses the PHY package helpers to access the control range at base
address plus 16. Keeping both backends in the same module lets the common
link implementation own the complete PHY without registering artificial
control devices.

Obtain and manage the selected package supply, calibration and input
clock, validate or program the link address, and apply the required reset,
clock, I/O and shutdown sequences. Start in the hardware-default MII mode
so the forced PHY device can probe before a MAC attaches, then apply the
MAC-provided MII or RMII mode before the normal PHY soft reset.

Apply the common vendor analog initialization, optional AC300
low-calibration tuning and board-selected receive-clock inversion. Keep
MDI/MDI-X in automatic mode. Preserve standard MAC-managed EEE support
while disabling the vendor PHY-autonomous Intelligent EEE mode.

Power down the package control block and its supply during PHY suspend,
then restore them and the vendor configuration during resume. If
shutdown fails, attempt to recover the running PHY before returning the
original error.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 drivers/net/phy/Kconfig          |  11 +
 drivers/net/phy/Makefile         |   1 +
 drivers/net/phy/xpowers/Makefile |   3 +
 drivers/net/phy/xpowers/ac200.c  | 315 +++++++++++++++++++++++
 drivers/net/phy/xpowers/ac300.c  | 387 ++++++++++++++++++++++++++++
 drivers/net/phy/xpowers/acx00.c  | 536 +++++++++++++++++++++++++++++++++++++++
 drivers/net/phy/xpowers/acx00.h  |  28 ++
 7 files changed, 1281 insertions(+)

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index a29d3fed8a05..27b8ffe4b539 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -475,6 +475,17 @@ config VITESSE_PHY
 	help
 	  Currently supports the vsc8244
 
+config XPOWERS_ACX00_PHY
+	tristate "X-Powers AC200/AC300 Ethernet PHY"
+	depends on COMMON_CLK && NVMEM && OF_MDIO && REGULATOR
+	depends on I2C || !I2C
+	select PHY_PACKAGE
+	help
+	  Enable the Fast Ethernet PHY driver shared by the X-Powers AC200
+	  and AC300 companion ICs. The driver configures their common MDIO PHY
+	  registers and the package-specific AC200 I2C or AC300 MDIO control
+	  registers.
+
 config XILINX_GMII2RGMII
 	tristate "Xilinx GMII2RGMII converter driver"
 	help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index e23df5e836e9..ffb0efbc4cc4 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_ACX00_PHY)	+= xpowers/
 obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
diff --git a/drivers/net/phy/xpowers/Makefile b/drivers/net/phy/xpowers/Makefile
new file mode 100644
index 000000000000..ae977a795832
--- /dev/null
+++ b/drivers/net/phy/xpowers/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+xpowers-acx00-y := acx00.o ac200.o ac300.o
+obj-$(CONFIG_XPOWERS_ACX00_PHY) += xpowers-acx00.o
diff --git a/drivers/net/phy/xpowers/ac200.c b/drivers/net/phy/xpowers/ac200.c
new file mode 100644
index 000000000000..681e013be401
--- /dev/null
+++ b/drivers/net/phy/xpowers/ac200.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * X-Powers AC200 Ethernet PHY package backend
+ *
+ * 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/i2c.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#include "acx00.h"
+
+#define AC200_EPHY_BPS_EFFUSE_OFFSET	3
+#define AC200_EPHY_CLK_RATE_24_MHZ	24000000
+#define AC200_EPHY_CLK_RATE_27_MHZ	27000000
+
+#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 regulator *vcc;
+	struct device *dev;
+	u16 ephy_ctl;
+	unsigned int phy_addr;
+	phy_interface_t interface;
+	bool supply_enabled;
+	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_write(struct ac200_ephy_ctl *priv,
+				unsigned int reg, u16 value)
+{
+	int ret;
+
+	ret = regmap_write(priv->regmap, reg, value);
+	if (ret)
+		dev_err(priv->dev, "failed to write register %#x: %pe\n",
+			reg, ERR_PTR(ret));
+
+	return ret;
+}
+
+static int ac200_ephy_ctl_disable(struct ac200_ephy_ctl *priv)
+{
+	int err;
+	int ret = 0;
+
+	if (priv->powered) {
+		ret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG,
+					   ac200_ephy_ctl_config(priv) |
+					   AC200_EPHY_SHUTDOWN);
+		err = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL1_REG, 0);
+		if (!ret)
+			ret = err;
+		err = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, 0);
+		if (!ret)
+			ret = err;
+
+		priv->powered = false;
+	}
+
+	if (priv->supply_enabled) {
+		err = regulator_disable(priv->vcc);
+		if (!err)
+			priv->supply_enabled = false;
+		else if (!ret)
+			ret = err;
+	}
+
+	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);
+
+	return ac200_ephy_ctl_disable(priv);
+}
+
+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);
+	int ret = 0;
+
+	if (interface != PHY_INTERFACE_MODE_MII &&
+	    interface != PHY_INTERFACE_MODE_RMII)
+		return -EINVAL;
+
+	if (priv->interface == interface)
+		return 0;
+
+	if (priv->powered)
+		ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL_REG,
+					 AC200_EPHY_RMII_SEL,
+					 interface == PHY_INTERFACE_MODE_RMII ?
+					 AC200_EPHY_RMII_SEL : 0);
+	if (!ret)
+		priv->interface = interface;
+
+	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;
+
+	if (priv->powered && priv->phy_addr == phy_addr)
+		return 0;
+
+	if (priv->powered) {
+		ret = ac200_ephy_ctl_disable(priv);
+		if (ret)
+			return ret;
+	}
+	priv->phy_addr = phy_addr;
+
+	if (!priv->supply_enabled) {
+		ret = regulator_enable(priv->vcc);
+		if (ret)
+			return ret;
+		priv->supply_enabled = true;
+	}
+
+	ephy_ctl = ac200_ephy_ctl_config(priv);
+
+	/* Start from a disabled state before applying the configuration. */
+	ret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, 0);
+	if (ret)
+		goto err_disable;
+
+	ret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL1_REG,
+				   AC200_EPHY_MII_IO_ENABLE);
+	if (ret)
+		goto err_disable;
+
+	ret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG,
+				   ephy_ctl | AC200_EPHY_SHUTDOWN);
+	if (ret)
+		goto err_disable;
+
+	ret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG,
+				   AC200_EPHY_RESET_DEASSERT |
+				   AC200_EPHY_SYSCLK_ENABLE);
+	if (ret)
+		goto err_disable;
+
+	ret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG, ephy_ctl);
+	if (ret)
+		goto err_disable;
+
+	priv->powered = true;
+	return 0;
+
+err_disable:
+	/* Attempt every step of the shutdown sequence after a partial start. */
+	priv->powered = true;
+	ac200_ephy_ctl_disable(priv);
+
+	return ret;
+}
+
+struct acx00_ephy_control *
+ac200_ephy_ctl_create(struct phy_device *phydev,
+		      struct device_node *package_node,
+		      bool has_calibration, u8 calibration)
+{
+	struct device *dev = &phydev->mdio.dev;
+	unsigned int internal_calibration;
+	struct device_node *ac200_node;
+	struct ac200_ephy_ctl *priv;
+	struct i2c_client *client;
+	struct device_link *link;
+	unsigned long clk_rate;
+	u8 bps_effuse_code;
+	struct clk *clk;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return ERR_PTR(-ENOMEM);
+	priv->dev = dev;
+	priv->vcc = devm_of_regulator_get(dev, package_node, "vcc");
+	if (IS_ERR(priv->vcc))
+		return ERR_PTR(dev_err_probe(dev, PTR_ERR(priv->vcc),
+					     "failed to get package supply\n"));
+
+	ac200_node = of_parse_phandle(package_node, "x-powers,ac200", 0);
+	if (!ac200_node)
+		return ERR_PTR(dev_err_probe(dev, -EINVAL,
+					     "missing x-powers,ac200 reference\n"));
+	client = of_find_i2c_device_by_node(ac200_node);
+	of_node_put(ac200_node);
+	if (!client) {
+		ret = IS_ENABLED(CONFIG_I2C) ? -EPROBE_DEFER : -ENODEV;
+		return ERR_PTR(dev_err_probe(dev, ret,
+					     "AC200 device is not registered\n"));
+	}
+
+	link = device_link_add(dev, &client->dev,
+			       DL_FLAG_AUTOREMOVE_CONSUMER);
+	if (!link) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "failed to link AC200 device\n");
+		goto out_put_client;
+	}
+
+	if (READ_ONCE(link->status) != DL_STATE_CONSUMER_PROBE) {
+		ret = dev_err_probe(dev, -EPROBE_DEFER,
+				    "AC200 driver is not ready\n");
+		goto out_put_client;
+	}
+
+	priv->regmap = syscon_regmap_lookup_by_phandle(package_node,
+						       "x-powers,ac200");
+	if (IS_ERR(priv->regmap)) {
+		ret = dev_err_probe(dev, PTR_ERR(priv->regmap),
+				    "failed to get AC200 regmap\n");
+		goto out_error;
+	}
+
+	if (!has_calibration) {
+		ret = regmap_read(priv->regmap, AC200_EFUSE_EPHY_REG,
+				  &internal_calibration);
+		if (ret)
+			goto out_error;
+		calibration = internal_calibration;
+	}
+
+	/* 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(&client->dev, NULL);
+	if (IS_ERR(clk)) {
+		ret = PTR_ERR(clk);
+		goto out_error;
+	}
+
+	clk_rate = clk_get_rate(clk);
+	clk_put(clk);
+
+	switch (clk_rate) {
+	case AC200_EPHY_CLK_RATE_24_MHZ:
+		priv->ephy_ctl |= AC200_EPHY_CLK_SEL_24_MHZ;
+		break;
+	case AC200_EPHY_CLK_RATE_27_MHZ:
+		break;
+	default:
+		ret = dev_err_probe(dev, -EINVAL,
+				    "unsupported AC200 clock rate %lu Hz\n",
+				    clk_rate);
+		goto out_put_client;
+	}
+
+	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;
+	put_device(&client->dev);
+
+	return &priv->control;
+
+out_error:
+	ret = dev_err_probe(dev, ret, "failed to initialize AC200 control\n");
+out_put_client:
+	put_device(&client->dev);
+	return ERR_PTR(ret);
+}
diff --git a/drivers/net/phy/xpowers/ac300.c b/drivers/net/phy/xpowers/ac300.c
new file mode 100644
index 000000000000..aa560432a14c
--- /dev/null
+++ b/drivers/net/phy/xpowers/ac300.c
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * X-Powers AC300 Ethernet PHY package backend
+ *
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/regulator/consumer.h>
+
+#include "../phylib.h"
+#include "acx00.h"
+
+#define AC300_EPHY_BGS_EFFUSE_OFFSET	3
+#define AC300_EPHY_CLK_RATE_24_MHZ	24000000
+#define AC300_EPHY_CLK_RATE_25_MHZ	25000000
+#define AC300_EPHY_CLK_RATE_27_MHZ	27000000
+#define AC300_SYS_CONTROL_REG			0x00
+#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_CLKIN_GATING_ENABLE		BIT(2)
+#define AC300_EPHY_RESET_DEASSERT		BIT(1)
+#define AC300_CHIP_RESET_DEASSERT		BIT(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 phy_device *phydev;
+	struct regulator *vcc;
+	struct clk *clk;
+	u16 sys_control;
+	u16 ephy_config;
+	phy_interface_t interface;
+	bool package_known;
+	bool internal_dldo;
+	bool supply_enabled;
+	bool powered;
+};
+
+static unsigned int
+ac300_ephy_ctl_link_addr(const struct ac300_ephy_ctl *priv)
+{
+	return priv->phydev->mdio.addr;
+}
+
+static int ac300_ephy_ctl_read(struct ac300_ephy_ctl *priv, u32 regnum)
+{
+	int ret;
+
+	phy_lock_mdio_bus(priv->phydev);
+	ret = __phy_package_read(priv->phydev,
+				 AC300_EPHY_CONTROL_ADDR_OFFSET, regnum);
+	phy_unlock_mdio_bus(priv->phydev);
+
+	return ret;
+}
+
+static int ac300_ephy_ctl_write(struct ac300_ephy_ctl *priv, u32 regnum,
+				u16 val)
+{
+	int ret;
+
+	phy_lock_mdio_bus(priv->phydev);
+	ret = __phy_package_write(priv->phydev,
+				  AC300_EPHY_CONTROL_ADDR_OFFSET, regnum, val);
+	phy_unlock_mdio_bus(priv->phydev);
+
+	return ret;
+}
+
+static int ac300_ephy_ctl_modify(struct ac300_ephy_ctl *priv, u32 regnum,
+				 u16 mask, u16 set)
+{
+	int ret;
+
+	phy_lock_mdio_bus(priv->phydev);
+	ret = __phy_package_read(priv->phydev,
+				 AC300_EPHY_CONTROL_ADDR_OFFSET, regnum);
+	if (ret >= 0) {
+		u16 val = (ret & ~mask) | (set & mask);
+
+		ret = val == ret ? 0 :
+			__phy_package_write(priv->phydev,
+					    AC300_EPHY_CONTROL_ADDR_OFFSET,
+					    regnum, val);
+	}
+	phy_unlock_mdio_bus(priv->phydev);
+
+	return ret;
+}
+
+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 u16 ac300_ephy_ctl_reset_value(const struct ac300_ephy_ctl *priv)
+{
+	/*
+	 * A chip reset restores DLDOEN to one. Until PKG_STATUS has identified
+	 * the supply arrangement, preserve any external-VDD setup left by the
+	 * bootloader by asserting only the EPHY reset.
+	 */
+	return !priv->package_known || !priv->internal_dldo ?
+		AC300_CHIP_RESET_DEASSERT : 0;
+}
+
+static int ac300_ephy_ctl_disable(struct ac300_ephy_ctl *priv)
+{
+	int err;
+	int ret = 0;
+
+	if (priv->powered) {
+		ret = ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG,
+					   ac300_ephy_ctl_config(priv) |
+					   AC300_EPHY_SHUTDOWN);
+		err = ac300_ephy_ctl_write(priv, AC300_SYS_IO_REG, 0);
+		if (!ret)
+			ret = err;
+		err = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG,
+					   ac300_ephy_ctl_reset_value(priv));
+		if (!ret)
+			ret = err;
+
+		clk_disable_unprepare(priv->clk);
+		priv->powered = false;
+	}
+
+	if (priv->supply_enabled) {
+		err = regulator_disable(priv->vcc);
+		if (!err)
+			priv->supply_enabled = false;
+		else if (!ret)
+			ret = err;
+	}
+
+	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);
+
+	return ac300_ephy_ctl_disable(priv);
+}
+
+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);
+	int ret = 0;
+
+	if (interface != PHY_INTERFACE_MODE_MII &&
+	    interface != PHY_INTERFACE_MODE_RMII)
+		return -EINVAL;
+
+	if (priv->interface == interface)
+		return 0;
+
+	if (priv->powered)
+		ret = ac300_ephy_ctl_modify(priv, AC300_EPHY_CONFIG_REG,
+					    AC300_EPHY_RMII_SEL,
+					    interface == PHY_INTERFACE_MODE_RMII ?
+					    AC300_EPHY_RMII_SEL : 0);
+	if (!ret)
+		priv->interface = interface;
+
+	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;
+
+	if (priv->powered)
+		return 0;
+
+	if (!priv->supply_enabled) {
+		ret = regulator_enable(priv->vcc);
+		if (ret)
+			return ret;
+		priv->supply_enabled = true;
+
+		/* Wait for the power-on reset interval specified by the manual. */
+		fsleep(10000);
+	}
+
+	ret = clk_prepare_enable(priv->clk);
+	if (ret)
+		goto err_power_off;
+	priv->powered = true;
+
+	/* Keep the external-supply configuration across chip resets. */
+	reset_value = ac300_ephy_ctl_reset_value(priv);
+	ret = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG, reset_value);
+	if (ret)
+		goto err_power_off;
+
+	/* The manual requires both resets to be released before the clocks. */
+	ret = ac300_ephy_ctl_write(priv, 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 = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG,
+				   priv->sys_control);
+	if (ret)
+		goto err_power_off;
+
+	sys_control = ac300_ephy_ctl_read(priv, AC300_SYS_CONTROL_REG);
+	if (sys_control < 0) {
+		ret = sys_control;
+		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 = ac300_ephy_ctl_modify(priv, 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 = ac300_ephy_ctl_write(priv, AC300_SYS_IO_REG,
+				   AC300_SYS_IO_VALUE);
+	if (ret)
+		goto err_power_off;
+
+	fsleep(10000);
+
+	ret = ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG,
+				   ac300_ephy_ctl_config(priv) |
+				  AC300_EPHY_SHUTDOWN);
+	if (ret)
+		goto err_power_off;
+
+	fsleep(10000);
+
+	ret = ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG,
+				   ac300_ephy_ctl_config(priv));
+	if (ret)
+		goto err_power_off;
+
+	return 0;
+
+err_power_off:
+	ac300_ephy_ctl_disable(priv);
+
+	return ret;
+}
+
+static void ac300_ephy_clk_put(void *data)
+{
+	clk_put(data);
+}
+
+struct acx00_ephy_control *
+ac300_ephy_ctl_create(struct phy_device *phydev,
+		      struct device_node *package_node, u8 calibration)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct ac300_ephy_ctl *priv;
+	unsigned long clk_rate;
+	u8 bgs_effuse_code;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return ERR_PTR(-ENOMEM);
+	if (phydev->mdio.addr > FIELD_MAX(AC300_PACKAGE_PHY_ADDR_MASK))
+		return ERR_PTR(dev_err_probe(dev, -EINVAL,
+					     "link address is outside the package range\n"));
+	priv->phydev = phydev;
+
+	priv->vcc = devm_of_regulator_get(dev, package_node, "vcc");
+	if (IS_ERR(priv->vcc))
+		return ERR_PTR(dev_err_probe(dev, PTR_ERR(priv->vcc),
+					     "failed to get package supply\n"));
+
+	priv->clk = of_clk_get(package_node, 0);
+	if (IS_ERR(priv->clk))
+		return ERR_PTR(dev_err_probe(dev, PTR_ERR(priv->clk),
+					     "failed to get input clock\n"));
+
+	ret = devm_add_action_or_reset(dev, ac300_ephy_clk_put, priv->clk);
+	if (ret)
+		return ERR_PTR(ret);
+
+	ret = devm_clk_rate_exclusive_get(dev, priv->clk);
+	if (ret)
+		return ERR_PTR(dev_err_probe(dev, ret,
+					     "failed to lock clock rate\n"));
+
+	clk_rate = clk_get_rate(priv->clk);
+	switch (clk_rate) {
+	case AC300_EPHY_CLK_RATE_24_MHZ:
+		priv->sys_control = AC300_EPHY_CLK_SEL_24_MHZ;
+		break;
+	case AC300_EPHY_CLK_RATE_25_MHZ:
+		priv->sys_control = AC300_EPHY_CLK_SEL_25_MHZ;
+		break;
+	case AC300_EPHY_CLK_RATE_27_MHZ:
+		priv->sys_control = AC300_EPHY_CLK_SEL_27_MHZ;
+		break;
+	default:
+		return ERR_PTR(dev_err_probe(dev, -EINVAL,
+					     "unsupported input clock rate %lu Hz\n",
+					     clk_rate));
+	}
+	priv->sys_control |= AC300_SYS_CONTROL_ENABLE_BITS;
+
+	/* 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;
+
+	return &priv->control;
+}
diff --git a/drivers/net/phy/xpowers/acx00.c b/drivers/net/phy/xpowers/acx00.c
new file mode 100644
index 000000000000..98f2b0b63909
--- /dev/null
+++ b/drivers/net/phy/xpowers/acx00.c
@@ -0,0 +1,536 @@
+// 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/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/slab.h>
+
+#include "../phylib.h"
+#include "acx00.h"
+
+#define ACX00_EPHY_ID				0x00441400
+
+#define ACX00_EPHY_CONFIG_CALIBRATION_MASK	GENMASK(3, 0)
+#define ACX00_EPHY_CONFIG_VARIANT_AC300		BIT(8)
+#define ACX00_EPHY_CONFIG_CALIBRATION_LOW	BIT(9)
+
+#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 int acx00_ephy_read_nvmem_u16(struct device_node *node,
+				     const char *name, u16 *value)
+{
+	struct nvmem_cell *cell;
+	size_t i;
+	size_t len;
+	u8 *buf;
+	u16 val = 0;
+
+	cell = of_nvmem_cell_get(node, name);
+	if (IS_ERR(cell))
+		return PTR_ERR(cell);
+
+	buf = nvmem_cell_read(cell, &len);
+	nvmem_cell_put(cell);
+	if (IS_ERR(buf))
+		return PTR_ERR(buf);
+	if (!len || len > sizeof(*value)) {
+		kfree(buf);
+		return len ? -ERANGE : -EINVAL;
+	}
+
+	for (i = 0; i < len; i++)
+		val |= (u16)buf[i] << (8 * i);
+	kfree(buf);
+	*value = val;
+
+	return 0;
+}
+
+static int acx00_ephy_init_package(struct phy_device *phydev,
+				   struct acx00_ephy_priv *priv)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct device_node *package_node;
+	bool selectable;
+	bool fixed_ac300;
+	bool has_configuration;
+	u8 calibration;
+	u32 base_addr;
+	u16 configuration = 0;
+	int ret;
+
+	package_node = of_get_parent(dev->of_node);
+	if (!package_node)
+		return -EINVAL;
+	if (!of_node_name_eq(package_node, "ethernet-phy-package")) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "PHY is not in an Ethernet PHY package\n");
+		goto out_put_node;
+	}
+
+	ret = of_property_read_u32(package_node, "reg", &base_addr);
+	if (ret || base_addr != phydev->mdio.addr) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "package and link PHY addresses differ\n");
+		goto out_put_node;
+	}
+
+	selectable = of_device_is_compatible(package_node,
+					     "x-powers,acx00-ephy-package");
+	fixed_ac300 = of_device_is_compatible(package_node,
+					      "x-powers,ac300-ephy-package");
+	if (!selectable && !fixed_ac300 &&
+	    !of_device_is_compatible(package_node,
+				     "x-powers,ac200-ephy-package")) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "unsupported Ethernet PHY package\n");
+		goto out_put_node;
+	}
+
+	ret = devm_of_phy_package_join(dev, phydev, 0);
+	if (ret)
+		goto out_put_node;
+
+	has_configuration =
+		of_property_match_string(package_node, "nvmem-cell-names",
+					 "configuration") >= 0;
+	if (has_configuration) {
+		ret = acx00_ephy_read_nvmem_u16(package_node, "configuration",
+						&configuration);
+		if (ret) {
+			ret = dev_err_probe(dev, ret,
+					    "failed to read package configuration\n");
+			goto out_put_node;
+		}
+	} else if (selectable || fixed_ac300) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "package configuration is required\n");
+		goto out_put_node;
+	}
+
+	if (selectable) {
+		priv->is_ac300 =
+			configuration & ACX00_EPHY_CONFIG_VARIANT_AC300;
+	} else {
+		priv->is_ac300 = fixed_ac300;
+		if (has_configuration &&
+		    !!(configuration & ACX00_EPHY_CONFIG_VARIANT_AC300) !=
+		    priv->is_ac300) {
+			ret = dev_err_probe(dev, -EINVAL,
+					    "configuration does not match package\n");
+			goto out_put_node;
+		}
+	}
+
+	calibration = FIELD_GET(ACX00_EPHY_CONFIG_CALIBRATION_MASK,
+				configuration);
+
+	priv->use_low_calibration_tuning =
+		priv->is_ac300 &&
+		!!(configuration & ACX00_EPHY_CONFIG_CALIBRATION_LOW);
+	priv->xmii_rx_clock_inverted =
+		of_property_read_bool(package_node,
+				      "x-powers,xmii-rx-clock-inverted");
+
+	if (priv->is_ac300)
+		priv->control =
+			ac300_ephy_ctl_create(phydev, package_node,
+					      calibration);
+	else
+		priv->control =
+			ac200_ephy_ctl_create(phydev, package_node,
+					      has_configuration,
+					      calibration);
+	ret = PTR_ERR_OR_ZERO(priv->control);
+
+out_put_node:
+	of_node_put(package_node);
+	return ret;
+}
+
+static int acx00_ephy_disable_autonomous_eee(struct phy_device *phydev)
+{
+	return phy_modify_paged(phydev, ACX00_PAGE_1,
+				ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG,
+				ACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0);
+}
+
+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_init_package(phydev, priv);
+	if (ret)
+		return ret;
+
+	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");
+
+	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)
+		goto out_restore_page;
+
+	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;
+	struct device_node *package_node;
+	bool match;
+
+	if (!genphy_match_phy_device(phydev, phydrv) || !node)
+		return 0;
+
+	/* RK630 reports the same PHY ID, so also match the package identity. */
+	package_node = of_get_parent(node);
+	if (!package_node)
+		return 0;
+	match = of_device_is_compatible(package_node,
+					"x-powers,ac200-ephy-package") ||
+		of_device_is_compatible(package_node,
+					"x-powers,ac300-ephy-package") ||
+		of_device_is_compatible(package_node,
+					"x-powers,acx00-ephy-package");
+	of_node_put(package_node);
+
+	return match;
+}
+
+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,
+		.disable_autonomous_eee = acx00_ephy_disable_autonomous_eee,
+		.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");
diff --git a/drivers/net/phy/xpowers/acx00.h b/drivers/net/phy/xpowers/acx00.h
new file mode 100644
index 000000000000..7df6084c7a77
--- /dev/null
+++ b/drivers/net/phy/xpowers/acx00.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __DRIVERS_NET_PHY_XPOWERS_ACX00_H
+#define __DRIVERS_NET_PHY_XPOWERS_ACX00_H
+
+#include <linux/err.h>
+#include <linux/phy.h>
+
+#define AC300_EPHY_CONTROL_ADDR_OFFSET	16
+
+struct device_node;
+
+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);
+};
+
+struct acx00_ephy_control *
+ac200_ephy_ctl_create(struct phy_device *phydev,
+		      struct device_node *package_node,
+		      bool has_calibration, u8 calibration);
+struct acx00_ephy_control *
+ac300_ephy_ctl_create(struct phy_device *phydev,
+		      struct device_node *package_node, u8 calibration);
+
+#endif

-- 
2.53.0


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

* Re: [PATCH net-next v5 1/5] dt-bindings: mfd: x-powers: Add AC200
  2026-08-09 20:28 ` [PATCH net-next v5 1/5] dt-bindings: mfd: x-powers: Add AC200 James Hilliard
@ 2026-08-11  7:07   ` Krzysztof Kozlowski
  2026-08-11  7:23     ` James Hilliard
  0 siblings, 1 reply; 11+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-11  7:07 UTC (permalink / raw)
  To: James Hilliard, Lee Jones, Arnd Bergmann, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
	Heiner Kallweit, Russell King
  Cc: mfd, devicetree, linux-kernel, netdev

On 09/08/2026 22:28, James Hilliard wrote:
> The AC200 is an I2C-controlled mixed-signal companion IC containing
> audio, video, RTC and Fast Ethernet PHY functions.
> 
> Describe the parent device and its shared input clock. The Ethernet PHY
> is represented by a PHY package on its MDIO bus, with a phandle back to
> this device for access to the package control registers, so it does not
> require an MFD child node. Function-specific supplies belong to their
> consumer bindings rather than the shared parent.
> 
> The input clock must run at 24 or 27 MHz when the Ethernet PHY is used,
> matching the rates encoded by its documented clock selector.
> 
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> ---

I do not understand why MFD and net are combined. And nothing in top
part of cover letter explains me that (which I asked already - any
dependency or merging is THE MOST important information. Where do you
put the most important information which maintainers should see
immediately?)

Bundling it together suggests this is somehow incomplete and later will
be finished, which is wrong by itself. How to fix it - decouple
dependencies.

Best regards,
Krzysztof

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

* Re: [PATCH net-next v5 4/5] dt-bindings: net: x-powers: add AC200/AC300 EPHY packages
  2026-08-09 20:28 ` [PATCH net-next v5 4/5] dt-bindings: net: x-powers: add AC200/AC300 EPHY packages James Hilliard
@ 2026-08-11  7:15   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 11+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-11  7:15 UTC (permalink / raw)
  To: James Hilliard, Lee Jones, Arnd Bergmann, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
	Heiner Kallweit, Russell King
  Cc: mfd, devicetree, linux-kernel, netdev

On 09/08/2026 22:28, James Hilliard wrote:
> The AC200 and AC300 contain compatible Fast Ethernet link PHYs which
> report the same Clause 22 identifier and use the same link-side register
> layout. Their package control paths differ: AC200 uses registers in its
> I2C MFD, while the AC300 control range is at a second Clause 22 address
> fixed at 16 plus the link address.
> 
> Describe both variants as Ethernet PHY packages. The package reg value is
> the link PHY base address and the child supplies the documented PHY ID,
> because the identification registers cannot be read until package setup
> has completed. The AC300 driver reaches its control range with the PHY
> package offset helpers. The AC200 package instead references its MFD.
> 
> Give fixed hardware variant-specific compatibles. Also provide an ACx00
> compatible for systems which can contain either package, with one packed
> NVMEM field containing calibration in bits 3 through 0, the AC300 selector
> in bit 8 and its low-calibration selector in bit 9. Such a description
> contains the potential resources for both backends; the driver consumes
> only those selected by the configuration field.
> 
> Describe the package clock, supply and optional xMII receive-clock
> inversion needed by some integrations. A fixed AC200 package may omit the
> NVMEM field and use its internal calibration copy. Limit AC300-capable link
> addresses to 0 through 7 so the control address remains in the documented
> range 16 through 23.
> 
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> ---
>  .../bindings/net/x-powers,acx00-ephy-package.yaml  | 207 +++++++++++++++++++++
>  1 file changed, 207 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml
> new file mode 100644
> index 000000000000..ae0c5e980c39
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml
> @@ -0,0 +1,207 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/net/x-powers,acx00-ephy-package.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: X-Powers AC200 and AC300 Ethernet PHY packages
> +
> +maintainers:
> +  - James Hilliard <james.hilliard1@gmail.com>
> +
> +description:
> +  The AC200 and AC300 contain compatible Fast Ethernet PHY blocks which report
> +  the same Clause 22 PHY ID and use the same link-side register layout. The
> +  package control paths differ. AC200 control registers are part of an I2C MFD,
> +  while AC300 control registers occupy a second Clause 22 address fixed at 16
> +  plus the link PHY address.
> +
> +  Systems with a known package use the corresponding AC200 or AC300 compatible.
> +  Systems which can contain either package use the ACx00 compatible and a
> +  packed NVMEM configuration field to select the backend at runtime.
> +
> +  The link PHY cannot report its ID until the package control sequence has
> +  completed, so its child node supplies the documented PHY ID. The package and
> +  child use the same base address. The AC200 driver programs that address into
> +  the package, while the AC300 driver derives its control address from it.
> +
> +select:
> +  properties:
> +    compatible:
> +      enum:
> +        - x-powers,ac200-ephy-package
> +        - x-powers,ac300-ephy-package
> +        - x-powers,acx00-ephy-package
> +  required:
> +    - compatible

Why do you need the select?

> +
> +properties:
> +  compatible:
> +    enum:
> +      - x-powers,ac200-ephy-package
> +      - x-powers,ac300-ephy-package
> +      - x-powers,acx00-ephy-package
> +
> +  reg:
> +    maximum: 31
> +
> +  clocks:
> +    maxItems: 1
> +    description:
> +      AC300 input clock, running at 24, 25, or 27 MHz. The AC200 input clock
> +      belongs to the referenced AC200 MFD instead.

Drop last sentence. Just confusing - what is a referenced MFD? There is
no such thing in the bindings as MFD and the bindings cannot depend on
Linux subsystem.

> +
> +  vcc-supply:
> +    description:
> +      3.3 V package supply. This is connected to EPHY_VCC on AC200 and VCC1
> +      on AC300.
> +
> +  nvmem-cells:
> +    maxItems: 1
> +    description:
> +      Packed EPHY configuration field. Bits 3 through 0 contain the analog
> +      calibration code, bits 7 through 4 are reserved, bit 8 selects AC300
> +      when set and AC200 when clear, and bit 9 requests the AC300
> +      low-calibration tuning sequence. A fixed AC200 package may provide only
> +      the low calibration bits or omit the cell and use its internal eFuse
> +      copy instead.
> +
> +  nvmem-cell-names:
> +    const: configuration
> +
> +  x-powers,ac200:
> +    $ref: /schemas/types.yaml#/definitions/phandle
> +    description: Reference to the AC200 MFD containing this Ethernet PHY

Confusing. "Containing" means this is part of the other one, but
phandles do not express that. Anyway, there is no such thing as MFD so
describe properly without using Linuxisms what do you need here. Then
say what for (purpose).


> +
> +  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.
> +
> +patternProperties:
> +  ^ethernet-phy@[a-f0-9]+$:
> +    $ref: ethernet-phy.yaml#
> +    properties:
> +      compatible:
> +        const: ethernet-phy-id0044.1400
> +    required:
> +      - compatible
> +    unevaluatedProperties: false
> +
> +required:
> +  - compatible
> +  - vcc-supply
> +
> +dependentRequired:
> +  nvmem-cells: [ nvmem-cell-names ]

Only this is needed

> +  nvmem-cell-names: [ nvmem-cells ]

This is not.

> +
> +allOf:
> +  - $ref: ethernet-phy-package.yaml#
> +  - if:
> +      properties:
> +        compatible:
> +          const: x-powers,ac200-ephy-package
> +    then:
> +      properties:
> +        clocks: false
> +      required:
> +        - x-powers,ac200
> +  - if:
> +      properties:
> +        compatible:
> +          const: x-powers,ac300-ephy-package
> +    then:
> +      properties:
> +        reg:
> +          maximum: 7
> +        x-powers,ac200: false
> +      required:
> +        - clocks
> +        - nvmem-cells
> +        - nvmem-cell-names
> +  - if:
> +      properties:
> +        compatible:
> +          const: x-powers,acx00-ephy-package
> +    then:
> +      properties:
> +        reg:
> +          maximum: 7
> +      required:
> +        - clocks
> +        - nvmem-cells
> +        - nvmem-cell-names
> +        - x-powers,ac200
> +
> +unevaluatedProperties: false
> +
> +examples:
> +  - |
> +    mdio {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        ethernet-phy-package@0 {
> +            compatible = "x-powers,acx00-ephy-package";
> +            reg = <0>;
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +            clocks = <&pwm 5>;
> +            vcc-supply = <&reg_3v3>;
> +            nvmem-cells = <&ephy_configuration>;
> +            nvmem-cell-names = "configuration";
> +            x-powers,ac200 = <&ac200>;
> +
> +            ethernet-phy@0 {
> +                compatible = "ethernet-phy-id0044.1400";
> +                reg = <0>;
> +            };
> +        };
> +    };

One example is enough, they are all almost the same (difference in one
property - clocks - does not warrant a new example usually).


Best regards,
Krzysztof

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

* Re: [PATCH net-next v5 1/5] dt-bindings: mfd: x-powers: Add AC200
  2026-08-11  7:07   ` Krzysztof Kozlowski
@ 2026-08-11  7:23     ` James Hilliard
  2026-08-11  7:27       ` Krzysztof Kozlowski
  2026-08-11 16:50       ` Andrew Lunn
  0 siblings, 2 replies; 11+ messages in thread
From: James Hilliard @ 2026-08-11  7:23 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Lee Jones, Arnd Bergmann, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Andrew Lunn, Heiner Kallweit,
	Russell King, mfd, devicetree, linux-kernel, netdev

On Tue, Aug 11, 2026 at 1:07 AM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>
> On 09/08/2026 22:28, James Hilliard wrote:
> > The AC200 is an I2C-controlled mixed-signal companion IC containing
> > audio, video, RTC and Fast Ethernet PHY functions.
> >
> > Describe the parent device and its shared input clock. The Ethernet PHY
> > is represented by a PHY package on its MDIO bus, with a phandle back to
> > this device for access to the package control registers, so it does not
> > require an MFD child node. Function-specific supplies belong to their
> > consumer bindings rather than the shared parent.
> >
> > The input clock must run at 24 or 27 MHz when the Ethernet PHY is used,
> > matching the rates encoded by its documented clock selector.
> >
> > Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> > ---
>
> I do not understand why MFD and net are combined. And nothing in top
> part of cover letter explains me that (which I asked already - any
> dependency or merging is THE MOST important information. Where do you
> put the most important information which maintainers should see
> immediately?)

The AC200 PHY control registers reside in the I2C-managed
multifunction device. The AC200 PHY backend therefore consumes
the regmap published by the AC200 MFD driver, while the AC300
backend accesses its control registers directly over MDIO.

The managed syscon registration change is used by the AC200 MFD
provider. The net driver itself uses the existing syscon lookup
interface, so there is no compile-time dependency from the net
patches on the new syscon API. There is, however, an unavoidable
runtime dependency for AC200 on its I2C provider. AC300 has no
such dependency.

> Bundling it together suggests this is somehow incomplete and later will
> be finished, which is wrong by itself. How to fix it - decouple
> dependencies.

Could you clarify whether you mean that the MFD and net changes
should be sent as separate submissions, or that the driver
interface should be redesigned so the net side has no dependency
on the AC200 MFD provider?

>
> Best regards,
> Krzysztof

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

* Re: [PATCH net-next v5 1/5] dt-bindings: mfd: x-powers: Add AC200
  2026-08-11  7:23     ` James Hilliard
@ 2026-08-11  7:27       ` Krzysztof Kozlowski
  2026-08-11 16:50       ` Andrew Lunn
  1 sibling, 0 replies; 11+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-11  7:27 UTC (permalink / raw)
  To: James Hilliard
  Cc: Lee Jones, Arnd Bergmann, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Andrew Lunn, Heiner Kallweit,
	Russell King, mfd, devicetree, linux-kernel, netdev

On 11/08/2026 09:23, James Hilliard wrote:
> On Tue, Aug 11, 2026 at 1:07 AM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>>
>> On 09/08/2026 22:28, James Hilliard wrote:
>>> The AC200 is an I2C-controlled mixed-signal companion IC containing
>>> audio, video, RTC and Fast Ethernet PHY functions.
>>>
>>> Describe the parent device and its shared input clock. The Ethernet PHY
>>> is represented by a PHY package on its MDIO bus, with a phandle back to
>>> this device for access to the package control registers, so it does not
>>> require an MFD child node. Function-specific supplies belong to their
>>> consumer bindings rather than the shared parent.
>>>
>>> The input clock must run at 24 or 27 MHz when the Ethernet PHY is used,
>>> matching the rates encoded by its documented clock selector.
>>>
>>> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
>>> ---
>>
>> I do not understand why MFD and net are combined. And nothing in top
>> part of cover letter explains me that (which I asked already - any
>> dependency or merging is THE MOST important information. Where do you
>> put the most important information which maintainers should see
>> immediately?)
> 
> The AC200 PHY control registers reside in the I2C-managed
> multifunction device. The AC200 PHY backend therefore consumes
> the regmap published by the AC200 MFD driver, while the AC300
> backend accesses its control registers directly over MDIO.
> 
> The managed syscon registration change is used by the AC200 MFD
> provider. The net driver itself uses the existing syscon lookup
> interface, so there is no compile-time dependency from the net
> patches on the new syscon API. There is, however, an unavoidable
> runtime dependency for AC200 on its I2C provider. AC300 has no
> such dependency.

Runtime dependency for new drivers is irrelevant. Patchset should go via
different subsystems thus should be split to make maintainers life easier.

The only reason to combine it - which is nowhere explained - is to agree
on common design of expressing child relationship with sibling phandle
style.

And again the problem is that cover letter explains nothing. I went
through first three paragraphs.

> 
>> Bundling it together suggests this is somehow incomplete and later will
>> be finished, which is wrong by itself. How to fix it - decouple
>> dependencies.
> 
> Could you clarify whether you mean that the MFD and net changes
> should be sent as separate submissions, or that the driver
> interface should be redesigned so the net side has no dependency
> on the AC200 MFD provider?

You organize independent work targeting independent subsystems into
independent patchsets. Simply act as maintainer. What maintainers are
supposed to do here? How much time they need to parse this patchset and
apply?

Best regards,
Krzysztof

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

* Re: [PATCH net-next v5 1/5] dt-bindings: mfd: x-powers: Add AC200
  2026-08-11  7:23     ` James Hilliard
  2026-08-11  7:27       ` Krzysztof Kozlowski
@ 2026-08-11 16:50       ` Andrew Lunn
  1 sibling, 0 replies; 11+ messages in thread
From: Andrew Lunn @ 2026-08-11 16:50 UTC (permalink / raw)
  To: James Hilliard
  Cc: Krzysztof Kozlowski, Lee Jones, Arnd Bergmann, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Heiner Kallweit,
	Russell King, mfd, devicetree, linux-kernel, netdev

> > Bundling it together suggests this is somehow incomplete and later will
> > be finished, which is wrong by itself. How to fix it - decouple
> > dependencies.
> 
> Could you clarify whether you mean that the MFD and net changes
> should be sent as separate submissions, or that the driver
> interface should be redesigned so the net side has no dependency
> on the AC200 MFD provider?

There are two parts to this.

DT describes hardware. The hardware is fixed, so you should be able to
describe it. Ideally, the DT binding should not have an Linux
concepts, or driver concepts, since that is not hardware.

The second part is, the MFD driver should be merged via the MFD
tree. The networking code should be merged via the net-next tree. Each
patchset needs to build independently. It does not matter if they
don't run, probe fails, if run on their own. The different patchsets
will eventually meet up in linux-next, and get merged together in
-rc1, at which point they should function.

If you have any build time dependencies, you clearly need to state
them in 0/X patch of each patchset, and explain why the build time
dependency exists. Sometimes they are unavoidable. If that is true,
the different subsystem Maintainers need to work together. Sometimes
one subsystem Maintainer gives Acked-by:, and allows another subsystem
Maintainer to merge the patches. Sometimes one subsystem Maintainer
will produce a stable branch, with the other subsystem Maintainer
merges into their subsystem, resulting in the needed build time
dependencies being fulfilled. This requires Maintainers to work
together, so you need to make it clear in the commit message this is
needed. But ideally, you avoid this, subsystems are mostly
independent.

      Andrew

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

end of thread, other threads:[~2026-08-11 16:50 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-09 20:28 [PATCH net-next v5 0/5] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
2026-08-09 20:28 ` [PATCH net-next v5 1/5] dt-bindings: mfd: x-powers: Add AC200 James Hilliard
2026-08-11  7:07   ` Krzysztof Kozlowski
2026-08-11  7:23     ` James Hilliard
2026-08-11  7:27       ` Krzysztof Kozlowski
2026-08-11 16:50       ` Andrew Lunn
2026-08-09 20:28 ` [PATCH net-next v5 2/5] mfd: syscon: Add managed registration for external regmaps James Hilliard
2026-08-09 20:28 ` [PATCH net-next v5 3/5] mfd: ac200: Add X-Powers AC200 support James Hilliard
2026-08-09 20:28 ` [PATCH net-next v5 4/5] dt-bindings: net: x-powers: add AC200/AC300 EPHY packages James Hilliard
2026-08-11  7:15   ` Krzysztof Kozlowski
2026-08-09 20:28 ` [PATCH net-next v5 5/5] net: phy: add X-Powers AC200/AC300 EPHY driver James Hilliard

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