* [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support
@ 2026-08-03 5:14 James Hilliard
2026-08-03 5:14 ` [PATCH 01/21] dt-bindings: net: allwinner: add H616 EMAC1 James Hilliard
` (24 more replies)
0 siblings, 25 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
The Allwinner H616/H618 secondary EMAC is commonly connected to a Fast
Ethernet PHY in an X-Powers AC200 or AC300 companion package. The two
packages expose the same link PHY ID and largely compatible link-side
registers, but require different control paths before those registers can
be accessed: AC200 is initialized through its I2C MFD, while AC300 uses a
non-PHY Clause 22 control endpoint.
Some H616-family products were shipped with either package under the same
board identity. For those systems, this series uses a SID NVMEM field to
select the control provider at run time. With CONFIG_OF_DYNAMIC, the link
PHY enables only the selected fail-needs-probe provider, so an AC300 board
does not probe the unused AC200 I2C controller or claim its pins. The same
drivers also support fixed descriptions without CONFIG_OF_DYNAMIC: a board
can reference one enabled provider, or enable both candidates when runtime
selection is still required.
The series adds:
- the H616 EMAC1 binding, stmmac variant and controller node;
- generic MDIO OF reconfiguration support for PHYs, packages, scanned
addresses and generic MDIO devices;
- the AC200 MFD and AC200/AC300 EPHY control providers;
- the shared AC200/AC300 link-PHY driver, including calibration, power and
reset sequencing, MDI/MDI-X, statistics, EDPD, PHY-managed EEE, package
LEDs, interrupts and Wake-on-LAN; and
- H6 and H616-family device-tree descriptions and arm64 defconfig support.
The AC200 and AC300 control and link paths were exercised on H616-family
hardware during development, including link traffic, address handling,
ethtool EDPD/EEE controls, statistics and LEDs. The H6 interrupt and
Wake-on-LAN description still needs validation by an H6 hardware user.
This series depends on Richard Genoud's v7 H616 PWM controller series,
which provides the bypass clock used by the companion EPHY:
https://lore.kernel.org/all/20260703152215.192859-1-richard.genoud@bootlin.com/
The PWM patches are an external prerequisite and are intentionally not
included in this 21-patch series.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
James Hilliard (18):
net: mdio: factor out OF child registration helpers
net: mdio: make device map changes hotplug-safe
net: mdio: support dynamic OF device changes
dt-bindings: net: x-powers: add AC200 EPHY control
dt-bindings: mfd: x-powers: add AC200
mfd: add X-Powers AC200 support
net: phy: add X-Powers AC200 EPHY control driver
dt-bindings: net: x-powers: add AC300 EPHY control
net: phy: add X-Powers AC300 EPHY control driver
dt-bindings: net: x-powers: add AC200/AC300 EPHY
net: phy: support configuring PHY-autonomous Tx LPI
net: phylink: allow PHY-autonomous EEE without MAC LPI
net: phy: add X-Powers AC200/AC300 EPHY driver
arm64: dts: allwinner: h616: add ACx00 EPHY resources
arm64: dts: allwinner: orangepi-zero2w: enable Ethernet expansion
arm64: dts: allwinner: h6: add AC200 EPHY resources
arm64: dts: allwinner: h6: tanix: enable AC200 EPHY
arm64: defconfig: enable X-Powers ACx00 Ethernet support
Richard Genoud (3):
dt-bindings: net: allwinner: add H616 EMAC1
net: stmmac: sun8i: add support for Allwinner H616 EMAC1
arm64: dts: allwinner: h616: add EMAC1 controller
.../devicetree/bindings/mfd/x-powers,ac200.yaml | 118 ++
.../bindings/net/allwinner,sun8i-a83t-emac.yaml | 18 +-
.../devicetree/bindings/net/snps,dwmac.yaml | 2 +
.../bindings/net/x-powers,ac200-ephy-ctl.yaml | 46 +
.../bindings/net/x-powers,ac300-ephy-ctl.yaml | 77 ++
.../bindings/net/x-powers,acx00-ephy.yaml | 217 +++
arch/arm64/boot/dts/allwinner/sun50i-h6-tanix.dtsi | 45 +
arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 73 +
.../boot/dts/allwinner/sun50i-h616-acx00-ephy.dtsi | 61 +
arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi | 51 +
.../dts/allwinner/sun50i-h618-orangepi-zero2w.dts | 13 +
arch/arm64/configs/defconfig | 6 +
drivers/mfd/Kconfig | 13 +
drivers/mfd/Makefile | 1 +
drivers/mfd/ac200.c | 302 ++++
drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c | 21 +
drivers/net/mdio/of_mdio.c | 508 ++++++-
drivers/net/phy/Kconfig | 30 +
drivers/net/phy/Makefile | 3 +
drivers/net/phy/mdio_bus.c | 14 +-
drivers/net/phy/mdio_bus_provider.c | 140 +-
drivers/net/phy/mdio_device.c | 235 +++-
drivers/net/phy/phy.c | 35 +-
drivers/net/phy/phy_device.c | 127 +-
drivers/net/phy/phylib-internal.h | 4 +-
drivers/net/phy/phylink.c | 35 +-
drivers/net/phy/xpowers-ac200-ctl.c | 330 +++++
drivers/net/phy/xpowers-ac300-ctl.c | 454 ++++++
drivers/net/phy/xpowers-acx00.c | 1457 ++++++++++++++++++++
drivers/net/phy/xpowers-acx00.h | 27 +
include/dt-bindings/mfd/x-powers,ac200.h | 13 +
include/linux/mdio.h | 6 +
include/linux/phy.h | 36 +
33 files changed, 4384 insertions(+), 134 deletions(-)
---
base-commit: 075b74841bd0065a3bda3440873c747938e69b68
change-id: 20260802-submit-acx00-of-dynamic-v1-94a0dc15f282
prerequisite-message-id: <20260703152215.192859-1-richard.genoud@bootlin.com>
prerequisite-patch-id: 8c447ebd677ea7bf9e63a77d34d8fdaa05eebe7f
prerequisite-patch-id: c7574f9c0e2571ce8c6129ac6db7aa959c986ef3
prerequisite-patch-id: e8b00768102951244927ecf054314f6e02f9ff1b
prerequisite-patch-id: a3e2055ed7f3b5751a3e02d57e674b25ee404ff7
Best regards,
--
James Hilliard <james.hilliard1@gmail.com>
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH 01/21] dt-bindings: net: allwinner: add H616 EMAC1
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 13:03 ` Andrew Lunn
2026-08-03 5:14 ` [PATCH 02/21] net: stmmac: sun8i: add support for Allwinner " James Hilliard
` (23 subsequent siblings)
24 siblings, 1 reply; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
From: Richard Genoud <richard.genoud@bootlin.com>
The H616 secondary EMAC uses a separate system-control clock register
and supports only RMII at 10/100 Mbps. Add its distinct compatible
without an older fallback, since using the primary EMAC clock register
would not work.
Constrain the PHY interface mode to RMII, matching the hardware. The
EMAC1 clock register also retains the three-bit TX and five-bit RX delay
chains, so allow the existing Allwinner delay properties for this
compatible.
Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
.../bindings/net/allwinner,sun8i-a83t-emac.yaml | 18 ++++++++++++++++--
Documentation/devicetree/bindings/net/snps,dwmac.yaml | 2 ++
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/net/allwinner,sun8i-a83t-emac.yaml b/Documentation/devicetree/bindings/net/allwinner,sun8i-a83t-emac.yaml
index 323a669fa982..34def84e6c21 100644
--- a/Documentation/devicetree/bindings/net/allwinner,sun8i-a83t-emac.yaml
+++ b/Documentation/devicetree/bindings/net/allwinner,sun8i-a83t-emac.yaml
@@ -21,6 +21,7 @@ select:
- allwinner,sun8i-r40-gmac
- allwinner,sun8i-v3s-emac
- allwinner,sun50i-a64-emac
+ - allwinner,sun50i-h616-emac1
- allwinner,sun55i-a523-gmac200
required:
- compatible
@@ -33,6 +34,7 @@ properties:
- const: allwinner,sun8i-r40-gmac
- const: allwinner,sun8i-v3s-emac
- const: allwinner,sun50i-a64-emac
+ - const: allwinner,sun50i-h616-emac1
- items:
- enum:
- allwinner,sun20i-d1-emac
@@ -91,6 +93,17 @@ required:
allOf:
- $ref: snps,dwmac.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: allwinner,sun50i-h616-emac1
+
+ then:
+ properties:
+ phy-mode:
+ const: rmii
+
- if:
properties:
compatible:
@@ -100,6 +113,7 @@ allOf:
- allwinner,sun8i-h3-emac
- allwinner,sun8i-v3s-emac
- allwinner,sun50i-a64-emac
+ - allwinner,sun50i-h616-emac1
then:
properties:
@@ -109,7 +123,7 @@ allOf:
maximum: 700
multipleOf: 100
description:
- External RGMII PHY TX clock delay chain value in ps.
+ PHY interface TX clock delay chain value in ps.
allwinner,rx-delay-ps:
default: 0
@@ -117,7 +131,7 @@ allOf:
maximum: 3100
multipleOf: 100
description:
- External RGMII PHY TX clock delay chain value in ps.
+ PHY interface RX clock delay chain value in ps.
- if:
properties:
diff --git a/Documentation/devicetree/bindings/net/snps,dwmac.yaml b/Documentation/devicetree/bindings/net/snps,dwmac.yaml
index 2449311c6d28..102ec98ac2a0 100644
--- a/Documentation/devicetree/bindings/net/snps,dwmac.yaml
+++ b/Documentation/devicetree/bindings/net/snps,dwmac.yaml
@@ -57,6 +57,7 @@ properties:
- allwinner,sun8i-r40-gmac
- allwinner,sun8i-v3s-emac
- allwinner,sun50i-a64-emac
+ - allwinner,sun50i-h616-emac1
- amlogic,meson6-dwmac
- amlogic,meson8b-dwmac
- amlogic,meson8m2-dwmac
@@ -622,6 +623,7 @@ allOf:
- allwinner,sun8i-r40-gmac
- allwinner,sun8i-v3s-emac
- allwinner,sun50i-a64-emac
+ - allwinner,sun50i-h616-emac1
- loongson,ls2k-dwmac
- loongson,ls7a-dwmac
- ingenic,jz4775-mac
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 02/21] net: stmmac: sun8i: add support for Allwinner H616 EMAC1
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
2026-08-03 5:14 ` [PATCH 01/21] dt-bindings: net: allwinner: add H616 EMAC1 James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 03/21] arm64: dts: allwinner: h616: add EMAC1 controller James Hilliard
` (22 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
From: Richard Genoud <richard.genoud@bootlin.com>
The H616 secondary EMAC uses a separate system-control clock register
and supports only RMII at 10/100 Mbps. Its EPHY is provided by a
companion AC200 or AC300 package rather than an internal PHY block.
Add an EMAC1 variant using the dedicated register and mark the PHY as
external. The register retains the standard five-bit RX and three-bit TX
clock delay chains, so expose their supported ranges to the shared
syscon setup.
Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
index 48c52eb96233..fdc0c3384af8 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
@@ -81,6 +81,13 @@ static const struct reg_field sun8i_syscon_reg_field = {
.msb = 31,
};
+/* EMAC1 clock register @ 0x34 in the "system control" address range */
+static const struct reg_field sun8i_syscon_reg_field_emac1 = {
+ .reg = 0x34,
+ .lsb = 0,
+ .msb = 31,
+};
+
/* EMAC clock register @ 0x164 in the CCU address range */
static const struct reg_field sun8i_ccu_reg_field = {
.reg = 0x164,
@@ -143,6 +150,18 @@ static const struct emac_variant emac_variant_h6 = {
.tx_delay_max = 7,
};
+static const struct emac_variant emac_variant_h616_emac1 = {
+ .syscon_field = &sun8i_syscon_reg_field_emac1,
+ /*
+ * The "internal PHY" is in a companion AC200 or AC300 package,
+ * rather than on the H616 die.
+ */
+ .soc_has_internal_phy = false,
+ .support_rmii = true,
+ .rx_delay_max = 31,
+ .tx_delay_max = 7,
+};
+
#define EMAC_BASIC_CTL0 0x00
#define EMAC_BASIC_CTL1 0x04
#define EMAC_INT_STA 0x08
@@ -1278,6 +1297,8 @@ static const struct of_device_id sun8i_dwmac_match[] = {
.data = &emac_variant_a64 },
{ .compatible = "allwinner,sun50i-h6-emac",
.data = &emac_variant_h6 },
+ { .compatible = "allwinner,sun50i-h616-emac1",
+ .data = &emac_variant_h616_emac1 },
{ }
};
MODULE_DEVICE_TABLE(of, sun8i_dwmac_match);
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 03/21] arm64: dts: allwinner: h616: add EMAC1 controller
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
2026-08-03 5:14 ` [PATCH 01/21] dt-bindings: net: allwinner: add H616 EMAC1 James Hilliard
2026-08-03 5:14 ` [PATCH 02/21] net: stmmac: sun8i: add support for Allwinner " James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 7:01 ` Krzysztof Kozlowski
2026-08-03 5:14 ` [PATCH 04/21] net: mdio: factor out OF child registration helpers James Hilliard
` (21 subsequent siblings)
24 siblings, 1 reply; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
From: Richard Genoud <richard.genoud@bootlin.com>
The H616 has a secondary EMAC controller that supports only RMII at
10/100 Mbps. It connects through PA0-PA9 to an EPHY in a companion
AC200 or AC300 package.
Add the EMAC1 controller and RMII pin group. Leave the controller
disabled for boards to enable when the companion Ethernet hardware
is present.
Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi | 30 ++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
index 90fe56a1d3cd..6879f27c2b2b 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
@@ -432,6 +432,14 @@ pwm5_pin: pwm5-pin {
function = "pwm5";
};
+ /omit-if-no-ref/
+ rmii_emac1_pins: rmii-emac1-pins {
+ pins = "PA0", "PA1", "PA2", "PA3", "PA4",
+ "PA5", "PA6", "PA7", "PA8", "PA9";
+ function = "emac1";
+ drive-strength = <40>;
+ };
+
/omit-if-no-ref/
spi0_pins: spi0-pins {
pins = "PC0", "PC2", "PC4";
@@ -789,6 +797,28 @@ mdio0: mdio {
};
};
+ emac1: ethernet@5030000 {
+ compatible = "allwinner,sun50i-h616-emac1";
+ reg = <0x05030000 0x10000>;
+ interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "macirq";
+ clocks = <&ccu CLK_BUS_EMAC1>;
+ clock-names = "stmmaceth";
+ resets = <&ccu RST_BUS_EMAC1>;
+ reset-names = "stmmaceth";
+ syscon = <&syscon>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&rmii_emac1_pins>;
+ phy-mode = "rmii";
+ status = "disabled";
+
+ mdio1: mdio {
+ compatible = "snps,dwmac-mdio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ };
+
gpadc: adc@5070000 {
compatible = "allwinner,sun50i-h616-gpadc",
"allwinner,sun20i-d1-gpadc";
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 04/21] net: mdio: factor out OF child registration helpers
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (2 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 03/21] arm64: dts: allwinner: h616: add EMAC1 controller James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 05/21] net: mdio: make device map changes hotplug-safe James Hilliard
` (20 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
Live OF reconfiguration needs to create the same PHY and generic MDIO
devices as initial bus population, including legacy PHY nodes that need
address scanning.
Factor fixed-address child registration and PHY address scanning into
helpers. The old scan loop also visited every free address for a child
without a reg property even when the child was not a PHY. Reject that case
before scanning while preserving enumeration of valid PHY and generic MDIO
children.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/mdio/of_mdio.c | 66 ++++++++++++++++++++++++++++------------------
1 file changed, 40 insertions(+), 26 deletions(-)
diff --git a/drivers/net/mdio/of_mdio.c b/drivers/net/mdio/of_mdio.c
index b8d298c04d3f..051e449bbe7c 100644
--- a/drivers/net/mdio/of_mdio.c
+++ b/drivers/net/mdio/of_mdio.c
@@ -77,6 +77,15 @@ static int of_mdiobus_register_device(struct mii_bus *mdio,
return 0;
}
+static int of_mdiobus_register_child(struct mii_bus *mdio,
+ struct device_node *child, u32 addr)
+{
+ if (of_mdiobus_child_is_phy(child))
+ return of_mdiobus_register_phy(mdio, child, addr);
+
+ return of_mdiobus_register_device(mdio, child, addr);
+}
+
/* The following is a list of PHY compatible strings which appear in
* some DTBs. The compatible string is never matched against a PHY
* driver, so is pointless. We only expect devices which are not PHYs
@@ -136,6 +145,32 @@ bool of_mdiobus_child_is_phy(struct device_node *child)
}
EXPORT_SYMBOL(of_mdiobus_child_is_phy);
+static int of_mdiobus_scan_phy(struct mii_bus *mdio,
+ struct device_node *child)
+{
+ int addr, rc;
+
+ if (!of_mdiobus_child_is_phy(child))
+ return -ENODEV;
+
+ for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
+ if (mdiobus_is_registered_device(mdio, addr))
+ continue;
+
+ dev_info(&mdio->dev, "scan phy %pOFn at address %i\n",
+ child, addr);
+
+ /* -ENODEV means that scanning should continue. */
+ rc = of_mdiobus_register_child(mdio, child, addr);
+ if (!rc)
+ return 0;
+ if (rc != -ENODEV)
+ return rc;
+ }
+
+ return -ENODEV;
+}
+
static int __of_mdiobus_parse_phys(struct mii_bus *mdio, struct device_node *np,
bool *scanphys)
{
@@ -164,10 +199,7 @@ static int __of_mdiobus_parse_phys(struct mii_bus *mdio, struct device_node *np,
continue;
}
- if (of_mdiobus_child_is_phy(child))
- rc = of_mdiobus_register_phy(mdio, child, addr);
- else
- rc = of_mdiobus_register_device(mdio, child, addr);
+ rc = of_mdiobus_register_child(mdio, child, addr);
if (rc == -ENODEV)
dev_err(&mdio->dev,
@@ -197,7 +229,7 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,
{
struct device_node *child;
bool scanphys = false;
- int addr, rc;
+ int rc;
if (!np)
return __mdiobus_register(mdio, owner);
@@ -238,27 +270,9 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,
of_node_name_eq(child, "ethernet-phy-package"))
continue;
- for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
- /* skip already registered PHYs */
- if (mdiobus_is_registered_device(mdio, addr))
- continue;
-
- /* be noisy to encourage people to set reg property */
- dev_info(&mdio->dev, "scan phy %pOFn at address %i\n",
- child, addr);
-
- if (of_mdiobus_child_is_phy(child)) {
- /* -ENODEV is the return code that PHYLIB has
- * standardized on to indicate that bus
- * scanning should continue.
- */
- rc = of_mdiobus_register_phy(mdio, child, addr);
- if (!rc)
- break;
- if (rc != -ENODEV)
- goto put_unregister;
- }
- }
+ rc = of_mdiobus_scan_phy(mdio, child);
+ if (rc && rc != -ENODEV)
+ goto put_unregister;
}
return 0;
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 05/21] net: mdio: make device map changes hotplug-safe
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (3 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 04/21] net: mdio: factor out OF child registration helpers James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 06/21] net: mdio: support dynamic OF device changes James Hilliard
` (19 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
MDIO device registration currently publishes directly into mdio_map
without serializing address ownership, and removal frees the object
immediately. That is sufficient while bus population is static, but
dynamic firmware changes can race registration, PHY attachment and bus
teardown. It also cannot safely preserve mdiobus_get_phy()'s
borrowed-pointer convention.
Serialize device-map state and reserve addresses while registration is
in progress. Track active scans, registrations and firmware changes so
bus teardown first blocks new work and waits for existing work before
consuming the final map. Block new registrations and PHY attachment while
a firmware removal owns the map.
Publish initialized devices with release ordering and make PHY attachment
atomic with map removal, including standalone PHY users. Have address
scans atomically claim a matching OF node as well, so explicit OF
population cannot instantiate the same device concurrently.
Keep dynamically removed devices pinned until the MDIO bus is torn down
so existing borrowed pointers cannot become use-after-free references.
Drop their firmware-node reference after device_del() so this does not
keep an overlay node alive. Normal MDIO removal and driver APIs remain
unchanged, and consumers do not need to adopt a new refcounting API.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/phy/mdio_bus.c | 14 ++-
drivers/net/phy/mdio_bus_provider.c | 140 +++++++++++++++------
drivers/net/phy/mdio_device.c | 235 ++++++++++++++++++++++++++++++++----
drivers/net/phy/phy_device.c | 102 ++++++++++++----
drivers/net/phy/phylib-internal.h | 4 +-
include/linux/mdio.h | 6 +
include/linux/phy.h | 18 +++
7 files changed, 434 insertions(+), 85 deletions(-)
diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
index 00d0e4159e9b..f45c936fe5aa 100644
--- a/drivers/net/phy/mdio_bus.c
+++ b/drivers/net/phy/mdio_bus.c
@@ -34,7 +34,8 @@ static struct mdio_device *mdiobus_find_device(struct mii_bus *bus, int addr)
if (WARN_ONCE(!addr_valid, "addr %d out of range\n", addr))
return NULL;
- return bus->mdio_map[addr];
+ /* Pair with map publication in mdiobus_register_device(). */
+ return smp_load_acquire(&bus->mdio_map[addr]);
}
struct phy_device *mdiobus_get_phy(struct mii_bus *bus, int addr)
@@ -54,7 +55,16 @@ EXPORT_SYMBOL(mdiobus_get_phy);
bool mdiobus_is_registered_device(struct mii_bus *bus, int addr)
{
- return mdiobus_find_device(bus, addr) != NULL;
+ bool addr_valid = addr >= 0 && addr < ARRAY_SIZE(bus->mdio_map);
+ bool registered;
+
+ if (WARN_ONCE(!addr_valid, "addr %d out of range\n", addr))
+ return false;
+
+ registered = READ_ONCE(bus->mdio_map[addr]) ||
+ (READ_ONCE(bus->mdio_map_pending) & BIT(addr));
+
+ return registered;
}
EXPORT_SYMBOL(mdiobus_is_registered_device);
diff --git a/drivers/net/phy/mdio_bus_provider.c b/drivers/net/phy/mdio_bus_provider.c
index ce3a607a40cb..b83a0a7a4235 100644
--- a/drivers/net/phy/mdio_bus_provider.c
+++ b/drivers/net/phy/mdio_bus_provider.c
@@ -330,6 +330,9 @@ struct mii_bus *mdiobus_alloc_size(size_t size)
return NULL;
bus->state = MDIOBUS_ALLOCATED;
+ mutex_init(&bus->mdio_map_lock);
+ init_waitqueue_head(&bus->mdio_map_wait);
+ INIT_LIST_HEAD(&bus->mdio_map_retired);
if (size)
bus->priv = (void *)bus + aligned_size;
@@ -360,18 +363,21 @@ static int of_mdiobus_find_phy(struct device *dev, struct mdio_device *mdiodev,
int addr;
if (of_node_name_eq(child, "ethernet-phy-package")) {
+ int ret;
+
/* Validate PHY package reg presence */
if (!of_property_present(child, "reg")) {
of_node_put(child);
return -EINVAL;
}
- if (!of_mdiobus_find_phy(dev, mdiodev, child)) {
+ ret = of_mdiobus_find_phy(dev, mdiodev, child);
+ if (ret != -ENODEV) {
/* The refcount for the PHY package will be
* incremented later when PHY join the Package.
*/
of_node_put(child);
- return 0;
+ return ret;
}
continue;
@@ -382,6 +388,11 @@ static int of_mdiobus_find_phy(struct device *dev, struct mdio_device *mdiodev,
continue;
if (addr == mdiodev->addr) {
+ if (of_node_test_and_set_flag(child, OF_POPULATED)) {
+ of_node_put(child);
+ return -EBUSY;
+ }
+
device_set_node(dev, of_fwnode_handle(child));
/* The refcount on "child" is passed to the mdio
* device. Do _not_ use of_node_put(child) here.
@@ -393,22 +404,26 @@ static int of_mdiobus_find_phy(struct device *dev, struct mdio_device *mdiodev,
return -ENODEV;
}
-static void of_mdiobus_link_mdiodev(struct mii_bus *bus,
- struct mdio_device *mdiodev)
+static int of_mdiobus_link_mdiodev(struct mii_bus *bus,
+ struct mdio_device *mdiodev)
{
struct device *dev = &mdiodev->dev;
if (dev->of_node || !bus->dev.of_node)
- return;
+ return 0;
- of_mdiobus_find_phy(dev, mdiodev, bus->dev.of_node);
+ return of_mdiobus_find_phy(dev, mdiodev, bus->dev.of_node);
}
#endif
-static struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr, bool c45)
+static struct phy_device *__mdiobus_scan(struct mii_bus *bus, int addr,
+ bool c45)
{
struct phy_device *phydev = ERR_PTR(-ENODEV);
struct fwnode_handle *fwnode;
+#if IS_ENABLED(CONFIG_OF_MDIO)
+ bool of_node_populated = false;
+#endif
char node_name[16];
int err;
@@ -420,7 +435,12 @@ static struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr, bool c45)
/* For DT, see if the auto-probed phy has a corresponding child
* in the bus node, and set the of_node pointer in this case.
*/
- of_mdiobus_link_mdiodev(bus, &phydev->mdio);
+ err = of_mdiobus_link_mdiodev(bus, &phydev->mdio);
+ if (err == -EBUSY) {
+ phy_device_free(phydev);
+ return ERR_PTR(-ENODEV);
+ }
+ of_node_populated = !!phydev->mdio.dev.of_node;
#endif
/* Search for a swnode for the phy in the swnode hierarchy of the bus.
@@ -437,6 +457,11 @@ static struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr, bool c45)
err = phy_device_register(phydev);
if (err) {
+#if IS_ENABLED(CONFIG_OF_MDIO)
+ if (of_node_populated)
+ of_node_clear_flag(phydev->mdio.dev.of_node,
+ OF_POPULATED);
+#endif
phy_device_free(phydev);
return ERR_PTR(-ENODEV);
}
@@ -458,7 +483,17 @@ static struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr, bool c45)
*/
struct phy_device *mdiobus_scan_c22(struct mii_bus *bus, int addr)
{
- return mdiobus_scan(bus, addr, false);
+ struct phy_device *phydev;
+ int err;
+
+ err = mdiobus_device_change_begin(bus, false);
+ if (err)
+ return ERR_PTR(err);
+
+ phydev = __mdiobus_scan(bus, addr, false);
+ mdiobus_device_change_end(bus, false);
+
+ return phydev;
}
EXPORT_SYMBOL(mdiobus_scan_c22);
@@ -476,7 +511,7 @@ EXPORT_SYMBOL(mdiobus_scan_c22);
*/
static struct phy_device *mdiobus_scan_c45(struct mii_bus *bus, int addr)
{
- return mdiobus_scan(bus, addr, true);
+ return __mdiobus_scan(bus, addr, true);
}
static int mdiobus_scan_bus_c22(struct mii_bus *bus)
@@ -487,7 +522,7 @@ static int mdiobus_scan_bus_c22(struct mii_bus *bus)
if ((bus->phy_mask & BIT(i)) == 0) {
struct phy_device *phydev;
- phydev = mdiobus_scan_c22(bus, i);
+ phydev = __mdiobus_scan(bus, i, false);
if (IS_ERR(phydev) && (PTR_ERR(phydev) != -ENODEV))
return PTR_ERR(phydev);
}
@@ -504,7 +539,7 @@ static int mdiobus_scan_bus_c45(struct mii_bus *bus)
struct phy_device *phydev;
/* Don't scan C45 if we already have a C22 device */
- if (bus->mdio_map[i])
+ if (mdiobus_is_registered_device(bus, i))
continue;
phydev = mdiobus_scan_c45(bus, i);
@@ -536,6 +571,44 @@ static bool mdiobus_prevent_c45_scan(struct mii_bus *bus)
return false;
}
+static void mdiobus_stop_device_changes(struct mii_bus *bus)
+{
+ mutex_lock(&bus->mdio_map_lock);
+ bus->state = MDIOBUS_UNREGISTERING;
+ mutex_unlock(&bus->mdio_map_lock);
+
+ wait_event(bus->mdio_map_wait, !READ_ONCE(bus->mdio_map_ops));
+}
+
+static void mdiobus_remove_devices(struct mii_bus *bus)
+{
+ LIST_HEAD(removed);
+ struct mdio_device *mdiodev, *next;
+ int i;
+
+ for (i = 0; i < PHY_MAX_ADDR; i++) {
+ mdiodev = bus->mdio_map[i];
+ if (!mdiodev)
+ continue;
+
+ mdiodev->device_remove(mdiodev);
+ mdiodev->device_free(mdiodev);
+ }
+
+ mutex_lock(&bus->mdio_map_lock);
+ list_splice_init(&bus->mdio_map_retired, &removed);
+ mutex_unlock(&bus->mdio_map_lock);
+
+ list_for_each_entry_safe(mdiodev, next, &removed, retired_node) {
+ list_del_init(&mdiodev->retired_node);
+ mdio_device_put(mdiodev);
+ }
+
+ mutex_lock(&bus->mdio_map_lock);
+ bus->state = MDIOBUS_UNREGISTERED;
+ mutex_unlock(&bus->mdio_map_lock);
+}
+
/**
* __mdiobus_register - bring up all the PHYs on a given bus and attach them to bus
* @bus: target mii_bus
@@ -552,10 +625,9 @@ static bool mdiobus_prevent_c45_scan(struct mii_bus *bus)
*/
int __mdiobus_register(struct mii_bus *bus, struct module *owner)
{
- struct mdio_device *mdiodev;
struct gpio_desc *gpiod;
bool prevent_c45_scan;
- int i, err;
+ int err;
if (!bus || !bus->name)
return -EINVAL;
@@ -596,7 +668,9 @@ int __mdiobus_register(struct mii_bus *bus, struct module *owner)
*
* State will be updated later in this function in case of success
*/
+ mutex_lock(&bus->mdio_map_lock);
bus->state = MDIOBUS_UNREGISTERED;
+ mutex_unlock(&bus->mdio_map_lock);
err = device_register(&bus->dev);
if (err) {
@@ -613,8 +687,7 @@ int __mdiobus_register(struct mii_bus *bus, struct module *owner)
err = dev_err_probe(&bus->dev, PTR_ERR(gpiod),
"mii_bus %s couldn't get reset GPIO\n",
bus->id);
- device_del(&bus->dev);
- return err;
+ goto error_reset_gpiod;
} else if (gpiod) {
bus->reset_gpiod = gpiod;
fsleep(bus->reset_delay_us);
@@ -629,6 +702,10 @@ int __mdiobus_register(struct mii_bus *bus, struct module *owner)
goto error_reset_gpiod;
}
+ mutex_lock(&bus->mdio_map_lock);
+ bus->state = MDIOBUS_REGISTERING;
+ mutex_unlock(&bus->mdio_map_lock);
+
if (bus->read) {
err = mdiobus_scan_bus_c22(bus);
if (err)
@@ -643,20 +720,17 @@ int __mdiobus_register(struct mii_bus *bus, struct module *owner)
goto error;
}
+ mutex_lock(&bus->mdio_map_lock);
bus->state = MDIOBUS_REGISTERED;
+ mutex_unlock(&bus->mdio_map_lock);
dev_dbg(&bus->dev, "probed\n");
return 0;
error:
- for (i = 0; i < PHY_MAX_ADDR; i++) {
- mdiodev = bus->mdio_map[i];
- if (!mdiodev)
- continue;
-
- mdiodev->device_remove(mdiodev);
- mdiodev->device_free(mdiodev);
- }
error_reset_gpiod:
+ mdiobus_stop_device_changes(bus);
+ mdiobus_remove_devices(bus);
+
/* Put PHYs in RESET to save power */
if (bus->reset_gpiod)
gpiod_set_value_cansleep(bus->reset_gpiod, 1);
@@ -668,21 +742,11 @@ EXPORT_SYMBOL(__mdiobus_register);
void mdiobus_unregister(struct mii_bus *bus)
{
- struct mdio_device *mdiodev;
- int i;
-
if (WARN_ON_ONCE(bus->state != MDIOBUS_REGISTERED))
return;
- bus->state = MDIOBUS_UNREGISTERED;
-
- for (i = 0; i < PHY_MAX_ADDR; i++) {
- mdiodev = bus->mdio_map[i];
- if (!mdiodev)
- continue;
- mdiodev->device_remove(mdiodev);
- mdiodev->device_free(mdiodev);
- }
+ mdiobus_stop_device_changes(bus);
+ mdiobus_remove_devices(bus);
/* Put PHYs in RESET to save power */
if (bus->reset_gpiod)
@@ -702,8 +766,11 @@ EXPORT_SYMBOL(mdiobus_unregister);
*/
void mdiobus_free(struct mii_bus *bus)
{
+ mutex_lock(&bus->mdio_map_lock);
+
/* For compatibility with error handling in drivers. */
if (bus->state == MDIOBUS_ALLOCATED) {
+ mutex_unlock(&bus->mdio_map_lock);
kfree(bus);
return;
}
@@ -711,6 +778,7 @@ void mdiobus_free(struct mii_bus *bus)
WARN(bus->state != MDIOBUS_UNREGISTERED,
"%s: not in UNREGISTERED state\n", bus->id);
bus->state = MDIOBUS_RELEASED;
+ mutex_unlock(&bus->mdio_map_lock);
put_device(&bus->dev);
}
diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c
index 56080d3d2d25..49092045831f 100644
--- a/drivers/net/phy/mdio_device.c
+++ b/drivers/net/phy/mdio_device.c
@@ -16,6 +16,7 @@
#include <linux/mdio.h>
#include <linux/mii.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/phy.h>
#include <linux/reset.h>
#include <linux/slab.h>
@@ -38,8 +39,12 @@ static int mdio_device_register_reset(struct mdio_device *mdiodev)
/* Deassert the optional reset signal */
mdiodev->reset_gpio = gpiod_get_optional(&mdiodev->dev,
"reset", GPIOD_OUT_LOW);
- if (IS_ERR(mdiodev->reset_gpio))
- return PTR_ERR(mdiodev->reset_gpio);
+ if (IS_ERR(mdiodev->reset_gpio)) {
+ int err = PTR_ERR(mdiodev->reset_gpio);
+
+ mdiodev->reset_gpio = NULL;
+ return err;
+ }
if (mdiodev->reset_gpio)
gpiod_set_consumer_name(mdiodev->reset_gpio, "PHY reset");
@@ -117,6 +122,13 @@ static void mdio_device_release(struct device *dev)
kfree(to_mdio_device(dev));
}
+static int __mdio_device_remove(struct mdio_device *mdiodev, bool dynamic);
+
+static int mdio_device_remove_dynamic(struct mdio_device *mdiodev)
+{
+ return __mdio_device_remove(mdiodev, true);
+}
+
struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr)
{
struct mdio_device *mdiodev;
@@ -131,9 +143,11 @@ struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr)
mdiodev->dev.bus = &mdio_bus_type;
mdiodev->device_free = mdio_device_free;
mdiodev->device_remove = mdio_device_remove;
+ mdiodev->device_remove_dynamic = mdio_device_remove_dynamic;
mdiodev->bus = bus;
mdiodev->addr = addr;
mdiodev->reset_state = -1;
+ INIT_LIST_HEAD(&mdiodev->retired_node);
dev_set_name(&mdiodev->dev, PHY_ID_FMT, bus->id, addr);
@@ -160,19 +174,27 @@ int mdio_device_register(struct mdio_device *mdiodev)
return err;
err = device_add(&mdiodev->dev);
- if (err) {
+ if (err)
pr_err("MDIO %d failed to add\n", mdiodev->addr);
- goto out;
- }
-
- return 0;
- out:
- mdiobus_unregister_device(mdiodev);
- return err;
+ return mdiobus_registration_done(mdiodev, err);
}
EXPORT_SYMBOL(mdio_device_register);
+static int __mdio_device_remove(struct mdio_device *mdiodev, bool dynamic)
+{
+ int err;
+
+ err = mdiobus_begin_remove(mdiodev, dynamic);
+ if (err)
+ return err;
+
+ device_del(&mdiodev->dev);
+ mdiobus_finish_remove(mdiodev, dynamic);
+
+ return 0;
+}
+
/**
* mdio_device_remove - Remove a previously registered mdio device from the
* MDIO bus
@@ -184,42 +206,211 @@ EXPORT_SYMBOL(mdio_device_register);
*/
void mdio_device_remove(struct mdio_device *mdiodev)
{
- device_del(&mdiodev->dev);
- mdiobus_unregister_device(mdiodev);
+ __mdio_device_remove(mdiodev, false);
}
EXPORT_SYMBOL(mdio_device_remove);
int mdiobus_register_device(struct mdio_device *mdiodev)
{
+ struct mii_bus *bus = mdiodev->bus;
int err;
- if (mdiodev->bus->mdio_map[mdiodev->addr])
- return -EBUSY;
+ mutex_lock(&bus->mdio_map_lock);
+ if (bus->state != MDIOBUS_REGISTERING &&
+ bus->state != MDIOBUS_REGISTERED) {
+ err = -ENODEV;
+ goto out_unlock;
+ }
+ if (bus->mdio_map_removals) {
+ err = -EBUSY;
+ goto out_unlock;
+ }
+
+ if (bus->mdio_map[mdiodev->addr] ||
+ bus->mdio_map_pending & BIT(mdiodev->addr)) {
+ err = -EBUSY;
+ goto out_unlock;
+ }
+
+ bus->mdio_map_pending |= BIT(mdiodev->addr);
+ bus->mdio_map_ops++;
+ mutex_unlock(&bus->mdio_map_lock);
if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) {
err = mdio_device_register_reset(mdiodev);
- if (err)
+ if (err) {
+ mdiobus_registration_done(mdiodev, err);
return err;
+ }
/* Assert the reset signal */
mdio_device_reset(mdiodev, 1);
}
- mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev;
+ mutex_lock(&bus->mdio_map_lock);
+ /* Teardown waits for this registration before consuming the map. */
+ smp_store_release(&bus->mdio_map[mdiodev->addr], mdiodev);
+ mutex_unlock(&bus->mdio_map_lock);
return 0;
+
+out_unlock:
+ mutex_unlock(&bus->mdio_map_lock);
+ return err;
}
-int mdiobus_unregister_device(struct mdio_device *mdiodev)
+/**
+ * mdiobus_device_change_begin - start changing devices on a registered bus
+ * @bus: MDIO bus that will be scanned or changed
+ * @removing: whether PHY attachment must be blocked during the change
+ *
+ * Return: zero on success or a negative error code when the bus is unavailable
+ */
+int mdiobus_device_change_begin(struct mii_bus *bus, bool removing)
{
- if (mdiodev->bus->mdio_map[mdiodev->addr] != mdiodev)
- return -EINVAL;
+ int err = 0;
+
+ mutex_lock(&bus->mdio_map_lock);
+ if (bus->state != MDIOBUS_REGISTERED) {
+ err = -ENODEV;
+ } else {
+ bus->mdio_map_ops++;
+ if (removing)
+ bus->mdio_map_removals++;
+ }
+ mutex_unlock(&bus->mdio_map_lock);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(mdiobus_device_change_begin);
- mdio_device_unregister_reset(mdiodev);
+static void mdiobus_operation_done_locked(struct mii_bus *bus)
+{
+ lockdep_assert_held(&bus->mdio_map_lock);
- mdiodev->bus->mdio_map[mdiodev->addr] = NULL;
+ if (WARN_ON_ONCE(!bus->mdio_map_ops))
+ return;
+ bus->mdio_map_ops--;
+ if (!bus->mdio_map_ops)
+ wake_up_all(&bus->mdio_map_wait);
+}
- return 0;
+/**
+ * mdiobus_device_change_end - finish changing devices on an MDIO bus
+ * @bus: MDIO bus previously passed to mdiobus_device_change_begin()
+ * @removing: value passed to mdiobus_device_change_begin()
+ */
+void mdiobus_device_change_end(struct mii_bus *bus, bool removing)
+{
+ mutex_lock(&bus->mdio_map_lock);
+ if (removing) {
+ if (!WARN_ON_ONCE(!bus->mdio_map_removals))
+ bus->mdio_map_removals--;
+ }
+ mdiobus_operation_done_locked(bus);
+ mutex_unlock(&bus->mdio_map_lock);
+}
+EXPORT_SYMBOL_GPL(mdiobus_device_change_end);
+
+static void mdiobus_operation_done(struct mii_bus *bus)
+{
+ mutex_lock(&bus->mdio_map_lock);
+ mdiobus_operation_done_locked(bus);
+ mutex_unlock(&bus->mdio_map_lock);
+}
+
+static void mdiobus_unpublish_device(struct mdio_device *mdiodev)
+{
+ struct mii_bus *bus = mdiodev->bus;
+
+ lockdep_assert_held(&bus->mdio_map_lock);
+
+ if (bus->mdio_map[mdiodev->addr] == mdiodev)
+ WRITE_ONCE(bus->mdio_map[mdiodev->addr], NULL);
+ if (mdiodev->dev.of_node)
+ of_node_clear_flag(mdiodev->dev.of_node, OF_POPULATED);
+}
+
+int mdiobus_registration_done(struct mdio_device *mdiodev, int err)
+{
+ struct mii_bus *bus = mdiodev->bus;
+
+ mutex_lock(&bus->mdio_map_lock);
+ if (WARN_ON_ONCE(!(bus->mdio_map_pending & BIT(mdiodev->addr))))
+ goto out_unlock;
+
+ if (err)
+ mdiobus_unpublish_device(mdiodev);
+ else
+ WARN_ON_ONCE(bus->mdio_map[mdiodev->addr] != mdiodev);
+
+ bus->mdio_map_pending &= ~BIT(mdiodev->addr);
+
+out_unlock:
+ mutex_unlock(&bus->mdio_map_lock);
+ if (err) {
+ if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) {
+ mdio_device_reset(mdiodev, 1);
+ mdio_device_unregister_reset(mdiodev);
+ }
+ }
+ mdiobus_operation_done(bus);
+
+ return err;
+}
+
+int mdiobus_begin_remove(struct mdio_device *mdiodev, bool dynamic)
+{
+ struct mii_bus *bus = mdiodev->bus;
+ int err = 0;
+
+ mutex_lock(&bus->mdio_map_lock);
+ if (dynamic && (bus->state == MDIOBUS_UNREGISTERED ||
+ bus->state == MDIOBUS_RELEASED)) {
+ err = -ENODEV;
+ goto out_unlock;
+ }
+ if (bus->mdio_map_pending & BIT(mdiodev->addr)) {
+ err = -EBUSY;
+ goto out_unlock;
+ }
+
+ if (bus->mdio_map[mdiodev->addr] != mdiodev) {
+ err = -ENODEV;
+ goto out_unlock;
+ }
+
+ if (dynamic && mdiodev->flags & MDIO_DEVICE_FLAG_PHY &&
+ to_phy_device(&mdiodev->dev)->attached) {
+ err = -EBUSY;
+ goto out_unlock;
+ }
+
+ mdiobus_unpublish_device(mdiodev);
+
+ if (dynamic) {
+ mdio_device_get(mdiodev);
+ list_add_tail(&mdiodev->retired_node, &bus->mdio_map_retired);
+ }
+
+out_unlock:
+ mutex_unlock(&bus->mdio_map_lock);
+ return err;
+}
+
+void mdiobus_finish_remove(struct mdio_device *mdiodev, bool dynamic)
+{
+ struct fwnode_handle *fwnode;
+
+ if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY)
+ mdio_device_unregister_reset(mdiodev);
+
+ /* Do not keep an overlay node alive with the retired device. */
+ if (dynamic) {
+ fwnode = dev_fwnode(&mdiodev->dev);
+ device_set_node(&mdiodev->dev, NULL);
+ fwnode_handle_put(fwnode);
+ }
}
/**
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 0615228459ef..f7472a743185 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -227,6 +227,8 @@ static void phy_device_release(struct device *dev)
kfree(to_phy_device(dev));
}
+static int __phy_device_remove(struct phy_device *phydev, bool dynamic);
+
static void phy_mdio_device_remove(struct mdio_device *mdiodev)
{
struct phy_device *phydev;
@@ -235,6 +237,14 @@ static void phy_mdio_device_remove(struct mdio_device *mdiodev)
phy_device_remove(phydev);
}
+static int phy_mdio_device_remove_dynamic(struct mdio_device *mdiodev)
+{
+ struct phy_device *phydev;
+
+ phydev = container_of(mdiodev, struct phy_device, mdio);
+ return __phy_device_remove(phydev, true);
+}
+
static struct phy_driver genphy_driver;
static LIST_HEAD(phy_fixup_list);
@@ -768,7 +778,9 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id,
mdiodev->flags = MDIO_DEVICE_FLAG_PHY;
mdiodev->device_free = phy_mdio_device_free;
mdiodev->device_remove = phy_mdio_device_remove;
+ mdiodev->device_remove_dynamic = phy_mdio_device_remove_dynamic;
mdiodev->reset_state = -1;
+ INIT_LIST_HEAD(&mdiodev->retired_node);
dev->speed = SPEED_UNKNOWN;
dev->duplex = DUPLEX_UNKNOWN;
@@ -1121,25 +1133,40 @@ int phy_device_register(struct phy_device *phydev)
err = phy_scan_fixups(phydev);
if (err) {
phydev_err(phydev, "failed to initialize\n");
- goto out;
+ return mdiobus_registration_done(&phydev->mdio, err);
}
err = device_add(&phydev->mdio.dev);
- if (err) {
+ if (err)
phydev_err(phydev, "failed to add\n");
- goto out;
- }
- return 0;
+ return mdiobus_registration_done(&phydev->mdio, err);
+}
+EXPORT_SYMBOL(phy_device_register);
+
+static int __phy_device_remove(struct phy_device *phydev, bool dynamic)
+{
+ int err;
+
+ err = mdiobus_begin_remove(&phydev->mdio, dynamic);
+ if (dynamic && err == -EBUSY)
+ dev_warn(&phydev->mdio.dev,
+ "cannot remove a PHY while it is attached or being registered\n");
+ if (err)
+ return err;
+
+ unregister_mii_timestamper(phydev->mii_ts);
+ pse_control_put(phydev->psec);
+
+ device_del(&phydev->mdio.dev);
- out:
/* Assert the reset signal */
phy_device_reset(phydev, 1);
- mdiobus_unregister_device(&phydev->mdio);
- return err;
+ mdiobus_finish_remove(&phydev->mdio, dynamic);
+
+ return 0;
}
-EXPORT_SYMBOL(phy_device_register);
/**
* phy_device_remove - Remove a previously registered phy device from the MDIO bus
@@ -1151,15 +1178,7 @@ EXPORT_SYMBOL(phy_device_register);
*/
void phy_device_remove(struct phy_device *phydev)
{
- unregister_mii_timestamper(phydev->mii_ts);
- pse_control_put(phydev->psec);
-
- device_del(&phydev->mdio.dev);
-
- /* Assert the reset signal */
- phy_device_reset(phydev, 1);
-
- mdiobus_unregister_device(&phydev->mdio);
+ __phy_device_remove(phydev, false);
}
EXPORT_SYMBOL(phy_device_remove);
@@ -1734,6 +1753,36 @@ static bool phy_drv_supports_irq(const struct phy_driver *phydrv)
return phydrv->config_intr && phydrv->handle_interrupt;
}
+static int phy_claim(struct phy_device *phydev)
+{
+ struct mdio_device *mdiodev = &phydev->mdio;
+ struct mii_bus *bus = mdiodev->bus;
+ int err = 0;
+
+ mutex_lock(&bus->mdio_map_lock);
+ if (bus->state != MDIOBUS_REGISTERED ||
+ bus->mdio_map_removals ||
+ bus->mdio_map[mdiodev->addr] != mdiodev ||
+ (bus->mdio_map_pending & BIT(mdiodev->addr)))
+ err = -ENODEV;
+ else if (phydev->attached)
+ err = -EBUSY;
+ else
+ phydev->attached = true;
+ mutex_unlock(&bus->mdio_map_lock);
+
+ return err;
+}
+
+static void phy_release(struct phy_device *phydev)
+{
+ struct mii_bus *bus = phydev->mdio.bus;
+
+ mutex_lock(&bus->mdio_map_lock);
+ phydev->attached = false;
+ mutex_unlock(&bus->mdio_map_lock);
+}
+
/**
* phy_attach_direct - attach a network device to a given PHY device pointer
* @dev: network device to attach
@@ -1755,6 +1804,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
struct mii_bus *bus = phydev->mdio.bus;
struct device *d = &phydev->mdio.dev;
struct module *ndev_owner = NULL;
+ bool claimed = false;
int err;
/* For Ethernet device drivers that register their own MDIO bus, we
@@ -1770,6 +1820,13 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
}
get_device(d);
+ err = phy_claim(phydev);
+ if (err == -EBUSY)
+ phydev_err(phydev, "PHY already attached\n");
+ if (!err)
+ claimed = true;
+ if (err)
+ goto error_put_device;
/* Assume that if there is no driver, that it doesn't
* exist, and we should use the genphy driver.
@@ -1798,12 +1855,6 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
goto error_module_put;
}
- if (phydev->attached_dev) {
- dev_err(&dev->dev, "PHY already attached\n");
- err = -EBUSY;
- goto error;
- }
-
phydev->phy_link_change = phy_link_change;
if (dev) {
phydev->attached_dev = dev;
@@ -1899,6 +1950,8 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
phydev->is_genphy_driven = 0;
d->driver = NULL;
error_put_device:
+ if (claimed)
+ phy_release(phydev);
put_device(d);
if (ndev_owner != bus->owner)
module_put(bus->owner);
@@ -1977,6 +2030,7 @@ void phy_detach(struct phy_device *phydev)
* a use-after-free bug by reading the underlying bus first.
*/
bus = phydev->mdio.bus;
+ phy_release(phydev);
put_device(&phydev->mdio.dev);
if (dev)
diff --git a/drivers/net/phy/phylib-internal.h b/drivers/net/phy/phylib-internal.h
index 664ed7faa518..7e9161fcdd90 100644
--- a/drivers/net/phy/phylib-internal.h
+++ b/drivers/net/phy/phylib-internal.h
@@ -25,7 +25,9 @@ int phy_speed_down_core(struct phy_device *phydev);
void phy_check_downshift(struct phy_device *phydev);
int mdiobus_register_device(struct mdio_device *mdiodev);
-int mdiobus_unregister_device(struct mdio_device *mdiodev);
+int mdiobus_registration_done(struct mdio_device *mdiodev, int err);
+int mdiobus_begin_remove(struct mdio_device *mdiodev, bool dynamic);
+void mdiobus_finish_remove(struct mdio_device *mdiodev, bool dynamic);
int genphy_c45_read_eee_adv(struct phy_device *phydev, unsigned long *adv);
diff --git a/include/linux/mdio.h b/include/linux/mdio.h
index f4f9d9609448..41c60798fa0e 100644
--- a/include/linux/mdio.h
+++ b/include/linux/mdio.h
@@ -8,6 +8,7 @@
#include <uapi/linux/mdio.h>
#include <linux/bitfield.h>
+#include <linux/list.h>
#include <linux/mod_devicetable.h>
struct gpio_desc;
@@ -33,6 +34,9 @@ struct mdio_device {
int (*bus_match)(struct device *dev, const struct device_driver *drv);
void (*device_free)(struct mdio_device *mdiodev);
void (*device_remove)(struct mdio_device *mdiodev);
+ int (*device_remove_dynamic)(struct mdio_device *mdiodev);
+ /* Entry in mii_bus::mdio_map_retired. */
+ struct list_head retired_node;
/* Bus address of the MDIO device (0-31) */
int addr;
@@ -690,6 +694,8 @@ static inline int mdiodev_c45_write(struct mdio_device *mdiodev, u32 devad,
bool mdiobus_is_registered_device(struct mii_bus *bus, int addr);
struct phy_device *mdiobus_get_phy(struct mii_bus *bus, int addr);
+int mdiobus_device_change_begin(struct mii_bus *bus, bool removing);
+void mdiobus_device_change_end(struct mii_bus *bus, bool removing);
/**
* mdio_module_driver() - Helper macro for registering mdio drivers
diff --git a/include/linux/phy.h b/include/linux/phy.h
index fc680901275b..2eba54e068f6 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -22,6 +22,7 @@
#include <linux/mii_timestamper.h>
#include <linux/module.h>
#include <linux/timer.h>
+#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/device-id/mdio.h>
#include <linux/u64_stats_sync.h>
@@ -391,7 +392,9 @@ struct mii_bus {
/** @state: State of bus structure */
enum {
MDIOBUS_ALLOCATED = 1,
+ MDIOBUS_REGISTERING,
MDIOBUS_REGISTERED,
+ MDIOBUS_UNREGISTERING,
MDIOBUS_UNREGISTERED,
MDIOBUS_RELEASED,
} state;
@@ -401,6 +404,18 @@ struct mii_bus {
/** @mdio_map: list of all MDIO devices on bus */
struct mdio_device *mdio_map[PHY_MAX_ADDR];
+ /** @mdio_map_pending: addresses with registration in progress */
+ u32 mdio_map_pending;
+ /** @mdio_map_lock: protects the MDIO device map and bus state */
+ struct mutex mdio_map_lock;
+ /** @mdio_map_wait: wait for active map operations during teardown */
+ wait_queue_head_t mdio_map_wait;
+ /** @mdio_map_ops: active registrations and firmware changes */
+ unsigned int mdio_map_ops;
+ /** @mdio_map_removals: firmware removals blocking PHY attachment */
+ unsigned int mdio_map_removals;
+ /** @mdio_map_retired: removed devices pinned until bus teardown */
+ struct list_head mdio_map_retired;
/** @phy_mask: PHY addresses to be ignored when probing */
u32 phy_mask;
@@ -652,6 +667,8 @@ struct phy_oatc14_sqi_capability {
* @n_ports: Number of ports currently attached to the PHY
* @max_n_ports: Max number of ports this PHY can expose
* @lock: Mutex for serialization access to PHY
+ * @attached: Whether a network device or standalone user attached the PHY;
+ * protected by the MDIO bus map lock
* @state_queue: Work queue for state machine
* @link_down_events: Number of times link was lost
* @shared: Pointer to private data shared by phys in one package
@@ -781,6 +798,7 @@ struct phy_device {
struct delayed_work state_queue;
struct mutex lock;
+ bool attached;
/* This may be modified under the rtnl lock */
bool sfp_bus_attached;
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 06/21] net: mdio: support dynamic OF device changes
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (4 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 05/21] net: mdio: make device map changes hotplug-safe James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 07/21] dt-bindings: net: x-powers: add AC200 EPHY control James Hilliard
` (18 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
MDIO buses enumerate firmware children only when the bus is registered.
A later DT overlay or status transition is ignored, unlike I2C and SPI.
Add an OF reconfiguration notifier. On addition, create fixed-address
PHYs and generic MDIO devices, scan free addresses for legacy PHY nodes
without reg, and recurse into Ethernet PHY packages. Track populated
nodes so static enumeration, address scanning and notifier-driven
enumeration cannot create duplicates. Walk every package child and report
the first population error without undoing devices which may already have
acquired consumers.
Serialize static and notifier-driven population while allowing a PHY
probe to enable another node on the same bus. Check the node's current
state after taking that lock so an older notification cannot override a
newer status change.
On removal, reject attached or in-flight PHYs and unregister the object
found by its OF node. This also covers PHYs found by address scanning.
Treat all PHYs in a package as one removal scope, so removing one member
cannot invalidate state used by an attached peer. Preflight overlay
removal while it can still be refused; normal changeset errors are
propagated so the caller can restore the previous state.
Hold a core MDIO change transaction across discovery and registration so
bus teardown cannot race the MDIO reads used to identify a PHY. Compile
the notifier only with CONFIG_OF_DYNAMIC and the overlay preflight only
with CONFIG_OF_OVERLAY.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/mdio/of_mdio.c | 448 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 438 insertions(+), 10 deletions(-)
diff --git a/drivers/net/mdio/of_mdio.c b/drivers/net/mdio/of_mdio.c
index 051e449bbe7c..a8313b3721f6 100644
--- a/drivers/net/mdio/of_mdio.c
+++ b/drivers/net/mdio/of_mdio.c
@@ -13,6 +13,7 @@
#include <linux/fwnode_mdio.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/netdevice.h>
#include <linux/of.h>
#include <linux/of_irq.h>
@@ -20,6 +21,8 @@
#include <linux/of_net.h>
#include <linux/phy.h>
#include <linux/phy_fixed.h>
+#include <linux/sched.h>
+#include <linux/string.h>
#define DEFAULT_GPIO_RESET_DELAY 10 /* in microseconds */
@@ -27,6 +30,52 @@ MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("OpenFirmware MDIO bus (Ethernet PHY) accessors");
+#if IS_ENABLED(CONFIG_OF_DYNAMIC)
+/*
+ * OF changes can nest when probing one MDIO device enables another node on
+ * the same bus. Serialize independent changes while allowing that nesting.
+ */
+static DEFINE_MUTEX(of_mdio_reconfig_mutex);
+static struct task_struct *of_mdio_reconfig_owner;
+static unsigned int of_mdio_reconfig_depth;
+
+static void of_mdio_reconfig_lock(void)
+{
+ if (!mutex_trylock(&of_mdio_reconfig_mutex)) {
+ if (of_mdio_reconfig_owner == current) {
+ of_mdio_reconfig_depth++;
+ return;
+ }
+ mutex_lock(&of_mdio_reconfig_mutex);
+ }
+
+ WARN_ON_ONCE(of_mdio_reconfig_owner);
+ WARN_ON_ONCE(of_mdio_reconfig_depth);
+ of_mdio_reconfig_owner = current;
+ of_mdio_reconfig_depth = 1;
+}
+
+static void of_mdio_reconfig_unlock(void)
+{
+ WARN_ON_ONCE(of_mdio_reconfig_owner != current);
+ WARN_ON_ONCE(!of_mdio_reconfig_depth);
+
+ if (--of_mdio_reconfig_depth)
+ return;
+
+ of_mdio_reconfig_owner = NULL;
+ mutex_unlock(&of_mdio_reconfig_mutex);
+}
+#else
+static inline void of_mdio_reconfig_lock(void)
+{
+}
+
+static inline void of_mdio_reconfig_unlock(void)
+{
+}
+#endif
+
/* Extract the clause 22 phy ID from the compatible string of the form
* ethernet-phy-idAAAA.BBBB */
static int of_get_phy_id(struct device_node *device, u32 *phy_id)
@@ -44,7 +93,7 @@ int of_mdiobus_phy_device_register(struct mii_bus *mdio, struct phy_device *phy,
EXPORT_SYMBOL(of_mdiobus_phy_device_register);
static int of_mdiobus_register_phy(struct mii_bus *mdio,
- struct device_node *child, u32 addr)
+ struct device_node *child, u32 addr)
{
return fwnode_mdiobus_register_phy(mdio, of_fwnode_handle(child), addr);
}
@@ -80,10 +129,20 @@ static int of_mdiobus_register_device(struct mii_bus *mdio,
static int of_mdiobus_register_child(struct mii_bus *mdio,
struct device_node *child, u32 addr)
{
+ int rc;
+
+ if (of_node_test_and_set_flag(child, OF_POPULATED))
+ return 0;
+
if (of_mdiobus_child_is_phy(child))
- return of_mdiobus_register_phy(mdio, child, addr);
+ rc = of_mdiobus_register_phy(mdio, child, addr);
+ else
+ rc = of_mdiobus_register_device(mdio, child, addr);
- return of_mdiobus_register_device(mdio, child, addr);
+ if (rc)
+ of_node_clear_flag(child, OF_POPULATED);
+
+ return rc;
}
/* The following is a list of PHY compatible strings which appear in
@@ -255,13 +314,21 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,
if (rc)
return rc;
+ of_mdio_reconfig_lock();
+ rc = mdiobus_device_change_begin(mdio, false);
+ if (rc) {
+ of_mdio_reconfig_unlock();
+ mdiobus_unregister(mdio);
+ return rc;
+ }
+
/* Loop over the child nodes and register a phy_device for each phy */
rc = __of_mdiobus_parse_phys(mdio, np, &scanphys);
if (rc)
- goto unregister;
+ goto out_change;
if (!scanphys)
- return 0;
+ goto out_change;
/* auto scan for PHYs with empty reg property */
for_each_available_child_of_node(np, child) {
@@ -272,19 +339,380 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,
rc = of_mdiobus_scan_phy(mdio, child);
if (rc && rc != -ENODEV)
- goto put_unregister;
+ goto put_child;
+ rc = 0;
}
- return 0;
+out_change:
+ mdiobus_device_change_end(mdio, false);
+ of_mdio_reconfig_unlock();
+ if (!rc)
+ return 0;
-put_unregister:
- of_node_put(child);
-unregister:
mdiobus_unregister(mdio);
return rc;
+
+put_child:
+ of_node_put(child);
+ goto out_change;
}
EXPORT_SYMBOL(__of_mdiobus_register);
+#if IS_ENABLED(CONFIG_OF_DYNAMIC)
+static bool of_mdiobus_node_is_available(struct device_node *node)
+{
+ return !of_node_check_flag(node, OF_DETACHED) &&
+ of_device_is_available(node);
+}
+
+static int of_mdiobus_add_node(struct mii_bus *mdio,
+ struct device_node *node)
+{
+ struct device_node *child;
+ int addr, rc, ret = 0;
+
+ if (!of_mdiobus_node_is_available(node))
+ return 0;
+
+ if (of_node_name_eq(node, "ethernet-phy-package")) {
+ if (!of_property_present(node, "reg"))
+ return 0;
+
+ for_each_available_child_of_node(node, child) {
+ rc = of_mdiobus_add_node(mdio, child);
+ if (rc && rc != -ENODEV && !ret)
+ ret = rc;
+ }
+
+ return ret;
+ }
+
+ addr = of_mdio_parse_addr(&mdio->dev, node);
+ if (addr < 0)
+ rc = of_mdiobus_scan_phy(mdio, node);
+ else
+ rc = of_mdiobus_register_child(mdio, node, addr);
+
+ if (rc == -ENODEV && addr >= 0)
+ dev_err(&mdio->dev,
+ "MDIO device at address %d is missing.\n", addr);
+
+ return rc;
+}
+
+static bool of_mdiobus_node_is_busy(struct mii_bus *mdio,
+ struct device_node *node)
+{
+ struct mdio_device *mdiodev;
+ bool busy = false;
+
+ if (of_node_name_eq(node, "ethernet-phy-package")) {
+ for_each_child_of_node_scoped(node, child) {
+ if (of_mdiobus_node_is_busy(mdio, child))
+ return true;
+ }
+
+ return false;
+ }
+
+ if (!of_node_check_flag(node, OF_POPULATED))
+ return false;
+
+ mdiodev = of_mdio_find_device(node);
+ if (!mdiodev)
+ return true;
+ if (mdiodev->bus != mdio) {
+ put_device(&mdiodev->dev);
+ return false;
+ }
+
+ mutex_lock(&mdio->mdio_map_lock);
+ busy = mdio->mdio_map[mdiodev->addr] != mdiodev ||
+ (mdio->mdio_map_pending & BIT(mdiodev->addr));
+ if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) {
+ struct phy_device *phydev = to_phy_device(&mdiodev->dev);
+
+ if (phydev->attached) {
+ busy = true;
+ dev_warn(&mdiodev->dev,
+ "cannot remove an attached PHY; remove its consumer first\n");
+ }
+ }
+ mutex_unlock(&mdio->mdio_map_lock);
+ put_device(&mdiodev->dev);
+
+ return busy;
+}
+
+static struct device_node *
+of_mdiobus_get_removal_scope(struct device_node *node)
+{
+ struct device_node *parent;
+
+ if (of_node_name_eq(node, "ethernet-phy-package"))
+ return of_node_get(node);
+
+ parent = of_get_parent(node);
+ if (of_node_name_eq(parent, "ethernet-phy-package"))
+ return parent;
+
+ of_node_put(parent);
+ return of_node_get(node);
+}
+
+static int of_mdiobus_remove_node(struct mii_bus *mdio,
+ struct device_node *node)
+{
+ struct mdio_device *mdiodev;
+ struct device_node *child;
+ int ret;
+
+ if (of_node_name_eq(node, "ethernet-phy-package")) {
+ for_each_child_of_node(node, child) {
+ ret = of_mdiobus_remove_node(mdio, child);
+ if (ret) {
+ of_node_put(child);
+ return ret;
+ }
+ }
+ return 0;
+ }
+
+ if (!of_node_check_flag(node, OF_POPULATED))
+ return 0;
+
+ /* The OF node lookup also covers PHYs found by address scanning. */
+ mdiodev = of_mdio_find_device(node);
+ if (!mdiodev)
+ return -EBUSY;
+ if (mdiodev->bus != mdio) {
+ put_device(&mdiodev->dev);
+ return -ENODEV;
+ }
+
+ ret = mdiodev->device_remove_dynamic(mdiodev);
+ if (!ret)
+ mdiodev->device_free(mdiodev);
+ put_device(&mdiodev->dev);
+
+ return ret == -ENODEV ? 0 : ret;
+}
+
+static struct mii_bus *of_mdiobus_find_parent(struct device_node *node)
+{
+ struct device_node *parent, *bus_node;
+ struct mii_bus *mdio;
+
+ parent = of_get_parent(node);
+ if (!parent)
+ return NULL;
+
+ if (of_node_name_eq(parent, "ethernet-phy-package")) {
+ if (!of_device_is_available(parent) ||
+ !of_property_present(parent, "reg")) {
+ of_node_put(parent);
+ return NULL;
+ }
+
+ bus_node = of_get_parent(parent);
+ of_node_put(parent);
+ } else {
+ bus_node = parent;
+ }
+
+ mdio = of_mdio_find_bus(bus_node);
+ of_node_put(bus_node);
+
+ return mdio;
+}
+
+#if IS_ENABLED(CONFIG_OF_OVERLAY)
+/* Overlay entry notifier errors cannot stop removal after the tree changed. */
+static bool of_mdiobus_live_node_is_busy(struct device_node *node)
+{
+ struct device_node *scope;
+ struct mii_bus *mdio;
+ bool busy = true;
+
+ scope = of_mdiobus_get_removal_scope(node);
+ mdio = of_mdiobus_find_parent(scope);
+ if (!mdio)
+ goto out_put_scope;
+
+ if (!mdiobus_device_change_begin(mdio, true)) {
+ busy = of_mdiobus_node_is_busy(mdio, scope);
+ mdiobus_device_change_end(mdio, true);
+ }
+ put_device(&mdio->dev);
+
+out_put_scope:
+ of_node_put(scope);
+ return busy;
+}
+
+static struct device_node *
+of_mdiobus_overlay_target_child(struct device_node *target,
+ struct device_node *overlay_child)
+{
+ const char *name = kbasename(overlay_child->full_name);
+ struct device_node *child;
+
+ for_each_child_of_node(target, child) {
+ if (!of_node_cmp(kbasename(child->full_name), name))
+ return child;
+ }
+
+ return NULL;
+}
+
+static bool of_mdiobus_overlay_node_is_busy(struct device_node *overlay,
+ struct device_node *target,
+ bool added)
+{
+ struct device_node *overlay_child, *target_child;
+ bool busy, child_added;
+
+ if ((added || of_property_present(overlay, "status")) &&
+ of_mdiobus_live_node_is_busy(target))
+ return true;
+
+ for_each_child_of_node(overlay, overlay_child) {
+ target_child = of_mdiobus_overlay_target_child(target,
+ overlay_child);
+ if (!target_child)
+ continue;
+
+ child_added = of_node_check_flag(target_child, OF_OVERLAY);
+ busy = of_mdiobus_overlay_node_is_busy(overlay_child,
+ target_child, child_added);
+ of_node_put(target_child);
+ if (busy) {
+ of_node_put(overlay_child);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int of_mdiobus_overlay_notify(struct notifier_block *nb,
+ unsigned long action, void *arg)
+{
+ struct of_overlay_notify_data *nd = arg;
+ bool busy;
+
+ if (action != OF_OVERLAY_PRE_REMOVE)
+ return NOTIFY_OK;
+
+ of_mdio_reconfig_lock();
+ busy = of_mdiobus_overlay_node_is_busy(nd->overlay, nd->target,
+ false);
+ of_mdio_reconfig_unlock();
+
+ return busy ? notifier_from_errno(-EBUSY) : NOTIFY_OK;
+}
+
+static struct notifier_block of_mdio_overlay_notifier = {
+ .notifier_call = of_mdiobus_overlay_notify,
+};
+#endif
+
+static int of_mdiobus_notify(struct notifier_block *nb, unsigned long action,
+ void *arg)
+{
+ struct of_reconfig_data *rd = arg;
+ struct device_node *scope;
+ struct mii_bus *mdio;
+ enum of_reconfig_change change;
+ bool removing;
+ int rc, ret = NOTIFY_OK;
+
+ of_mdio_reconfig_lock();
+ change = of_reconfig_get_state_change(action, rd);
+ switch (change) {
+ case OF_RECONFIG_CHANGE_ADD:
+ /* A newer change may have made this notification stale. */
+ if (!of_mdiobus_node_is_available(rd->dn))
+ goto out_unlock;
+ removing = false;
+ break;
+ case OF_RECONFIG_CHANGE_REMOVE:
+ /* A newer change may have made this notification stale. */
+ if (of_mdiobus_node_is_available(rd->dn))
+ goto out_unlock;
+ removing = true;
+ break;
+ default:
+ goto out_unlock;
+ }
+
+ mdio = of_mdiobus_find_parent(rd->dn);
+ if (!mdio)
+ goto out_unlock;
+
+ rc = mdiobus_device_change_begin(mdio, removing);
+ if (rc) {
+ if (!removing)
+ ret = notifier_from_errno(-EPROBE_DEFER);
+ goto out_put_mdio;
+ }
+
+ if (!removing) {
+ rc = of_mdiobus_add_node(mdio, rd->dn);
+ } else {
+ /* The node may already be detached from its parent hierarchy. */
+ scope = of_mdiobus_get_removal_scope(rd->dn);
+ if (of_mdiobus_node_is_busy(mdio, scope))
+ rc = -EBUSY;
+ else
+ rc = of_mdiobus_remove_node(mdio, rd->dn);
+ of_node_put(scope);
+ }
+
+ mdiobus_device_change_end(mdio, removing);
+ if (rc && (removing || rc != -ENODEV))
+ ret = notifier_from_errno(rc);
+
+out_put_mdio:
+ put_device(&mdio->dev);
+out_unlock:
+ of_mdio_reconfig_unlock();
+
+ return ret;
+}
+
+static struct notifier_block of_mdio_notifier = {
+ .notifier_call = of_mdiobus_notify,
+};
+
+static int __init of_mdio_init(void)
+{
+ int ret;
+
+ ret = of_reconfig_notifier_register(&of_mdio_notifier);
+ if (ret)
+ return ret;
+
+#if IS_ENABLED(CONFIG_OF_OVERLAY)
+ ret = of_overlay_notifier_register(&of_mdio_overlay_notifier);
+ if (ret)
+ of_reconfig_notifier_unregister(&of_mdio_notifier);
+#endif
+
+ return ret;
+}
+module_init(of_mdio_init);
+
+static void __exit of_mdio_exit(void)
+{
+#if IS_ENABLED(CONFIG_OF_OVERLAY)
+ of_overlay_notifier_unregister(&of_mdio_overlay_notifier);
+#endif
+ of_reconfig_notifier_unregister(&of_mdio_notifier);
+}
+module_exit(of_mdio_exit);
+#endif /* CONFIG_OF_DYNAMIC */
+
/**
* of_mdio_find_device - Given a device tree node, find the mdio_device
* @np: pointer to the mdio_device's device tree node
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 07/21] dt-bindings: net: x-powers: add AC200 EPHY control
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (5 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 06/21] net: mdio: support dynamic OF device changes James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 13:40 ` Andrew Lunn
2026-08-03 5:14 ` [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200 James Hilliard
` (17 subsequent siblings)
24 siblings, 1 reply; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
The AC200 companion IC contains a Fast Ethernet PHY controlled through
the parent AC200 I2C regmap.
Describe its control function, including the interface mode and optional
SoC SID calibration cell. The vendor sequence adds three to the calibration
value modulo 16 before writing the BPS_EFFUSE field. If the SID cell is
omitted, the driver can use the AC200's internal copy of the calibration
eFuse. The link-PHY driver supplies its Clause 22 address when enabling the
control function.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
.../bindings/net/x-powers,ac200-ephy-ctl.yaml | 46 ++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/x-powers,ac200-ephy-ctl.yaml b/Documentation/devicetree/bindings/net/x-powers,ac200-ephy-ctl.yaml
new file mode 100644
index 000000000000..b3337c3aa62a
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/x-powers,ac200-ephy-ctl.yaml
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/x-powers,ac200-ephy-ctl.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: X-Powers AC200 Ethernet PHY control
+
+maintainers:
+ - James Hilliard <james.hilliard1@gmail.com>
+
+description:
+ The AC200 Ethernet PHY control function configures and powers the Fast
+ Ethernet PHY in the AC200. Its registers are accessed through the parent
+ AC200 I2C regmap.
+
+properties:
+ compatible:
+ const: x-powers,ac200-ephy-ctl
+
+ nvmem-cells:
+ maxItems: 1
+ description:
+ Optional SoC SID cell containing the Ethernet PHY calibration value
+ written to the AC200 BPS_EFFUSE field after adding 3 modulo 16, as
+ prescribed by the vendor initialization sequence. If omitted, the
+ driver reads the AC200's internal copy of the calibration eFuse.
+
+ nvmem-cell-names:
+ const: calibration
+
+ phy-mode:
+ enum:
+ - mii
+ - rmii
+
+required:
+ - compatible
+ - phy-mode
+
+dependentRequired:
+ nvmem-cells: [ nvmem-cell-names ]
+ nvmem-cell-names: [ nvmem-cells ]
+
+additionalProperties: false
+...
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (6 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 07/21] dt-bindings: net: x-powers: add AC200 EPHY control James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 7:07 ` Krzysztof Kozlowski
2026-08-03 13:18 ` Andrew Lunn
2026-08-03 5:14 ` [PATCH 09/21] mfd: add X-Powers AC200 support James Hilliard
` (16 subsequent siblings)
24 siblings, 2 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
The AC200 is an I2C-controlled mixed-signal companion IC containing
audio, video, RTC and Fast Ethernet PHY functions.
Describe the parent device, its input clock, required function supplies,
the optional SID bandgap calibration cell used by the vendor initialization
sequence, and its optional Ethernet PHY control child. Document the 24 and
27 MHz rates encoded by the public EPHY clock selector.
Also describe the optional open-drain, level-triggered INTB output and the
nested interrupt controller which exposes the TV encoder, Ethernet PHY and
RTC sources. Define the interrupt source numbers for child consumers.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
.../devicetree/bindings/mfd/x-powers,ac200.yaml | 118 +++++++++++++++++++++
include/dt-bindings/mfd/x-powers,ac200.h | 13 +++
2 files changed, 131 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..017629cca73c
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml
@@ -0,0 +1,118 @@
+# 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.
+
+ interrupts:
+ maxItems: 1
+ description:
+ The INTB pin, which is a shared open-drain, level-triggered output for
+ the TV encoder, Ethernet PHY and RTC interrupt sources.
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 1
+ description:
+ The interrupt number, as defined in
+ include/dt-bindings/mfd/x-powers,ac200.h.
+
+ ac-ldoin-supply:
+ description: 3.3 V supply for the audio-codec LDO input
+
+ ephy-vcc-supply:
+ description: 3.3 V supply for the Ethernet PHY analog front end
+
+ rtc-vcc-supply:
+ description: 3.3 V supply for the RTC
+
+ tv-vcc-supply:
+ description: 3.3 V supply for the TV encoder DAC
+
+ nvmem-cells:
+ maxItems: 1
+ description:
+ Optional SoC SID cell containing the AC200 bandgap calibration value.
+ The trim is used by the TV encoder; the public AC200 datasheet does not
+ document the corresponding register fields.
+
+ nvmem-cell-names:
+ const: bandgap
+
+ ethernet-phy-control:
+ $ref: /schemas/net/x-powers,ac200-ephy-ctl.yaml#
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - ac-ldoin-supply
+ - ephy-vcc-supply
+ - rtc-vcc-supply
+ - tv-vcc-supply
+
+dependencies:
+ interrupts: [ interrupt-controller ]
+ interrupt-controller: [ '#interrupt-cells', interrupts ]
+ '#interrupt-cells': [ interrupt-controller ]
+ nvmem-cells: [ nvmem-cell-names ]
+ nvmem-cell-names: [ nvmem-cells ]
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ mixed-signal@10 {
+ compatible = "x-powers,ac200";
+ reg = <0x10>;
+ clocks = <&pwm 5>;
+ ac-ldoin-supply = <®_3v3>;
+ ephy-vcc-supply = <®_3v3>;
+ rtc-vcc-supply = <®_3v3>;
+ tv-vcc-supply = <®_3v3>;
+ interrupt-parent = <&pio>;
+ interrupts = <1 20 IRQ_TYPE_LEVEL_LOW>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ nvmem-cells = <&ac200_bandgap>;
+ nvmem-cell-names = "bandgap";
+
+ ethernet-phy-control {
+ compatible = "x-powers,ac200-ephy-ctl";
+ nvmem-cells = <&ephy_calibration>;
+ nvmem-cell-names = "calibration";
+ phy-mode = "rmii";
+ };
+ };
+ };
+...
diff --git a/include/dt-bindings/mfd/x-powers,ac200.h b/include/dt-bindings/mfd/x-powers,ac200.h
new file mode 100644
index 000000000000..cc59e2ab4912
--- /dev/null
+++ b/include/dt-bindings/mfd/x-powers,ac200.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Interrupt numbers of the X-Powers AC200 interrupt controller.
+ */
+
+#ifndef _DT_BINDINGS_MFD_X_POWERS_AC200_H
+#define _DT_BINDINGS_MFD_X_POWERS_AC200_H
+
+#define AC200_IRQ_TVE 0
+#define AC200_IRQ_EPHY 1
+#define AC200_IRQ_RTC 2
+
+#endif /* _DT_BINDINGS_MFD_X_POWERS_AC200_H */
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 09/21] mfd: add X-Powers AC200 support
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (7 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200 James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 10/21] net: phy: add X-Powers AC200 EPHY control driver James Hilliard
` (15 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
The X-Powers AC200 is a mixed-signal companion IC with a paged register
map accessed over I2C. Enable its required supplies and board-configured
input clock, lock the clock rate, and wait for the settling interval used
by the vendor initialization sequence before accessing the device.
Initialize the common control interface, report the chip and package
revision, and populate the Ethernet PHY control child when firmware
describes it. Cache only the common page selector because individual
function resets can invalidate functional registers without regmap's
knowledge.
Optionally apply the SoC SID bandgap calibration using the opaque fixed
bits from the vendor sequence. The public manual marks that register as
reserved. Boards which do not use the affected TV encoder path may omit
the calibration cell.
When firmware describes INTB, register a regmap IRQ domain for the TV
encoder, Ethernet PHY and RTC sources. Program the shared output enable
and requested level polarity while preserving the independently managed
source enables. Systems without a routed interrupt remain unchanged.
The AC200 and its children cannot initiate DMA. Mark the parent as
DMA-incapable before adding the MFD cells so OF DMA setup does not
synthesize a mask for the child.
Register a managed action that disables the AC200 control block before
adding the MFD children. Managed resources unwind in reverse order, so
the children are removed before their parent control block is disabled
on driver removal or a later probe failure. Disable the block during
system shutdown as well.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mfd/Kconfig | 13 +++
drivers/mfd/Makefile | 1 +
drivers/mfd/ac200.c | 302 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 316 insertions(+)
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 763ce6a34782..a71870bcf868 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -205,6 +205,19 @@ config MFD_AC100
This driver include only the core APIs. You have to select individual
components like codecs or RTC under the corresponding menus.
+config MFD_AC200
+ tristate "X-Powers AC200"
+ depends on I2C
+ depends on OF
+ select MFD_CORE
+ select REGMAP_I2C
+ select REGMAP_IRQ
+ 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..bd9930a02cb2
--- /dev/null
+++ b/drivers/mfd/ac200.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MFD core driver for the X-Powers AC200
+ *
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <dt-bindings/mfd/x-powers,ac200.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)
+
+#define AC200_SYS_IRQ_ENABLE_REG 0x0004
+#define AC200_SYS_IRQ_INTB_ENABLE BIT(15)
+#define AC200_SYS_IRQ_INTB_ACTIVE_HIGH BIT(14)
+#define AC200_SYS_IRQ_RTC_ENABLE BIT(12)
+#define AC200_SYS_IRQ_EPHY_ENABLE BIT(8)
+#define AC200_SYS_IRQ_TVE_ENABLE BIT(4)
+
+#define AC200_SYS_IRQ_STATUS_REG 0x0006
+
+#define AC200_SYS_BG_CTL_REG 0x0050
+/*
+ * The vendor initialization sequence composes this as SID data | BIT(15) |
+ * (0xa << 6). The public datasheet marks the whole register reserved and
+ * does not name the fixed fields.
+ */
+#define AC200_SYS_BG_CTL_FIXED_BITS (BIT(15) | (0xa << 6))
+
+/* Interface register accessible from every register page. */
+#define AC200_TWI_REG_ADDR_H 0x00fe
+#define AC200_MAX_REG 0xa1f2
+
+struct ac200 {
+ struct regmap *regmap;
+};
+
+static const char * const ac200_supplies[] = {
+ "ac-ldoin",
+ "ephy-vcc",
+ "rtc-vcc",
+ "tv-vcc",
+};
+
+static const struct regmap_range_cfg ac200_range_cfg[] = {
+ {
+ .range_max = AC200_MAX_REG,
+ .selector_reg = AC200_TWI_REG_ADDR_H,
+ .selector_mask = 0xff,
+ .window_len = 256,
+ },
+};
+
+/*
+ * Each AC200 sub-block can reset independently, invalidating its register
+ * contents without regmap's knowledge. Cache only the common page selector;
+ * this avoids a selector read-modify-write for every access on the same page
+ * without ever returning stale functional-register values.
+ */
+static bool ac200_volatile_reg(struct device *dev, unsigned int reg)
+{
+ return reg != AC200_TWI_REG_ADDR_H;
+}
+
+static const struct regmap_config ac200_regmap_config = {
+ .name = "ac200",
+ .reg_bits = 8,
+ .reg_stride = 2,
+ .val_bits = 16,
+ .ranges = ac200_range_cfg,
+ .num_ranges = ARRAY_SIZE(ac200_range_cfg),
+ .max_register = AC200_MAX_REG,
+ .volatile_reg = ac200_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static const struct regmap_irq ac200_irqs[] = {
+ REGMAP_IRQ_REG(AC200_IRQ_TVE, 0, AC200_SYS_IRQ_TVE_ENABLE),
+ REGMAP_IRQ_REG(AC200_IRQ_EPHY, 0, AC200_SYS_IRQ_EPHY_ENABLE),
+ REGMAP_IRQ_REG(AC200_IRQ_RTC, 0, AC200_SYS_IRQ_RTC_ENABLE),
+};
+
+/*
+ * SYS_IRQ_ENABLE is an enable register rather than a mask register, hence
+ * unmask_base. The INTB output enable and polarity bits share the register,
+ * but regmap-irq only updates the bits described by ac200_irqs.
+ *
+ * SYS_IRQ_STATUS follows the state of each source block and is read-only.
+ * Interrupts must therefore be acknowledged in the source block itself.
+ */
+static const struct regmap_irq_chip ac200_irq_chip = {
+ .name = "ac200",
+ .status_base = AC200_SYS_IRQ_STATUS_REG,
+ .unmask_base = AC200_SYS_IRQ_ENABLE_REG,
+ .num_regs = 1,
+ .irqs = ac200_irqs,
+ .num_irqs = ARRAY_SIZE(ac200_irqs),
+};
+
+static const struct mfd_cell ac200_cells[] = {
+ {
+ .name = "ac200-ephy-ctl",
+ .of_compatible = "x-powers,ac200-ephy-ctl",
+ },
+};
+
+static int ac200_disable(struct ac200 *ac200)
+{
+ return regmap_write(ac200->regmap, AC200_SYS_CONTROL_REG, 0);
+}
+
+static void ac200_disable_action(void *data)
+{
+ ac200_disable(data);
+}
+
+static int ac200_init_irq(struct ac200 *ac200, int irq)
+{
+ struct device *dev = regmap_get_device(ac200->regmap);
+ struct regmap_irq_chip_data *irq_data;
+ u16 value = AC200_SYS_IRQ_INTB_ENABLE;
+ u32 trigger;
+ int ret;
+
+ trigger = irq_get_trigger_type(irq);
+ switch (trigger) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ value |= AC200_SYS_IRQ_INTB_ACTIVE_HIGH;
+ break;
+ case IRQ_TYPE_NONE:
+ case IRQ_TYPE_LEVEL_LOW:
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "INTB requires a level interrupt, not type %u\n",
+ trigger);
+ }
+
+ ret = regmap_update_bits(ac200->regmap, AC200_SYS_IRQ_ENABLE_REG,
+ AC200_SYS_IRQ_INTB_ENABLE |
+ AC200_SYS_IRQ_INTB_ACTIVE_HIGH, value);
+ if (ret)
+ return ret;
+
+ ret = devm_regmap_add_irq_chip(dev, ac200->regmap, irq, IRQF_ONESHOT,
+ 0, &ac200_irq_chip, &irq_data);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to add interrupt controller\n");
+
+ return 0;
+}
+
+static int ac200_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct device_node *ephy_node __free(device_node) = NULL;
+ struct ac200 *ac200;
+ struct clk *clk;
+ unsigned int version;
+ u16 bandgap_calibration = 0;
+ int ret;
+
+ ac200 = devm_kzalloc(dev, sizeof(*ac200), GFP_KERNEL);
+ if (!ac200)
+ return -ENOMEM;
+
+ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ac200_supplies),
+ ac200_supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable supplies\n");
+
+ clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk),
+ "failed to enable input clock\n");
+
+ ret = devm_clk_rate_exclusive_get(dev, clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to lock clock rate\n");
+
+ ac200->regmap = devm_regmap_init_i2c(client, &ac200_regmap_config);
+ if (IS_ERR(ac200->regmap))
+ return dev_err_probe(dev, PTR_ERR(ac200->regmap),
+ "failed to initialize regmap\n");
+
+ i2c_set_clientdata(client, ac200);
+
+ if (device_property_present(dev, "nvmem-cells")) {
+ ret = nvmem_cell_read_u16(dev, "bandgap",
+ &bandgap_calibration);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read bandgap data\n");
+ }
+
+ /*
+ * No minimum delay is documented. Match the vendor driver's 40 ms delay
+ * before its first AC200 register access after enabling the input clock.
+ */
+ msleep(40);
+
+ ret = regmap_read(ac200->regmap, AC200_SYS_VERSION_REG, &version);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read chip version\n");
+
+ dev_info(dev, "AC200 revision %#lx in package %lu\n",
+ FIELD_GET(AC200_SYS_VERSION_CHIP_MASK, version),
+ FIELD_GET(AC200_SYS_VERSION_PACKAGE_MASK, version));
+
+ /* Run after the MFD children have been removed. */
+ ret = devm_add_action_or_reset(dev, ac200_disable_action, ac200);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL_REG, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL_REG,
+ AC200_SYS_CONTROL_CHIP_RESET_DEASSERT);
+ if (ret)
+ return ret;
+
+ if (bandgap_calibration) {
+ ret = regmap_write(ac200->regmap, AC200_SYS_BG_CTL_REG,
+ AC200_SYS_BG_CTL_FIXED_BITS |
+ bandgap_calibration);
+ if (ret)
+ return ret;
+ }
+
+ if (client->irq > 0) {
+ ret = ac200_init_irq(ac200, client->irq);
+ if (ret)
+ return ret;
+ }
+
+ /* Neither the AC200 nor its child devices can perform DMA. */
+ dev->coherent_dma_mask = 0;
+ dev->dma_mask = &dev->coherent_dma_mask;
+ ephy_node = of_get_compatible_child(dev->of_node,
+ "x-powers,ac200-ephy-ctl");
+ if (!ephy_node)
+ return 0;
+
+ return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, ac200_cells,
+ ARRAY_SIZE(ac200_cells), NULL, 0, NULL);
+}
+
+static void ac200_shutdown(struct i2c_client *client)
+{
+ struct ac200 *ac200 = i2c_get_clientdata(client);
+
+ ac200_disable(ac200);
+}
+
+static const struct of_device_id ac200_of_match[] = {
+ { .compatible = "x-powers,ac200" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ac200_of_match);
+
+static const struct i2c_device_id ac200_i2c_ids[] = {
+ { "ac200" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ac200_i2c_ids);
+
+static struct i2c_driver ac200_driver = {
+ .driver = {
+ .name = "ac200",
+ .of_match_table = ac200_of_match,
+ },
+ .probe = ac200_probe,
+ .shutdown = ac200_shutdown,
+ .id_table = ac200_i2c_ids,
+};
+module_i2c_driver(ac200_driver);
+
+MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
+MODULE_DESCRIPTION("X-Powers AC200 MFD core driver");
+MODULE_LICENSE("GPL");
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 10/21] net: phy: add X-Powers AC200 EPHY control driver
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (8 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 09/21] mfd: add X-Powers AC200 support James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 13:50 ` Andrew Lunn
2026-08-03 5:14 ` [PATCH 11/21] dt-bindings: net: x-powers: add AC300 EPHY control James Hilliard
` (14 subsequent siblings)
24 siblings, 1 reply; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
The AC200 Fast Ethernet PHY is powered and configured through registers
in the parent I2C regmap before its Clause 22 link registers become usable.
Add a control driver that prepares the calibration value and vendor
offset, interface mode and input-clock selection. Use an optional SoC SID
cell when supplied, otherwise read the AC200's internal calibration eFuse.
Derive the documented 24 or 27 MHz selector from the actual locked clock
rate. Although the feature summary also mentions 25 MHz, EPHY_CTL provides
no encoding for it, so reject that rate rather than silently selecting
27 MHz.
Program the link PHY MDIO address into EPHY_CTL.PHY_ADDR before bringing
the PHY out of its disabled state using the vendor initialization sequence.
Expose serialized, idempotent power-on and power-off operations so the
link-PHY driver can sequence the control block across resets and system
sleep. Also expose operations for the global LED polarity and package pad
enables. Initialize all three outputs active-low as in the vendor kernel,
while allowing an explicit link-PHY LED topology to replace that fallback.
Quiesce the block during probe failures, driver removal and system
shutdown.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/phy/Kconfig | 9 +
drivers/net/phy/Makefile | 1 +
drivers/net/phy/xpowers-ac200-ctl.c | 330 ++++++++++++++++++++++++++++++++++++
drivers/net/phy/xpowers-acx00.h | 25 +++
4 files changed, 365 insertions(+)
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 099f25dceabb..e059bd6c5eda 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -469,6 +469,15 @@ config VITESSE_PHY
help
Currently supports the vsc8244
+config XPOWERS_AC200_PHY_CTL
+ tristate "X-Powers AC200 Ethernet PHY control"
+ depends on MFD_AC200
+ help
+ Enable the control driver for the Fast Ethernet PHY function in
+ the X-Powers AC200 mixed-signal companion IC. It programs the PHY
+ address, interface mode, calibration and I/O controls through the
+ parent AC200 I2C regmap.
+
config XILINX_GMII2RGMII
tristate "Xilinx GMII2RGMII converter driver"
help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index de660ae94945..e53ca319f9c4 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -99,4 +99,5 @@ obj-$(CONFIG_SMSC_PHY) += smsc.o
obj-$(CONFIG_STE10XP) += ste10Xp.o
obj-$(CONFIG_TERANETICS_PHY) += teranetics.o
obj-$(CONFIG_VITESSE_PHY) += vitesse.o
+obj-$(CONFIG_XPOWERS_AC200_PHY_CTL) += xpowers-ac200-ctl.o
obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
diff --git a/drivers/net/phy/xpowers-ac200-ctl.c b/drivers/net/phy/xpowers-ac200-ctl.c
new file mode 100644
index 000000000000..c2a37f958565
--- /dev/null
+++ b/drivers/net/phy/xpowers-ac200-ctl.c
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * X-Powers AC200 Ethernet PHY control driver
+ *
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include "xpowers-acx00.h"
+
+#define AC200_EPHY_BPS_EFFUSE_OFFSET 3
+
+#define AC200_SYS_EPHY_CTL0_REG 0x0014
+#define AC200_EPHY_RESET_DEASSERT BIT(0)
+#define AC200_EPHY_SYSCLK_ENABLE BIT(1)
+
+#define AC200_SYS_EPHY_CTL1_REG 0x0016
+#define AC200_EPHY_MII_IO_ENABLE BIT(0)
+#define AC200_EPHY_LINK_LED_IO_ENABLE BIT(1)
+#define AC200_EPHY_SPEED_LED_IO_ENABLE BIT(2)
+#define AC200_EPHY_DUPLEX_LED_IO_ENABLE BIT(3)
+#define AC200_EPHY_LED_IO_ENABLE_MASK \
+ (AC200_EPHY_LINK_LED_IO_ENABLE | AC200_EPHY_SPEED_LED_IO_ENABLE | \
+ AC200_EPHY_DUPLEX_LED_IO_ENABLE)
+
+/* 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_LED_ACTIVE_LOW BIT(1)
+#define AC200_EPHY_CLK_SEL_24_MHZ BIT(2)
+#define AC200_EPHY_PHY_ADDR_MASK GENMASK(8, 4)
+#define AC200_EPHY_RMII_SEL BIT(11)
+#define AC200_EPHY_BPS_EFFUSE_MASK GENMASK(15, 12)
+
+struct ac200_ephy_ctl {
+ struct acx00_ephy_control control;
+ struct regmap *regmap;
+ struct mutex lock; /* Serializes power sequencing and state. */
+ u16 ephy_ctl;
+ u16 led_io_enable;
+ unsigned int phy_addr;
+ bool powered;
+};
+
+static int
+ac200_ephy_ctl_set_led_outputs(struct acx00_ephy_control *control,
+ unsigned long outputs)
+{
+ struct ac200_ephy_ctl *priv =
+ container_of(control, struct ac200_ephy_ctl, control);
+ u16 led_io_enable;
+ int ret = 0;
+
+ if (outputs & ~GENMASK(ACX00_EPHY_LED_COUNT - 1, 0))
+ return -EINVAL;
+
+ led_io_enable = (outputs << 1) & AC200_EPHY_LED_IO_ENABLE_MASK;
+
+ mutex_lock(&priv->lock);
+ if (priv->powered)
+ ret = regmap_update_bits(priv->regmap, AC200_SYS_EPHY_CTL1_REG,
+ AC200_EPHY_LED_IO_ENABLE_MASK,
+ led_io_enable);
+ if (!ret)
+ priv->led_io_enable = led_io_enable;
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int
+ac200_ephy_ctl_set_led_polarity(struct acx00_ephy_control *control,
+ bool active_low)
+{
+ struct ac200_ephy_ctl *priv =
+ container_of(control, struct ac200_ephy_ctl, control);
+ u16 value = active_low ? AC200_EPHY_LED_ACTIVE_LOW : 0;
+ int ret = 0;
+
+ mutex_lock(&priv->lock);
+ if (priv->powered)
+ ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL_REG,
+ AC200_EPHY_LED_ACTIVE_LOW, value);
+ if (!ret) {
+ priv->ephy_ctl &= ~AC200_EPHY_LED_ACTIVE_LOW;
+ priv->ephy_ctl |= value;
+ }
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int ac200_ephy_ctl_power_off_locked(struct ac200_ephy_ctl *priv)
+{
+ int err;
+ int ret;
+
+ if (!priv->powered)
+ return 0;
+
+ ret = regmap_write(priv->regmap, AC200_EPHY_CTL_REG,
+ priv->ephy_ctl |
+ FIELD_PREP(AC200_EPHY_PHY_ADDR_MASK,
+ priv->phy_addr) |
+ AC200_EPHY_SHUTDOWN);
+ err = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL1_REG, 0);
+ if (!ret)
+ ret = err;
+ err = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0_REG, 0);
+ if (!ret)
+ ret = err;
+
+ priv->powered = false;
+
+ return ret;
+}
+
+static int ac200_ephy_ctl_power_off(struct acx00_ephy_control *control)
+{
+ struct ac200_ephy_ctl *priv =
+ container_of(control, struct ac200_ephy_ctl, control);
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = ac200_ephy_ctl_power_off_locked(priv);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int ac200_ephy_ctl_power_on(struct acx00_ephy_control *control,
+ unsigned int phy_addr)
+{
+ struct ac200_ephy_ctl *priv =
+ container_of(control, struct ac200_ephy_ctl, control);
+ u16 ephy_ctl;
+ int ret;
+
+ if (phy_addr > FIELD_MAX(AC200_EPHY_PHY_ADDR_MASK))
+ return -EINVAL;
+
+ mutex_lock(&priv->lock);
+ if (priv->powered && priv->phy_addr == phy_addr) {
+ ret = 0;
+ goto out_unlock;
+ }
+ if (priv->powered) {
+ ret = ac200_ephy_ctl_power_off_locked(priv);
+ if (ret)
+ goto out_unlock;
+ }
+
+ ephy_ctl = priv->ephy_ctl |
+ FIELD_PREP(AC200_EPHY_PHY_ADDR_MASK, phy_addr);
+
+ /* Start from a disabled state before applying the configuration. */
+ ret = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0_REG, 0);
+ if (ret)
+ goto err_disable;
+
+ ret = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL1_REG,
+ AC200_EPHY_MII_IO_ENABLE |
+ priv->led_io_enable);
+ if (ret)
+ goto err_disable;
+
+ ret = regmap_write(priv->regmap, AC200_EPHY_CTL_REG,
+ ephy_ctl | AC200_EPHY_SHUTDOWN);
+ if (ret)
+ goto err_disable;
+
+ ret = regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0_REG,
+ AC200_EPHY_RESET_DEASSERT |
+ AC200_EPHY_SYSCLK_ENABLE);
+ if (ret)
+ goto err_disable;
+
+ fsleep(10000);
+
+ ret = regmap_write(priv->regmap, AC200_EPHY_CTL_REG, ephy_ctl);
+ if (ret)
+ goto err_disable;
+
+ priv->phy_addr = phy_addr;
+ priv->powered = true;
+ goto out_unlock;
+
+err_disable:
+ /* Mark it active so the cleanup sequence is always attempted. */
+ priv->phy_addr = phy_addr;
+ priv->powered = true;
+ ac200_ephy_ctl_power_off_locked(priv);
+out_unlock:
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int ac200_ephy_ctl_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ac200_ephy_ctl *priv;
+ phy_interface_t interface;
+ struct clk *clk;
+ unsigned long clk_rate;
+ unsigned int calibration;
+ u8 nvmem_calibration;
+ u8 bps_effuse_code;
+ u16 ephy_ctl;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ mutex_init(&priv->lock);
+
+ priv->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!priv->regmap)
+ return dev_err_probe(dev, -EPROBE_DEFER,
+ "parent regmap is not ready\n");
+
+ if (device_property_present(dev, "nvmem-cells")) {
+ ret = nvmem_cell_read_u8(dev, "calibration",
+ &nvmem_calibration);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read calibration data\n");
+ calibration = nvmem_calibration;
+ } else {
+ ret = regmap_read(priv->regmap, AC200_EFUSE_EPHY_REG,
+ &calibration);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read on-chip calibration data\n");
+ }
+
+ ret = device_get_phy_mode(dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to read PHY mode\n");
+ interface = ret;
+
+ /* 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);
+ ephy_ctl = FIELD_PREP(AC200_EPHY_BPS_EFFUSE_MASK, bps_effuse_code) |
+ AC200_EPHY_LED_ACTIVE_LOW;
+ /* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_MII:
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ ephy_ctl |= AC200_EPHY_RMII_SEL;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "unsupported PHY mode %s\n",
+ phy_modes(interface));
+ }
+
+ clk = clk_get(dev->parent, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk),
+ "failed to get input clock\n");
+
+ clk_rate = clk_get_rate(clk);
+ clk_put(clk);
+
+ switch (clk_rate) {
+ case 24000000:
+ ephy_ctl |= AC200_EPHY_CLK_SEL_24_MHZ;
+ break;
+ case 27000000:
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "unsupported input clock rate %lu Hz\n",
+ clk_rate);
+ }
+
+ priv->control.power_on = ac200_ephy_ctl_power_on;
+ priv->control.power_off = ac200_ephy_ctl_power_off;
+ priv->control.set_led_outputs = ac200_ephy_ctl_set_led_outputs;
+ priv->control.set_led_polarity = ac200_ephy_ctl_set_led_polarity;
+ priv->control.interface = interface;
+ priv->ephy_ctl = ephy_ctl;
+ priv->led_io_enable = AC200_EPHY_LED_IO_ENABLE_MASK;
+ platform_set_drvdata(pdev, &priv->control);
+ return 0;
+}
+
+static void ac200_ephy_ctl_remove(struct platform_device *pdev)
+{
+ struct acx00_ephy_control *control = platform_get_drvdata(pdev);
+
+ control->power_off(control);
+}
+
+static const struct of_device_id ac200_ephy_ctl_of_match[] = {
+ { .compatible = "x-powers,ac200-ephy-ctl" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ac200_ephy_ctl_of_match);
+
+static struct platform_driver ac200_ephy_ctl_driver = {
+ .probe = ac200_ephy_ctl_probe,
+ .remove = ac200_ephy_ctl_remove,
+ .shutdown = ac200_ephy_ctl_remove,
+ .driver = {
+ .name = "ac200-ephy-ctl",
+ .of_match_table = ac200_ephy_ctl_of_match,
+ },
+};
+module_platform_driver(ac200_ephy_ctl_driver);
+
+MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
+MODULE_DESCRIPTION("X-Powers AC200 Ethernet PHY control driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/xpowers-acx00.h b/drivers/net/phy/xpowers-acx00.h
new file mode 100644
index 000000000000..8cf6fe616137
--- /dev/null
+++ b/drivers/net/phy/xpowers-acx00.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __DRIVERS_NET_PHY_XPOWERS_ACX00_H
+#define __DRIVERS_NET_PHY_XPOWERS_ACX00_H
+
+#include <linux/phy.h>
+
+enum acx00_ephy_led {
+ ACX00_EPHY_LED_LINK_ACTIVITY,
+ ACX00_EPHY_LED_SPEED,
+ ACX00_EPHY_LED_DUPLEX,
+ ACX00_EPHY_LED_COUNT,
+};
+
+struct acx00_ephy_control {
+ phy_interface_t interface;
+ int (*power_on)(struct acx00_ephy_control *control,
+ unsigned int phy_addr);
+ int (*power_off)(struct acx00_ephy_control *control);
+ int (*set_led_outputs)(struct acx00_ephy_control *control,
+ unsigned long outputs);
+ int (*set_led_polarity)(struct acx00_ephy_control *control,
+ bool active_low);
+};
+
+#endif
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 11/21] dt-bindings: net: x-powers: add AC300 EPHY control
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (9 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 10/21] net: phy: add X-Powers AC200 EPHY control driver James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 12/21] net: phy: add X-Powers AC300 EPHY control driver James Hilliard
` (13 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
The AC300 companion IC exposes the Fast Ethernet PHY control registers
through a separate non-PHY Clause 22 address. The manual defines the
control address as the link-PHY address plus 16, giving address ranges
of 16-23 and 0-7 respectively.
Describe the control endpoint, its required VCC1 supply, its 24, 25 or
27 MHz input clock, interface mode and calibration cell.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
.../bindings/net/x-powers,ac300-ephy-ctl.yaml | 77 ++++++++++++++++++++++
1 file changed, 77 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/x-powers,ac300-ephy-ctl.yaml b/Documentation/devicetree/bindings/net/x-powers,ac300-ephy-ctl.yaml
new file mode 100644
index 000000000000..a6634fcfafd4
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/x-powers,ac300-ephy-ctl.yaml
@@ -0,0 +1,77 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/x-powers,ac300-ephy-ctl.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: X-Powers AC300 Ethernet PHY control interface
+
+maintainers:
+ - James Hilliard <james.hilliard1@gmail.com>
+
+description:
+ The AC300 Fast Ethernet PHY has a separate control interface accessed as a
+ non-PHY Clause 22 device. Its address is 16 plus the address of the link PHY.
+
+properties:
+ compatible:
+ const: x-powers,ac300-ephy-ctl
+
+ reg:
+ minimum: 16
+ maximum: 23
+
+ clocks:
+ maxItems: 1
+ description:
+ AC300 input clock. Its configured rate must be 24, 25, or 27 MHz and
+ determines the corresponding EPHY_CLK_SEL value.
+
+ vcc1-supply:
+ description:
+ 3.3 V supply for the AC300 I/O, bandgap, Ethernet PHY analog front end,
+ and internal digital LDO
+
+ nvmem-cells:
+ maxItems: 1
+ description:
+ SoC SID cell containing the Ethernet PHY calibration value written to
+ the AC300 BGS_EFFUSE field after adding 3 modulo 16, as prescribed by
+ the vendor initialization sequence.
+
+ nvmem-cell-names:
+ const: calibration
+
+ phy-mode:
+ enum:
+ - mii
+ - rmii
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - vcc1-supply
+ - nvmem-cells
+ - nvmem-cell-names
+ - phy-mode
+
+additionalProperties: false
+
+examples:
+ - |
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-phy-control@10 {
+ compatible = "x-powers,ac300-ephy-ctl";
+ reg = <0x10>;
+ clocks = <&pwm 5>;
+ vcc1-supply = <®_3v3>;
+ nvmem-cells = <&ephy_calibration>;
+ nvmem-cell-names = "calibration";
+ phy-mode = "rmii";
+ };
+ };
+...
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 12/21] net: phy: add X-Powers AC300 EPHY control driver
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (10 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 11/21] dt-bindings: net: x-powers: add AC300 EPHY control James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 13/21] dt-bindings: net: x-powers: add AC200/AC300 EPHY James Hilliard
` (12 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
The AC300 Fast Ethernet PHY uses a non-PHY Clause 22 endpoint for the
power-up and configuration sequence required before its link registers
become usable.
Enable the required VCC1 supply and wait the manual's 10 ms power-on-reset
interval before accessing the device. Accept the configured 24, 25 or
27 MHz input clock and program its corresponding selector, apply the SoC
SID calibration value and vendor offset, select the interface mode,
configure the I/O drive controls, and follow the manual reset-before-clock
power-up sequence. Retain the vendor eFuse and register-clock enable
settings.
Validate the control address and supply configuration against the package
strap state. Clear a latched MDIO error after reset and fail power-up if
it remains asserted. Enable the internal digital LDO only for the
corresponding package and preserve the external-supply configuration
across later power cycles. Report the chip, mask and package revisions.
Expose serialized, idempotent power-on and power-off operations so the
link-PHY driver can sequence the input clock and control block across
resets and system sleep. Also expose operations for the global LED polarity
and package pad enables, retaining the documented default drive strength.
Initialize all three outputs active-low as in the vendor kernel, while
allowing an explicit link-PHY LED topology to replace that fallback.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/phy/Kconfig | 9 +
drivers/net/phy/Makefile | 1 +
drivers/net/phy/xpowers-ac300-ctl.c | 454 ++++++++++++++++++++++++++++++++++++
drivers/net/phy/xpowers-acx00.h | 2 +
4 files changed, 466 insertions(+)
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index e059bd6c5eda..9b202072d5a0 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -478,6 +478,15 @@ config XPOWERS_AC200_PHY_CTL
address, interface mode, calibration and I/O controls through the
parent AC200 I2C regmap.
+config XPOWERS_AC300_PHY_CTL
+ tristate "X-Powers AC300 Ethernet PHY control"
+ depends on COMMON_CLK && NVMEM && OF_MDIO
+ help
+ Enable the MDIO control driver for the Fast Ethernet PHY in the
+ X-Powers AC300 companion IC. It programs the PHY calibration,
+ interface mode, clock and I/O controls through the separate MDIO
+ control address.
+
config XILINX_GMII2RGMII
tristate "Xilinx GMII2RGMII converter driver"
help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index e53ca319f9c4..2d22d5a4c93d 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -100,4 +100,5 @@ obj-$(CONFIG_STE10XP) += ste10Xp.o
obj-$(CONFIG_TERANETICS_PHY) += teranetics.o
obj-$(CONFIG_VITESSE_PHY) += vitesse.o
obj-$(CONFIG_XPOWERS_AC200_PHY_CTL) += xpowers-ac200-ctl.o
+obj-$(CONFIG_XPOWERS_AC300_PHY_CTL) += xpowers-ac300-ctl.o
obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
diff --git a/drivers/net/phy/xpowers-ac300-ctl.c b/drivers/net/phy/xpowers-ac300-ctl.c
new file mode 100644
index 000000000000..eb3faaab9337
--- /dev/null
+++ b/drivers/net/phy/xpowers-ac300-ctl.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * X-Powers AC300 Ethernet PHY control driver
+ *
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/phy.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+
+#include "xpowers-acx00.h"
+
+#define AC300_EPHY_BGS_EFFUSE_OFFSET 3
+
+#define AC300_SYS_CONTROL_REG 0x00
+#define AC300_CHIP_VERSION_MASK GENMASK(15, 12)
+#define AC300_PACKAGE_STATUS_MASK GENMASK(11, 8)
+#define AC300_EPHY_CLK_SEL_MASK GENMASK(7, 6)
+#define AC300_EPHY_CLK_SEL_25_MHZ FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 0)
+#define AC300_EPHY_CLK_SEL_27_MHZ FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 1)
+#define AC300_EPHY_CLK_SEL_24_MHZ FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 2)
+#define AC300_EFUSE_CLK_ENABLE BIT(5)
+#define AC300_EPHY_REG_CLK_ENABLE BIT(4)
+#define AC300_MDIO_ERROR BIT(3)
+#define AC300_CLKIN_GATING_ENABLE BIT(2)
+#define AC300_EPHY_RESET_DEASSERT BIT(1)
+#define AC300_CHIP_RESET_DEASSERT BIT(0)
+
+#define AC300_MASK_VERSION_REG 0x04
+#define AC300_MASK_VERSION_MASK GENMASK(2, 0)
+
+#define AC300_PACKAGE_POR_INTERNAL_DLDO BIT(3)
+#define AC300_PACKAGE_PHY_ADDR_MASK GENMASK(2, 0)
+
+#define AC300_SYS_BIAS1_REG 0x02
+#define AC300_INTERNAL_DLDO_ENABLE BIT(15)
+
+#define AC300_SYS_IO_REG 0x05
+#define AC300_MDIO_DRV_MASK GENMASK(15, 14)
+#define AC300_LED_DRV_MASK GENMASK(13, 12)
+#define AC300_MII_DRV_MASK GENMASK(11, 10)
+#define AC300_IO_DRV_LEVEL_2 2
+#define AC300_CLKIN_PAD_ENABLE BIT(4)
+#define AC300_DUPLEX_LED_IO_ENABLE BIT(3)
+#define AC300_SPEED_LED_IO_ENABLE BIT(2)
+#define AC300_LINK_LED_IO_ENABLE BIT(1)
+#define AC300_EPHY_MII_IO_ENABLE BIT(0)
+#define AC300_LED_IO_ENABLE_MASK \
+ (AC300_DUPLEX_LED_IO_ENABLE | AC300_SPEED_LED_IO_ENABLE | \
+ AC300_LINK_LED_IO_ENABLE)
+
+#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_LED_ACTIVE_LOW BIT(1)
+#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_BASE_VALUE \
+ (FIELD_PREP(AC300_MDIO_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \
+ FIELD_PREP(AC300_LED_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \
+ FIELD_PREP(AC300_MII_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \
+ AC300_CLKIN_PAD_ENABLE | AC300_EPHY_MII_IO_ENABLE)
+
+struct ac300_ephy_ctl {
+ struct acx00_ephy_control control;
+ struct mdio_device *mdiodev;
+ struct clk *clk;
+ struct mutex lock; /* Serializes power sequencing and state. */
+ u16 sys_control;
+ u16 ephy_config;
+ u16 led_io_enable;
+ bool package_known;
+ bool internal_dldo;
+ bool powered;
+};
+
+static unsigned int
+ac300_ephy_ctl_link_addr(const struct ac300_ephy_ctl *priv)
+{
+ return priv->mdiodev->addr - AC300_EPHY_CONTROL_ADDR_OFFSET;
+}
+
+static int
+ac300_ephy_ctl_set_led_outputs(struct acx00_ephy_control *control,
+ unsigned long outputs)
+{
+ struct ac300_ephy_ctl *priv =
+ container_of(control, struct ac300_ephy_ctl, control);
+ u16 led_io_enable;
+ int ret = 0;
+
+ if (outputs & ~GENMASK(ACX00_EPHY_LED_COUNT - 1, 0))
+ return -EINVAL;
+
+ led_io_enable = (outputs << 1) & AC300_LED_IO_ENABLE_MASK;
+
+ mutex_lock(&priv->lock);
+ if (priv->powered)
+ ret = mdiodev_modify(priv->mdiodev, AC300_SYS_IO_REG,
+ AC300_LED_IO_ENABLE_MASK, led_io_enable);
+ if (!ret)
+ priv->led_io_enable = led_io_enable;
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int
+ac300_ephy_ctl_set_led_polarity(struct acx00_ephy_control *control,
+ bool active_low)
+{
+ struct ac300_ephy_ctl *priv =
+ container_of(control, struct ac300_ephy_ctl, control);
+ u16 value = active_low ? AC300_EPHY_LED_ACTIVE_LOW : 0;
+ int ret = 0;
+
+ mutex_lock(&priv->lock);
+ if (priv->powered)
+ ret = mdiodev_modify(priv->mdiodev, AC300_EPHY_CONFIG_REG,
+ AC300_EPHY_LED_ACTIVE_LOW, value);
+ if (!ret) {
+ priv->ephy_config &= ~AC300_EPHY_LED_ACTIVE_LOW;
+ priv->ephy_config |= value;
+ }
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int ac300_ephy_ctl_power_off_locked(struct ac300_ephy_ctl *priv)
+{
+ int err;
+ int ret;
+
+ if (!priv->powered)
+ return 0;
+
+ ret = mdiodev_write(priv->mdiodev, AC300_EPHY_CONFIG_REG,
+ priv->ephy_config | AC300_EPHY_SHUTDOWN);
+ err = mdiodev_write(priv->mdiodev, AC300_SYS_IO_REG, 0);
+ if (!ret)
+ ret = err;
+ err = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG,
+ priv->package_known && !priv->internal_dldo ?
+ AC300_CHIP_RESET_DEASSERT : 0);
+ if (!ret)
+ ret = err;
+
+ clk_disable_unprepare(priv->clk);
+ priv->powered = false;
+
+ return ret;
+}
+
+static int ac300_ephy_ctl_power_off(struct acx00_ephy_control *control)
+{
+ struct ac300_ephy_ctl *priv =
+ container_of(control, struct ac300_ephy_ctl, control);
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = ac300_ephy_ctl_power_off_locked(priv);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int ac300_ephy_ctl_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;
+ int sys_control;
+ u16 reset_value;
+ int ret;
+
+ if (phy_addr != ac300_ephy_ctl_link_addr(priv))
+ return -EINVAL;
+
+ mutex_lock(&priv->lock);
+ if (priv->powered) {
+ ret = 0;
+ goto out_unlock;
+ }
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret)
+ goto out_unlock;
+ priv->powered = true;
+
+ /* Keep the external-supply configuration across subsequent resets. */
+ reset_value = priv->package_known && !priv->internal_dldo ?
+ AC300_CHIP_RESET_DEASSERT : 0;
+ ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG, reset_value);
+ if (ret)
+ goto err_power_off;
+
+ /* The manual requires both resets to be released before the clocks. */
+ ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG,
+ AC300_EPHY_RESET_DEASSERT |
+ AC300_CHIP_RESET_DEASSERT);
+ if (ret)
+ goto err_power_off;
+
+ /* Retain the vendor clock-enable defaults, including the eFuse clock. */
+ ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG,
+ priv->sys_control);
+ if (ret)
+ goto err_power_off;
+
+ sys_control = mdiodev_read(priv->mdiodev, AC300_SYS_CONTROL_REG);
+ if (sys_control < 0) {
+ ret = sys_control;
+ goto err_power_off;
+ }
+ if (sys_control & AC300_MDIO_ERROR) {
+ ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG,
+ priv->sys_control | AC300_MDIO_ERROR);
+ if (ret)
+ goto err_power_off;
+
+ sys_control = mdiodev_read(priv->mdiodev, AC300_SYS_CONTROL_REG);
+ if (sys_control < 0) {
+ ret = sys_control;
+ goto err_power_off;
+ }
+ if (sys_control & AC300_MDIO_ERROR) {
+ ret = -EIO;
+ goto err_power_off;
+ }
+ }
+
+ package_status = FIELD_GET(AC300_PACKAGE_STATUS_MASK, sys_control);
+ if ((~package_status & AC300_PACKAGE_PHY_ADDR_MASK) !=
+ ac300_ephy_ctl_link_addr(priv)) {
+ ret = -EINVAL;
+ goto err_power_off;
+ }
+
+ priv->internal_dldo = package_status &
+ AC300_PACKAGE_POR_INTERNAL_DLDO;
+ priv->package_known = true;
+ ret = mdiodev_modify(priv->mdiodev, AC300_SYS_BIAS1_REG,
+ AC300_INTERNAL_DLDO_ENABLE,
+ priv->internal_dldo ?
+ AC300_INTERNAL_DLDO_ENABLE : 0);
+ if (ret)
+ goto err_power_off;
+
+ /* Keep the documented default drive level and leave the IRQ disabled. */
+ ret = mdiodev_write(priv->mdiodev, AC300_SYS_IO_REG,
+ AC300_SYS_IO_BASE_VALUE | priv->led_io_enable);
+ if (ret)
+ goto err_power_off;
+
+ fsleep(10000);
+
+ ret = mdiodev_write(priv->mdiodev, AC300_EPHY_CONFIG_REG,
+ priv->ephy_config | AC300_EPHY_SHUTDOWN);
+ if (ret)
+ goto err_power_off;
+
+ fsleep(10000);
+
+ ret = mdiodev_write(priv->mdiodev, AC300_EPHY_CONFIG_REG,
+ priv->ephy_config);
+ if (ret)
+ goto err_power_off;
+
+ goto out_unlock;
+
+err_power_off:
+ ac300_ephy_ctl_power_off_locked(priv);
+out_unlock:
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int ac300_ephy_ctl_probe(struct mdio_device *mdiodev)
+{
+ struct device *dev = &mdiodev->dev;
+ struct ac300_ephy_ctl *priv;
+ phy_interface_t interface;
+ u8 calibration;
+ u8 bgs_effuse_code;
+ u8 package_status;
+ u16 ephy_config;
+ struct clk *clk;
+ int mask_version;
+ int sys_control;
+ unsigned long clk_rate;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ if (mdiodev->addr < AC300_EPHY_CONTROL_ADDR_OFFSET ||
+ mdiodev->addr > AC300_EPHY_CONTROL_ADDR_OFFSET +
+ FIELD_MAX(AC300_PACKAGE_PHY_ADDR_MASK))
+ return dev_err_probe(dev, -EINVAL,
+ "control address is outside the package range\n");
+ priv->mdiodev = mdiodev;
+ mutex_init(&priv->lock);
+
+ ret = devm_regulator_get_enable(dev, "vcc1");
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to enable VCC1 supply\n");
+
+ /* Wait for the power-on reset interval specified by the manual. */
+ fsleep(10000);
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk),
+ "failed to get input clock\n");
+ priv->clk = clk;
+
+ ret = devm_clk_rate_exclusive_get(dev, clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to lock clock rate\n");
+
+ clk_rate = clk_get_rate(clk);
+ switch (clk_rate) {
+ case 24000000:
+ priv->sys_control = AC300_EPHY_CLK_SEL_24_MHZ;
+ break;
+ case 25000000:
+ priv->sys_control = AC300_EPHY_CLK_SEL_25_MHZ;
+ break;
+ case 27000000:
+ priv->sys_control = AC300_EPHY_CLK_SEL_27_MHZ;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "unsupported input clock rate %lu Hz\n",
+ clk_rate);
+ }
+ priv->sys_control |= AC300_SYS_CONTROL_ENABLE_BITS;
+
+ ret = nvmem_cell_read_u8(dev, "calibration", &calibration);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read calibration data\n");
+
+ ret = device_get_phy_mode(dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to read PHY mode\n");
+ interface = ret;
+
+ /* 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);
+ ephy_config = FIELD_PREP(AC300_EPHY_BGS_EFFUSE_MASK, bgs_effuse_code) |
+ AC300_EPHY_LED_ACTIVE_LOW;
+ /* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_MII:
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ ephy_config |= AC300_EPHY_RMII_SEL;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "unsupported PHY mode %s\n",
+ phy_modes(interface));
+ }
+
+ priv->control.power_on = ac300_ephy_ctl_power_on;
+ priv->control.power_off = ac300_ephy_ctl_power_off;
+ priv->control.set_led_outputs = ac300_ephy_ctl_set_led_outputs;
+ priv->control.set_led_polarity = ac300_ephy_ctl_set_led_polarity;
+ priv->control.interface = interface;
+ priv->ephy_config = ephy_config;
+ priv->led_io_enable = AC300_LED_IO_ENABLE_MASK;
+ mdiodev_set_drvdata(mdiodev, &priv->control);
+
+ ret = ac300_ephy_ctl_power_on(&priv->control,
+ ac300_ephy_ctl_link_addr(priv));
+ if (ret)
+ goto err_disable;
+
+ sys_control = mdiodev_read(mdiodev, AC300_SYS_CONTROL_REG);
+ if (sys_control < 0) {
+ ret = sys_control;
+ goto err_disable;
+ }
+ package_status = FIELD_GET(AC300_PACKAGE_STATUS_MASK, sys_control);
+
+ mask_version = mdiodev_read(mdiodev, AC300_MASK_VERSION_REG);
+ if (mask_version < 0) {
+ ret = mask_version;
+ goto err_disable;
+ }
+
+ dev_info(dev, "chip version %u, mask version %u, package %#x, %s supplies, PHY %u, %lu Hz clock\n",
+ (unsigned int)FIELD_GET(AC300_CHIP_VERSION_MASK, sys_control),
+ (unsigned int)FIELD_GET(AC300_MASK_VERSION_MASK, mask_version),
+ package_status,
+ package_status & AC300_PACKAGE_POR_INTERNAL_DLDO ?
+ "POR/internal DLDO" : "reset pin/external VDD",
+ ac300_ephy_ctl_link_addr(priv), clk_rate);
+
+ return 0;
+
+err_disable:
+ ac300_ephy_ctl_power_off(&priv->control);
+ return ret;
+}
+
+static void ac300_ephy_ctl_remove(struct mdio_device *mdiodev)
+{
+ struct acx00_ephy_control *control = mdiodev_get_drvdata(mdiodev);
+
+ control->power_off(control);
+}
+
+static const struct of_device_id ac300_ephy_ctl_of_match[] = {
+ { .compatible = "x-powers,ac300-ephy-ctl" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ac300_ephy_ctl_of_match);
+
+static struct mdio_driver ac300_ephy_ctl_driver = {
+ .probe = ac300_ephy_ctl_probe,
+ .remove = ac300_ephy_ctl_remove,
+ .shutdown = ac300_ephy_ctl_remove,
+ .mdiodrv.driver = {
+ .name = "ac300-ephy-ctl",
+ .of_match_table = ac300_ephy_ctl_of_match,
+ },
+};
+
+mdio_module_driver(ac300_ephy_ctl_driver);
+
+MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
+MODULE_DESCRIPTION("X-Powers AC300 Ethernet PHY control driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/xpowers-acx00.h b/drivers/net/phy/xpowers-acx00.h
index 8cf6fe616137..8ed15bc4eed5 100644
--- a/drivers/net/phy/xpowers-acx00.h
+++ b/drivers/net/phy/xpowers-acx00.h
@@ -4,6 +4,8 @@
#include <linux/phy.h>
+#define AC300_EPHY_CONTROL_ADDR_OFFSET 16
+
enum acx00_ephy_led {
ACX00_EPHY_LED_LINK_ACTIVITY,
ACX00_EPHY_LED_SPEED,
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 13/21] dt-bindings: net: x-powers: add AC200/AC300 EPHY
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (11 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 12/21] net: phy: add X-Powers AC300 EPHY control driver James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 14/21] net: phy: support configuring PHY-autonomous Tx LPI James Hilliard
` (11 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
AC200 and AC300 companion ICs contain compatible Fast Ethernet
link-PHY implementations which report the same PHY ID but use
different control interfaces.
Describe the forced-ID link PHY and its control-device references.
Fixed systems reference one already enabled control interface. A fixed
AC300 system may optionally provide the configuration field for its
calibration-range bit; omitting it selects the normal range. Systems which
can contain either companion reference both candidates and require a
logical two-bit NVMEM field for variant selection and AC300 calibration-
range tuning.
The link node reg value is programmed through the AC200 control
interface, while AC300 requires it to match the package-strapped
address. A control reference is required to distinguish this
integration from other PHYs which report the same ID. Also describe
the optional xMII receive-clock inversion required by some integrations.
Describe the three fixed-function package LED outputs using standard
PHY LED children. Their functions are link/activity, 100 Mbps speed
and duplex, and their polarity is shared. Omitting the container
retains the vendor fallback with all outputs active-low; an explicit
container enables only the outputs it describes and may select the
shared polarity.
Allow a PHY with a routed interrupt to be marked as a wakeup source
for magic-packet and link-change Wake-on-LAN.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
.../bindings/net/x-powers,acx00-ephy.yaml | 217 +++++++++++++++++++++
1 file changed, 217 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/x-powers,acx00-ephy.yaml b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy.yaml
new file mode 100644
index 000000000000..3e01f6b44006
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy.yaml
@@ -0,0 +1,217 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/x-powers,acx00-ephy.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: X-Powers AC200 and AC300 Ethernet PHY
+
+maintainers:
+ - James Hilliard <james.hilliard1@gmail.com>
+
+description:
+ The AC200 and AC300 contain compatible Fast Ethernet link-PHY
+ implementations which report the same PHY ID, but use different control
+ interfaces. Systems with a fixed companion IC describe one control
+ interface, which must already be enabled. A fixed AC300 system may provide
+ an NVMEM configuration field to select its calibration range; omitting the
+ field selects the normal range. Systems that can contain either companion IC
+ describe both control interfaces and require the field to select the
+ matching one before the PHY identification registers are accessible. In
+ that dual-control topology, the AC200 I2C controller and AC300 control
+ device may use status "fail-needs-probe" so the selected one can be enabled
+ dynamically. The PHY node's reg value is programmed through the AC200
+ control interface. For AC300, it must match the address selected by the
+ package straps.
+
+select:
+ properties:
+ compatible:
+ contains:
+ const: ethernet-phy-id0044.1400
+ required:
+ - compatible
+ anyOf:
+ - required:
+ - x-powers,ac200-control
+ - required:
+ - x-powers,ac300-control
+
+properties:
+ compatible:
+ const: ethernet-phy-id0044.1400
+
+ nvmem-cells:
+ maxItems: 1
+ description:
+ Logical SoC SID field whose bit 0 selects the AC300 control interface in
+ a dual-control topology and whose bit 1 selects its low-calibration
+ tuning sequence.
+
+ nvmem-cell-names:
+ description:
+ In a dual-control topology the configuration field uses bit 0 to select
+ AC200 (zero) or AC300 (one). Bit 1 selects the AC300 vendor sequence for
+ a low-level calibration value in both dual and fixed AC300 topologies.
+ const: configuration
+
+ x-powers,ac200-control:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: Reference to the AC200 Ethernet PHY control function
+
+ x-powers,ac300-control:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: Reference to the AC300 MDIO Ethernet PHY control device
+
+ x-powers,xmii-rx-clock-inverted:
+ type: boolean
+ description:
+ Invert the xMII receive clock inside the PHY. This is required by some
+ SoC integrations, including the Allwinner H6 AC200 connection.
+
+ wakeup-source:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Indicates that the PHY interrupt is routed to a wake-capable interrupt
+ controller and may be used for magic-packet or link-change Wake-on-LAN.
+
+ leds:
+ type: object
+ description: |
+ Package LED outputs. Output 0 indicates link and flashes for transmit or
+ receive activity, output 1 indicates a 100 Mbps link, and output 2
+ indicates full duplex and flashes on a half-duplex collision. The
+ polarity setting is shared by all outputs, so active-high or active-low
+ declarations on different children must agree. Without an leds
+ container, all three outputs are enabled active-low to match the vendor
+ initialization. An explicit container replaces that fallback: only
+ outputs described by available child nodes are enabled, with active-low
+ used when no child specifies a polarity.
+
+ properties:
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ patternProperties:
+ '^led@[0-2]$':
+ type: object
+ $ref: /schemas/leds/common.yaml#
+
+ properties:
+ reg:
+ enum: [0, 1, 2]
+
+ inactive-high-impedance: false
+
+ required:
+ - reg
+
+ unevaluatedProperties: false
+
+ additionalProperties: false
+
+required:
+ - compatible
+
+oneOf:
+ - properties:
+ nvmem-cells: false
+ nvmem-cell-names: false
+ x-powers,ac300-control: false
+ required:
+ - x-powers,ac200-control
+ - properties:
+ x-powers,ac200-control: false
+ required:
+ - x-powers,ac300-control
+ - required:
+ - nvmem-cells
+ - nvmem-cell-names
+ - x-powers,ac200-control
+ - x-powers,ac300-control
+
+dependentRequired:
+ nvmem-cells: [ nvmem-cell-names ]
+ nvmem-cell-names: [ nvmem-cells ]
+
+allOf:
+ - $ref: ethernet-phy.yaml#
+ - if:
+ required:
+ - x-powers,ac300-control
+ then:
+ properties:
+ reg:
+ maximum: 7
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/leds/common.h>
+
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-phy@0 {
+ compatible = "ethernet-phy-id0044.1400";
+ reg = <0>;
+ nvmem-cells = <&ephy_config>;
+ nvmem-cell-names = "configuration";
+ x-powers,ac300-control = <&ac300_ephy_ctl>;
+
+ leds {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led@0 {
+ reg = <0>;
+ color = <LED_COLOR_ID_GREEN>;
+ function = LED_FUNCTION_LAN;
+ active-low;
+ default-state = "keep";
+ };
+
+ led@1 {
+ reg = <1>;
+ color = <LED_COLOR_ID_YELLOW>;
+ function = LED_FUNCTION_LAN;
+ function-enumerator = <1>;
+ active-low;
+ default-state = "keep";
+ };
+ };
+ };
+ };
+
+ - |
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-phy@0 {
+ compatible = "ethernet-phy-id0044.1400";
+ reg = <0>;
+ x-powers,ac200-control = <&ac200_ephy_ctl>;
+ };
+ };
+
+ - |
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-phy@0 {
+ compatible = "ethernet-phy-id0044.1400";
+ reg = <0>;
+ nvmem-cells = <&ephy_config>;
+ nvmem-cell-names = "configuration";
+ x-powers,ac200-control = <&ac200_ephy_ctl>;
+ x-powers,ac300-control = <&ac300_ephy_ctl>;
+ };
+ };
+...
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 14/21] net: phy: support configuring PHY-autonomous Tx LPI
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (12 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 13/21] dt-bindings: net: x-powers: add AC200/AC300 EPHY James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 15/21] net: phylink: allow PHY-autonomous EEE without MAC LPI James Hilliard
` (10 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
The ethtool Tx LPI fields currently update phylib software state and
notify an attached MAC, but they do not configure PHYs which generate LPI
autonomously. This can make a successful ethtool request leave the PHY
hardware unchanged.
Add a PHY driver callback for applying the Tx LPI enable state and timer.
Call it before updating EEE advertisement, and attempt to restore the old
hardware state if either the callback itself or the advertisement update
fails. Use the callback to disable autonomous LPI when phy_support_eee()
hands LPI control to the MAC, and reapply that state after PHY reset.
Drivers without the new callback retain the existing behavior in which
the Tx LPI fields configure phylib and the attached MAC. Existing drivers
which only implement disable_autonomous_eee() therefore need no changes.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/phy/phy.c | 35 +++++++++++++++++++++++++++++++++--
drivers/net/phy/phy_device.c | 25 ++++++++++++++++++++-----
include/linux/phy.h | 18 ++++++++++++++++++
3 files changed, 71 insertions(+), 7 deletions(-)
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index fce9bc7be330..c76d5baa1f84 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -1996,6 +1996,8 @@ static void phy_ethtool_set_eee_noneg(struct phy_device *phydev,
int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_keee *data)
{
struct eee_config old_cfg;
+ bool tx_lpi_cfg_attempted = false;
+ bool tx_lpi_cfg_changed;
int ret;
if (!phydev->drv)
@@ -2005,16 +2007,45 @@ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_keee *data)
old_cfg = phydev->eee_cfg;
eee_to_eeecfg(&phydev->eee_cfg, data);
+ tx_lpi_cfg_changed = phydev->eee_cfg.tx_lpi_enabled !=
+ old_cfg.tx_lpi_enabled ||
+ phydev->eee_cfg.tx_lpi_timer !=
+ old_cfg.tx_lpi_timer;
+
+ if (tx_lpi_cfg_changed && !phydev->autonomous_eee_disabled &&
+ phydev->drv->set_tx_lpi) {
+ ret = phydev->drv->set_tx_lpi(phydev, &phydev->eee_cfg);
+ tx_lpi_cfg_attempted = true;
+ if (ret)
+ goto restore_tx_lpi;
+ }
ret = genphy_c45_ethtool_set_eee(phydev, data);
if (ret == 0)
phy_ethtool_set_eee_noneg(phydev, &old_cfg);
else if (ret < 0)
- phydev->eee_cfg = old_cfg;
+ goto restore_tx_lpi;
mutex_unlock(&phydev->lock);
- return ret < 0 ? ret : 0;
+ return 0;
+
+restore_tx_lpi:
+ if (tx_lpi_cfg_attempted) {
+ int rollback_ret;
+
+ rollback_ret = phydev->drv->set_tx_lpi(phydev, &old_cfg);
+ if (rollback_ret)
+ phydev_warn(phydev,
+ "Failed to restore autonomous Tx LPI: %pe\n",
+ ERR_PTR(rollback_ret));
+ }
+
+ phydev->eee_cfg = old_cfg;
+
+ mutex_unlock(&phydev->lock);
+
+ return ret;
}
EXPORT_SYMBOL(phy_ethtool_set_eee);
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index f7472a743185..30929b0354b6 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1357,6 +1357,21 @@ static int phy_poll_reset(struct phy_device *phydev)
return 0;
}
+static int phy_disable_autonomous_tx_lpi(struct phy_device *phydev)
+{
+ struct eee_config config = phydev->eee_cfg;
+
+ if (phydev->drv->set_tx_lpi) {
+ config.tx_lpi_enabled = false;
+ return phydev->drv->set_tx_lpi(phydev, &config);
+ }
+
+ if (phydev->drv->disable_autonomous_eee)
+ return phydev->drv->disable_autonomous_eee(phydev);
+
+ return 0;
+}
+
int phy_init_hw(struct phy_device *phydev)
{
int ret = 0;
@@ -1395,9 +1410,8 @@ int phy_init_hw(struct phy_device *phydev)
}
/* Re-apply autonomous EEE disable after soft reset */
- if (phydev->autonomous_eee_disabled &&
- phydev->drv->disable_autonomous_eee) {
- ret = phydev->drv->disable_autonomous_eee(phydev);
+ if (phydev->autonomous_eee_disabled) {
+ ret = phy_disable_autonomous_tx_lpi(phydev);
if (ret)
return ret;
}
@@ -2973,8 +2987,9 @@ void phy_support_eee(struct phy_device *phydev)
* manage LPI signaling instead. The flag is stored so it can be
* re-applied after a PHY soft reset (e.g. suspend/resume).
*/
- if (phydev->drv && phydev->drv->disable_autonomous_eee) {
- int ret = phydev->drv->disable_autonomous_eee(phydev);
+ if (phydev->drv && (phydev->drv->set_tx_lpi ||
+ phydev->drv->disable_autonomous_eee)) {
+ int ret = phy_disable_autonomous_tx_lpi(phydev);
if (ret)
phydev_warn(phydev, "Failed to disable autonomous EEE: %pe\n",
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 2eba54e068f6..55053b914144 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -1391,6 +1391,24 @@ struct phy_driver {
*/
int (*disable_autonomous_eee)(struct phy_device *dev);
+ /**
+ * @set_tx_lpi: Configure PHY-autonomous Tx LPI
+ * @dev: PHY device to configure
+ * @config: Requested Tx LPI enable state and idle timer
+ *
+ * The presence of this callback advertises that the driver supports
+ * configuring PHY-autonomous LPI through the ethtool EEE Tx LPI fields.
+ * It is not used for userspace configuration after phylib has handed LPI
+ * control to the MAC. Drivers without this callback retain the legacy
+ * behavior in which those fields only configure phylib and the attached
+ * MAC. A request with Tx LPI disabled must be accepted regardless of the
+ * timer, since the timer has no meaning while the transmitter is disabled.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+ int (*set_tx_lpi)(struct phy_device *dev,
+ const struct eee_config *config);
+
/* Get and Set PHY tunables */
/** @get_tunable: Return the value of a tunable */
int (*get_tunable)(struct phy_device *dev,
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 15/21] net: phylink: allow PHY-autonomous EEE without MAC LPI
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (13 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 14/21] net: phy: support configuring PHY-autonomous Tx LPI James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 13:23 ` Andrew Lunn
2026-08-03 5:14 ` [PATCH 16/21] net: phy: add X-Powers AC200/AC300 EPHY driver James Hilliard
` (9 subsequent siblings)
24 siblings, 1 reply; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
Phylink treats a MAC which implements its LPI operations but advertises no
LPI capability as incapable of EEE. It disables PHY EEE during attach and
rejects ethtool EEE requests. The same problem occurs when the MAC supports
LPI on another interface but not on the interface currently connected to
the PHY. Both cases are incorrect when the attached PHY can generate LPI
autonomously without help from the MAC.
Recognize the PHY autonomous-EEE capability, preserve its EEE
advertisement, and allow ethtool EEE requests in that configuration.
Evaluate MAC LPI support for the active interface, and restrict EEE modes
to the MAC capability mask only when the MAC actually manages LPI.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/phy/phylink.c | 35 ++++++++++++++++++++++++++++-------
1 file changed, 28 insertions(+), 7 deletions(-)
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 18d2ead97aa5..e64f72793aa0 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1512,6 +1512,19 @@ static void phylink_deactivate_lpi(struct phylink *pl)
}
}
+static bool phylink_phy_supports_autonomous_eee(struct phy_device *phy)
+{
+ return phy && phy->drv && phy->drv->set_tx_lpi;
+}
+
+static bool
+phylink_mac_supports_eee_interface(struct phylink *pl,
+ phy_interface_t interface)
+{
+ return pl->mac_supports_eee &&
+ test_bit(interface, pl->config->lpi_interfaces);
+}
+
static void phylink_activate_lpi(struct phylink *pl)
{
int err;
@@ -2090,6 +2103,7 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
{
struct phylink_link_state config;
__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+ bool mac_eee;
char *irq_str;
int ret;
@@ -2106,6 +2120,7 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
linkmode_copy(supported, phy->supported);
linkmode_copy(config.advertising, phy->advertising);
config.interface = interface;
+ mac_eee = phylink_mac_supports_eee_interface(pl, interface);
ret = phylink_validate_phy(pl, phy, supported, &config);
if (ret) {
@@ -2144,7 +2159,7 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
/* If the MAC supports phylink managed EEE, restrict the EEE
* advertisement according to the MAC's LPI capabilities.
*/
- if (pl->mac_supports_eee) {
+ if (mac_eee) {
/* If EEE is enabled, then we need to call phy_support_eee()
* to ensure that the advertising mask is appropriately set.
* This also enables EEE at the PHY.
@@ -2165,7 +2180,8 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
*/
linkmode_and(phy->advertising_eee, phy->advertising_eee,
pl->supported_lpi);
- } else if (pl->mac_supports_eee_ops) {
+ } else if (pl->mac_supports_eee_ops &&
+ !phylink_phy_supports_autonomous_eee(phy)) {
/* MAC supports phylink EEE, but wants EEE always disabled. */
phy_disable_eee(phy);
}
@@ -3300,17 +3316,20 @@ EXPORT_SYMBOL_GPL(phylink_get_eee_err);
*/
int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_keee *eee)
{
+ bool mac_eee;
int ret = -EOPNOTSUPP;
ASSERT_RTNL();
+ mac_eee = phylink_mac_supports_eee_interface(pl, pl->cur_interface);
- if (pl->mac_supports_eee_ops && !pl->mac_supports_eee)
+ if (pl->mac_supports_eee_ops && !mac_eee &&
+ !phylink_phy_supports_autonomous_eee(pl->phydev))
return ret;
if (pl->phydev) {
ret = phy_ethtool_get_eee(pl->phydev, eee);
/* Restrict supported linkmode mask */
- if (ret == 0 && pl->mac_supports_eee_ops)
+ if (ret == 0 && mac_eee)
linkmode_and(eee->supported, eee->supported,
pl->supported_lpi);
}
@@ -3326,10 +3345,11 @@ EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee);
*/
int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_keee *eee)
{
- bool mac_eee = pl->mac_supports_eee;
+ bool mac_eee;
int ret = -EOPNOTSUPP;
ASSERT_RTNL();
+ mac_eee = phylink_mac_supports_eee_interface(pl, pl->cur_interface);
phylink_dbg(pl, "mac %s phylink EEE%s, adv %*pbl, LPI%s timer %uus\n",
mac_eee ? "supports" : "does not support",
@@ -3337,12 +3357,13 @@ int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_keee *eee)
__ETHTOOL_LINK_MODE_MASK_NBITS, eee->advertised,
eee->tx_lpi_enabled ? " enabled" : "", eee->tx_lpi_timer);
- if (pl->mac_supports_eee_ops && !mac_eee)
+ if (pl->mac_supports_eee_ops && !mac_eee &&
+ !phylink_phy_supports_autonomous_eee(pl->phydev))
return ret;
if (pl->phydev) {
/* Restrict advertisement mask */
- if (pl->mac_supports_eee_ops)
+ if (mac_eee)
linkmode_and(eee->advertised, eee->advertised,
pl->supported_lpi);
ret = phy_ethtool_set_eee(pl->phydev, eee);
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 16/21] net: phy: add X-Powers AC200/AC300 EPHY driver
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (14 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 15/21] net: phylink: allow PHY-autonomous EEE without MAC LPI James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 17/21] arm64: dts: allwinner: h616: add ACx00 EPHY resources James Hilliard
` (8 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
AC200 and AC300 contain compatible Fast Ethernet link-PHY implementations
which report the same ID, but their identification registers are not
accessible until the companion-specific control interface completes
its power-up sequence. Match the ID supplied by firmware and require
an X-Powers control reference because the Rockchip RK630 PHY reports
the same ID.
On fixed hardware, reference one already enabled control device. A fixed
AC300 system may optionally read the configuration field for calibration-
range tuning; its absence selects the normal range. On systems which may
contain either companion, require a logical two-bit NVMEM field to select
the control interface and the AC300 calibration range. Enable a selected
fail-needs-probe node when CONFIG_OF_DYNAMIC is available; otherwise the
same topology works when both candidate providers are already enabled.
Validate that each phandle references the expected control-device type and
restore the selector's status changeset if dynamic device creation fails.
Keep a device link to the selected provider and defer until its driver
binds. Verify that the MAC interface agrees with the provider's phy-mode
before resetting the hardware. Power the control block before each PHY
reset, shut it down during PHY suspend, and restore the vendor
configuration after resume. If control power-off fails during suspend,
attempt to power the PHY back on and restore its link configuration before
returning the error. Pass the link address to the AC200 provider for
EPHY_CTL.PHY_ADDR programming; the AC300 provider instead validates its
package-strapped address relationship.
Apply the common vendor analog initialization and SID-derived tuning while
preserving unrelated register fields. Support a board-selected xMII
receive-clock inversion and report and configure forced or automatic
MDI/MDI-X state.
Expose UAPS through the ethtool energy-detect power-down tunable as its
no-transmit mode. Accumulate the hardware receive-byte, receive-packet and
receive-CRC-error counters across PHY resets, expose all three as driver
statistics and map CRC errors to the standardized PHY receive-error
counter. Treat counter snapshots as best-effort telemetry so a read failure
cannot prevent PHY recovery.
Start both standard EEE advertisement and the PHY-autonomous Intelligent
EEE transmitter disabled, matching the vendor configuration. Preserve the
supported modes so ethtool can enable and configure both at runtime. The
PHY has no documented programmable LPI timer, so reject a nonzero timer
when enabling Tx LPI while accepting any timer value when disabling it.
Clear BMCR_PDOWN before soft reset because ACx00 can acknowledge reset
while powered down without restarting its link logic.
Use the shared PHY interrupt for link changes when firmware provides one;
otherwise retain the normal phylib polling fallback. On wakeup-source
systems with a routed interrupt, expose magic-packet and link-change
Wake-on-LAN, program the detector MAC address for magic wake, and arm the
nested IRQ as a wake source. Keep the PHY powered while either PHY or MAC
Wake-on-LAN is active. During suspend, leave only requested PHY wake events
unmasked, then restore normal link monitoring on resume. Do not advertise
either wake mode in polling mode because the CPU cannot poll while
suspended.
Register standard PHY LEDs for the described package outputs and expose
their fixed link/activity, 100 Mbps speed and duplex rules to the netdev
trigger. Use the vendor fallback of all three outputs active-low when no
LED topology is provided; otherwise apply the described output set and
shared polarity. Quiesce the provider if PHY probing or removal tears down
the link device.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/net/phy/Kconfig | 12 +
drivers/net/phy/Makefile | 1 +
drivers/net/phy/xpowers-acx00.c | 1457 +++++++++++++++++++++++++++++++++++++++
3 files changed, 1470 insertions(+)
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 9b202072d5a0..e3796a2b7be4 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -487,6 +487,18 @@ config XPOWERS_AC300_PHY_CTL
interface mode, clock and I/O controls through the separate MDIO
control address.
+config XPOWERS_ACX00_PHY
+ tristate "X-Powers AC200/AC300 Ethernet PHY"
+ depends on OF_MDIO
+ help
+ Enable the Fast Ethernet PHY driver shared by the X-Powers AC200
+ and AC300 companion ICs. This driver configures the common link PHY
+ and can select the matching control device at runtime on systems which
+ contain either variant. Runtime variant detection requires NVMEM
+ support. Dynamically enabling a control device which is not already
+ available requires OF_DYNAMIC support. Fixed-function package LED
+ outputs can be exposed through the PHY LED framework.
+
config XILINX_GMII2RGMII
tristate "Xilinx GMII2RGMII converter driver"
help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 2d22d5a4c93d..c4b17e1965aa 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -101,4 +101,5 @@ obj-$(CONFIG_TERANETICS_PHY) += teranetics.o
obj-$(CONFIG_VITESSE_PHY) += vitesse.o
obj-$(CONFIG_XPOWERS_AC200_PHY_CTL) += xpowers-ac200-ctl.o
obj-$(CONFIG_XPOWERS_AC300_PHY_CTL) += xpowers-ac300-ctl.o
+obj-$(CONFIG_XPOWERS_ACX00_PHY) += xpowers-acx00.o
obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
diff --git a/drivers/net/phy/xpowers-acx00.c b/drivers/net/phy/xpowers-acx00.c
new file mode 100644
index 000000000000..98d0ee3937ef
--- /dev/null
+++ b/drivers/net/phy/xpowers-acx00.c
@@ -0,0 +1,1457 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * X-Powers AC200/AC300 Ethernet PHY driver
+ *
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/etherdevice.h>
+#include <linux/leds.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "xpowers-acx00.h"
+
+#define ACX00_EPHY_ID 0x00441400
+
+#define ACX00_EPHY_CONFIG_VARIANT_AC300 BIT(0)
+#define ACX00_EPHY_CONFIG_CALIBRATION_LOW BIT(1)
+
+#define ACX00_PAGE_SELECT_REG 0x1f
+#define ACX00_PAGE_SELECT_MASK GENMASK(12, 8)
+#define ACX00_PAGE_0 0
+#define ACX00_PAGE_1 1
+#define ACX00_PAGE_2 2
+#define ACX00_PAGE_6 6
+#define ACX00_PAGE_8 8
+#define ACX00_PAGE_9 9
+
+#define ACX00_PAGE0_INTERRUPT_STATUS_REG 0x10
+#define ACX00_PAGE0_INTERRUPT_MASK_REG 0x11
+#define ACX00_PAGE0_INTERRUPT_LINK_CHANGE BIT(15)
+#define ACX00_PAGE0_INTERRUPT_MAGIC_PACKET BIT(14)
+#define ACX00_PAGE0_INTERRUPT_MANAGED_EVENTS \
+ (ACX00_PAGE0_INTERRUPT_LINK_CHANGE | \
+ ACX00_PAGE0_INTERRUPT_MAGIC_PACKET)
+#define ACX00_PAGE0_GLOBAL_CONFIG_REG 0x13
+#define ACX00_PAGE0_XMII_RX_CLOCK_INVERT BIT(12)
+#define ACX00_PAGE0_MAGIC_PACKET_WAKE_ENABLE BIT(10)
+#define ACX00_PAGE0_MAGIC_PACKET_BROADCAST_ENABLE BIT(8)
+#define ACX00_PAGE0_MAGIC_PACKET_PASSWORD_ENABLE BIT(7)
+#define ACX00_PAGE0_MDI_MODE_MASK GENMASK(1, 0)
+#define ACX00_PAGE0_MDI_MODE_MDI 0
+#define ACX00_PAGE0_MDI_MODE_MDIX 1
+#define ACX00_PAGE0_MDI_MODE_AUTO 2
+#define ACX00_PAGE0_STATUS_REG 0x19
+#define ACX00_PAGE0_STATUS_MDIX BIT(7)
+#define ACX00_PAGE0_MAGIC_PACKET_MAC_REG(_index) (0x16 + (_index))
+
+#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 ACX00_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_100M_DEFAULT 0x15
+#define ACX00_PAGE6_TX_LEVEL_10M_DEFAULT 0x30
+#define ACX00_PAGE6_TX_LEVEL_100M_LOW_CAL 0x35
+#define ACX00_PAGE6_TX_LEVEL_10M_LOW_CAL 0x33
+#define ACX00_PAGE6_TX_LEVEL_DEFAULT_VALUE \
+ ACX00_PAGE6_TX_LEVEL_VALUE(ACX00_PAGE6_TX_LEVEL_100M_DEFAULT, \
+ ACX00_PAGE6_TX_LEVEL_10M_DEFAULT)
+#define ACX00_PAGE6_TX_LEVEL_LOW_CAL_VALUE \
+ ACX00_PAGE6_TX_LEVEL_VALUE(ACX00_PAGE6_TX_LEVEL_100M_LOW_CAL, \
+ ACX00_PAGE6_TX_LEVEL_10M_LOW_CAL)
+
+#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)
+
+#define ACX00_PAGE9_EPGC_COMMAND_REG 0x10
+#define ACX00_PAGE9_EPC_CLEAR_COUNTERS BIT(3)
+#define ACX00_PAGE9_EPG_CLEAR_COUNTERS BIT(2)
+#define ACX00_PAGE9_RX_BYTES_HIGH_REG 0x19
+#define ACX00_PAGE9_RX_BYTES_LOW_REG 0x1a
+#define ACX00_PAGE9_RX_PACKETS_HIGH_REG 0x1b
+#define ACX00_PAGE9_RX_PACKETS_LOW_REG 0x1c
+#define ACX00_PAGE9_RX_CRC_ERRORS_HIGH_REG 0x1d
+#define ACX00_PAGE9_RX_CRC_ERRORS_LOW_REG 0x1e
+
+enum acx00_ephy_stat {
+ ACX00_STAT_RX_BYTES,
+ ACX00_STAT_RX_PACKETS,
+ ACX00_STAT_RX_CRC_ERRORS,
+ ACX00_STAT_COUNT,
+};
+
+static const char acx00_ephy_stat_names[][ETH_GSTRING_LEN] = {
+ [ACX00_STAT_RX_BYTES] = "phy_rx_bytes",
+ [ACX00_STAT_RX_PACKETS] = "phy_rx_packets",
+ [ACX00_STAT_RX_CRC_ERRORS] = "phy_rx_crc_errors",
+};
+
+/*
+ * 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 acx00_ephy_control *control;
+ bool is_ac300;
+ bool use_low_calibration_tuning;
+ bool xmii_rx_clock_inverted;
+ bool eee_initialized;
+ bool uaps_enabled;
+ bool stats_valid;
+ bool wol_irq_enabled;
+ bool wol_suspended;
+ unsigned long led_outputs;
+ int led_polarity_mode;
+ u32 wolopts;
+ u32 stats_last[ACX00_STAT_COUNT];
+ u64 stats[ACX00_STAT_COUNT];
+};
+
+#if IS_ENABLED(CONFIG_OF_DYNAMIC)
+static int acx00_enable_node(struct device *dev, struct device_node *node)
+{
+ struct of_changeset ocs;
+ const char *status;
+ int revert_ret;
+ int ret;
+
+ ret = of_property_read_string(node, "status", &status);
+ if (ret || strcmp(status, "fail-needs-probe"))
+ return dev_err_probe(dev, -EINVAL,
+ "%pOF must use status fail-needs-probe\n",
+ node);
+
+ of_changeset_init(&ocs);
+ ret = of_changeset_update_prop_string(&ocs, node, "status", "okay");
+ if (!ret) {
+ ret = of_changeset_apply(&ocs);
+ if (ret && of_device_is_available(node)) {
+ revert_ret = of_changeset_revert(&ocs);
+ if (revert_ret)
+ dev_err(dev,
+ "failed to restore %pOF after enable error: %pe\n",
+ node, ERR_PTR(revert_ret));
+ }
+ }
+ of_changeset_destroy(&ocs);
+
+ return ret;
+}
+#endif
+
+static int acx00_prepare_node(struct device *dev, struct device_node *node,
+ bool runtime_selection)
+{
+ if (of_device_is_available(node))
+ return 0;
+
+ if (!runtime_selection)
+ return dev_err_probe(dev, -ENODEV,
+ "%pOF must be enabled for static control selection\n",
+ node);
+
+#if IS_ENABLED(CONFIG_OF_DYNAMIC)
+ return acx00_enable_node(dev, node);
+#else
+ return dev_err_probe(dev, -EOPNOTSUPP,
+ "enabling %pOF requires CONFIG_OF_DYNAMIC\n", node);
+#endif
+}
+
+static struct device_node *
+acx00_ac200_get_enable_node(struct device *dev, struct device_node *control)
+{
+ struct device_node *parent;
+ struct device_node *enable_node;
+
+ if (!of_device_is_available(control)) {
+ dev_err(dev, "%pOF must be enabled\n", control);
+ return ERR_PTR(-ENODEV);
+ }
+
+ parent = of_get_parent(control);
+ if (!parent)
+ return ERR_PTR(-EINVAL);
+
+ if (!of_device_is_compatible(parent, "x-powers,ac200")) {
+ dev_err(dev, "%pOF is not an AC200 control function\n", control);
+ of_node_put(parent);
+ return ERR_PTR(-EINVAL);
+ }
+ if (!of_device_is_available(parent)) {
+ dev_err(dev, "%pOF must be enabled\n", parent);
+ of_node_put(parent);
+ return ERR_PTR(-ENODEV);
+ }
+
+ enable_node = of_get_parent(parent);
+ of_node_put(parent);
+ if (!enable_node)
+ return ERR_PTR(-EINVAL);
+
+ return enable_node;
+}
+
+static struct device *
+acx00_find_supplier(struct device_node *control, bool is_ac300)
+{
+ struct platform_device *pdev;
+ struct mdio_device *mdiodev;
+
+ if (is_ac300) {
+ mdiodev = of_mdio_find_device(control);
+ return mdiodev ? &mdiodev->dev : NULL;
+ }
+
+ pdev = of_find_device_by_node(control);
+ return pdev ? &pdev->dev : NULL;
+}
+
+static int acx00_select_control(struct phy_device *phydev, bool is_ac300,
+ bool runtime_selection,
+ unsigned long led_outputs,
+ bool led_active_low,
+ struct acx00_ephy_control **selected_control)
+{
+ struct acx00_ephy_control *ephy_control;
+ struct device *dev = &phydev->mdio.dev;
+ struct device_node *control;
+ struct device_node *enable_node;
+ struct device *supplier;
+ struct device_link *link;
+ const char *control_compatible;
+ const char *control_property;
+ u32 configured_addr;
+ int ret;
+
+ control_property = is_ac300 ? "x-powers,ac300-control" :
+ "x-powers,ac200-control";
+ control_compatible = is_ac300 ? "x-powers,ac300-ephy-ctl" :
+ "x-powers,ac200-ephy-ctl";
+ control = of_parse_phandle(dev->of_node, control_property, 0);
+ if (!control)
+ return dev_err_probe(dev, -EINVAL,
+ "missing %s phandle\n", control_property);
+ if (!of_device_is_compatible(control, control_compatible)) {
+ ret = dev_err_probe(dev, -EINVAL,
+ "%s does not reference a %s device\n",
+ control_property, control_compatible);
+ goto out_put_control;
+ }
+
+ if (is_ac300) {
+ ret = of_property_read_u32(control, "reg", &configured_addr);
+ if (ret || configured_addr != phydev->mdio.addr +
+ AC300_EPHY_CONTROL_ADDR_OFFSET) {
+ ret = dev_err_probe(dev, -EINVAL,
+ "AC300 control address does not match PHY address\n");
+ goto out_put_control;
+ }
+
+ enable_node = of_node_get(control);
+ } else {
+ enable_node = acx00_ac200_get_enable_node(dev, control);
+ if (IS_ERR(enable_node)) {
+ ret = PTR_ERR(enable_node);
+ goto out_put_control;
+ }
+ }
+
+ ret = acx00_prepare_node(dev, enable_node, runtime_selection);
+ of_node_put(enable_node);
+ if (ret)
+ goto out_put_control;
+
+ supplier = acx00_find_supplier(control, is_ac300);
+ if (!supplier) {
+ ret = -EPROBE_DEFER;
+ goto out_put_control;
+ }
+
+ link = device_link_add(dev, supplier, DL_FLAG_AUTOPROBE_CONSUMER);
+ if (!link) {
+ ret = -EINVAL;
+ } else {
+ device_lock(supplier);
+ if (!device_is_bound(supplier)) {
+ ret = -EPROBE_DEFER;
+ } else {
+ ephy_control = dev_get_drvdata(supplier);
+ if (!ephy_control || !ephy_control->power_on ||
+ !ephy_control->power_off ||
+ !ephy_control->set_led_outputs ||
+ !ephy_control->set_led_polarity) {
+ ret = -EINVAL;
+ } else {
+ ret = ephy_control->set_led_polarity(ephy_control,
+ led_active_low);
+ if (!ret)
+ ret = ephy_control->set_led_outputs(ephy_control,
+ led_outputs);
+ if (!ret)
+ ret = ephy_control->power_on(ephy_control,
+ phydev->mdio.addr);
+ if (!ret) {
+ *selected_control = ephy_control;
+ } else {
+ ephy_control->set_led_outputs(ephy_control, 0);
+ ephy_control->set_led_polarity(ephy_control,
+ false);
+ }
+ }
+ }
+ device_unlock(supplier);
+ }
+
+ put_device(supplier);
+ if (ret && ret != -EPROBE_DEFER)
+ ret = dev_err_probe(dev, ret,
+ "failed to enable %s control\n",
+ is_ac300 ? "AC300" : "AC200");
+
+out_put_control:
+ of_node_put(control);
+ return ret;
+}
+
+static int acx00_ephy_parse_leds(struct phy_device *phydev,
+ unsigned long *led_outputs,
+ int *led_polarity_mode)
+{
+ struct device_node *leds;
+ u32 index;
+ int ret = 0;
+
+ /* Match the vendor fallback when firmware provides no LED topology. */
+ *led_outputs = GENMASK(ACX00_EPHY_LED_COUNT - 1, 0);
+ *led_polarity_mode = 1;
+
+ leds = of_get_child_by_name(phydev->mdio.dev.of_node, "leds");
+ if (!leds)
+ return 0;
+
+ /* An explicit container replaces the fallback package configuration. */
+ *led_outputs = 0;
+ *led_polarity_mode = -1;
+
+ for_each_available_child_of_node_scoped(leds, led) {
+ bool active_high = of_property_read_bool(led, "active-high");
+ bool active_low = of_property_read_bool(led, "active-low");
+ int polarity_mode;
+
+ ret = of_property_read_u32(led, "reg", &index);
+ if (ret)
+ break;
+ if (index >= ACX00_EPHY_LED_COUNT ||
+ (*led_outputs & BIT(index))) {
+ ret = -EINVAL;
+ break;
+ }
+ *led_outputs |= BIT(index);
+
+ if (active_high && active_low) {
+ ret = -EINVAL;
+ break;
+ }
+ if (of_property_read_bool(led, "inactive-high-impedance")) {
+ ret = -EOPNOTSUPP;
+ break;
+ }
+ if (!active_high && !active_low)
+ continue;
+
+ polarity_mode = active_low;
+ if (*led_polarity_mode >= 0 &&
+ *led_polarity_mode != polarity_mode) {
+ ret = -EINVAL;
+ break;
+ }
+ *led_polarity_mode = polarity_mode;
+ }
+
+ of_node_put(leds);
+ if (ret)
+ return dev_err_probe(&phydev->mdio.dev, ret,
+ "invalid EPHY LED description\n");
+ if (*led_polarity_mode < 0)
+ *led_polarity_mode = 1;
+
+ return 0;
+}
+
+static void acx00_ephy_power_off(void *data)
+{
+ struct phy_device *phydev = data;
+ struct acx00_ephy_priv *priv = phydev->priv;
+ int ret;
+
+ ret = priv->control->set_led_outputs(priv->control, 0);
+ if (ret)
+ phydev_warn(phydev, "failed to disable LED outputs: %pe\n",
+ ERR_PTR(ret));
+
+ ret = priv->control->set_led_polarity(priv->control, false);
+ if (ret)
+ phydev_warn(phydev, "failed to restore LED polarity: %pe\n",
+ ERR_PTR(ret));
+
+ ret = priv->control->power_off(priv->control);
+ if (ret)
+ phydev_warn(phydev, "failed to power off control block: %pe\n",
+ ERR_PTR(ret));
+}
+
+static int acx00_ephy_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct acx00_ephy_priv *priv;
+ bool has_ac200_control;
+ bool has_ac300_control;
+ bool runtime_selection;
+ u32 configuration = 0;
+ int led_polarity_mode;
+ unsigned long led_outputs;
+ int ret;
+
+ has_ac200_control = of_property_present(dev->of_node,
+ "x-powers,ac200-control");
+ has_ac300_control = of_property_present(dev->of_node,
+ "x-powers,ac300-control");
+ if (!has_ac200_control && !has_ac300_control)
+ return -ENODEV;
+ runtime_selection = has_ac200_control && has_ac300_control;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ ret = acx00_ephy_parse_leds(phydev, &led_outputs,
+ &led_polarity_mode);
+ if (ret)
+ return ret;
+
+ if (has_ac300_control &&
+ (runtime_selection ||
+ of_property_present(dev->of_node, "nvmem-cells"))) {
+ ret = nvmem_cell_read_variable_le_u32(dev, "configuration",
+ &configuration);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read PHY configuration\n");
+ }
+
+ if (runtime_selection) {
+ priv->is_ac300 = configuration &
+ ACX00_EPHY_CONFIG_VARIANT_AC300;
+ } else {
+ priv->is_ac300 = has_ac300_control;
+ }
+ if (priv->is_ac300)
+ priv->use_low_calibration_tuning = configuration &
+ ACX00_EPHY_CONFIG_CALIBRATION_LOW;
+ priv->xmii_rx_clock_inverted =
+ device_property_read_bool(dev,
+ "x-powers,xmii-rx-clock-inverted");
+
+ ret = acx00_select_control(phydev, priv->is_ac300,
+ runtime_selection,
+ led_outputs, led_polarity_mode > 0,
+ &priv->control);
+ if (ret)
+ return ret;
+
+ priv->led_outputs = led_outputs;
+ priv->led_polarity_mode = led_polarity_mode;
+ phydev->priv = priv;
+ ret = devm_add_action_or_reset(dev, acx00_ephy_power_off, phydev);
+ if (ret)
+ return ret;
+
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+
+ /*
+ * The Wake-on-LAN events use the normal PHY interrupt. Only advertise
+ * wake support when firmware describes a routed interrupt.
+ */
+ if (device_property_read_bool(dev, "wakeup-source") &&
+ phy_interrupt_is_valid(phydev))
+ device_set_wakeup_capable(dev, true);
+
+ return 0;
+}
+
+static int acx00_ephy_led_rules(u8 index, unsigned long *rules)
+{
+ switch (index) {
+ case ACX00_EPHY_LED_LINK_ACTIVITY:
+ *rules = BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) |
+ BIT(TRIGGER_NETDEV_RX);
+ break;
+ case ACX00_EPHY_LED_SPEED:
+ *rules = BIT(TRIGGER_NETDEV_LINK_100);
+ break;
+ case ACX00_EPHY_LED_DUPLEX:
+ /* Half-duplex collision flashing is fixed and has no rule bit. */
+ *rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int acx00_ephy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ unsigned long supported_rules;
+ int ret;
+
+ if (index >= ACX00_EPHY_LED_COUNT ||
+ !test_bit(index, &priv->led_outputs))
+ return -EINVAL;
+
+ ret = acx00_ephy_led_rules(index, &supported_rules);
+ if (ret)
+ return ret;
+
+ return rules == supported_rules ? 0 : -EOPNOTSUPP;
+}
+
+static int acx00_ephy_led_hw_control_set(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ return acx00_ephy_led_hw_is_supported(phydev, index, rules);
+}
+
+static int acx00_ephy_led_hw_control_get(struct phy_device *phydev, u8 index,
+ unsigned long *rules)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+
+ if (index >= ACX00_EPHY_LED_COUNT ||
+ !test_bit(index, &priv->led_outputs))
+ return -EINVAL;
+
+ return acx00_ephy_led_rules(index, rules);
+}
+
+static int acx00_ephy_led_polarity_set(struct phy_device *phydev, int index,
+ unsigned long modes)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ bool active_high;
+ bool active_low;
+ int polarity_mode;
+ int ret;
+
+ if (index < 0 || index >= ACX00_EPHY_LED_COUNT ||
+ !test_bit(index, &priv->led_outputs))
+ return -EINVAL;
+
+ active_high = test_bit(PHY_LED_ACTIVE_HIGH, &modes);
+ active_low = test_bit(PHY_LED_ACTIVE_LOW, &modes);
+ if (modes & ~(BIT(PHY_LED_ACTIVE_HIGH) |
+ BIT(PHY_LED_ACTIVE_LOW)))
+ return -EOPNOTSUPP;
+ if (active_high == active_low)
+ return -EINVAL;
+
+ polarity_mode = active_low;
+ if (priv->led_polarity_mode != polarity_mode) {
+ phydev_err(phydev,
+ "LED polarity is global and must match for all outputs\n");
+ return -EINVAL;
+ }
+
+ ret = priv->control->set_led_polarity(priv->control, active_low);
+
+ return ret;
+}
+
+static int acx00_ephy_read_counter(struct phy_device *phydev, u32 high_reg,
+ u32 low_reg, u32 *counter)
+{
+ int high;
+ int high_check;
+ int low;
+
+ do {
+ high = __phy_read(phydev, high_reg);
+ if (high < 0)
+ return high;
+
+ low = __phy_read(phydev, low_reg);
+ if (low < 0)
+ return low;
+
+ high_check = __phy_read(phydev, high_reg);
+ if (high_check < 0)
+ return high_check;
+ } while (high != high_check);
+
+ *counter = (u32)high << 16 | low;
+
+ return 0;
+}
+
+static int acx00_ephy_update_stats(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ u32 counters[ACX00_STAT_COUNT];
+ int oldpage;
+ int ret;
+ int i;
+
+ oldpage = phy_select_page(phydev, ACX00_PAGE_9);
+ if (oldpage < 0)
+ return phy_restore_page(phydev, oldpage, 0);
+
+ ret = acx00_ephy_read_counter(phydev,
+ ACX00_PAGE9_RX_BYTES_HIGH_REG,
+ ACX00_PAGE9_RX_BYTES_LOW_REG,
+ &counters[ACX00_STAT_RX_BYTES]);
+ if (ret)
+ goto out_restore_page;
+
+ ret = acx00_ephy_read_counter(phydev,
+ ACX00_PAGE9_RX_PACKETS_HIGH_REG,
+ ACX00_PAGE9_RX_PACKETS_LOW_REG,
+ &counters[ACX00_STAT_RX_PACKETS]);
+ if (ret)
+ goto out_restore_page;
+
+ ret = acx00_ephy_read_counter(phydev,
+ ACX00_PAGE9_RX_CRC_ERRORS_HIGH_REG,
+ ACX00_PAGE9_RX_CRC_ERRORS_LOW_REG,
+ &counters[ACX00_STAT_RX_CRC_ERRORS]);
+
+out_restore_page:
+ ret = phy_restore_page(phydev, oldpage, ret);
+ if (ret)
+ return ret;
+
+ if (priv->stats_valid) {
+ for (i = 0; i < ACX00_STAT_COUNT; i++)
+ priv->stats[i] += counters[i] - priv->stats_last[i];
+ }
+
+ memcpy(priv->stats_last, counters, sizeof(counters));
+ priv->stats_valid = true;
+
+ return 0;
+}
+
+static void acx00_ephy_snapshot_and_invalidate_stats(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ int ret;
+
+ if (!priv->stats_valid)
+ return;
+
+ ret = acx00_ephy_update_stats(phydev);
+ if (ret)
+ phydev_dbg(phydev, "failed to snapshot statistics: %pe\n",
+ ERR_PTR(ret));
+ priv->stats_valid = false;
+}
+
+static int acx00_ephy_validate_interface(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+
+ if (phydev->interface == priv->control->interface)
+ return 0;
+
+ phydev_err(phydev, "MAC uses %s but control device is configured for %s\n",
+ phy_modes(phydev->interface),
+ phy_modes(priv->control->interface));
+
+ return -EINVAL;
+}
+
+static int acx00_ephy_soft_reset(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ int ret;
+
+ ret = acx00_ephy_validate_interface(phydev);
+ if (ret)
+ return ret;
+
+ /* The control sequence below may reset the hardware counters. */
+ acx00_ephy_snapshot_and_invalidate_stats(phydev);
+
+ ret = priv->control->power_on(priv->control, phydev->mdio.addr);
+ if (ret)
+ return ret;
+
+ /*
+ * ACx00 acknowledges reset while powered down but does not restart.
+ * This can happen when reattaching a previously suspended PHY.
+ */
+ ret = genphy_resume(phydev);
+ if (ret)
+ return ret;
+
+ return genphy_soft_reset(phydev);
+}
+
+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_ack_interrupt(struct phy_device *phydev)
+{
+ int status;
+
+ status = __phy_read(phydev, ACX00_PAGE0_INTERRUPT_STATUS_REG);
+ if (status < 0)
+ return status;
+
+ /* All interrupt status bits are write-one-to-clear. */
+ return __phy_write(phydev, ACX00_PAGE0_INTERRUPT_STATUS_REG, status);
+}
+
+static int __acx00_ephy_config_interrupts(struct phy_device *phydev,
+ u32 wolopts, bool wake_only)
+{
+ u16 disable;
+ u16 mask = 0;
+ int ret;
+
+ if ((!wake_only && phydev->interrupts == PHY_INTERRUPT_ENABLED) ||
+ (wolopts & WAKE_PHY))
+ mask |= ACX00_PAGE0_INTERRUPT_LINK_CHANGE;
+ if (wolopts & WAKE_MAGIC)
+ mask |= ACX00_PAGE0_INTERRUPT_MAGIC_PACKET;
+
+ /* Mask sources being removed before clearing their latched status. */
+ disable = ACX00_PAGE0_INTERRUPT_MANAGED_EVENTS & ~mask;
+ if (disable) {
+ ret = __phy_modify(phydev, ACX00_PAGE0_INTERRUPT_MASK_REG,
+ disable, 0);
+ if (ret)
+ return ret;
+ }
+
+ ret = __acx00_ephy_ack_interrupt(phydev);
+ if (ret)
+ return ret;
+
+ return __phy_modify(phydev, ACX00_PAGE0_INTERRUPT_MASK_REG,
+ ACX00_PAGE0_INTERRUPT_MANAGED_EVENTS, mask);
+}
+
+static int acx00_ephy_config_interrupts(struct phy_device *phydev,
+ u32 wolopts, bool wake_only)
+{
+ int oldpage;
+ int ret;
+
+ oldpage = phy_select_page(phydev, ACX00_PAGE_0);
+ if (oldpage < 0)
+ return phy_restore_page(phydev, oldpage, 0);
+
+ ret = __acx00_ephy_config_interrupts(phydev, wolopts, wake_only);
+
+ return phy_restore_page(phydev, oldpage, ret);
+}
+
+static int acx00_ephy_config_intr(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+
+ return acx00_ephy_config_interrupts(phydev, priv->wolopts,
+ priv->wol_suspended);
+}
+
+static irqreturn_t acx00_ephy_handle_interrupt(struct phy_device *phydev)
+{
+ unsigned int active = 0;
+ int oldpage;
+ int enabled;
+ int status;
+ int ret = 0;
+
+ oldpage = phy_select_page(phydev, ACX00_PAGE_0);
+ if (oldpage < 0) {
+ ret = phy_restore_page(phydev, oldpage, 0);
+ goto out_error;
+ }
+
+ enabled = __phy_read(phydev, ACX00_PAGE0_INTERRUPT_MASK_REG);
+ if (enabled < 0) {
+ ret = enabled;
+ goto out_restore_page;
+ }
+
+ status = __phy_read(phydev, ACX00_PAGE0_INTERRUPT_STATUS_REG);
+ if (status < 0) {
+ ret = status;
+ goto out_restore_page;
+ }
+
+ active = status & enabled;
+ if (active)
+ /* Clear every event latched in the write-one-to-clear register. */
+ ret = __phy_write(phydev, ACX00_PAGE0_INTERRUPT_STATUS_REG,
+ status);
+
+out_restore_page:
+ ret = phy_restore_page(phydev, oldpage, ret);
+ if (ret)
+ goto out_error;
+
+ if (!active)
+ return IRQ_NONE;
+
+ if (active & ACX00_PAGE0_INTERRUPT_LINK_CHANGE)
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+
+out_error:
+ phy_error(phydev);
+ return IRQ_NONE;
+}
+
+/*
+ * Arm the nested interrupt as a wake source immediately. The AC200 is on an
+ * I2C bus, so deferring this until the noirq suspend phase could require an
+ * I2C register update after the controller has already been suspended.
+ */
+static int acx00_ephy_set_wake_irq(struct phy_device *phydev, bool enable)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ int ret;
+
+ if (priv->wol_irq_enabled == enable)
+ return 0;
+ if (!phy_interrupt_is_valid(phydev))
+ return enable ? -EOPNOTSUPP : 0;
+
+ if (enable)
+ ret = enable_irq_wake(phydev->irq);
+ else
+ ret = disable_irq_wake(phydev->irq);
+ if (ret)
+ return ret;
+
+ priv->wol_irq_enabled = enable;
+
+ return 0;
+}
+
+static void acx00_ephy_get_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+
+ wol->supported = 0;
+ wol->wolopts = 0;
+
+ if (!device_can_wakeup(dev) || !phy_interrupt_is_valid(phydev))
+ return;
+
+ wol->supported = WAKE_MAGIC | WAKE_PHY;
+ wol->wolopts = priv->wolopts;
+}
+
+static int acx00_ephy_config_wol(struct phy_device *phydev, u32 wolopts)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ struct net_device *ndev = phydev->attached_dev;
+ const u8 *mac;
+ int oldpage;
+ int ret = 0;
+ int i;
+
+ if (wolopts & WAKE_MAGIC && !ndev)
+ return -ENODEV;
+
+ oldpage = phy_select_page(phydev, ACX00_PAGE_0);
+ if (oldpage < 0)
+ return phy_restore_page(phydev, oldpage, 0);
+
+ if (wolopts & WAKE_MAGIC) {
+ /* The detector stores the MAC address big-endian in three registers. */
+ mac = ndev->dev_addr;
+ for (i = 0; i < ETH_ALEN / 2; i++) {
+ ret = __phy_write(phydev,
+ ACX00_PAGE0_MAGIC_PACKET_MAC_REG(i),
+ mac[2 * i] << 8 | mac[2 * i + 1]);
+ if (ret)
+ goto out_restore_page;
+ }
+ }
+
+ ret = __phy_modify(phydev, ACX00_PAGE0_GLOBAL_CONFIG_REG,
+ ACX00_PAGE0_MAGIC_PACKET_WAKE_ENABLE |
+ ACX00_PAGE0_MAGIC_PACKET_BROADCAST_ENABLE |
+ ACX00_PAGE0_MAGIC_PACKET_PASSWORD_ENABLE,
+ wolopts & WAKE_MAGIC ?
+ ACX00_PAGE0_MAGIC_PACKET_WAKE_ENABLE |
+ ACX00_PAGE0_MAGIC_PACKET_BROADCAST_ENABLE : 0);
+ if (ret)
+ goto out_restore_page;
+
+ ret = __acx00_ephy_config_interrupts(phydev, wolopts,
+ priv->wol_suspended);
+
+out_restore_page:
+ return phy_restore_page(phydev, oldpage, ret);
+}
+
+static int acx00_ephy_set_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ u32 old_wolopts = priv->wolopts;
+ bool enable = !!wol->wolopts;
+ bool old_enable = !!old_wolopts;
+ int rollback_ret;
+ int ret;
+
+ if (wol->wolopts & ~(WAKE_MAGIC | WAKE_PHY))
+ return -EOPNOTSUPP;
+ if (enable && (!device_can_wakeup(dev) ||
+ !phy_interrupt_is_valid(phydev)))
+ return -EOPNOTSUPP;
+
+ ret = acx00_ephy_config_wol(phydev, wol->wolopts);
+ if (ret)
+ goto out_rollback;
+
+ ret = acx00_ephy_set_wake_irq(phydev, enable);
+ if (ret)
+ goto out_rollback;
+
+ ret = device_set_wakeup_enable(dev, enable);
+ if (ret)
+ goto out_rollback;
+
+ priv->wolopts = wol->wolopts;
+
+ return 0;
+
+out_rollback:
+ rollback_ret = acx00_ephy_config_wol(phydev, old_wolopts);
+ if (rollback_ret)
+ phydev_warn(phydev,
+ "failed to restore Wake-on-LAN configuration: %pe\n",
+ ERR_PTR(rollback_ret));
+
+ rollback_ret = acx00_ephy_set_wake_irq(phydev, old_enable);
+ if (rollback_ret)
+ phydev_warn(phydev,
+ "failed to restore wake IRQ state: %pe\n",
+ ERR_PTR(rollback_ret));
+
+ rollback_ret = device_set_wakeup_enable(dev, old_enable);
+ if (rollback_ret)
+ phydev_warn(phydev,
+ "failed to restore device wake state: %pe\n",
+ ERR_PTR(rollback_ret));
+
+ return ret;
+}
+
+static int acx00_ephy_config_init(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ bool use_low_calibration_tuning = priv->use_low_calibration_tuning;
+ bool is_ac300 = priv->is_ac300;
+ u16 afe_eq_rx_detect = ACX00_PAGE6_AFE_EQ_RX_DETECT_VALUE;
+ u16 tx_level_value = ACX00_PAGE6_TX_LEVEL_DEFAULT_VALUE;
+ int oldpage;
+ int ret;
+ bool tx_lpi_enabled;
+
+ /* Configuration clears the hardware counters below. */
+ acx00_ephy_snapshot_and_invalidate_stats(phydev);
+
+ if (!priv->eee_initialized) {
+ /* The vendor configuration starts with both EEE modes disabled. */
+ phydev->eee_cfg.eee_enabled = false;
+ phydev->eee_cfg.tx_lpi_enabled = false;
+ priv->eee_initialized = true;
+ }
+ tx_lpi_enabled = phydev->eee_cfg.tx_lpi_enabled &&
+ !phydev->autonomous_eee_disabled;
+
+ ret = phy_modify_paged(phydev, ACX00_PAGE_0,
+ ACX00_PAGE0_GLOBAL_CONFIG_REG,
+ ACX00_PAGE0_XMII_RX_CLOCK_INVERT,
+ priv->xmii_rx_clock_inverted ?
+ ACX00_PAGE0_XMII_RX_CLOCK_INVERT : 0);
+ if (ret)
+ return ret;
+
+ if (is_ac300) {
+ if (use_low_calibration_tuning) {
+ afe_eq_rx_detect =
+ ACX00_PAGE6_AFE_EQ_RX_DETECT_LOW_CAL_VALUE;
+ tx_level_value = ACX00_PAGE6_TX_LEVEL_LOW_CAL_VALUE;
+ }
+ }
+
+ oldpage = phy_select_page(phydev, ACX00_PAGE_1);
+ if (oldpage < 0)
+ return phy_restore_page(phydev, oldpage, 0);
+
+ 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,
+ priv->uaps_enabled ? ACX00_PAGE1_UAPS_ENABLE : 0);
+ if (ret)
+ goto out_restore_page;
+ /* Intelligent EEE lets the PHY generate LPI without MAC support. */
+ ret = __phy_modify(phydev, ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG,
+ ACX00_PAGE1_INTELLIGENT_EEE_ENABLE,
+ tx_lpi_enabled ?
+ 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 (is_ac300 && 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 (is_ac300 && 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);
+ if (ret)
+ goto out_restore_page;
+
+ ret = acx00_ephy_write_page(phydev, ACX00_PAGE_9);
+ if (ret)
+ goto out_restore_page;
+ ret = __phy_write(phydev, ACX00_PAGE9_EPGC_COMMAND_REG,
+ ACX00_PAGE9_EPC_CLEAR_COUNTERS |
+ ACX00_PAGE9_EPG_CLEAR_COUNTERS);
+ if (!ret) {
+ memset(priv->stats_last, 0, sizeof(priv->stats_last));
+ priv->stats_valid = true;
+ }
+
+out_restore_page:
+ ret = phy_restore_page(phydev, oldpage, ret);
+ if (ret)
+ return ret;
+
+ /* Apply the retained Linux EEE policy after every hardware reset. */
+ ret = genphy_c45_an_config_eee_aneg(phydev);
+ if (ret < 0)
+ return ret;
+
+ /* A soft reset also clears the retained Wake-on-LAN configuration. */
+ return acx00_ephy_config_wol(phydev, priv->wolopts);
+}
+
+static int acx00_ephy_set_intelligent_eee(struct phy_device *phydev,
+ bool enable)
+{
+ return phy_modify_paged(phydev, ACX00_PAGE_1,
+ ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG,
+ ACX00_PAGE1_INTELLIGENT_EEE_ENABLE,
+ enable ? ACX00_PAGE1_INTELLIGENT_EEE_ENABLE : 0);
+}
+
+static int acx00_ephy_set_tx_lpi(struct phy_device *phydev,
+ const struct eee_config *config)
+{
+ if (config->tx_lpi_enabled && config->tx_lpi_timer)
+ return -EOPNOTSUPP;
+
+ return acx00_ephy_set_intelligent_eee(phydev,
+ config->tx_lpi_enabled);
+}
+
+static int acx00_ephy_config_aneg(struct phy_device *phydev)
+{
+ u16 mode;
+ int ret;
+
+ switch (phydev->mdix_ctrl) {
+ case ETH_TP_MDI:
+ mode = ACX00_PAGE0_MDI_MODE_MDI;
+ break;
+ case ETH_TP_MDI_X:
+ mode = ACX00_PAGE0_MDI_MODE_MDIX;
+ break;
+ case ETH_TP_MDI_AUTO:
+ mode = ACX00_PAGE0_MDI_MODE_AUTO;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = phy_modify_paged_changed(phydev, ACX00_PAGE_0,
+ ACX00_PAGE0_GLOBAL_CONFIG_REG,
+ ACX00_PAGE0_MDI_MODE_MASK,
+ FIELD_PREP(ACX00_PAGE0_MDI_MODE_MASK,
+ mode));
+ if (ret < 0)
+ return ret;
+
+ return __genphy_config_aneg(phydev, ret > 0);
+}
+
+static int acx00_ephy_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_read_status(phydev);
+ if (ret)
+ return ret;
+
+ if (!phydev->link) {
+ phydev->mdix = ETH_TP_MDI_INVALID;
+ return 0;
+ }
+
+ ret = phy_read_paged(phydev, ACX00_PAGE_0, ACX00_PAGE0_STATUS_REG);
+ if (ret < 0)
+ return ret;
+
+ phydev->mdix = ret & ACX00_PAGE0_STATUS_MDIX ? ETH_TP_MDI_X :
+ ETH_TP_MDI;
+
+ return 0;
+}
+
+static int acx00_ephy_get_sset_count(struct phy_device *phydev)
+{
+ return ACX00_STAT_COUNT;
+}
+
+static void acx00_ephy_get_strings(struct phy_device *phydev, u8 *data)
+{
+ unsigned int i;
+
+ for (i = 0; i < ACX00_STAT_COUNT; i++)
+ ethtool_puts(&data, acx00_ephy_stat_names[i]);
+}
+
+static void acx00_ephy_get_stats(struct phy_device *phydev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ unsigned int i;
+ int ret;
+
+ ret = acx00_ephy_update_stats(phydev);
+ for (i = 0; i < ACX00_STAT_COUNT; i++)
+ data[i] = ret ? U64_MAX : priv->stats[i];
+}
+
+static void acx00_ephy_get_phy_stats(struct phy_device *phydev,
+ struct ethtool_eth_phy_stats *eth_stats,
+ struct ethtool_phy_stats *stats)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+
+ stats->rx_errors = priv->stats[ACX00_STAT_RX_CRC_ERRORS];
+}
+
+static int acx00_ephy_get_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tunable, void *data)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ int ret;
+
+ if (tunable->id != ETHTOOL_PHY_EDPD)
+ return -EOPNOTSUPP;
+
+ ret = phy_read_paged(phydev, ACX00_PAGE_1,
+ ACX00_PAGE1_UAPS_CONTROL_REG);
+ if (ret < 0)
+ return ret;
+ priv->uaps_enabled = ret & ACX00_PAGE1_UAPS_ENABLE;
+
+ *(u16 *)data = priv->uaps_enabled ? ETHTOOL_PHY_EDPD_NO_TX :
+ ETHTOOL_PHY_EDPD_DISABLE;
+
+ return 0;
+}
+
+static int acx00_ephy_set_tunable(struct phy_device *phydev,
+ struct ethtool_tunable *tunable,
+ const void *data)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ bool enable;
+ u16 edpd;
+ int ret;
+
+ if (tunable->id != ETHTOOL_PHY_EDPD)
+ return -EOPNOTSUPP;
+
+ edpd = *(const u16 *)data;
+ switch (edpd) {
+ case ETHTOOL_PHY_EDPD_DISABLE:
+ enable = false;
+ break;
+ case ETHTOOL_PHY_EDPD_NO_TX:
+ enable = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = phy_modify_paged(phydev, ACX00_PAGE_1,
+ ACX00_PAGE1_UAPS_CONTROL_REG,
+ ACX00_PAGE1_UAPS_ENABLE,
+ enable ? ACX00_PAGE1_UAPS_ENABLE : 0);
+ if (!ret)
+ priv->uaps_enabled = enable;
+
+ return ret;
+}
+
+static int acx00_ephy_power_on_and_resume(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ int ret;
+
+ ret = priv->control->power_on(priv->control, phydev->mdio.addr);
+ if (ret)
+ return ret;
+
+ ret = genphy_resume(phydev);
+ if (ret)
+ return ret;
+
+ /* The control block loses the link-PHY vendor state while powered off. */
+ return acx00_ephy_config_init(phydev);
+}
+
+static int acx00_ephy_resume(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ int ret;
+
+ ret = acx00_ephy_validate_interface(phydev);
+ if (ret)
+ return ret;
+
+ if (priv->wol_suspended) {
+ ret = acx00_ephy_config_interrupts(phydev, priv->wolopts,
+ false);
+ if (ret)
+ return ret;
+
+ priv->wol_suspended = false;
+ return 0;
+ }
+
+ 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;
+
+ mutex_lock(&phydev->lock);
+ if (priv->stats_valid)
+ acx00_ephy_update_stats(phydev);
+ mutex_unlock(&phydev->lock);
+
+ if (phydev->wol_enabled) {
+ ret = acx00_ephy_config_interrupts(phydev, priv->wolopts,
+ true);
+ if (!ret)
+ priv->wol_suspended = true;
+
+ return ret;
+ }
+
+ ret = genphy_suspend(phydev);
+ if (ret)
+ return ret;
+
+ ret = priv->control->power_off(priv->control);
+
+ mutex_lock(&phydev->lock);
+ priv->stats_valid = false;
+ mutex_unlock(&phydev->lock);
+ 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 void acx00_ephy_remove(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ int ret;
+
+ ret = acx00_ephy_set_wake_irq(phydev, false);
+ if (ret)
+ phydev_warn(phydev, "failed to disarm wake IRQ: %pe\n",
+ ERR_PTR(ret));
+
+ device_set_wakeup_enable(dev, false);
+ device_set_wakeup_capable(dev, false);
+}
+
+static int acx00_ephy_match_phy_device(struct phy_device *phydev,
+ const struct phy_driver *phydrv)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+
+ if (!genphy_match_phy_device(phydev, phydrv) || !node)
+ return 0;
+
+ /* RK630 reports the same PHY ID, so require an X-Powers control link. */
+ return of_property_present(node, "x-powers,ac200-control") ||
+ of_property_present(node, "x-powers,ac300-control");
+}
+
+static struct phy_driver acx00_ephy_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(ACX00_EPHY_ID),
+ .name = "X-Powers AC200/AC300 EPHY",
+ .flags = PHY_ALWAYS_CALL_SUSPEND,
+ .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,
+ .config_aneg = acx00_ephy_config_aneg,
+ .read_status = acx00_ephy_read_status,
+ .config_intr = acx00_ephy_config_intr,
+ .handle_interrupt = acx00_ephy_handle_interrupt,
+ .get_wol = acx00_ephy_get_wol,
+ .set_wol = acx00_ephy_set_wol,
+ .remove = acx00_ephy_remove,
+ .set_tx_lpi = acx00_ephy_set_tx_lpi,
+ .get_sset_count = acx00_ephy_get_sset_count,
+ .get_strings = acx00_ephy_get_strings,
+ .get_stats = acx00_ephy_get_stats,
+ .get_phy_stats = acx00_ephy_get_phy_stats,
+ .update_stats = acx00_ephy_update_stats,
+ .get_tunable = acx00_ephy_get_tunable,
+ .set_tunable = acx00_ephy_set_tunable,
+ .led_hw_is_supported = acx00_ephy_led_hw_is_supported,
+ .led_hw_control_set = acx00_ephy_led_hw_control_set,
+ .led_hw_control_get = acx00_ephy_led_hw_control_get,
+ .led_polarity_set = acx00_ephy_led_polarity_set,
+ .suspend = acx00_ephy_suspend,
+ .resume = acx00_ephy_resume,
+ },
+};
+module_phy_driver(acx00_ephy_driver);
+
+static const struct mdio_device_id __maybe_unused acx00_ephy_tbl[] = {
+ { PHY_ID_MATCH_MODEL(ACX00_EPHY_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE(mdio, acx00_ephy_tbl);
+
+MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
+MODULE_DESCRIPTION("X-Powers AC200/AC300 Ethernet PHY driver");
+MODULE_LICENSE("GPL");
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 17/21] arm64: dts: allwinner: h616: add ACx00 EPHY resources
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (15 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 16/21] net: phy: add X-Powers AC200/AC300 EPHY driver James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 18/21] arm64: dts: allwinner: orangepi-zero2w: enable Ethernet expansion James Hilliard
` (7 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
Expose logical SID cells for the AC200/AC300 EPHY calibration, the combined
companion-variant and AC300-calibration-range configuration, and the AC200
bandgap fields.
Also describe the PA10/PA11 I2C3 pin group used by the AC200 control
interface.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
index 6879f27c2b2b..2cedda6e87a9 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
@@ -252,6 +252,20 @@ ths_calibration: thermal-sensor-calibration@14 {
cpu_speed_grade: cpu-speed-grade@0 {
reg = <0x0 2>;
};
+
+ ephy_calibration: ethernet-phy-calibration@2c,0 {
+ reg = <0x2c 1>;
+ bits = <0 4>;
+ };
+
+ ephy_config: ethernet-phy-configuration@2c {
+ reg = <0x2c 2>;
+ bits = <8 2>;
+ };
+
+ ac200_bandgap: bandgap@30 {
+ reg = <0x30 2>;
+ };
};
timer0: timer@3009000 {
@@ -324,6 +338,13 @@ i2c0_pins: i2c0-pins {
function = "i2c0";
};
+ /omit-if-no-ref/
+ i2c3_pa_pins: i2c3-pa-pins {
+ pins = "PA10", "PA11";
+ function = "i2c3";
+ bias-pull-up;
+ };
+
i2c3_ph_pins: i2c3-ph-pins {
pins = "PH4", "PH5";
function = "i2c3";
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 18/21] arm64: dts: allwinner: orangepi-zero2w: enable Ethernet expansion
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (16 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 17/21] arm64: dts: allwinner: h616: add ACx00 EPHY resources James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 19/21] arm64: dts: allwinner: h6: add AC200 EPHY resources James Hilliard
` (6 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
The optional Orange Pi Zero 2W expansion board exposes H618 EMAC1 through
a 100 Mbit Ethernet jack.
Add a reusable H616-family description of the AC200/AC300 EPHY wiring
alongside its first board user. The forced-ID link PHY selects the
populated control path and AC300 tuning range from the logical SID
configuration field. Keep the AC200 I2C controller and AC300 MDIO control
node at status fail-needs-probe so only the selected path claims its pins.
Enable EMAC1 and its PWM clock provider through the common include,
provide the board's 3.3 V rail to either companion variant, and make EMAC1
the board Ethernet alias.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
.../boot/dts/allwinner/sun50i-h616-acx00-ephy.dtsi | 61 ++++++++++++++++++++++
.../dts/allwinner/sun50i-h618-orangepi-zero2w.dts | 13 +++++
2 files changed, 74 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616-acx00-ephy.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616-acx00-ephy.dtsi
new file mode 100644
index 000000000000..26521429957a
--- /dev/null
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-acx00-ephy.dtsi
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Common X-Powers AC200/AC300 Ethernet PHY support for H616-family SoCs
+ *
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ */
+
+&emac1 {
+ phy-handle = <&acx00_ephy>;
+ status = "okay";
+};
+
+&i2c3 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2c3_pa_pins>;
+ status = "fail-needs-probe";
+
+ ac200: mixed-signal@10 {
+ compatible = "x-powers,ac200";
+ reg = <0x10>;
+ clocks = <&pwm 5>;
+ nvmem-cells = <&ac200_bandgap>;
+ nvmem-cell-names = "bandgap";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pwm5_pin>;
+
+ ac200_ephy_ctl: ethernet-phy-control {
+ compatible = "x-powers,ac200-ephy-ctl";
+ nvmem-cells = <&ephy_calibration>;
+ nvmem-cell-names = "calibration";
+ phy-mode = "rmii";
+ };
+ };
+};
+
+&mdio1 {
+ ac300_ephy_ctl: ethernet-phy-control@10 {
+ compatible = "x-powers,ac300-ephy-ctl";
+ reg = <0x10>;
+ clocks = <&pwm 5>;
+ nvmem-cells = <&ephy_calibration>;
+ nvmem-cell-names = "calibration";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pwm5_pin>;
+ phy-mode = "rmii";
+ status = "fail-needs-probe";
+ };
+
+ acx00_ephy: ethernet-phy@0 {
+ compatible = "ethernet-phy-id0044.1400";
+ reg = <0>;
+ nvmem-cells = <&ephy_config>;
+ nvmem-cell-names = "configuration";
+ x-powers,ac200-control = <&ac200_ephy_ctl>;
+ x-powers,ac300-control = <&ac300_ephy_ctl>;
+ };
+};
+
+&pwm {
+ status = "okay";
+};
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts b/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts
index b340bbcb710d..e833ee267c83 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h618-orangepi-zero2w.dts
@@ -6,6 +6,7 @@
/dts-v1/;
#include "sun50i-h616.dtsi"
+#include "sun50i-h616-acx00-ephy.dtsi"
#include "sun50i-h616-cpu-opp.dtsi"
#include <dt-bindings/gpio/gpio.h>
@@ -17,6 +18,7 @@ / {
compatible = "xunlong,orangepi-zero2w", "allwinner,sun50i-h618";
aliases {
+ ethernet0 = &emac1;
serial0 = &uart0;
};
@@ -54,6 +56,17 @@ reg_vcc3v3: vcc3v3 {
};
};
+&ac200 {
+ ac-ldoin-supply = <®_vcc3v3>;
+ ephy-vcc-supply = <®_vcc3v3>;
+ rtc-vcc-supply = <®_vcc3v3>;
+ tv-vcc-supply = <®_vcc3v3>;
+};
+
+&ac300_ephy_ctl {
+ vcc1-supply = <®_vcc3v3>;
+};
+
&codec {
allwinner,audio-routing = "Line Out", "LINEOUT";
status = "okay";
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 19/21] arm64: dts: allwinner: h6: add AC200 EPHY resources
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (17 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 18/21] arm64: dts: allwinner: orangepi-zero2w: enable Ethernet expansion James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 20/21] arm64: dts: allwinner: h6: tanix: enable AC200 EPHY James Hilliard
` (5 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
Add the H6 I2C3 controller and pinmux, the PWM-derived 24 MHz AC200
clock, SID calibration cells, RMII pinmux, and the AC200 MFD and EPHY
control nodes.
Describe the co-packaged AC200 INTB output on PB20. Keep both reusable
AC200 nodes disabled so boards must explicitly enable the parent and its
EPHY control function when the companion is present.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 73 ++++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
index 72ce1a75647b..55e6e185a4e5 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
@@ -17,6 +17,16 @@ / {
#address-cells = <1>;
#size-cells = <1>;
+ ac200_pwm_clk: ac200-clock {
+ compatible = "pwm-clock";
+ #clock-cells = <0>;
+ clock-frequency = <24000000>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pwm1_pin>;
+ pwms = <&pwm 1 42 0>;
+ status = "disabled";
+ };
+
cpus {
#address-cells = <1>;
#size-cells = <0>;
@@ -314,6 +324,15 @@ ths_calibration: thermal-sensor-calibration@14 {
cpu_speed_grade: cpu-speed-grade@1c {
reg = <0x1c 0x4>;
};
+
+ ephy_calibration: ethernet-phy-calibration@2c,0 {
+ reg = <0x2c 1>;
+ bits = <0 4>;
+ };
+
+ ac200_bandgap: bandgap@30 {
+ reg = <0x30 2>;
+ };
};
timer@3009000 {
@@ -368,6 +387,13 @@ ext_rgmii_pins: rgmii-pins {
drive-strength = <40>;
};
+ ext_rmii_pins: rmii-pins {
+ pins = "PA0", "PA1", "PA2", "PA3", "PA4",
+ "PA5", "PA6", "PA7", "PA8", "PA9";
+ function = "emac";
+ drive-strength = <40>;
+ };
+
hdmi_pins: hdmi-pins {
pins = "PH8", "PH9", "PH10";
function = "hdmi";
@@ -388,6 +414,12 @@ i2c2_pins: i2c2-pins {
function = "i2c2";
};
+ i2c3_pins: i2c3-pins {
+ pins = "PB17", "PB18";
+ function = "i2c3";
+ bias-pull-up;
+ };
+
mmc0_pins: mmc0-pins {
pins = "PF0", "PF1", "PF2", "PF3",
"PF4", "PF5";
@@ -414,6 +446,11 @@ mmc2_pins: mmc2-pins {
bias-pull-up;
};
+ pwm1_pin: pwm1-pin {
+ pins = "PB19";
+ function = "pwm1";
+ };
+
/omit-if-no-ref/
spi0_pins: spi0-pins {
pins = "PC0", "PC2", "PC3";
@@ -626,6 +663,42 @@ i2c2: i2c@5002800 {
#size-cells = <0>;
};
+ i2c3: i2c@5002c00 {
+ compatible = "allwinner,sun50i-h6-i2c",
+ "allwinner,sun6i-a31-i2c";
+ reg = <0x05002c00 0x400>;
+ interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_I2C3>;
+ resets = <&ccu RST_BUS_I2C3>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2c3_pins>;
+ clock-frequency = <400000>;
+ status = "disabled";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ac200: mixed-signal@10 {
+ compatible = "x-powers,ac200";
+ reg = <0x10>;
+ clocks = <&ac200_pwm_clk>;
+ interrupt-parent = <&pio>;
+ interrupts = <1 20 IRQ_TYPE_LEVEL_LOW>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ nvmem-cells = <&ac200_bandgap>;
+ nvmem-cell-names = "bandgap";
+ status = "disabled";
+
+ ac200_ephy_ctl: ethernet-phy-control {
+ compatible = "x-powers,ac200-ephy-ctl";
+ nvmem-cells = <&ephy_calibration>;
+ nvmem-cell-names = "calibration";
+ phy-mode = "rmii";
+ status = "disabled";
+ };
+ };
+ };
+
spi0: spi@5010000 {
compatible = "allwinner,sun50i-h6-spi",
"allwinner,sun8i-h3-spi";
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 20/21] arm64: dts: allwinner: h6: tanix: enable AC200 EPHY
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (18 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 19/21] arm64: dts: allwinner: h6: add AC200 EPHY resources James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 5:14 ` [PATCH 21/21] arm64: defconfig: enable X-Powers ACx00 Ethernet support James Hilliard
` (4 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
Enable the AC200 control path and RMII Ethernet link on the Tanix TX6
family, and provide the board's 3.3 V rail to the AC200 function supplies.
Route the PHY interrupt through the AC200 interrupt controller and mark
it as a wakeup source for link interrupts and magic-packet wake.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
arch/arm64/boot/dts/allwinner/sun50i-h6-tanix.dtsi | 45 ++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6-tanix.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6-tanix.dtsi
index bb7de37c0d58..6cd75961e3cc 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6-tanix.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6-tanix.dtsi
@@ -7,9 +7,11 @@
#include "sun50i-h6-cpu-opp.dtsi"
#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/mfd/x-powers,ac200.h>
/ {
aliases {
+ ethernet0 = &emac;
serial0 = &uart0;
};
@@ -84,6 +86,22 @@ wifi_pwrseq: wifi-pwrseq {
};
};
+&ac200 {
+ ac-ldoin-supply = <®_vcc3v3>;
+ ephy-vcc-supply = <®_vcc3v3>;
+ rtc-vcc-supply = <®_vcc3v3>;
+ tv-vcc-supply = <®_vcc3v3>;
+ status = "okay";
+};
+
+&ac200_ephy_ctl {
+ status = "okay";
+};
+
+&ac200_pwm_clk {
+ status = "okay";
+};
+
&cpu0 {
cpu-supply = <®_vdd_cpu_gpu>;
};
@@ -104,6 +122,14 @@ &ehci3 {
status = "okay";
};
+&emac {
+ pinctrl-names = "default";
+ pinctrl-0 = <&ext_rmii_pins>;
+ phy-mode = "rmii";
+ phy-handle = <&ac200_ephy>;
+ status = "okay";
+};
+
&gpu {
mali-supply = <®_vdd_cpu_gpu>;
status = "okay";
@@ -119,6 +145,21 @@ hdmi_out_con: endpoint {
};
};
+&i2c3 {
+ status = "okay";
+};
+
+&mdio {
+ ac200_ephy: ethernet-phy@1 {
+ compatible = "ethernet-phy-id0044.1400";
+ reg = <1>;
+ interrupts-extended = <&ac200 AC200_IRQ_EPHY>;
+ wakeup-source;
+ x-powers,ac200-control = <&ac200_ephy_ctl>;
+ x-powers,xmii-rx-clock-inverted;
+ };
+};
+
&mmc0 {
pinctrl-names = "default";
pinctrl-0 = <&mmc0_pins>;
@@ -162,6 +203,10 @@ &pio {
vcc-pg-supply = <®_vcc1v8>;
};
+&pwm {
+ status = "okay";
+};
+
&r_ir {
status = "okay";
};
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 21/21] arm64: defconfig: enable X-Powers ACx00 Ethernet support
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (19 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 20/21] arm64: dts: allwinner: h6: tanix: enable AC200 EPHY James Hilliard
@ 2026-08-03 5:14 ` James Hilliard
2026-08-03 7:00 ` Krzysztof Kozlowski
2026-08-03 13:01 ` [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support Andrew Lunn
` (3 subsequent siblings)
24 siblings, 1 reply; 51+ messages in thread
From: James Hilliard @ 2026-08-03 5:14 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip, James Hilliard
Build the H616 PWM clock provider, AC200 MFD, both variant-specific
EPHY control drivers and the shared link-PHY driver as modules. Enable the
netdev LED trigger as a module so DT-described PHY LEDs can use hardware
offload.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
arch/arm64/configs/defconfig | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 654a102cb5bc..53f6b8edaf33 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -475,6 +475,9 @@ CONFIG_DP83869_PHY=m
CONFIG_DP83TD510_PHY=y
CONFIG_DP83TG720_PHY=m
CONFIG_VITESSE_PHY=y
+CONFIG_XPOWERS_AC200_PHY_CTL=m
+CONFIG_XPOWERS_AC300_PHY_CTL=m
+CONFIG_XPOWERS_ACX00_PHY=m
CONFIG_XILINX_GMII2RGMII=m
CONFIG_CAN_FLEXCAN=m
CONFIG_CAN_XILINXCAN=m
@@ -828,6 +831,7 @@ CONFIG_BCM7038_WDT=m
CONFIG_MFD_ADP5585=m
CONFIG_MFD_ALTERA_SYSMGR=y
CONFIG_MFD_BD9571MWV=y
+CONFIG_MFD_AC200=m
CONFIG_MFD_AXP20X_I2C=y
CONFIG_MFD_AXP20X_RSB=y
CONFIG_MFD_MACSMC=m
@@ -1325,6 +1329,7 @@ CONFIG_LEDS_TRIGGER_HEARTBEAT=y
CONFIG_LEDS_TRIGGER_CPU=y
CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
CONFIG_LEDS_TRIGGER_PANIC=y
+CONFIG_LEDS_TRIGGER_NETDEV=m
CONFIG_EDAC=y
CONFIG_EDAC_GHES=y
CONFIG_EDAC_LAYERSCAPE=m
@@ -1726,6 +1731,7 @@ CONFIG_PWM_SAMSUNG=y
CONFIG_PWM_SL28CPLD=m
CONFIG_PWM_STM32=m
CONFIG_PWM_SUN4I=m
+CONFIG_PWM_SUN8I=m
CONFIG_PWM_TEGRA=m
CONFIG_PWM_TIECAP=m
CONFIG_PWM_TIEHRPWM=m
--
2.53.0
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH 21/21] arm64: defconfig: enable X-Powers ACx00 Ethernet support
2026-08-03 5:14 ` [PATCH 21/21] arm64: defconfig: enable X-Powers ACx00 Ethernet support James Hilliard
@ 2026-08-03 7:00 ` Krzysztof Kozlowski
2026-08-03 7:22 ` James Hilliard
0 siblings, 1 reply; 51+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-03 7:00 UTC (permalink / raw)
To: James Hilliard, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Andre Przywara, Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip
On 03/08/2026 07:14, James Hilliard wrote:
> Build the H616 PWM clock provider, AC200 MFD, both variant-specific
Use full names. Is it Samsung H616 PWM clock controller?
> EPHY control drivers and the shared link-PHY driver as modules. Enable the
> netdev LED trigger as a module so DT-described PHY LEDs can use hardware
> offload.
Why? IOW, which upstream board needs all of these?
Best regards,
Krzysztof
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 03/21] arm64: dts: allwinner: h616: add EMAC1 controller
2026-08-03 5:14 ` [PATCH 03/21] arm64: dts: allwinner: h616: add EMAC1 controller James Hilliard
@ 2026-08-03 7:01 ` Krzysztof Kozlowski
0 siblings, 0 replies; 51+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-03 7:01 UTC (permalink / raw)
To: James Hilliard, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Andre Przywara, Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip
On 03/08/2026 07:14, James Hilliard wrote:
> From: Richard Genoud <richard.genoud@bootlin.com>
>
> The H616 has a secondary EMAC controller that supports only RMII at
> 10/100 Mbps. It connects through PA0-PA9 to an EPHY in a companion
> AC200 or AC300 package.
>
> Add the EMAC1 controller and RMII pin group. Leave the controller
> disabled for boards to enable when the companion Ethernet hardware
> is present.
>
> Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> ---
You order patches quite odd. DTS cannot be in the middle of drivers,
because it suggests dependency.
You cannot have such dependency.
Please read submitting patches in DT dir.
Best regards,
Krzysztof
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200
2026-08-03 5:14 ` [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200 James Hilliard
@ 2026-08-03 7:07 ` Krzysztof Kozlowski
2026-08-03 7:54 ` James Hilliard
2026-08-03 13:18 ` Andrew Lunn
1 sibling, 1 reply; 51+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-03 7:07 UTC (permalink / raw)
To: James Hilliard, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Andre Przywara, Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip
On 03/08/2026 07:14, James Hilliard wrote:
> The AC200 is an I2C-controlled mixed-signal companion IC containing
> audio, video, RTC and Fast Ethernet PHY functions.
This fails when applied, because you did not explain the
dependencies/merging of this patchset.
This is THE MOST important information of cover letter. The first thing
to explain.
>
> Describe the parent device, its input clock, required function supplies,
> the optional SID bandgap calibration cell used by the vendor initialization
> sequence, and its optional Ethernet PHY control child. Document the 24 and
> 27 MHz rates encoded by the public EPHY clock selector.
>
...
> +required:
> + - compatible
> + - reg
> + - clocks
> + - ac-ldoin-supply
> + - ephy-vcc-supply
> + - rtc-vcc-supply
> + - tv-vcc-supply
> +
> +dependencies:
> + interrupts: [ interrupt-controller ]
> + interrupt-controller: [ '#interrupt-cells', interrupts ]
> + '#interrupt-cells': [ interrupt-controller ]
> + nvmem-cells: [ nvmem-cell-names ]
> + nvmem-cell-names: [ nvmem-cells ]
Why do you need all these dependencies? What are you trying to express?
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + mixed-signal@10 {
...
> +...
> diff --git a/include/dt-bindings/mfd/x-powers,ac200.h b/include/dt-bindings/mfd/x-powers,ac200.h
> new file mode 100644
> index 000000000000..cc59e2ab4912
> --- /dev/null
> +++ b/include/dt-bindings/mfd/x-powers,ac200.h
> @@ -0,0 +1,13 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +/*
> + * Interrupt numbers of the X-Powers AC200 interrupt controller.
> + */
> +
> +#ifndef _DT_BINDINGS_MFD_X_POWERS_AC200_H
> +#define _DT_BINDINGS_MFD_X_POWERS_AC200_H
> +
> +#define AC200_IRQ_TVE 0
> +#define AC200_IRQ_EPHY 1
> +#define AC200_IRQ_RTC 2
Hardware constants are not really bindings, even though you use them in
the driver.
> +
> +#endif /* _DT_BINDINGS_MFD_X_POWERS_AC200_H */
>
Best regards,
Krzysztof
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 21/21] arm64: defconfig: enable X-Powers ACx00 Ethernet support
2026-08-03 7:00 ` Krzysztof Kozlowski
@ 2026-08-03 7:22 ` James Hilliard
0 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 7:22 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner, netdev, devicetree,
linux-arm-kernel, linux-sunxi, linux-kernel, linux-stm32, mfd,
linux-rockchip
On Mon, Aug 3, 2026 at 1:00 AM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>
> On 03/08/2026 07:14, James Hilliard wrote:
> > Build the H616 PWM clock provider, AC200 MFD, both variant-specific
>
> Use full names. Is it Samsung H616 PWM clock controller?
It's the Allwinner H616 PWM.
>
> > EPHY control drivers and the shared link-PHY driver as modules. Enable the
> > netdev LED trigger as a module so DT-described PHY LEDs can use hardware
> > offload.
>
> Why? IOW, which upstream board needs all of these?
The Orange Pi Zero 2W for example would use this along with
any other boards using this copackaged x-powers ephy.
>
>
> Best regards,
> Krzysztof
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200
2026-08-03 7:07 ` Krzysztof Kozlowski
@ 2026-08-03 7:54 ` James Hilliard
2026-08-03 8:20 ` Krzysztof Kozlowski
0 siblings, 1 reply; 51+ messages in thread
From: James Hilliard @ 2026-08-03 7:54 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner, netdev, devicetree,
linux-arm-kernel, linux-sunxi, linux-kernel, linux-stm32, mfd,
linux-rockchip
On Mon, Aug 3, 2026 at 1:07 AM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>
> On 03/08/2026 07:14, James Hilliard wrote:
> > The AC200 is an I2C-controlled mixed-signal companion IC containing
> > audio, video, RTC and Fast Ethernet PHY functions.
>
> This fails when applied, because you did not explain the
> dependencies/merging of this patchset.
>
> This is THE MOST important information of cover letter. The first thing
> to explain.
I did mention in the cover letter that the pwm series is a dependency:
https://lore.kernel.org/all/20260703152215.192859-1-richard.genoud@bootlin.com/
With the pwm series first this should apply on top of master. Should
I just mention that it applies on master or should I reference a specific
commit hash or something?
> >
> > Describe the parent device, its input clock, required function supplies,
> > the optional SID bandgap calibration cell used by the vendor initialization
> > sequence, and its optional Ethernet PHY control child. Document the 24 and
> > 27 MHz rates encoded by the public EPHY clock selector.
> >
>
> ...
>
> > +required:
> > + - compatible
> > + - reg
> > + - clocks
> > + - ac-ldoin-supply
> > + - ephy-vcc-supply
> > + - rtc-vcc-supply
> > + - tv-vcc-supply
> > +
> > +dependencies:
> > + interrupts: [ interrupt-controller ]
> > + interrupt-controller: [ '#interrupt-cells', interrupts ]
> > + '#interrupt-cells': [ interrupt-controller ]
> > + nvmem-cells: [ nvmem-cell-names ]
> > + nvmem-cell-names: [ nvmem-cells ]
>
> Why do you need all these dependencies? What are you trying to express?
Looks like we probably can get rid of all except these:
interrupt-controller: [ interrupts ]
nvmem-cells: [ nvmem-cell-names ]
I was just trying to express the MFD controller dependencies.
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > + - |
> > + #include <dt-bindings/interrupt-controller/irq.h>
> > +
> > + i2c {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + mixed-signal@10 {
>
> ...
>
> > +...
> > diff --git a/include/dt-bindings/mfd/x-powers,ac200.h b/include/dt-bindings/mfd/x-powers,ac200.h
> > new file mode 100644
> > index 000000000000..cc59e2ab4912
> > --- /dev/null
> > +++ b/include/dt-bindings/mfd/x-powers,ac200.h
> > @@ -0,0 +1,13 @@
> > +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> > +/*
> > + * Interrupt numbers of the X-Powers AC200 interrupt controller.
> > + */
> > +
> > +#ifndef _DT_BINDINGS_MFD_X_POWERS_AC200_H
> > +#define _DT_BINDINGS_MFD_X_POWERS_AC200_H
> > +
> > +#define AC200_IRQ_TVE 0
> > +#define AC200_IRQ_EPHY 1
> > +#define AC200_IRQ_RTC 2
>
> Hardware constants are not really bindings, even though you use them in
> the driver.
Should I do something different for this?
>
> > +
> > +#endif /* _DT_BINDINGS_MFD_X_POWERS_AC200_H */
> >
>
>
> Best regards,
> Krzysztof
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200
2026-08-03 7:54 ` James Hilliard
@ 2026-08-03 8:20 ` Krzysztof Kozlowski
2026-08-03 8:21 ` Krzysztof Kozlowski
2026-08-03 21:34 ` James Hilliard
0 siblings, 2 replies; 51+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-03 8:20 UTC (permalink / raw)
To: James Hilliard
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner, netdev, devicetree,
linux-arm-kernel, linux-sunxi, linux-kernel, linux-stm32, mfd,
linux-rockchip
On 03/08/2026 09:54, James Hilliard wrote:
> On Mon, Aug 3, 2026 at 1:07 AM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>>
>> On 03/08/2026 07:14, James Hilliard wrote:
>>> The AC200 is an I2C-controlled mixed-signal companion IC containing
>>> audio, video, RTC and Fast Ethernet PHY functions.
>>
>> This fails when applied, because you did not explain the
>> dependencies/merging of this patchset.
>>
>> This is THE MOST important information of cover letter. The first thing
>> to explain.
>
> I did mention in the cover letter that the pwm series is a dependency:
> https://lore.kernel.org/all/20260703152215.192859-1-richard.genoud@bootlin.com/
Apply this patch and test.
>
> With the pwm series first this should apply on top of master. Should
> I just mention that it applies on master or should I reference a specific
> commit hash or something?
>
>>>
>>> Describe the parent device, its input clock, required function supplies,
>>> the optional SID bandgap calibration cell used by the vendor initialization
>>> sequence, and its optional Ethernet PHY control child. Document the 24 and
>>> 27 MHz rates encoded by the public EPHY clock selector.
>>>
>>
>> ...
>>
>>> +required:
>>> + - compatible
>>> + - reg
>>> + - clocks
>>> + - ac-ldoin-supply
>>> + - ephy-vcc-supply
>>> + - rtc-vcc-supply
>>> + - tv-vcc-supply
>>> +
>>> +dependencies:
>>> + interrupts: [ interrupt-controller ]
>>> + interrupt-controller: [ '#interrupt-cells', interrupts ]
>>> + '#interrupt-cells': [ interrupt-controller ]
>>> + nvmem-cells: [ nvmem-cell-names ]
>>> + nvmem-cell-names: [ nvmem-cells ]
>>
>> Why do you need all these dependencies? What are you trying to express?
>
> Looks like we probably can get rid of all except these:
> interrupt-controller: [ interrupts ]
> nvmem-cells: [ nvmem-cell-names ]
>
> I was just trying to express the MFD controller dependencies.
>
>>> +
>>> +additionalProperties: false
>>> +
>>> +examples:
>>> + - |
>>> + #include <dt-bindings/interrupt-controller/irq.h>
>>> +
>>> + i2c {
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> +
>>> + mixed-signal@10 {
>>
>> ...
>>
>>> +...
>>> diff --git a/include/dt-bindings/mfd/x-powers,ac200.h b/include/dt-bindings/mfd/x-powers,ac200.h
>>> new file mode 100644
>>> index 000000000000..cc59e2ab4912
>>> --- /dev/null
>>> +++ b/include/dt-bindings/mfd/x-powers,ac200.h
>>> @@ -0,0 +1,13 @@
>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
>>> +/*
>>> + * Interrupt numbers of the X-Powers AC200 interrupt controller.
>>> + */
>>> +
>>> +#ifndef _DT_BINDINGS_MFD_X_POWERS_AC200_H
>>> +#define _DT_BINDINGS_MFD_X_POWERS_AC200_H
>>> +
>>> +#define AC200_IRQ_TVE 0
>>> +#define AC200_IRQ_EPHY 1
>>> +#define AC200_IRQ_RTC 2
>>
>> Hardware constants are not really bindings, even though you use them in
>> the driver.
>
> Should I do something different for this?
I would just drop the defines and the header, because these are fixed
hardware numbers.
Best regards,
Krzysztof
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200
2026-08-03 8:20 ` Krzysztof Kozlowski
@ 2026-08-03 8:21 ` Krzysztof Kozlowski
2026-08-03 16:36 ` James Hilliard
2026-08-03 21:34 ` James Hilliard
1 sibling, 1 reply; 51+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-03 8:21 UTC (permalink / raw)
To: James Hilliard
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner, netdev, devicetree,
linux-arm-kernel, linux-sunxi, linux-kernel, linux-stm32, mfd,
linux-rockchip
On 03/08/2026 10:20, Krzysztof Kozlowski wrote:
>>>> --- /dev/null
>>>> +++ b/include/dt-bindings/mfd/x-powers,ac200.h
>>>> @@ -0,0 +1,13 @@
>>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
>>>> +/*
>>>> + * Interrupt numbers of the X-Powers AC200 interrupt controller.
>>>> + */
>>>> +
>>>> +#ifndef _DT_BINDINGS_MFD_X_POWERS_AC200_H
>>>> +#define _DT_BINDINGS_MFD_X_POWERS_AC200_H
>>>> +
>>>> +#define AC200_IRQ_TVE 0
>>>> +#define AC200_IRQ_EPHY 1
>>>> +#define AC200_IRQ_RTC 2
>>>
>>> Hardware constants are not really bindings, even though you use them in
>>> the driver.
>>
>> Should I do something different for this?
>
> I would just drop the defines and the header, because these are fixed
> hardware numbers.
>
Hm, unless they are not and you added abstract ID numbers for both DTS
and drivers? Then this would be fine.
Best regards,
Krzysztof
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (20 preceding siblings ...)
2026-08-03 5:14 ` [PATCH 21/21] arm64: defconfig: enable X-Powers ACx00 Ethernet support James Hilliard
@ 2026-08-03 13:01 ` Andrew Lunn
2026-08-03 13:14 ` Andrew Lunn
` (2 subsequent siblings)
24 siblings, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2026-08-03 13:01 UTC (permalink / raw)
To: James Hilliard
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
On Sun, Aug 02, 2026 at 11:14:10PM -0600, James Hilliard wrote:
> The Allwinner H616/H618 secondary EMAC is commonly connected to a Fast
> Ethernet PHY in an X-Powers AC200 or AC300 companion package. The two
> packages expose the same link PHY ID and largely compatible link-side
> registers, but require different control paths before those registers can
> be accessed: AC200 is initialized through its I2C MFD, while AC300 uses a
> non-PHY Clause 22 control endpoint.
>
> Some H616-family products were shipped with either package under the same
> board identity. For those systems, this series uses a SID NVMEM field to
> select the control provider at run time. With CONFIG_OF_DYNAMIC, the link
> PHY enables only the selected fail-needs-probe provider, so an AC300 board
> does not probe the unused AC200 I2C controller or claim its pins. The same
> drivers also support fixed descriptions without CONFIG_OF_DYNAMIC: a board
> can reference one enabled provider, or enable both candidates when runtime
> selection is still required.
>
> The series adds:
>
> - the H616 EMAC1 binding, stmmac variant and controller node;
> - generic MDIO OF reconfiguration support for PHYs, packages, scanned
> addresses and generic MDIO devices;
> - the AC200 MFD and AC200/AC300 EPHY control providers;
> - the shared AC200/AC300 link-PHY driver, including calibration, power and
> reset sequencing, MDI/MDI-X, statistics, EDPD, PHY-managed EEE, package
> LEDs, interrupts and Wake-on-LAN; and
> - H6 and H616-family device-tree descriptions and arm64 defconfig support.
>
> The AC200 and AC300 control and link paths were exercised on H616-family
> hardware during development, including link traffic, address handling,
> ethtool EDPD/EEE controls, statistics and LEDs. The H6 interrupt and
> Wake-on-LAN description still needs validation by an H6 hardware user.
>
> This series depends on Richard Genoud's v7 H616 PWM controller series,
> which provides the bypass clock used by the companion EPHY:
>
> https://lore.kernel.org/all/20260703152215.192859-1-richard.genoud@bootlin.com/
>
> The PWM patches are an external prerequisite and are intentionally not
> included in this 21-patch series.
21 patches is on the big side. Now you have shown the big picture, can
you break this up into a couple of series?
Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 01/21] dt-bindings: net: allwinner: add H616 EMAC1
2026-08-03 5:14 ` [PATCH 01/21] dt-bindings: net: allwinner: add H616 EMAC1 James Hilliard
@ 2026-08-03 13:03 ` Andrew Lunn
0 siblings, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2026-08-03 13:03 UTC (permalink / raw)
To: James Hilliard
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
> Constrain the PHY interface mode to RMII, matching the hardware. The
> EMAC1 clock register also retains the three-bit TX and five-bit RX delay
> chains, so allow the existing Allwinner delay properties for this
> compatible.
The delay properties are for RGMII, not RMII. What do they actually do
when used with RMII?
Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (21 preceding siblings ...)
2026-08-03 13:01 ` [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support Andrew Lunn
@ 2026-08-03 13:14 ` Andrew Lunn
2026-08-03 13:26 ` Andre Przywara
2026-08-03 13:52 ` Andrew Lunn
24 siblings, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2026-08-03 13:14 UTC (permalink / raw)
To: James Hilliard
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
On Sun, Aug 02, 2026 at 11:14:10PM -0600, James Hilliard wrote:
> The Allwinner H616/H618 secondary EMAC is commonly connected to a Fast
> Ethernet PHY in an X-Powers AC200 or AC300 companion package. The two
> packages expose the same link PHY ID and
I have a couple of terminology questions, one here, anther later
What do you mean by link PHY ID? Especially the link part?
Are you talking about 802.3 Clause 22 registers 2 and 3?
Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200
2026-08-03 5:14 ` [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200 James Hilliard
2026-08-03 7:07 ` Krzysztof Kozlowski
@ 2026-08-03 13:18 ` Andrew Lunn
2026-08-03 16:45 ` James Hilliard
1 sibling, 1 reply; 51+ messages in thread
From: Andrew Lunn @ 2026-08-03 13:18 UTC (permalink / raw)
To: James Hilliard
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
> + ethernet-phy-control {
> + compatible = "x-powers,ac200-ephy-ctl";
> + nvmem-cells = <&ephy_calibration>;
> + nvmem-cell-names = "calibration";
> + phy-mode = "rmii";
> + };
What do you mean by an ethernet PHY control?
I assume this is not an actual Ethernet PHY, but some control logic
around it? Where is the ethernet PHY itself?
Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 15/21] net: phylink: allow PHY-autonomous EEE without MAC LPI
2026-08-03 5:14 ` [PATCH 15/21] net: phylink: allow PHY-autonomous EEE without MAC LPI James Hilliard
@ 2026-08-03 13:23 ` Andrew Lunn
0 siblings, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2026-08-03 13:23 UTC (permalink / raw)
To: James Hilliard
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
On Sun, Aug 02, 2026 at 11:14:25PM -0600, James Hilliard wrote:
> Phylink treats a MAC which implements its LPI operations but advertises no
> LPI capability as incapable of EEE. It disables PHY EEE during attach and
> rejects ethtool EEE requests. The same problem occurs when the MAC supports
> LPI on another interface but not on the interface currently connected to
> the PHY. Both cases are incorrect when the attached PHY can generate LPI
> autonomously without help from the MAC.
What you are talking about is SmartEEE, or other such names. It is not
part of 802.3, so there is no agreed upon name for it.
You are correct, phylink does not support it. It also needs a lot of
thought and careful design, which is why it has not been implemented
yet. Please search back through the netdev archive and find the
discussion.
Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (22 preceding siblings ...)
2026-08-03 13:14 ` Andrew Lunn
@ 2026-08-03 13:26 ` Andre Przywara
2026-08-03 13:30 ` Andrew Lunn
2026-08-03 15:57 ` James Hilliard
2026-08-03 13:52 ` Andrew Lunn
24 siblings, 2 replies; 51+ messages in thread
From: Andre Przywara @ 2026-08-03 13:26 UTC (permalink / raw)
To: James Hilliard, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner
Cc: netdev, devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip
Hi James,
thanks for sending this to the list, but as Andrew already mentioned:
this is quite a beast, and would need to be broken down.
Jernej and I created some patches just for the AC200 PHY on the H6,
which Jernej recently revived, and IIUC, wanted to send to the list.
This would probably be a more workable route: start easy, then add
support for more SoCs (H616 depends on the WIP PWM driver) and more
variants (AC300) later.
Which also brings me to some attribution questions: If I understand
correctly, this has been written mostly by some LLM? I think you would
need at least to disclose that, if not even add an official tag?
Also I wonder how this relates to the patches floating around for a
while already: https://github.com/jernejsk/linux-1/commits/ac200-v4.
This branch is relatively new, but the ac200-v3 branch for instance is
much older.
On 8/3/26 07:14, James Hilliard wrote:
> The Allwinner H616/H618 secondary EMAC is commonly connected to a Fast
> Ethernet PHY in an X-Powers AC200 or AC300 companion package. The two
> packages expose the same link PHY ID and largely compatible link-side
> registers, but require different control paths before those registers can
> be accessed: AC200 is initialized through its I2C MFD, while AC300 uses a
> non-PHY Clause 22 control endpoint.
>
> Some H616-family products were shipped with either package under the same
> board identity. For those systems, this series uses a SID NVMEM field to
> select the control provider at run time. With CONFIG_OF_DYNAMIC, the link
> PHY enables only the selected fail-needs-probe provider, so an AC300 board
Just to bring that shallow discussion we had on IRC to the list: I
wonder if this is the right solution? Alternatively I was wondering if
just patching the DT in U-Boot would simplify things, and also help
other DT users like *BSD or U-Boot (proper) itself, to not boil the
ocean, but just allowing them to follow a standard DT description.
Cheers,
Andre
> does not probe the unused AC200 I2C controller or claim its pins. The same
> drivers also support fixed descriptions without CONFIG_OF_DYNAMIC: a board
> can reference one enabled provider, or enable both candidates when runtime
> selection is still required.
>
> The series adds:
>
> - the H616 EMAC1 binding, stmmac variant and controller node;
> - generic MDIO OF reconfiguration support for PHYs, packages, scanned
> addresses and generic MDIO devices;
> - the AC200 MFD and AC200/AC300 EPHY control providers;
> - the shared AC200/AC300 link-PHY driver, including calibration, power and
> reset sequencing, MDI/MDI-X, statistics, EDPD, PHY-managed EEE, package
> LEDs, interrupts and Wake-on-LAN; and
> - H6 and H616-family device-tree descriptions and arm64 defconfig support.
>
> The AC200 and AC300 control and link paths were exercised on H616-family
> hardware during development, including link traffic, address handling,
> ethtool EDPD/EEE controls, statistics and LEDs. The H6 interrupt and
> Wake-on-LAN description still needs validation by an H6 hardware user.
>
> This series depends on Richard Genoud's v7 H616 PWM controller series,
> which provides the bypass clock used by the companion EPHY:
>
> https://lore.kernel.org/all/20260703152215.192859-1-richard.genoud@bootlin.com/
>
> The PWM patches are an external prerequisite and are intentionally not
> included in this 21-patch series.
>
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> ---
> James Hilliard (18):
> net: mdio: factor out OF child registration helpers
> net: mdio: make device map changes hotplug-safe
> net: mdio: support dynamic OF device changes
> dt-bindings: net: x-powers: add AC200 EPHY control
> dt-bindings: mfd: x-powers: add AC200
> mfd: add X-Powers AC200 support
> net: phy: add X-Powers AC200 EPHY control driver
> dt-bindings: net: x-powers: add AC300 EPHY control
> net: phy: add X-Powers AC300 EPHY control driver
> dt-bindings: net: x-powers: add AC200/AC300 EPHY
> net: phy: support configuring PHY-autonomous Tx LPI
> net: phylink: allow PHY-autonomous EEE without MAC LPI
> net: phy: add X-Powers AC200/AC300 EPHY driver
> arm64: dts: allwinner: h616: add ACx00 EPHY resources
> arm64: dts: allwinner: orangepi-zero2w: enable Ethernet expansion
> arm64: dts: allwinner: h6: add AC200 EPHY resources
> arm64: dts: allwinner: h6: tanix: enable AC200 EPHY
> arm64: defconfig: enable X-Powers ACx00 Ethernet support
>
> Richard Genoud (3):
> dt-bindings: net: allwinner: add H616 EMAC1
> net: stmmac: sun8i: add support for Allwinner H616 EMAC1
> arm64: dts: allwinner: h616: add EMAC1 controller
>
> .../devicetree/bindings/mfd/x-powers,ac200.yaml | 118 ++
> .../bindings/net/allwinner,sun8i-a83t-emac.yaml | 18 +-
> .../devicetree/bindings/net/snps,dwmac.yaml | 2 +
> .../bindings/net/x-powers,ac200-ephy-ctl.yaml | 46 +
> .../bindings/net/x-powers,ac300-ephy-ctl.yaml | 77 ++
> .../bindings/net/x-powers,acx00-ephy.yaml | 217 +++
> arch/arm64/boot/dts/allwinner/sun50i-h6-tanix.dtsi | 45 +
> arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 73 +
> .../boot/dts/allwinner/sun50i-h616-acx00-ephy.dtsi | 61 +
> arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi | 51 +
> .../dts/allwinner/sun50i-h618-orangepi-zero2w.dts | 13 +
> arch/arm64/configs/defconfig | 6 +
> drivers/mfd/Kconfig | 13 +
> drivers/mfd/Makefile | 1 +
> drivers/mfd/ac200.c | 302 ++++
> drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c | 21 +
> drivers/net/mdio/of_mdio.c | 508 ++++++-
> drivers/net/phy/Kconfig | 30 +
> drivers/net/phy/Makefile | 3 +
> drivers/net/phy/mdio_bus.c | 14 +-
> drivers/net/phy/mdio_bus_provider.c | 140 +-
> drivers/net/phy/mdio_device.c | 235 +++-
> drivers/net/phy/phy.c | 35 +-
> drivers/net/phy/phy_device.c | 127 +-
> drivers/net/phy/phylib-internal.h | 4 +-
> drivers/net/phy/phylink.c | 35 +-
> drivers/net/phy/xpowers-ac200-ctl.c | 330 +++++
> drivers/net/phy/xpowers-ac300-ctl.c | 454 ++++++
> drivers/net/phy/xpowers-acx00.c | 1457 ++++++++++++++++++++
> drivers/net/phy/xpowers-acx00.h | 27 +
> include/dt-bindings/mfd/x-powers,ac200.h | 13 +
> include/linux/mdio.h | 6 +
> include/linux/phy.h | 36 +
> 33 files changed, 4384 insertions(+), 134 deletions(-)
> ---
> base-commit: 075b74841bd0065a3bda3440873c747938e69b68
> change-id: 20260802-submit-acx00-of-dynamic-v1-94a0dc15f282
> prerequisite-message-id: <20260703152215.192859-1-richard.genoud@bootlin.com>
> prerequisite-patch-id: 8c447ebd677ea7bf9e63a77d34d8fdaa05eebe7f
> prerequisite-patch-id: c7574f9c0e2571ce8c6129ac6db7aa959c986ef3
> prerequisite-patch-id: e8b00768102951244927ecf054314f6e02f9ff1b
> prerequisite-patch-id: a3e2055ed7f3b5751a3e02d57e674b25ee404ff7
>
> Best regards,
> --
> James Hilliard <james.hilliard1@gmail.com>
>
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support
2026-08-03 13:26 ` Andre Przywara
@ 2026-08-03 13:30 ` Andrew Lunn
2026-08-03 15:57 ` James Hilliard
1 sibling, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2026-08-03 13:30 UTC (permalink / raw)
To: Andre Przywara
Cc: James Hilliard, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
> Just to bring that shallow discussion we had on IRC to the list: I wonder if
> this is the right solution? Alternatively I was wondering if just patching
> the DT in U-Boot would simplify things, and also help other DT users like
> *BSD or U-Boot (proper) itself, to not boil the ocean, but just allowing
> them to follow a standard DT description.
I've not looked into the MDIO bus probing changes in detail yet, but
that was also what i was thinking.
Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 07/21] dt-bindings: net: x-powers: add AC200 EPHY control
2026-08-03 5:14 ` [PATCH 07/21] dt-bindings: net: x-powers: add AC200 EPHY control James Hilliard
@ 2026-08-03 13:40 ` Andrew Lunn
2026-08-03 18:03 ` James Hilliard
0 siblings, 1 reply; 51+ messages in thread
From: Andrew Lunn @ 2026-08-03 13:40 UTC (permalink / raw)
To: James Hilliard
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
> + phy-mode:
> + enum:
> + - mii
> + - rmii
Despite the phy- bit, phy-mode is a MAC property, it defines the MII
the MAC uses. I would not expect to see this anywhere else.
The PHY driver is told what MII to use, phydev->interface, which takes
the values PHY_INTERFACE_MODE_RMII, PHY_INTERFACE_MODE_MII etc.
Please explain why you need this here, why is the MAC nodes phy-mode
property not sufficient.
Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 10/21] net: phy: add X-Powers AC200 EPHY control driver
2026-08-03 5:14 ` [PATCH 10/21] net: phy: add X-Powers AC200 EPHY control driver James Hilliard
@ 2026-08-03 13:50 ` Andrew Lunn
2026-08-03 16:09 ` James Hilliard
0 siblings, 1 reply; 51+ messages in thread
From: Andrew Lunn @ 2026-08-03 13:50 UTC (permalink / raw)
To: James Hilliard
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
> +static int
> +ac200_ephy_ctl_set_led_outputs(struct acx00_ephy_control *control,
> + unsigned long outputs)
> +{
> +}
> +static int
> +ac200_ephy_ctl_set_led_polarity(struct acx00_ephy_control *control,
> + bool active_low)
> +{
> +}
I've not looked into the details, but these look like pinmux. Does
this I2C device have a GPIO controller? Can these pins be used for
other things than LEDs? Maybe you should be implementing a GPIO and
pinmux driver. The Ethernet PHY driver can then just select the
correct pinmux configuration.
> +static int ac200_ephy_ctl_power_off_locked(struct ac200_ephy_ctl *priv)
> +{
> +}
> +
> +static int ac200_ephy_ctl_power_off(struct acx00_ephy_control *control)
> +{
> +}
> +
> +static int ac200_ephy_ctl_power_on(struct acx00_ephy_control *control,
> + unsigned int phy_addr)
> +{
> +}
These look like a regulator? The phylib core has support for
regulators.
Maybe take a step back and look at the overall architecture, and how
Linux can represent these blocks.
Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
` (23 preceding siblings ...)
2026-08-03 13:26 ` Andre Przywara
@ 2026-08-03 13:52 ` Andrew Lunn
2026-08-03 13:54 ` Andre Przywara
24 siblings, 1 reply; 51+ messages in thread
From: Andrew Lunn @ 2026-08-03 13:52 UTC (permalink / raw)
To: James Hilliard
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
On Sun, Aug 02, 2026 at 11:14:10PM -0600, James Hilliard wrote:
> The Allwinner H616/H618 secondary EMAC is commonly connected to a Fast
> Ethernet PHY in an X-Powers AC200 or AC300 companion package.
Is there an open data sheet for these devices?
Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support
2026-08-03 13:52 ` Andrew Lunn
@ 2026-08-03 13:54 ` Andre Przywara
2026-08-03 13:57 ` Andrew Lunn
0 siblings, 1 reply; 51+ messages in thread
From: Andre Przywara @ 2026-08-03 13:54 UTC (permalink / raw)
To: Andrew Lunn, James Hilliard
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Richard Genoud,
Maxime Ripard, Alexandre Torgue, Giuseppe Cavallaro, Jose Abreu,
Maxime Chevallier, Maxime Coquelin, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner, netdev, devicetree,
linux-arm-kernel, linux-sunxi, linux-kernel, linux-stm32, mfd,
linux-rockchip
Hi Andrew,
On 8/3/26 15:52, Andrew Lunn wrote:
> On Sun, Aug 02, 2026 at 11:14:10PM -0600, James Hilliard wrote:
>> The Allwinner H616/H618 secondary EMAC is commonly connected to a Fast
>> Ethernet PHY in an X-Powers AC200 or AC300 companion package.
>
> Is there an open data sheet for these devices?
Find the datasheets at the end of this Wiki page:
https://linux-sunxi.org/AC200
Cheers,
Andre
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support
2026-08-03 13:54 ` Andre Przywara
@ 2026-08-03 13:57 ` Andrew Lunn
0 siblings, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2026-08-03 13:57 UTC (permalink / raw)
To: Andre Przywara
Cc: James Hilliard, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
On Mon, Aug 03, 2026 at 03:54:26PM +0200, Andre Przywara wrote:
> Hi Andrew,
>
> On 8/3/26 15:52, Andrew Lunn wrote:
> > On Sun, Aug 02, 2026 at 11:14:10PM -0600, James Hilliard wrote:
> > > The Allwinner H616/H618 secondary EMAC is commonly connected to a Fast
> > > Ethernet PHY in an X-Powers AC200 or AC300 companion package.
> >
> > Is there an open data sheet for these devices?
>
> Find the datasheets at the end of this Wiki page:
> https://linux-sunxi.org/AC200
Thanks.
It would be useful to add that to patch 0/X.
Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support
2026-08-03 13:26 ` Andre Przywara
2026-08-03 13:30 ` Andrew Lunn
@ 2026-08-03 15:57 ` James Hilliard
1 sibling, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 15:57 UTC (permalink / raw)
To: Andre Przywara
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Richard Genoud,
Maxime Ripard, Alexandre Torgue, Giuseppe Cavallaro, Jose Abreu,
Maxime Chevallier, Maxime Coquelin, Andrew Lunn, Heiner Kallweit,
Russell King, Saravana Kannan, Lee Jones, Heiko Stuebner, netdev,
devicetree, linux-arm-kernel, linux-sunxi, linux-kernel,
linux-stm32, mfd, linux-rockchip
On Mon, Aug 3, 2026 at 7:26 AM Andre Przywara <andre.przywara@arm.com> wrote:
>
> Hi James,
>
> thanks for sending this to the list, but as Andrew already mentioned:
> this is quite a beast, and would need to be broken down.
> Jernej and I created some patches just for the AC200 PHY on the H6,
> which Jernej recently revived, and IIUC, wanted to send to the list.
> This would probably be a more workable route: start easy, then add
> support for more SoCs (H616 depends on the WIP PWM driver) and more
> variants (AC300) later.
Well, I wanted to validate that the overall driver design is suitable for
the H616 which has to support both PHY variants at runtime effectively
since that is the most complex use case, I also don't have a H6 board
to test with myself at the moment. I think being able to see a more
complete implementation like this is helpful as it can better inform how
we can tie the different control interfaces to the link PHY in a way that
works for the more complex H616 case.
> Which also brings me to some attribution questions: If I understand
> correctly, this has been written mostly by some LLM? I think you would
> need at least to disclose that, if not even add an official tag?
Some patches were, sure, my understanding is that it's not required to
explicitly call that out but I can add more info if that helps, this was
tested/validated using a heavily automated hardware in the loop
testbench setup on an AC200 and AC300 variant H616 board.
> Also I wonder how this relates to the patches floating around for a
> while already: https://github.com/jernejsk/linux-1/commits/ac200-v4.
> This branch is relatively new, but the ac200-v3 branch for instance is
> much older.
Yeah, parts of this are loosely based off of some existing patches that
were floating around like those, although it has diverged quite a bit as
well since this series is designed to handle both phy variants.
I guess I should link to some of the earlier work in the cover letter?
> On 8/3/26 07:14, James Hilliard wrote:
> > The Allwinner H616/H618 secondary EMAC is commonly connected to a Fast
> > Ethernet PHY in an X-Powers AC200 or AC300 companion package. The two
> > packages expose the same link PHY ID and largely compatible link-side
> > registers, but require different control paths before those registers can
> > be accessed: AC200 is initialized through its I2C MFD, while AC300 uses a
> > non-PHY Clause 22 control endpoint.
> >
> > Some H616-family products were shipped with either package under the same
> > board identity. For those systems, this series uses a SID NVMEM field to
> > select the control provider at run time. With CONFIG_OF_DYNAMIC, the link
> > PHY enables only the selected fail-needs-probe provider, so an AC300 board
>
> Just to bring that shallow discussion we had on IRC to the list: I
> wonder if this is the right solution? Alternatively I was wondering if
> just patching the DT in U-Boot would simplify things, and also help
> other DT users like *BSD or U-Boot (proper) itself, to not boil the
> ocean, but just allowing them to follow a standard DT description.
This can be used either way, CONFIG_OF_DYNAMIC isn't a hard
dependency with this design, aside from adding CONFIG_OF_DYNAMIC
support for mdio the dynamic enablement of the correct phy control
drivers is relatively simple with this architecture.
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 10/21] net: phy: add X-Powers AC200 EPHY control driver
2026-08-03 13:50 ` Andrew Lunn
@ 2026-08-03 16:09 ` James Hilliard
2026-08-03 17:48 ` Andrew Lunn
0 siblings, 1 reply; 51+ messages in thread
From: James Hilliard @ 2026-08-03 16:09 UTC (permalink / raw)
To: Andrew Lunn
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
On Mon, Aug 3, 2026 at 7:51 AM Andrew Lunn <andrew@lunn.ch> wrote:
>
> > +static int
> > +ac200_ephy_ctl_set_led_outputs(struct acx00_ephy_control *control,
> > + unsigned long outputs)
> > +{
> > +}
>
> > +static int
> > +ac200_ephy_ctl_set_led_polarity(struct acx00_ephy_control *control,
> > + bool active_low)
> > +{
> > +}
>
> I've not looked into the details, but these look like pinmux. Does
> this I2C device have a GPIO controller? Can these pins be used for
> other things than LEDs? Maybe you should be implementing a GPIO and
> pinmux driver. The Ethernet PHY driver can then just select the
> correct pinmux configuration.
These do not appear to be GPIO or muxable pins. The AC200
documentation describes them as three dedicated EPHY LED
outputs: link/activity, speed, and duplex. The bits in SYS_EPHY_CTL1
enable those output pads, while EPHY_CTL provides their shared
polarity setting. I have not found any documented GPIO data
registers or alternative functions for these pins.
Therefore, implementing a GPIO controller would expose capabilities
the hardware does not have, and a pinmux driver would only provide
a single fixed function. The callbacks exist because the LED function
belongs to the PHY, while the pad-enable and polarity registers are
reached through the AC200 I2C regmap rather than the PHY’s MDIO
registers.
>
> > +static int ac200_ephy_ctl_power_off_locked(struct ac200_ephy_ctl *priv)
> > +{
> > +}
> > +
> > +static int ac200_ephy_ctl_power_off(struct acx00_ephy_control *control)
> > +{
>
> > +}
> > +
> > +static int ac200_ephy_ctl_power_on(struct acx00_ephy_control *control,
> > + unsigned int phy_addr)
> > +{
> > +}
>
> These look like a regulator? The phylib core has support for
> regulators.
The external supplies are already represented as regulators. These callbacks
do more than enable a supply: they sequence the package clock, reset and
shutdown controls, then program the PHY address, calibration and interface
state required before the normal Clause 22 endpoint becomes usable.
> Maybe take a step back and look at the overall architecture, and how
> Linux can represent these blocks.
>
> Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200
2026-08-03 8:21 ` Krzysztof Kozlowski
@ 2026-08-03 16:36 ` James Hilliard
0 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 16:36 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner, netdev, devicetree,
linux-arm-kernel, linux-sunxi, linux-kernel, linux-stm32, mfd,
linux-rockchip
On Mon, Aug 3, 2026 at 2:21 AM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>
> On 03/08/2026 10:20, Krzysztof Kozlowski wrote:
> >>>> --- /dev/null
> >>>> +++ b/include/dt-bindings/mfd/x-powers,ac200.h
> >>>> @@ -0,0 +1,13 @@
> >>>> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> >>>> +/*
> >>>> + * Interrupt numbers of the X-Powers AC200 interrupt controller.
> >>>> + */
> >>>> +
> >>>> +#ifndef _DT_BINDINGS_MFD_X_POWERS_AC200_H
> >>>> +#define _DT_BINDINGS_MFD_X_POWERS_AC200_H
> >>>> +
> >>>> +#define AC200_IRQ_TVE 0
> >>>> +#define AC200_IRQ_EPHY 1
> >>>> +#define AC200_IRQ_RTC 2
> >>>
> >>> Hardware constants are not really bindings, even though you use them in
> >>> the driver.
> >>
> >> Should I do something different for this?
> >
> > I would just drop the defines and the header, because these are fixed
> > hardware numbers.
> >
>
> Hm, unless they are not and you added abstract ID numbers for both DTS
> and drivers? Then this would be fine.
Yeah, these are more abstract ID numbers, they aren't really hardware
constants as the driver translates them.
>
> Best regards,
> Krzysztof
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200
2026-08-03 13:18 ` Andrew Lunn
@ 2026-08-03 16:45 ` James Hilliard
2026-08-03 16:51 ` Chen-Yu Tsai
0 siblings, 1 reply; 51+ messages in thread
From: James Hilliard @ 2026-08-03 16:45 UTC (permalink / raw)
To: Andrew Lunn
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
On Mon, Aug 3, 2026 at 7:18 AM Andrew Lunn <andrew@lunn.ch> wrote:
>
> > + ethernet-phy-control {
> > + compatible = "x-powers,ac200-ephy-ctl";
> > + nvmem-cells = <&ephy_calibration>;
> > + nvmem-cell-names = "calibration";
> > + phy-mode = "rmii";
> > + };
>
> What do you mean by an ethernet PHY control?
>
> I assume this is not an actual Ethernet PHY, but some control logic
> around it? Where is the ethernet PHY itself?
Correct, this is not the Ethernet PHY itself. It is the package-specific
sideband control block that must be configured before the normal Clause 22
PHY registers become usable. For AC200, this control block is accessed
through the parent AC200 I2C regmap. For AC300, the equivalent control block
is accessed through a separate non-PHY Clause 22 address.
The actual Ethernet PHY is a separate device on the MAC's MDIO bus. The
shared PHY driver for that device is added in patch 16:
https://lore.kernel.org/linux-sunxi/20260802-submit-acx00-of-dynamic-v1-v1-16-0a53cd9e21cc@gmail.com/
The later board DTS patches instantiate it as an ethernet-phy node beneath
the MAC's MDIO bus and reference the appropriate sideband control provider.
>
> Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200
2026-08-03 16:45 ` James Hilliard
@ 2026-08-03 16:51 ` Chen-Yu Tsai
0 siblings, 0 replies; 51+ messages in thread
From: Chen-Yu Tsai @ 2026-08-03 16:51 UTC (permalink / raw)
To: James Hilliard
Cc: Andrew Lunn, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
On Tue, Aug 4, 2026 at 12:45 AM James Hilliard
<james.hilliard1@gmail.com> wrote:
>
> On Mon, Aug 3, 2026 at 7:18 AM Andrew Lunn <andrew@lunn.ch> wrote:
> >
> > > + ethernet-phy-control {
> > > + compatible = "x-powers,ac200-ephy-ctl";
> > > + nvmem-cells = <&ephy_calibration>;
> > > + nvmem-cell-names = "calibration";
> > > + phy-mode = "rmii";
> > > + };
> >
> > What do you mean by an ethernet PHY control?
> >
> > I assume this is not an actual Ethernet PHY, but some control logic
> > around it? Where is the ethernet PHY itself?
>
> Correct, this is not the Ethernet PHY itself. It is the package-specific
> sideband control block that must be configured before the normal Clause 22
> PHY registers become usable. For AC200, this control block is accessed
> through the parent AC200 I2C regmap. For AC300, the equivalent control block
> is accessed through a separate non-PHY Clause 22 address.
Basically, all the PHY behavior that is normally configured using strapping
pins on a discrete PHY is done over this I2C channel.
> The actual Ethernet PHY is a separate device on the MAC's MDIO bus. The
> shared PHY driver for that device is added in patch 16:
>
> https://lore.kernel.org/linux-sunxi/20260802-submit-acx00-of-dynamic-v1-v1-16-0a53cd9e21cc@gmail.com/
>
> The later board DTS patches instantiate it as an ethernet-phy node beneath
> the MAC's MDIO bus and reference the appropriate sideband control provider.
>
> >
> > Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 10/21] net: phy: add X-Powers AC200 EPHY control driver
2026-08-03 16:09 ` James Hilliard
@ 2026-08-03 17:48 ` Andrew Lunn
2026-08-03 18:45 ` James Hilliard
0 siblings, 1 reply; 51+ messages in thread
From: Andrew Lunn @ 2026-08-03 17:48 UTC (permalink / raw)
To: James Hilliard
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
On Mon, Aug 03, 2026 at 10:09:01AM -0600, James Hilliard wrote:
> On Mon, Aug 3, 2026 at 7:51 AM Andrew Lunn <andrew@lunn.ch> wrote:
> >
> > > +static int
> > > +ac200_ephy_ctl_set_led_outputs(struct acx00_ephy_control *control,
> > > + unsigned long outputs)
> > > +{
> > > +}
> >
> > > +static int
> > > +ac200_ephy_ctl_set_led_polarity(struct acx00_ephy_control *control,
> > > + bool active_low)
> > > +{
> > > +}
> >
> > I've not looked into the details, but these look like pinmux. Does
> > this I2C device have a GPIO controller? Can these pins be used for
> > other things than LEDs? Maybe you should be implementing a GPIO and
> > pinmux driver. The Ethernet PHY driver can then just select the
> > correct pinmux configuration.
>
> These do not appear to be GPIO or muxable pins. The AC200
> documentation describes them as three dedicated EPHY LED
> outputs: link/activity, speed, and duplex. The bits in SYS_EPHY_CTL1
> enable those output pads, while EPHY_CTL provides their shared
> polarity setting. I have not found any documented GPIO data
> registers or alternative functions for these pins.
So what i'm trying to do with both of these is make it look more like
standard Linux building blocks.
If that is not possible, i would actually put all this code into the
PHY driver. You then have a regmap property to gain access to the I2C
registers.
Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 07/21] dt-bindings: net: x-powers: add AC200 EPHY control
2026-08-03 13:40 ` Andrew Lunn
@ 2026-08-03 18:03 ` James Hilliard
0 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 18:03 UTC (permalink / raw)
To: Andrew Lunn
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
On Mon, Aug 3, 2026 at 7:40 AM Andrew Lunn <andrew@lunn.ch> wrote:
>
> > + phy-mode:
> > + enum:
> > + - mii
> > + - rmii
>
> Despite the phy- bit, phy-mode is a MAC property, it defines the MII
> the MAC uses. I would not expect to see this anywhere else.
>
> The PHY driver is told what MII to use, phydev->interface, which takes
> the values PHY_INTERFACE_MODE_RMII, PHY_INTERFACE_MODE_MII etc.
>
> Please explain why you need this here, why is the MAC nodes phy-mode
> property not sufficient.
I have reworked this for v2. The phy-mode properties will be removed from
both control bindings and their DT nodes, leaving the MAC node as the sole
source of the interface mode. The PHY driver instead passes
phydev->interface to the selected control provider.
There is one ordering complication: the control block must be powered before
the Clause 22 PHY identification registers become accessible, which happens
before a MAC has attached. Early discovery therefore uses the hardware's
reset-default MII mode.
For v2, I split the provider interface into separate power_on() and
set_interface() operations. Once the MAC has supplied phydev->interface, the
PHY reset path calls set_interface(). If the provider is already powered,
this changes only the RMII_SEL bit; the normal phylib soft reset then
completes the handoff. It does not repeat the full control-block power and
reset sequence.
>
> Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 10/21] net: phy: add X-Powers AC200 EPHY control driver
2026-08-03 17:48 ` Andrew Lunn
@ 2026-08-03 18:45 ` James Hilliard
0 siblings, 0 replies; 51+ messages in thread
From: James Hilliard @ 2026-08-03 18:45 UTC (permalink / raw)
To: Andrew Lunn
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
On Mon, Aug 3, 2026 at 11:57 AM Andrew Lunn <andrew@lunn.ch> wrote:
>
> On Mon, Aug 03, 2026 at 10:09:01AM -0600, James Hilliard wrote:
> > On Mon, Aug 3, 2026 at 7:51 AM Andrew Lunn <andrew@lunn.ch> wrote:
> > >
> > > > +static int
> > > > +ac200_ephy_ctl_set_led_outputs(struct acx00_ephy_control *control,
> > > > + unsigned long outputs)
> > > > +{
> > > > +}
> > >
> > > > +static int
> > > > +ac200_ephy_ctl_set_led_polarity(struct acx00_ephy_control *control,
> > > > + bool active_low)
> > > > +{
> > > > +}
> > >
> > > I've not looked into the details, but these look like pinmux. Does
> > > this I2C device have a GPIO controller? Can these pins be used for
> > > other things than LEDs? Maybe you should be implementing a GPIO and
> > > pinmux driver. The Ethernet PHY driver can then just select the
> > > correct pinmux configuration.
> >
> > These do not appear to be GPIO or muxable pins. The AC200
> > documentation describes them as three dedicated EPHY LED
> > outputs: link/activity, speed, and duplex. The bits in SYS_EPHY_CTL1
> > enable those output pads, while EPHY_CTL provides their shared
> > polarity setting. I have not found any documented GPIO data
> > registers or alternative functions for these pins.
>
> So what i'm trying to do with both of these is make it look more like
> standard Linux building blocks.
>
> If that is not possible, i would actually put all this code into the
> PHY driver. You then have a regmap property to gain access to the I2C
> registers.
I understand the goal of using standard Linux building blocks where they
fit, but I do not think folding these control paths into the common PHY
driver gives the right device model here.
The AC200 and AC300 expose the same Clause 22 PHY interface, but their
control functions are separate devices with different transports and
lifetimes. The AC200 control function is an MFD child accessed through
the parent I2C regmap. The AC300 control function is a separately
addressed non-PHY MDIO device. A regmap reference only describes the
AC200 transport; it does not represent the AC300 control device.
Folding both implementations into the common PHY driver would make that
driver manage AC200 MFD registers and resources as well as an AC300 MDIO
control endpoint. That would mix the package-specific control transports
with the otherwise common Clause 22 implementation.
The current split uses standard subsystems wherever they provide a
matching abstraction: MFD, regmap, MDIO devices, clocks, regulators,
NVMEM, device links, and the PHY LED framework. The narrow private
interface covers only the compound operation which those frameworks
cannot express: calibrating, addressing, configuring, and sequencing the
package control block before the Clause 22 interface becomes usable.
The link address is an important example. It is defined once by
phydev->mdio.addr. AC200 must program that value through its I2C control
interface, while AC300 must validate it against the address of its MDIO
control endpoint. Generic PHY operations do not carry a consumer MDIO
address. Encoding it in a phys specifier or control-node property would
duplicate the PHY node's reg value. Giving the common PHY driver a raw
regmap avoids passing the address only by moving the provider-specific
implementation across the driver boundary.
The LED pad-enable and polarity bits are similarly part of the package
control function. They control dedicated EPHY outputs rather than GPIOs
or muxable pins. The common PHY driver still owns the PHY LED semantics,
while the selected control driver performs the package-specific register
access.
For those reasons, I think keeping the transport-specific control drivers
and the common Clause 22 PHY driver separate is the cleaner division.
>
> Andrew
>
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200
2026-08-03 8:20 ` Krzysztof Kozlowski
2026-08-03 8:21 ` Krzysztof Kozlowski
@ 2026-08-03 21:34 ` James Hilliard
2026-08-03 22:06 ` Andrew Lunn
1 sibling, 1 reply; 51+ messages in thread
From: James Hilliard @ 2026-08-03 21:34 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Andre Przywara,
Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Andrew Lunn, Heiner Kallweit, Russell King,
Saravana Kannan, Lee Jones, Heiko Stuebner, netdev, devicetree,
linux-arm-kernel, linux-sunxi, linux-kernel, linux-stm32, mfd,
linux-rockchip
On Mon, Aug 3, 2026 at 2:20 AM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>
> On 03/08/2026 09:54, James Hilliard wrote:
> > On Mon, Aug 3, 2026 at 1:07 AM Krzysztof Kozlowski <krzk@kernel.org> wrote:
> >>
> >> On 03/08/2026 07:14, James Hilliard wrote:
> >>> The AC200 is an I2C-controlled mixed-signal companion IC containing
> >>> audio, video, RTC and Fast Ethernet PHY functions.
> >>
> >> This fails when applied, because you did not explain the
> >> dependencies/merging of this patchset.
> >>
> >> This is THE MOST important information of cover letter. The first thing
> >> to explain.
> >
> > I did mention in the cover letter that the pwm series is a dependency:
> > https://lore.kernel.org/all/20260703152215.192859-1-richard.genoud@bootlin.com/
>
> Apply this patch and test.
>
How were you applying the series?
I noticed sashiko was also failing to apply the series:
https://sashiko.dev/#/patchset/20260802-submit-acx00-of-dynamic-v1-v1-0-0a53cd9e21cc%40gmail.com
But that appeared to be due to sashiko missing dependency resolution,
I went ahead and created a PR that should hopefully fix that:
https://github.com/sashiko-dev/sashiko/pull/389
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200
2026-08-03 21:34 ` James Hilliard
@ 2026-08-03 22:06 ` Andrew Lunn
0 siblings, 0 replies; 51+ messages in thread
From: Andrew Lunn @ 2026-08-03 22:06 UTC (permalink / raw)
To: James Hilliard
Cc: Krzysztof Kozlowski, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Andre Przywara, Richard Genoud, Maxime Ripard, Alexandre Torgue,
Giuseppe Cavallaro, Jose Abreu, Maxime Chevallier,
Maxime Coquelin, Heiner Kallweit, Russell King, Saravana Kannan,
Lee Jones, Heiko Stuebner, netdev, devicetree, linux-arm-kernel,
linux-sunxi, linux-kernel, linux-stm32, mfd, linux-rockchip
> How were you applying the series?
All the networking patches will be applied to net-next.
https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html
You are going to need to split them out.
Andrew
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 51+ messages in thread
end of thread, other threads:[~2026-08-03 22:07 UTC | newest]
Thread overview: 51+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03 5:14 [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support James Hilliard
2026-08-03 5:14 ` [PATCH 01/21] dt-bindings: net: allwinner: add H616 EMAC1 James Hilliard
2026-08-03 13:03 ` Andrew Lunn
2026-08-03 5:14 ` [PATCH 02/21] net: stmmac: sun8i: add support for Allwinner " James Hilliard
2026-08-03 5:14 ` [PATCH 03/21] arm64: dts: allwinner: h616: add EMAC1 controller James Hilliard
2026-08-03 7:01 ` Krzysztof Kozlowski
2026-08-03 5:14 ` [PATCH 04/21] net: mdio: factor out OF child registration helpers James Hilliard
2026-08-03 5:14 ` [PATCH 05/21] net: mdio: make device map changes hotplug-safe James Hilliard
2026-08-03 5:14 ` [PATCH 06/21] net: mdio: support dynamic OF device changes James Hilliard
2026-08-03 5:14 ` [PATCH 07/21] dt-bindings: net: x-powers: add AC200 EPHY control James Hilliard
2026-08-03 13:40 ` Andrew Lunn
2026-08-03 18:03 ` James Hilliard
2026-08-03 5:14 ` [PATCH 08/21] dt-bindings: mfd: x-powers: add AC200 James Hilliard
2026-08-03 7:07 ` Krzysztof Kozlowski
2026-08-03 7:54 ` James Hilliard
2026-08-03 8:20 ` Krzysztof Kozlowski
2026-08-03 8:21 ` Krzysztof Kozlowski
2026-08-03 16:36 ` James Hilliard
2026-08-03 21:34 ` James Hilliard
2026-08-03 22:06 ` Andrew Lunn
2026-08-03 13:18 ` Andrew Lunn
2026-08-03 16:45 ` James Hilliard
2026-08-03 16:51 ` Chen-Yu Tsai
2026-08-03 5:14 ` [PATCH 09/21] mfd: add X-Powers AC200 support James Hilliard
2026-08-03 5:14 ` [PATCH 10/21] net: phy: add X-Powers AC200 EPHY control driver James Hilliard
2026-08-03 13:50 ` Andrew Lunn
2026-08-03 16:09 ` James Hilliard
2026-08-03 17:48 ` Andrew Lunn
2026-08-03 18:45 ` James Hilliard
2026-08-03 5:14 ` [PATCH 11/21] dt-bindings: net: x-powers: add AC300 EPHY control James Hilliard
2026-08-03 5:14 ` [PATCH 12/21] net: phy: add X-Powers AC300 EPHY control driver James Hilliard
2026-08-03 5:14 ` [PATCH 13/21] dt-bindings: net: x-powers: add AC200/AC300 EPHY James Hilliard
2026-08-03 5:14 ` [PATCH 14/21] net: phy: support configuring PHY-autonomous Tx LPI James Hilliard
2026-08-03 5:14 ` [PATCH 15/21] net: phylink: allow PHY-autonomous EEE without MAC LPI James Hilliard
2026-08-03 13:23 ` Andrew Lunn
2026-08-03 5:14 ` [PATCH 16/21] net: phy: add X-Powers AC200/AC300 EPHY driver James Hilliard
2026-08-03 5:14 ` [PATCH 17/21] arm64: dts: allwinner: h616: add ACx00 EPHY resources James Hilliard
2026-08-03 5:14 ` [PATCH 18/21] arm64: dts: allwinner: orangepi-zero2w: enable Ethernet expansion James Hilliard
2026-08-03 5:14 ` [PATCH 19/21] arm64: dts: allwinner: h6: add AC200 EPHY resources James Hilliard
2026-08-03 5:14 ` [PATCH 20/21] arm64: dts: allwinner: h6: tanix: enable AC200 EPHY James Hilliard
2026-08-03 5:14 ` [PATCH 21/21] arm64: defconfig: enable X-Powers ACx00 Ethernet support James Hilliard
2026-08-03 7:00 ` Krzysztof Kozlowski
2026-08-03 7:22 ` James Hilliard
2026-08-03 13:01 ` [PATCH 00/21] net: phy: add X-Powers AC200/AC300 EPHY support Andrew Lunn
2026-08-03 13:14 ` Andrew Lunn
2026-08-03 13:26 ` Andre Przywara
2026-08-03 13:30 ` Andrew Lunn
2026-08-03 15:57 ` James Hilliard
2026-08-03 13:52 ` Andrew Lunn
2026-08-03 13:54 ` Andre Przywara
2026-08-03 13:57 ` Andrew Lunn
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox