| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/09 20:38 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "The patch adds drivers for X-Powers AC200 MFD and ACx00 Ethernet PHYs, which strictly require device tree nodes (of_node) to probe and function. Since syzkaller does not dynamically generate arbitrary device tree nodes, and these devices are not present in the default virtualized environments (like QEMU 'virt' or x86_64), the code is unreachable. The minor refactoring in syscon.c is only reachable via SoC-specific drivers (Exynos, Sunxi, Renesas) that are also not present in standard virtualized environments.",
"WorthFuzzing": false
} |
| 1/1 | 2026/08/09 20:38 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit d94a5a99c55e956a2ffa0ba0575afa00146a50c4\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sun Aug 9 20:38:40 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml b/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml\nnew file mode 100644\nindex 0000000000000..609831b15c806\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml\n@@ -0,0 +1,49 @@\n+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)\n+%YAML 1.2\n+---\n+$id: http://devicetree.org/schemas/mfd/x-powers,ac200.yaml#\n+$schema: http://devicetree.org/meta-schemas/core.yaml#\n+\n+title: X-Powers AC200 mixed-signal IC\n+\n+maintainers:\n+ - James Hilliard \u003cjames.hilliard1@gmail.com\u003e\n+\n+description:\n+ The AC200 is a mixed-signal companion IC containing audio, video, RTC and\n+ Fast Ethernet PHY functions. Its control registers are accessed over I2C.\n+\n+properties:\n+ compatible:\n+ const: x-powers,ac200\n+\n+ reg:\n+ maxItems: 1\n+\n+ clocks:\n+ maxItems: 1\n+ description:\n+ AC200 input clock. When using the Ethernet PHY, its configured rate must\n+ be 24 or 27 MHz, matching the rates encoded by the documented EPHY clock\n+ selector.\n+\n+required:\n+ - compatible\n+ - reg\n+ - clocks\n+\n+additionalProperties: false\n+\n+examples:\n+ - |\n+ i2c {\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+\n+ mixed-signal@10 {\n+ compatible = \"x-powers,ac200\";\n+ reg = \u003c0x10\u003e;\n+ clocks = \u003c\u0026pwm 5\u003e;\n+ };\n+ };\n+...\ndiff --git a/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml\nnew file mode 100644\nindex 0000000000000..ae0c5e980c39e\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml\n@@ -0,0 +1,207 @@\n+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)\n+%YAML 1.2\n+---\n+$id: http://devicetree.org/schemas/net/x-powers,acx00-ephy-package.yaml#\n+$schema: http://devicetree.org/meta-schemas/core.yaml#\n+\n+title: X-Powers AC200 and AC300 Ethernet PHY packages\n+\n+maintainers:\n+ - James Hilliard \u003cjames.hilliard1@gmail.com\u003e\n+\n+description:\n+ The AC200 and AC300 contain compatible Fast Ethernet PHY blocks which report\n+ the same Clause 22 PHY ID and use the same link-side register layout. The\n+ package control paths differ. AC200 control registers are part of an I2C MFD,\n+ while AC300 control registers occupy a second Clause 22 address fixed at 16\n+ plus the link PHY address.\n+\n+ Systems with a known package use the corresponding AC200 or AC300 compatible.\n+ Systems which can contain either package use the ACx00 compatible and a\n+ packed NVMEM configuration field to select the backend at runtime.\n+\n+ The link PHY cannot report its ID until the package control sequence has\n+ completed, so its child node supplies the documented PHY ID. The package and\n+ child use the same base address. The AC200 driver programs that address into\n+ the package, while the AC300 driver derives its control address from it.\n+\n+select:\n+ properties:\n+ compatible:\n+ enum:\n+ - x-powers,ac200-ephy-package\n+ - x-powers,ac300-ephy-package\n+ - x-powers,acx00-ephy-package\n+ required:\n+ - compatible\n+\n+properties:\n+ compatible:\n+ enum:\n+ - x-powers,ac200-ephy-package\n+ - x-powers,ac300-ephy-package\n+ - x-powers,acx00-ephy-package\n+\n+ reg:\n+ maximum: 31\n+\n+ clocks:\n+ maxItems: 1\n+ description:\n+ AC300 input clock, running at 24, 25, or 27 MHz. The AC200 input clock\n+ belongs to the referenced AC200 MFD instead.\n+\n+ vcc-supply:\n+ description:\n+ 3.3 V package supply. This is connected to EPHY_VCC on AC200 and VCC1\n+ on AC300.\n+\n+ nvmem-cells:\n+ maxItems: 1\n+ description:\n+ Packed EPHY configuration field. Bits 3 through 0 contain the analog\n+ calibration code, bits 7 through 4 are reserved, bit 8 selects AC300\n+ when set and AC200 when clear, and bit 9 requests the AC300\n+ low-calibration tuning sequence. A fixed AC200 package may provide only\n+ the low calibration bits or omit the cell and use its internal eFuse\n+ copy instead.\n+\n+ nvmem-cell-names:\n+ const: configuration\n+\n+ x-powers,ac200:\n+ $ref: /schemas/types.yaml#/definitions/phandle\n+ description: Reference to the AC200 MFD containing this Ethernet PHY\n+\n+ x-powers,xmii-rx-clock-inverted:\n+ type: boolean\n+ description:\n+ Invert the xMII receive clock inside the PHY. This is required by some\n+ SoC integrations, including the Allwinner H6 AC200 connection.\n+\n+patternProperties:\n+ ^ethernet-phy@[a-f0-9]+$:\n+ $ref: ethernet-phy.yaml#\n+ properties:\n+ compatible:\n+ const: ethernet-phy-id0044.1400\n+ required:\n+ - compatible\n+ unevaluatedProperties: false\n+\n+required:\n+ - compatible\n+ - vcc-supply\n+\n+dependentRequired:\n+ nvmem-cells: [ nvmem-cell-names ]\n+ nvmem-cell-names: [ nvmem-cells ]\n+\n+allOf:\n+ - $ref: ethernet-phy-package.yaml#\n+ - if:\n+ properties:\n+ compatible:\n+ const: x-powers,ac200-ephy-package\n+ then:\n+ properties:\n+ clocks: false\n+ required:\n+ - x-powers,ac200\n+ - if:\n+ properties:\n+ compatible:\n+ const: x-powers,ac300-ephy-package\n+ then:\n+ properties:\n+ reg:\n+ maximum: 7\n+ x-powers,ac200: false\n+ required:\n+ - clocks\n+ - nvmem-cells\n+ - nvmem-cell-names\n+ - if:\n+ properties:\n+ compatible:\n+ const: x-powers,acx00-ephy-package\n+ then:\n+ properties:\n+ reg:\n+ maximum: 7\n+ required:\n+ - clocks\n+ - nvmem-cells\n+ - nvmem-cell-names\n+ - x-powers,ac200\n+\n+unevaluatedProperties: false\n+\n+examples:\n+ - |\n+ mdio {\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+\n+ ethernet-phy-package@0 {\n+ compatible = \"x-powers,acx00-ephy-package\";\n+ reg = \u003c0\u003e;\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+ clocks = \u003c\u0026pwm 5\u003e;\n+ vcc-supply = \u003c\u0026reg_3v3\u003e;\n+ nvmem-cells = \u003c\u0026ephy_configuration\u003e;\n+ nvmem-cell-names = \"configuration\";\n+ x-powers,ac200 = \u003c\u0026ac200\u003e;\n+\n+ ethernet-phy@0 {\n+ compatible = \"ethernet-phy-id0044.1400\";\n+ reg = \u003c0\u003e;\n+ };\n+ };\n+ };\n+\n+ - |\n+ mdio {\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+\n+ ethernet-phy-package@0 {\n+ compatible = \"x-powers,ac300-ephy-package\";\n+ reg = \u003c0\u003e;\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+ clocks = \u003c\u0026pwm 5\u003e;\n+ vcc-supply = \u003c\u0026reg_3v3\u003e;\n+ nvmem-cells = \u003c\u0026ephy_configuration\u003e;\n+ nvmem-cell-names = \"configuration\";\n+\n+ ethernet-phy@0 {\n+ compatible = \"ethernet-phy-id0044.1400\";\n+ reg = \u003c0\u003e;\n+ };\n+ };\n+ };\n+\n+ - |\n+ mdio {\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+\n+ ethernet-phy-package@1 {\n+ compatible = \"x-powers,ac200-ephy-package\";\n+ reg = \u003c1\u003e;\n+ #address-cells = \u003c1\u003e;\n+ #size-cells = \u003c0\u003e;\n+ vcc-supply = \u003c\u0026reg_3v3\u003e;\n+ x-powers,ac200 = \u003c\u0026ac200\u003e;\n+ nvmem-cells = \u003c\u0026ephy_configuration\u003e;\n+ nvmem-cell-names = \"configuration\";\n+\n+ ethernet-phy@1 {\n+ compatible = \"ethernet-phy-id0044.1400\";\n+ reg = \u003c1\u003e;\n+ };\n+ };\n+ };\n+...\ndiff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig\nindex 763ce6a34782b..95e3c62742bb6 100644\n--- a/drivers/mfd/Kconfig\n+++ b/drivers/mfd/Kconfig\n@@ -205,6 +205,18 @@ config MFD_AC100\n \t This driver include only the core APIs. You have to select individual\n \t components like codecs or RTC under the corresponding menus.\n \n+config MFD_AC200\n+\ttristate \"X-Powers AC200\"\n+\tdepends on I2C\n+\tdepends on OF\n+\tselect MFD_SYSCON\n+\tselect REGMAP_I2C\n+\thelp\n+\t Support for the X-Powers AC200 mixed-signal companion IC. The AC200\n+\t contains audio, video, RTC and Fast Ethernet PHY functions and is\n+\t co-packaged with some Allwinner H6 and H616 SoCs. This driver provides\n+\t the shared register access used by the individual function drivers.\n+\n config MFD_AXP20X\n \ttristate\n \tselect MFD_CORE\ndiff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile\nindex dd4bb7e77c336..890e76a9ad00a 100644\n--- a/drivers/mfd/Makefile\n+++ b/drivers/mfd/Makefile\n@@ -150,6 +150,7 @@ obj-$(CONFIG_MFD_DA9052_SPI)\t+= da9052-spi.o\n obj-$(CONFIG_MFD_DA9052_I2C)\t+= da9052-i2c.o\n \n obj-$(CONFIG_MFD_AC100)\t\t+= ac100.o\n+obj-$(CONFIG_MFD_AC200)\t\t+= ac200.o\n obj-$(CONFIG_MFD_AXP20X)\t+= axp20x.o\n obj-$(CONFIG_MFD_AXP20X_I2C)\t+= axp20x-i2c.o\n obj-$(CONFIG_MFD_AXP20X_RSB)\t+= axp20x-rsb.o\ndiff --git a/drivers/mfd/ac200.c b/drivers/mfd/ac200.c\nnew file mode 100644\nindex 0000000000000..25066bb36ac76\n--- /dev/null\n+++ b/drivers/mfd/ac200.c\n@@ -0,0 +1,170 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * MFD core driver for the X-Powers AC200\n+ *\n+ * Copyright (C) 2019 Jernej Skrabec \u003cjernej.skrabec@gmail.com\u003e\n+ * Copyright (C) 2026 James Hilliard \u003cjames.hilliard1@gmail.com\u003e\n+ *\n+ * Based on the AC100 driver:\n+ * Copyright (C) 2016 Chen-Yu Tsai\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/clk.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/i2c.h\u003e\n+#include \u003clinux/mfd/syscon.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/regmap.h\u003e\n+\n+#define AC200_SYS_VERSION_REG\t\t\t0x0000\n+#define AC200_SYS_VERSION_PACKAGE_MASK\t\tGENMASK(15, 14)\n+#define AC200_SYS_VERSION_CHIP_MASK\t\tGENMASK(11, 0)\n+\n+#define AC200_SYS_CONTROL_REG\t\t\t0x0002\n+#define AC200_SYS_CONTROL_CHIP_RESET_DEASSERT\tBIT(0)\n+\n+/* Interface register accessible from every register page. */\n+#define AC200_TWI_REG_ADDR_H\t0x00fe\n+#define AC200_MAX_REG\t\t0xa1f2\n+\n+struct ac200 {\n+\tstruct regmap *regmap;\n+};\n+\n+static const struct regmap_range_cfg ac200_range_cfg[] = {\n+\t{\n+\t\t.range_max = AC200_MAX_REG,\n+\t\t.selector_reg = AC200_TWI_REG_ADDR_H,\n+\t\t.selector_mask = 0xff,\n+\t\t.window_len = 256,\n+\t},\n+};\n+\n+/*\n+ * Each AC200 sub-block can reset independently, invalidating its register\n+ * contents without regmap's knowledge. Cache only the common page selector;\n+ * this avoids a selector read-modify-write for every access on the same page\n+ * without ever returning stale functional-register values.\n+ */\n+static bool ac200_volatile_reg(struct device *dev, unsigned int reg)\n+{\n+\treturn reg != AC200_TWI_REG_ADDR_H;\n+}\n+\n+static const struct regmap_config ac200_regmap_config = {\n+\t.name = \"ac200\",\n+\t.reg_bits = 8,\n+\t.reg_stride = 2,\n+\t.val_bits = 16,\n+\t.ranges = ac200_range_cfg,\n+\t.num_ranges = ARRAY_SIZE(ac200_range_cfg),\n+\t.max_register = AC200_MAX_REG,\n+\t.volatile_reg = ac200_volatile_reg,\n+\t.cache_type = REGCACHE_MAPLE,\n+};\n+\n+static void ac200_disable(void *data)\n+{\n+\tstruct ac200 *ddata = data;\n+\n+\tregmap_write(ddata-\u003eregmap, AC200_SYS_CONTROL_REG, 0);\n+}\n+\n+static int ac200_probe(struct i2c_client *client)\n+{\n+\tstruct device *dev = \u0026client-\u003edev;\n+\tunsigned int version;\n+\tstruct ac200 *ddata;\n+\tstruct clk *clk;\n+\tint ret;\n+\n+\tddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);\n+\tif (!ddata)\n+\t\treturn -ENOMEM;\n+\n+\tclk = devm_clk_get_enabled(dev, NULL);\n+\tif (IS_ERR(clk))\n+\t\treturn dev_err_probe(dev, PTR_ERR(clk),\n+\t\t\t\t \"failed to enable input clock\\n\");\n+\n+\tret = devm_clk_rate_exclusive_get(dev, clk);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"failed to lock clock rate\\n\");\n+\n+\tddata-\u003eregmap = devm_regmap_init_i2c(client, \u0026ac200_regmap_config);\n+\tif (IS_ERR(ddata-\u003eregmap))\n+\t\treturn dev_err_probe(dev, PTR_ERR(ddata-\u003eregmap),\n+\t\t\t\t \"failed to initialize regmap\\n\");\n+\n+\ti2c_set_clientdata(client, ddata);\n+\n+\t/*\n+\t * No minimum delay is documented. Match the vendor driver's 40 ms delay\n+\t * before its first AC200 register access after enabling the input clock.\n+\t */\n+\tmsleep(40);\n+\n+\tret = regmap_read(ddata-\u003eregmap, AC200_SYS_VERSION_REG, \u0026version);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret,\n+\t\t\t\t \"failed to read chip version\\n\");\n+\n+\tdev_info(dev, \"AC200 revision %#lx in package %lu\\n\",\n+\t\t FIELD_GET(AC200_SYS_VERSION_CHIP_MASK, version),\n+\t\t FIELD_GET(AC200_SYS_VERSION_PACKAGE_MASK, version));\n+\n+\t/* Reset the chip after dependent function drivers have unbound. */\n+\tret = devm_add_action_or_reset(dev, ac200_disable, ddata);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = regmap_write(ddata-\u003eregmap, AC200_SYS_CONTROL_REG, 0);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = regmap_write(ddata-\u003eregmap, AC200_SYS_CONTROL_REG,\n+\t\t\t AC200_SYS_CONTROL_CHIP_RESET_DEASSERT);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = devm_of_syscon_register_regmap(dev, dev-\u003eof_node,\n+\t\t\t\t\t ddata-\u003eregmap);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret,\n+\t\t\t\t \"failed to register regmap\\n\");\n+\n+\treturn 0;\n+}\n+\n+static void ac200_shutdown(struct i2c_client *client)\n+{\n+\tac200_disable(i2c_get_clientdata(client));\n+}\n+\n+static const struct of_device_id ac200_of_match[] = {\n+\t{ .compatible = \"x-powers,ac200\" },\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(of, ac200_of_match);\n+\n+static const struct i2c_device_id ac200_i2c_ids[] = {\n+\t{ .name = \"ac200\" },\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(i2c, ac200_i2c_ids);\n+\n+static struct i2c_driver ac200_driver = {\n+\t.driver = {\n+\t\t.name = \"ac200\",\n+\t\t.of_match_table = ac200_of_match,\n+\t},\n+\t.probe = ac200_probe,\n+\t.shutdown = ac200_shutdown,\n+\t.id_table = ac200_i2c_ids,\n+};\n+module_i2c_driver(ac200_driver);\n+\n+MODULE_AUTHOR(\"James Hilliard \u003cjames.hilliard1@gmail.com\u003e\");\n+MODULE_DESCRIPTION(\"X-Powers AC200 MFD core driver\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/mfd/syscon.c b/drivers/mfd/syscon.c\nindex 21a7fcdd27379..448422fe4f21c 100644\n--- a/drivers/mfd/syscon.c\n+++ b/drivers/mfd/syscon.c\n@@ -10,6 +10,7 @@\n \n #include \u003clinux/cleanup.h\u003e\n #include \u003clinux/clk.h\u003e\n+#include \u003clinux/device.h\u003e\n #include \u003clinux/err.h\u003e\n #include \u003clinux/hwspinlock.h\u003e\n #include \u003clinux/list.h\u003e\n@@ -193,53 +194,95 @@ static struct regmap *device_node_get_regmap(struct device_node *np,\n \treturn syscon-\u003eregmap;\n }\n \n-/**\n- * of_syscon_register_regmap() - Register regmap for specified device node\n- * @np: Device tree node\n- * @regmap: Pointer to regmap object\n- *\n- * Register an externally created regmap object with syscon for the specified\n- * device tree node. This regmap will then be returned to client drivers using\n- * the syscon_regmap_lookup_by_phandle() API.\n- *\n- * Return: 0 on success, negative error code on failure.\n- */\n-int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)\n+static struct syscon *\n+of_syscon_register_regmap_internal(struct device_node *np,\n+\t\t\t\t struct regmap *regmap)\n {\n \tstruct syscon *entry, *syscon = NULL;\n-\tint ret;\n \n \tif (!np || !regmap)\n-\t\treturn -EINVAL;\n+\t\treturn ERR_PTR(-EINVAL);\n \n \tsyscon = kzalloc_obj(*syscon);\n \tif (!syscon)\n-\t\treturn -ENOMEM;\n+\t\treturn ERR_PTR(-ENOMEM);\n \n \t/* check if syscon entry already exists */\n \tmutex_lock(\u0026syscon_list_lock);\n \n \tlist_for_each_entry(entry, \u0026syscon_list, list)\n \t\tif (entry-\u003enp == np) {\n-\t\t\tret = -EEXIST;\n-\t\t\tgoto err_unlock;\n+\t\t\tmutex_unlock(\u0026syscon_list_lock);\n+\t\t\tkfree(syscon);\n+\t\t\treturn ERR_PTR(-EEXIST);\n \t\t}\n \n \tsyscon-\u003eregmap = regmap;\n-\tsyscon-\u003enp = np;\n+\tsyscon-\u003enp = of_node_get(np);\n \n \t/* register the regmap in syscon list */\n \tlist_add_tail(\u0026syscon-\u003elist, \u0026syscon_list);\n \tmutex_unlock(\u0026syscon_list_lock);\n \n-\treturn 0;\n+\treturn syscon;\n+}\n+\n+/**\n+ * of_syscon_register_regmap() - Register regmap for specified device node\n+ * @np: Device tree node\n+ * @regmap: Pointer to regmap object\n+ *\n+ * Register an externally created regmap object with syscon for the specified\n+ * device tree node. This regmap will then be returned to client drivers using\n+ * the syscon_regmap_lookup_by_phandle() API.\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)\n+{\n+\treturn PTR_ERR_OR_ZERO(of_syscon_register_regmap_internal(np, regmap));\n+}\n+EXPORT_SYMBOL_GPL(of_syscon_register_regmap);\n+\n+static void devm_of_syscon_register_regmap_release(void *data)\n+{\n+\tstruct syscon *syscon = data;\n \n-err_unlock:\n+\tmutex_lock(\u0026syscon_list_lock);\n+\tlist_del(\u0026syscon-\u003elist);\n \tmutex_unlock(\u0026syscon_list_lock);\n+\n+\tof_node_put(syscon-\u003enp);\n \tkfree(syscon);\n-\treturn ret;\n }\n-EXPORT_SYMBOL_GPL(of_syscon_register_regmap);\n+\n+/**\n+ * devm_of_syscon_register_regmap() - Register a managed external syscon regmap\n+ * @dev: Device that owns the regmap\n+ * @np: Device tree node associated with the regmap\n+ * @regmap: Pointer to the externally created regmap\n+ *\n+ * Register an externally created regmap object with syscon and remove it when\n+ * @dev is unbound. Consumers must stop using the regmap before the provider is\n+ * unbound, for example by establishing a managed device link to @dev.\n+ *\n+ * Return: 0 on success, negative error code on failure.\n+ */\n+int devm_of_syscon_register_regmap(struct device *dev,\n+\t\t\t\t struct device_node *np,\n+\t\t\t\t struct regmap *regmap)\n+{\n+\tstruct syscon *syscon;\n+\n+\tsyscon = of_syscon_register_regmap_internal(np, regmap);\n+\tif (IS_ERR(syscon))\n+\t\treturn PTR_ERR(syscon);\n+\n+\treturn devm_add_action_or_reset(dev,\n+\t\t\t\t\tdevm_of_syscon_register_regmap_release,\n+\t\t\t\t\tsyscon);\n+}\n+EXPORT_SYMBOL_GPL(devm_of_syscon_register_regmap);\n \n /**\n * device_node_to_regmap() - Get or create a regmap for specified device node\ndiff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig\nindex a29d3fed8a053..27b8ffe4b5390 100644\n--- a/drivers/net/phy/Kconfig\n+++ b/drivers/net/phy/Kconfig\n@@ -475,6 +475,17 @@ config VITESSE_PHY\n \thelp\n \t Currently supports the vsc8244\n \n+config XPOWERS_ACX00_PHY\n+\ttristate \"X-Powers AC200/AC300 Ethernet PHY\"\n+\tdepends on COMMON_CLK \u0026\u0026 NVMEM \u0026\u0026 OF_MDIO \u0026\u0026 REGULATOR\n+\tdepends on I2C || !I2C\n+\tselect PHY_PACKAGE\n+\thelp\n+\t Enable the Fast Ethernet PHY driver shared by the X-Powers AC200\n+\t and AC300 companion ICs. The driver configures their common MDIO PHY\n+\t registers and the package-specific AC200 I2C or AC300 MDIO control\n+\t registers.\n+\n config XILINX_GMII2RGMII\n \ttristate \"Xilinx GMII2RGMII converter driver\"\n \thelp\ndiff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile\nindex e23df5e836e9e..ffb0efbc4cc4e 100644\n--- a/drivers/net/phy/Makefile\n+++ b/drivers/net/phy/Makefile\n@@ -100,4 +100,5 @@ obj-$(CONFIG_SMSC_PHY)\t\t+= smsc.o\n obj-$(CONFIG_STE10XP)\t\t+= ste10Xp.o\n obj-$(CONFIG_TERANETICS_PHY)\t+= teranetics.o\n obj-$(CONFIG_VITESSE_PHY)\t+= vitesse.o\n+obj-$(CONFIG_XPOWERS_ACX00_PHY)\t+= xpowers/\n obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o\ndiff --git a/drivers/net/phy/xpowers/Makefile b/drivers/net/phy/xpowers/Makefile\nnew file mode 100644\nindex 0000000000000..ae977a7958325\n--- /dev/null\n+++ b/drivers/net/phy/xpowers/Makefile\n@@ -0,0 +1,3 @@\n+# SPDX-License-Identifier: GPL-2.0\n+xpowers-acx00-y := acx00.o ac200.o ac300.o\n+obj-$(CONFIG_XPOWERS_ACX00_PHY) += xpowers-acx00.o\ndiff --git a/drivers/net/phy/xpowers/ac200.c b/drivers/net/phy/xpowers/ac200.c\nnew file mode 100644\nindex 0000000000000..681e013be401b\n--- /dev/null\n+++ b/drivers/net/phy/xpowers/ac200.c\n@@ -0,0 +1,315 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * X-Powers AC200 Ethernet PHY package backend\n+ *\n+ * Copyright (c) 2022 Arm Ltd. (Andre Przywara \u003candre.przywara@arm.com\u003e)\n+ * Copyright (C) 2026 James Hilliard \u003cjames.hilliard1@gmail.com\u003e\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/clk.h\u003e\n+#include \u003clinux/i2c.h\u003e\n+#include \u003clinux/mfd/syscon.h\u003e\n+#include \u003clinux/of.h\u003e\n+#include \u003clinux/phy.h\u003e\n+#include \u003clinux/regmap.h\u003e\n+#include \u003clinux/regulator/consumer.h\u003e\n+#include \u003clinux/slab.h\u003e\n+\n+#include \"acx00.h\"\n+\n+#define AC200_EPHY_BPS_EFFUSE_OFFSET\t3\n+#define AC200_EPHY_CLK_RATE_24_MHZ\t24000000\n+#define AC200_EPHY_CLK_RATE_27_MHZ\t27000000\n+\n+#define AC200_SYS_EPHY_CTL0_REG\t\t\t0x0014\n+#define AC200_EPHY_RESET_DEASSERT\t\tBIT(0)\n+#define AC200_EPHY_SYSCLK_ENABLE\t\t\tBIT(1)\n+\n+#define AC200_SYS_EPHY_CTL1_REG\t\t\t0x0016\n+#define AC200_EPHY_MII_IO_ENABLE\t\t\tBIT(0)\n+\n+/* AC200-internal copy of the Ethernet PHY calibration eFuse. */\n+#define AC200_EFUSE_EPHY_REG\t\t\t0x8004\n+\n+#define AC200_EPHY_CTL_REG\t\t\t0x6000\n+#define AC200_EPHY_SHUTDOWN\t\t\tBIT(0)\n+#define AC200_EPHY_CLK_SEL_24_MHZ\t\tBIT(2)\n+#define AC200_EPHY_PHY_ADDR_MASK\t\t\tGENMASK(8, 4)\n+#define AC200_EPHY_RMII_SEL\t\t\tBIT(11)\n+#define AC200_EPHY_BPS_EFFUSE_MASK\t\tGENMASK(15, 12)\n+\n+struct ac200_ephy_ctl {\n+\tstruct acx00_ephy_control control;\n+\tstruct regmap *regmap;\n+\tstruct regulator *vcc;\n+\tstruct device *dev;\n+\tu16 ephy_ctl;\n+\tunsigned int phy_addr;\n+\tphy_interface_t interface;\n+\tbool supply_enabled;\n+\tbool powered;\n+};\n+\n+static u16 ac200_ephy_ctl_config(const struct ac200_ephy_ctl *priv)\n+{\n+\treturn priv-\u003eephy_ctl |\n+\t\t(priv-\u003einterface == PHY_INTERFACE_MODE_RMII ?\n+\t\t AC200_EPHY_RMII_SEL : 0) |\n+\t\tFIELD_PREP(AC200_EPHY_PHY_ADDR_MASK, priv-\u003ephy_addr);\n+}\n+\n+static int ac200_ephy_ctl_write(struct ac200_ephy_ctl *priv,\n+\t\t\t\tunsigned int reg, u16 value)\n+{\n+\tint ret;\n+\n+\tret = regmap_write(priv-\u003eregmap, reg, value);\n+\tif (ret)\n+\t\tdev_err(priv-\u003edev, \"failed to write register %#x: %pe\\n\",\n+\t\t\treg, ERR_PTR(ret));\n+\n+\treturn ret;\n+}\n+\n+static int ac200_ephy_ctl_disable(struct ac200_ephy_ctl *priv)\n+{\n+\tint err;\n+\tint ret = 0;\n+\n+\tif (priv-\u003epowered) {\n+\t\tret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG,\n+\t\t\t\t\t ac200_ephy_ctl_config(priv) |\n+\t\t\t\t\t AC200_EPHY_SHUTDOWN);\n+\t\terr = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL1_REG, 0);\n+\t\tif (!ret)\n+\t\t\tret = err;\n+\t\terr = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, 0);\n+\t\tif (!ret)\n+\t\t\tret = err;\n+\n+\t\tpriv-\u003epowered = false;\n+\t}\n+\n+\tif (priv-\u003esupply_enabled) {\n+\t\terr = regulator_disable(priv-\u003evcc);\n+\t\tif (!err)\n+\t\t\tpriv-\u003esupply_enabled = false;\n+\t\telse if (!ret)\n+\t\t\tret = err;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int ac200_ephy_ctl_power_off(struct acx00_ephy_control *control)\n+{\n+\tstruct ac200_ephy_ctl *priv =\n+\t\tcontainer_of(control, struct ac200_ephy_ctl, control);\n+\n+\treturn ac200_ephy_ctl_disable(priv);\n+}\n+\n+static int\n+ac200_ephy_ctl_set_interface(struct acx00_ephy_control *control,\n+\t\t\t phy_interface_t interface)\n+{\n+\tstruct ac200_ephy_ctl *priv =\n+\t\tcontainer_of(control, struct ac200_ephy_ctl, control);\n+\tint ret = 0;\n+\n+\tif (interface != PHY_INTERFACE_MODE_MII \u0026\u0026\n+\t interface != PHY_INTERFACE_MODE_RMII)\n+\t\treturn -EINVAL;\n+\n+\tif (priv-\u003einterface == interface)\n+\t\treturn 0;\n+\n+\tif (priv-\u003epowered)\n+\t\tret = regmap_update_bits(priv-\u003eregmap, AC200_EPHY_CTL_REG,\n+\t\t\t\t\t AC200_EPHY_RMII_SEL,\n+\t\t\t\t\t interface == PHY_INTERFACE_MODE_RMII ?\n+\t\t\t\t\t AC200_EPHY_RMII_SEL : 0);\n+\tif (!ret)\n+\t\tpriv-\u003einterface = interface;\n+\n+\treturn ret;\n+}\n+\n+static int ac200_ephy_ctl_power_on(struct acx00_ephy_control *control,\n+\t\t\t\t unsigned int phy_addr)\n+{\n+\tstruct ac200_ephy_ctl *priv =\n+\t\tcontainer_of(control, struct ac200_ephy_ctl, control);\n+\tu16 ephy_ctl;\n+\tint ret;\n+\n+\tif (phy_addr \u003e FIELD_MAX(AC200_EPHY_PHY_ADDR_MASK))\n+\t\treturn -EINVAL;\n+\n+\tif (priv-\u003epowered \u0026\u0026 priv-\u003ephy_addr == phy_addr)\n+\t\treturn 0;\n+\n+\tif (priv-\u003epowered) {\n+\t\tret = ac200_ephy_ctl_disable(priv);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\tpriv-\u003ephy_addr = phy_addr;\n+\n+\tif (!priv-\u003esupply_enabled) {\n+\t\tret = regulator_enable(priv-\u003evcc);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t\tpriv-\u003esupply_enabled = true;\n+\t}\n+\n+\tephy_ctl = ac200_ephy_ctl_config(priv);\n+\n+\t/* Start from a disabled state before applying the configuration. */\n+\tret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, 0);\n+\tif (ret)\n+\t\tgoto err_disable;\n+\n+\tret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL1_REG,\n+\t\t\t\t AC200_EPHY_MII_IO_ENABLE);\n+\tif (ret)\n+\t\tgoto err_disable;\n+\n+\tret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG,\n+\t\t\t\t ephy_ctl | AC200_EPHY_SHUTDOWN);\n+\tif (ret)\n+\t\tgoto err_disable;\n+\n+\tret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG,\n+\t\t\t\t AC200_EPHY_RESET_DEASSERT |\n+\t\t\t\t AC200_EPHY_SYSCLK_ENABLE);\n+\tif (ret)\n+\t\tgoto err_disable;\n+\n+\tret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG, ephy_ctl);\n+\tif (ret)\n+\t\tgoto err_disable;\n+\n+\tpriv-\u003epowered = true;\n+\treturn 0;\n+\n+err_disable:\n+\t/* Attempt every step of the shutdown sequence after a partial start. */\n+\tpriv-\u003epowered = true;\n+\tac200_ephy_ctl_disable(priv);\n+\n+\treturn ret;\n+}\n+\n+struct acx00_ephy_control *\n+ac200_ephy_ctl_create(struct phy_device *phydev,\n+\t\t struct device_node *package_node,\n+\t\t bool has_calibration, u8 calibration)\n+{\n+\tstruct device *dev = \u0026phydev-\u003emdio.dev;\n+\tunsigned int internal_calibration;\n+\tstruct device_node *ac200_node;\n+\tstruct ac200_ephy_ctl *priv;\n+\tstruct i2c_client *client;\n+\tstruct device_link *link;\n+\tunsigned long clk_rate;\n+\tu8 bps_effuse_code;\n+\tstruct clk *clk;\n+\tint ret;\n+\n+\tpriv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);\n+\tif (!priv)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\tpriv-\u003edev = dev;\n+\tpriv-\u003evcc = devm_of_regulator_get(dev, package_node, \"vcc\");\n+\tif (IS_ERR(priv-\u003evcc))\n+\t\treturn ERR_PTR(dev_err_probe(dev, PTR_ERR(priv-\u003evcc),\n+\t\t\t\t\t \"failed to get package supply\\n\"));\n+\n+\tac200_node = of_parse_phandle(package_node, \"x-powers,ac200\", 0);\n+\tif (!ac200_node)\n+\t\treturn ERR_PTR(dev_err_probe(dev, -EINVAL,\n+\t\t\t\t\t \"missing x-powers,ac200 reference\\n\"));\n+\tclient = of_find_i2c_device_by_node(ac200_node);\n+\tof_node_put(ac200_node);\n+\tif (!client) {\n+\t\tret = IS_ENABLED(CONFIG_I2C) ? -EPROBE_DEFER : -ENODEV;\n+\t\treturn ERR_PTR(dev_err_probe(dev, ret,\n+\t\t\t\t\t \"AC200 device is not registered\\n\"));\n+\t}\n+\n+\tlink = device_link_add(dev, \u0026client-\u003edev,\n+\t\t\t DL_FLAG_AUTOREMOVE_CONSUMER);\n+\tif (!link) {\n+\t\tret = dev_err_probe(dev, -EINVAL,\n+\t\t\t\t \"failed to link AC200 device\\n\");\n+\t\tgoto out_put_client;\n+\t}\n+\n+\tif (READ_ONCE(link-\u003estatus) != DL_STATE_CONSUMER_PROBE) {\n+\t\tret = dev_err_probe(dev, -EPROBE_DEFER,\n+\t\t\t\t \"AC200 driver is not ready\\n\");\n+\t\tgoto out_put_client;\n+\t}\n+\n+\tpriv-\u003eregmap = syscon_regmap_lookup_by_phandle(package_node,\n+\t\t\t\t\t\t \"x-powers,ac200\");\n+\tif (IS_ERR(priv-\u003eregmap)) {\n+\t\tret = dev_err_probe(dev, PTR_ERR(priv-\u003eregmap),\n+\t\t\t\t \"failed to get AC200 regmap\\n\");\n+\t\tgoto out_error;\n+\t}\n+\n+\tif (!has_calibration) {\n+\t\tret = regmap_read(priv-\u003eregmap, AC200_EFUSE_EPHY_REG,\n+\t\t\t\t \u0026internal_calibration);\n+\t\tif (ret)\n+\t\t\tgoto out_error;\n+\t\tcalibration = internal_calibration;\n+\t}\n+\n+\t/* The vendor driver supplies no transfer function beyond this offset. */\n+\tbps_effuse_code = (calibration + AC200_EPHY_BPS_EFFUSE_OFFSET) \u0026\n+\t\t\t FIELD_MAX(AC200_EPHY_BPS_EFFUSE_MASK);\n+\tpriv-\u003eephy_ctl =\n+\t\tFIELD_PREP(AC200_EPHY_BPS_EFFUSE_MASK, bps_effuse_code);\n+\t/* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */\n+\n+\tclk = clk_get(\u0026client-\u003edev, NULL);\n+\tif (IS_ERR(clk)) {\n+\t\tret = PTR_ERR(clk);\n+\t\tgoto out_error;\n+\t}\n+\n+\tclk_rate = clk_get_rate(clk);\n+\tclk_put(clk);\n+\n+\tswitch (clk_rate) {\n+\tcase AC200_EPHY_CLK_RATE_24_MHZ:\n+\t\tpriv-\u003eephy_ctl |= AC200_EPHY_CLK_SEL_24_MHZ;\n+\t\tbreak;\n+\tcase AC200_EPHY_CLK_RATE_27_MHZ:\n+\t\tbreak;\n+\tdefault:\n+\t\tret = dev_err_probe(dev, -EINVAL,\n+\t\t\t\t \"unsupported AC200 clock rate %lu Hz\\n\",\n+\t\t\t\t clk_rate);\n+\t\tgoto out_put_client;\n+\t}\n+\n+\tpriv-\u003econtrol.power_on = ac200_ephy_ctl_power_on;\n+\tpriv-\u003econtrol.power_off = ac200_ephy_ctl_power_off;\n+\tpriv-\u003econtrol.set_interface = ac200_ephy_ctl_set_interface;\n+\t/* MII is the reset default used until the MAC supplies its interface. */\n+\tpriv-\u003einterface = PHY_INTERFACE_MODE_MII;\n+\tput_device(\u0026client-\u003edev);\n+\n+\treturn \u0026priv-\u003econtrol;\n+\n+out_error:\n+\tret = dev_err_probe(dev, ret, \"failed to initialize AC200 control\\n\");\n+out_put_client:\n+\tput_device(\u0026client-\u003edev);\n+\treturn ERR_PTR(ret);\n+}\ndiff --git a/drivers/net/phy/xpowers/ac300.c b/drivers/net/phy/xpowers/ac300.c\nnew file mode 100644\nindex 0000000000000..aa560432a14c9\n--- /dev/null\n+++ b/drivers/net/phy/xpowers/ac300.c\n@@ -0,0 +1,387 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * X-Powers AC300 Ethernet PHY package backend\n+ *\n+ * Copyright (C) 2026 James Hilliard \u003cjames.hilliard1@gmail.com\u003e\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/clk.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/of.h\u003e\n+#include \u003clinux/phy.h\u003e\n+#include \u003clinux/regulator/consumer.h\u003e\n+\n+#include \"../phylib.h\"\n+#include \"acx00.h\"\n+\n+#define AC300_EPHY_BGS_EFFUSE_OFFSET\t3\n+#define AC300_EPHY_CLK_RATE_24_MHZ\t24000000\n+#define AC300_EPHY_CLK_RATE_25_MHZ\t25000000\n+#define AC300_EPHY_CLK_RATE_27_MHZ\t27000000\n+#define AC300_SYS_CONTROL_REG\t\t\t0x00\n+#define AC300_PACKAGE_STATUS_MASK\t\tGENMASK(11, 8)\n+#define AC300_EPHY_CLK_SEL_MASK\t\t\tGENMASK(7, 6)\n+#define AC300_EPHY_CLK_SEL_25_MHZ\t\tFIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 0)\n+#define AC300_EPHY_CLK_SEL_27_MHZ\t\tFIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 1)\n+#define AC300_EPHY_CLK_SEL_24_MHZ\t\tFIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 2)\n+#define AC300_EFUSE_CLK_ENABLE\t\t\tBIT(5)\n+#define AC300_EPHY_REG_CLK_ENABLE\t\tBIT(4)\n+#define AC300_CLKIN_GATING_ENABLE\t\tBIT(2)\n+#define AC300_EPHY_RESET_DEASSERT\t\tBIT(1)\n+#define AC300_CHIP_RESET_DEASSERT\t\tBIT(0)\n+\n+#define AC300_PACKAGE_POR_INTERNAL_DLDO\t\tBIT(3)\n+#define AC300_PACKAGE_PHY_ADDR_MASK\t\tGENMASK(2, 0)\n+\n+#define AC300_SYS_BIAS1_REG\t\t\t0x02\n+#define AC300_INTERNAL_DLDO_ENABLE\t\tBIT(15)\n+\n+#define AC300_SYS_IO_REG\t\t\t0x05\n+#define AC300_MDIO_DRV_MASK\t\t\tGENMASK(15, 14)\n+#define AC300_MII_DRV_MASK\t\t\tGENMASK(11, 10)\n+#define AC300_IO_DRV_LEVEL_2\t\t\t2\n+#define AC300_CLKIN_PAD_ENABLE\t\t\tBIT(4)\n+#define AC300_EPHY_MII_IO_ENABLE\t\t\tBIT(0)\n+\n+#define AC300_EPHY_CONFIG_REG\t\t\t0x06\n+#define AC300_EPHY_BGS_EFFUSE_MASK\t\tGENMASK(15, 12)\n+#define AC300_EPHY_RMII_SEL\t\t\tBIT(11)\n+#define AC300_EPHY_SHUTDOWN\t\t\tBIT(0)\n+\n+#define AC300_SYS_CONTROL_ENABLE_BITS \\\n+\t(AC300_EFUSE_CLK_ENABLE | AC300_EPHY_REG_CLK_ENABLE | \\\n+\t AC300_CLKIN_GATING_ENABLE | AC300_EPHY_RESET_DEASSERT | \\\n+\t AC300_CHIP_RESET_DEASSERT)\n+\n+#define AC300_SYS_IO_VALUE \\\n+\t(FIELD_PREP(AC300_MDIO_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \\\n+\t FIELD_PREP(AC300_MII_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \\\n+\t AC300_CLKIN_PAD_ENABLE | AC300_EPHY_MII_IO_ENABLE)\n+\n+struct ac300_ephy_ctl {\n+\tstruct acx00_ephy_control control;\n+\tstruct phy_device *phydev;\n+\tstruct regulator *vcc;\n+\tstruct clk *clk;\n+\tu16 sys_control;\n+\tu16 ephy_config;\n+\tphy_interface_t interface;\n+\tbool package_known;\n+\tbool internal_dldo;\n+\tbool supply_enabled;\n+\tbool powered;\n+};\n+\n+static unsigned int\n+ac300_ephy_ctl_link_addr(const struct ac300_ephy_ctl *priv)\n+{\n+\treturn priv-\u003ephydev-\u003emdio.addr;\n+}\n+\n+static int ac300_ephy_ctl_read(struct ac300_ephy_ctl *priv, u32 regnum)\n+{\n+\tint ret;\n+\n+\tphy_lock_mdio_bus(priv-\u003ephydev);\n+\tret = __phy_package_read(priv-\u003ephydev,\n+\t\t\t\t AC300_EPHY_CONTROL_ADDR_OFFSET, regnum);\n+\tphy_unlock_mdio_bus(priv-\u003ephydev);\n+\n+\treturn ret;\n+}\n+\n+static int ac300_ephy_ctl_write(struct ac300_ephy_ctl *priv, u32 regnum,\n+\t\t\t\tu16 val)\n+{\n+\tint ret;\n+\n+\tphy_lock_mdio_bus(priv-\u003ephydev);\n+\tret = __phy_package_write(priv-\u003ephydev,\n+\t\t\t\t AC300_EPHY_CONTROL_ADDR_OFFSET, regnum, val);\n+\tphy_unlock_mdio_bus(priv-\u003ephydev);\n+\n+\treturn ret;\n+}\n+\n+static int ac300_ephy_ctl_modify(struct ac300_ephy_ctl *priv, u32 regnum,\n+\t\t\t\t u16 mask, u16 set)\n+{\n+\tint ret;\n+\n+\tphy_lock_mdio_bus(priv-\u003ephydev);\n+\tret = __phy_package_read(priv-\u003ephydev,\n+\t\t\t\t AC300_EPHY_CONTROL_ADDR_OFFSET, regnum);\n+\tif (ret \u003e= 0) {\n+\t\tu16 val = (ret \u0026 ~mask) | (set \u0026 mask);\n+\n+\t\tret = val == ret ? 0 :\n+\t\t\t__phy_package_write(priv-\u003ephydev,\n+\t\t\t\t\t AC300_EPHY_CONTROL_ADDR_OFFSET,\n+\t\t\t\t\t regnum, val);\n+\t}\n+\tphy_unlock_mdio_bus(priv-\u003ephydev);\n+\n+\treturn ret;\n+}\n+\n+static u16 ac300_ephy_ctl_config(const struct ac300_ephy_ctl *priv)\n+{\n+\treturn priv-\u003eephy_config |\n+\t\t(priv-\u003einterface == PHY_INTERFACE_MODE_RMII ?\n+\t\t AC300_EPHY_RMII_SEL : 0);\n+}\n+\n+static u16 ac300_ephy_ctl_reset_value(const struct ac300_ephy_ctl *priv)\n+{\n+\t/*\n+\t * A chip reset restores DLDOEN to one. Until PKG_STATUS has identified\n+\t * the supply arrangement, preserve any external-VDD setup left by the\n+\t * bootloader by asserting only the EPHY reset.\n+\t */\n+\treturn !priv-\u003epackage_known || !priv-\u003einternal_dldo ?\n+\t\tAC300_CHIP_RESET_DEASSERT : 0;\n+}\n+\n+static int ac300_ephy_ctl_disable(struct ac300_ephy_ctl *priv)\n+{\n+\tint err;\n+\tint ret = 0;\n+\n+\tif (priv-\u003epowered) {\n+\t\tret = ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG,\n+\t\t\t\t\t ac300_ephy_ctl_config(priv) |\n+\t\t\t\t\t AC300_EPHY_SHUTDOWN);\n+\t\terr = ac300_ephy_ctl_write(priv, AC300_SYS_IO_REG, 0);\n+\t\tif (!ret)\n+\t\t\tret = err;\n+\t\terr = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG,\n+\t\t\t\t\t ac300_ephy_ctl_reset_value(priv));\n+\t\tif (!ret)\n+\t\t\tret = err;\n+\n+\t\tclk_disable_unprepare(priv-\u003eclk);\n+\t\tpriv-\u003epowered = false;\n+\t}\n+\n+\tif (priv-\u003esupply_enabled) {\n+\t\terr = regulator_disable(priv-\u003evcc);\n+\t\tif (!err)\n+\t\t\tpriv-\u003esupply_enabled = false;\n+\t\telse if (!ret)\n+\t\t\tret = err;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int ac300_ephy_ctl_power_off(struct acx00_ephy_control *control)\n+{\n+\tstruct ac300_ephy_ctl *priv =\n+\t\tcontainer_of(control, struct ac300_ephy_ctl, control);\n+\n+\treturn ac300_ephy_ctl_disable(priv);\n+}\n+\n+static int\n+ac300_ephy_ctl_set_interface(struct acx00_ephy_control *control,\n+\t\t\t phy_interface_t interface)\n+{\n+\tstruct ac300_ephy_ctl *priv =\n+\t\tcontainer_of(control, struct ac300_ephy_ctl, control);\n+\tint ret = 0;\n+\n+\tif (interface != PHY_INTERFACE_MODE_MII \u0026\u0026\n+\t interface != PHY_INTERFACE_MODE_RMII)\n+\t\treturn -EINVAL;\n+\n+\tif (priv-\u003einterface == interface)\n+\t\treturn 0;\n+\n+\tif (priv-\u003epowered)\n+\t\tret = ac300_ephy_ctl_modify(priv, AC300_EPHY_CONFIG_REG,\n+\t\t\t\t\t AC300_EPHY_RMII_SEL,\n+\t\t\t\t\t interface == PHY_INTERFACE_MODE_RMII ?\n+\t\t\t\t\t AC300_EPHY_RMII_SEL : 0);\n+\tif (!ret)\n+\t\tpriv-\u003einterface = interface;\n+\n+\treturn ret;\n+}\n+\n+static int ac300_ephy_ctl_power_on(struct acx00_ephy_control *control,\n+\t\t\t\t unsigned int phy_addr)\n+{\n+\tstruct ac300_ephy_ctl *priv =\n+\t\tcontainer_of(control, struct ac300_ephy_ctl, control);\n+\tu8 package_status;\n+\tu16 reset_value;\n+\tint sys_control;\n+\tint ret;\n+\n+\tif (phy_addr != ac300_ephy_ctl_link_addr(priv))\n+\t\treturn -EINVAL;\n+\n+\tif (priv-\u003epowered)\n+\t\treturn 0;\n+\n+\tif (!priv-\u003esupply_enabled) {\n+\t\tret = regulator_enable(priv-\u003evcc);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t\tpriv-\u003esupply_enabled = true;\n+\n+\t\t/* Wait for the power-on reset interval specified by the manual. */\n+\t\tfsleep(10000);\n+\t}\n+\n+\tret = clk_prepare_enable(priv-\u003eclk);\n+\tif (ret)\n+\t\tgoto err_power_off;\n+\tpriv-\u003epowered = true;\n+\n+\t/* Keep the external-supply configuration across chip resets. */\n+\treset_value = ac300_ephy_ctl_reset_value(priv);\n+\tret = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG, reset_value);\n+\tif (ret)\n+\t\tgoto err_power_off;\n+\n+\t/* The manual requires both resets to be released before the clocks. */\n+\tret = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG,\n+\t\t\t\t AC300_EPHY_RESET_DEASSERT |\n+\t\t\t\t AC300_CHIP_RESET_DEASSERT);\n+\tif (ret)\n+\t\tgoto err_power_off;\n+\n+\t/* Retain the vendor clock-enable defaults, including the eFuse clock. */\n+\tret = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG,\n+\t\t\t\t priv-\u003esys_control);\n+\tif (ret)\n+\t\tgoto err_power_off;\n+\n+\tsys_control = ac300_ephy_ctl_read(priv, AC300_SYS_CONTROL_REG);\n+\tif (sys_control \u003c 0) {\n+\t\tret = sys_control;\n+\t\tgoto err_power_off;\n+\t}\n+\n+\tpackage_status = FIELD_GET(AC300_PACKAGE_STATUS_MASK, sys_control);\n+\tif ((~package_status \u0026 AC300_PACKAGE_PHY_ADDR_MASK) !=\n+\t ac300_ephy_ctl_link_addr(priv)) {\n+\t\tret = -EINVAL;\n+\t\tgoto err_power_off;\n+\t}\n+\n+\tpriv-\u003einternal_dldo = package_status \u0026 AC300_PACKAGE_POR_INTERNAL_DLDO;\n+\tpriv-\u003epackage_known = true;\n+\tret = ac300_ephy_ctl_modify(priv, AC300_SYS_BIAS1_REG,\n+\t\t\t\t AC300_INTERNAL_DLDO_ENABLE,\n+\t\t\t\t priv-\u003einternal_dldo ?\n+\t\t\t\t AC300_INTERNAL_DLDO_ENABLE : 0);\n+\tif (ret)\n+\t\tgoto err_power_off;\n+\n+\t/* Keep the documented default drive level and leave the IRQ disabled. */\n+\tret = ac300_ephy_ctl_write(priv, AC300_SYS_IO_REG,\n+\t\t\t\t AC300_SYS_IO_VALUE);\n+\tif (ret)\n+\t\tgoto err_power_off;\n+\n+\tfsleep(10000);\n+\n+\tret = ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG,\n+\t\t\t\t ac300_ephy_ctl_config(priv) |\n+\t\t\t\t AC300_EPHY_SHUTDOWN);\n+\tif (ret)\n+\t\tgoto err_power_off;\n+\n+\tfsleep(10000);\n+\n+\tret = ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG,\n+\t\t\t\t ac300_ephy_ctl_config(priv));\n+\tif (ret)\n+\t\tgoto err_power_off;\n+\n+\treturn 0;\n+\n+err_power_off:\n+\tac300_ephy_ctl_disable(priv);\n+\n+\treturn ret;\n+}\n+\n+static void ac300_ephy_clk_put(void *data)\n+{\n+\tclk_put(data);\n+}\n+\n+struct acx00_ephy_control *\n+ac300_ephy_ctl_create(struct phy_device *phydev,\n+\t\t struct device_node *package_node, u8 calibration)\n+{\n+\tstruct device *dev = \u0026phydev-\u003emdio.dev;\n+\tstruct ac300_ephy_ctl *priv;\n+\tunsigned long clk_rate;\n+\tu8 bgs_effuse_code;\n+\tint ret;\n+\n+\tpriv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);\n+\tif (!priv)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\tif (phydev-\u003emdio.addr \u003e FIELD_MAX(AC300_PACKAGE_PHY_ADDR_MASK))\n+\t\treturn ERR_PTR(dev_err_probe(dev, -EINVAL,\n+\t\t\t\t\t \"link address is outside the package range\\n\"));\n+\tpriv-\u003ephydev = phydev;\n+\n+\tpriv-\u003evcc = devm_of_regulator_get(dev, package_node, \"vcc\");\n+\tif (IS_ERR(priv-\u003evcc))\n+\t\treturn ERR_PTR(dev_err_probe(dev, PTR_ERR(priv-\u003evcc),\n+\t\t\t\t\t \"failed to get package supply\\n\"));\n+\n+\tpriv-\u003eclk = of_clk_get(package_node, 0);\n+\tif (IS_ERR(priv-\u003eclk))\n+\t\treturn ERR_PTR(dev_err_probe(dev, PTR_ERR(priv-\u003eclk),\n+\t\t\t\t\t \"failed to get input clock\\n\"));\n+\n+\tret = devm_add_action_or_reset(dev, ac300_ephy_clk_put, priv-\u003eclk);\n+\tif (ret)\n+\t\treturn ERR_PTR(ret);\n+\n+\tret = devm_clk_rate_exclusive_get(dev, priv-\u003eclk);\n+\tif (ret)\n+\t\treturn ERR_PTR(dev_err_probe(dev, ret,\n+\t\t\t\t\t \"failed to lock clock rate\\n\"));\n+\n+\tclk_rate = clk_get_rate(priv-\u003eclk);\n+\tswitch (clk_rate) {\n+\tcase AC300_EPHY_CLK_RATE_24_MHZ:\n+\t\tpriv-\u003esys_control = AC300_EPHY_CLK_SEL_24_MHZ;\n+\t\tbreak;\n+\tcase AC300_EPHY_CLK_RATE_25_MHZ:\n+\t\tpriv-\u003esys_control = AC300_EPHY_CLK_SEL_25_MHZ;\n+\t\tbreak;\n+\tcase AC300_EPHY_CLK_RATE_27_MHZ:\n+\t\tpriv-\u003esys_control = AC300_EPHY_CLK_SEL_27_MHZ;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn ERR_PTR(dev_err_probe(dev, -EINVAL,\n+\t\t\t\t\t \"unsupported input clock rate %lu Hz\\n\",\n+\t\t\t\t\t clk_rate));\n+\t}\n+\tpriv-\u003esys_control |= AC300_SYS_CONTROL_ENABLE_BITS;\n+\n+\t/* The vendor driver supplies no transfer function beyond this offset. */\n+\tbgs_effuse_code = (calibration + AC300_EPHY_BGS_EFFUSE_OFFSET) \u0026\n+\t\t\t FIELD_MAX(AC300_EPHY_BGS_EFFUSE_MASK);\n+\tpriv-\u003eephy_config =\n+\t\tFIELD_PREP(AC300_EPHY_BGS_EFFUSE_MASK, bgs_effuse_code);\n+\t/* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */\n+\n+\tpriv-\u003econtrol.power_on = ac300_ephy_ctl_power_on;\n+\tpriv-\u003econtrol.power_off = ac300_ephy_ctl_power_off;\n+\tpriv-\u003econtrol.set_interface = ac300_ephy_ctl_set_interface;\n+\t/* MII is the reset default used until the MAC supplies its interface. */\n+\tpriv-\u003einterface = PHY_INTERFACE_MODE_MII;\n+\n+\treturn \u0026priv-\u003econtrol;\n+}\ndiff --git a/drivers/net/phy/xpowers/acx00.c b/drivers/net/phy/xpowers/acx00.c\nnew file mode 100644\nindex 0000000000000..98f2b0b639096\n--- /dev/null\n+++ b/drivers/net/phy/xpowers/acx00.c\n@@ -0,0 +1,536 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * X-Powers AC200/AC300 Ethernet PHY driver\n+ *\n+ * Copyright (C) 2019 Jernej Skrabec \u003cjernej.skrabec@gmail.com\u003e\n+ * Copyright (C) 2026 James Hilliard \u003cjames.hilliard1@gmail.com\u003e\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/nvmem-consumer.h\u003e\n+#include \u003clinux/of.h\u003e\n+#include \u003clinux/phy.h\u003e\n+#include \u003clinux/slab.h\u003e\n+\n+#include \"../phylib.h\"\n+#include \"acx00.h\"\n+\n+#define ACX00_EPHY_ID\t\t\t\t0x00441400\n+\n+#define ACX00_EPHY_CONFIG_CALIBRATION_MASK\tGENMASK(3, 0)\n+#define ACX00_EPHY_CONFIG_VARIANT_AC300\t\tBIT(8)\n+#define ACX00_EPHY_CONFIG_CALIBRATION_LOW\tBIT(9)\n+\n+#define ACX00_PAGE_SELECT_REG\t\t\t0x1f\n+#define ACX00_PAGE_SELECT_MASK\t\t\tGENMASK(12, 8)\n+#define ACX00_PAGE_0\t\t\t\t0\n+#define ACX00_PAGE_1\t\t\t\t1\n+#define ACX00_PAGE_2\t\t\t\t2\n+#define ACX00_PAGE_6\t\t\t\t6\n+#define ACX00_PAGE_8\t\t\t\t8\n+\n+#define ACX00_PAGE0_GLOBAL_CONFIG_REG\t\t0x13\n+#define ACX00_PAGE0_XMII_RX_CLOCK_INVERT\tBIT(12)\n+#define ACX00_PAGE0_MDI_MODE_MASK\t\tGENMASK(1, 0)\n+#define ACX00_PAGE0_MDI_MODE_AUTO\t\t2\n+\n+#define ACX00_PAGE1_APS_CONTROL_REG\t\t0x12\n+#define ACX00_PAGE1_APS_DISABLED_4S_VALUE\t0x4824\n+#define ACX00_PAGE1_UAPS_CONTROL_REG\t\t0x13\n+#define ACX00_PAGE1_UAPS_ENABLE\t\t\tBIT(15)\n+#define ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG\t0x17\n+#define ACX00_PAGE1_INTELLIGENT_EEE_ENABLE\tBIT(3)\n+\n+#define ACX00_PAGE2_TX_DATA_CONTROL_REG\t\t0x18\n+#define ACX00_PAGE2_10BT_FIR_SELECT_MASK\t\tGENMASK(14, 12)\n+#define ACX00_PAGE2_10BT_FIR_SELECT_DEFAULT\t0\n+\n+#define ACX00_PAGE6_ADC_CONTROL_REG\t\t0x10\n+#define ACX00_PAGE6_ADC_CONTROL_LOW_CAL_VALUE\t0x5523\n+#define ACX00_PAGE6_AFE_RX_CONTROL_REG\t\t0x13\n+#define ACX00_PAGE6_AFE_RX_CONTROL_VALUE\t\t0xf000\n+#define ACX00_PAGE6_AFE_EQ_RX_DETECT_CONTROL_REG\t0x14\n+#define AC200_PAGE6_AFE_EQ_RX_DETECT_VALUE\t0x708f\n+#define AC300_PAGE6_AFE_EQ_RX_DETECT_VALUE\t0x708b\n+#define ACX00_PAGE6_AFE_EQ_RX_DETECT_LOW_CAL_VALUE 0x7809\n+#define ACX00_PAGE6_TX_LEVEL_REG\t\t\t0x15\n+#define ACX00_PAGE6_TX_LEVEL_100M_MASK\t\tGENMASK(15, 8)\n+#define ACX00_PAGE6_TX_LEVEL_10M_MASK\t\tGENMASK(7, 0)\n+#define ACX00_PAGE6_TX_LEVEL_VALUE(_100m, _10m) \\\n+\t(FIELD_PREP(ACX00_PAGE6_TX_LEVEL_100M_MASK, (_100m)) | \\\n+\t FIELD_PREP(ACX00_PAGE6_TX_LEVEL_10M_MASK, (_10m)))\n+#define ACX00_PAGE6_TX_LEVEL_DEFAULT_VALUE \\\n+\tACX00_PAGE6_TX_LEVEL_VALUE(0x15, 0x30)\n+#define ACX00_PAGE6_TX_LEVEL_LOW_CAL_VALUE \\\n+\tACX00_PAGE6_TX_LEVEL_VALUE(0x35, 0x33)\n+\n+#define ACX00_PAGE8_AFE_CONTROL_REG\t\t0x18\n+#define ACX00_PAGE8_AFE_CONTROL_VALUE\t\t0x00bc\n+#define ACX00_PAGE8_AUTO_CAL_CONTROL_REG\t\t0x1d\n+#define ACX00_PAGE8_AUTO_CAL_TX_LEVEL_ADJUST_BYPASS BIT(11)\n+#define ACX00_PAGE8_AUTO_CAL_LOW_CAL_OPAQUE_BITS\t0x0044\n+#define ACX00_PAGE8_AUTO_CAL_LOW_VALUE \\\n+\t(ACX00_PAGE8_AUTO_CAL_TX_LEVEL_ADJUST_BYPASS | \\\n+\t ACX00_PAGE8_AUTO_CAL_LOW_CAL_OPAQUE_BITS)\n+\n+/*\n+ * Another integration of this exact-ID PHY documents its digital vendor\n+ * register map, which also matches the observed ACx00 reset values. The ACx00\n+ * analog-page field encodings remain unpublished, so keep those as opaque\n+ * vendor initialization values instead of inventing bit definitions.\n+ */\n+\n+struct acx00_ephy_priv {\n+\tstruct phy_device *phydev;\n+\tstruct acx00_ephy_control *control;\n+\tbool is_ac300;\n+\tbool use_low_calibration_tuning;\n+\tbool xmii_rx_clock_inverted;\n+};\n+\n+static int acx00_ephy_read_page(struct phy_device *phydev)\n+{\n+\tint ret;\n+\n+\tret = __phy_read(phydev, ACX00_PAGE_SELECT_REG);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\treturn FIELD_GET(ACX00_PAGE_SELECT_MASK, ret);\n+}\n+\n+static int acx00_ephy_write_page(struct phy_device *phydev, int page)\n+{\n+\treturn __phy_write(phydev, ACX00_PAGE_SELECT_REG,\n+\t\t\t FIELD_PREP(ACX00_PAGE_SELECT_MASK, page));\n+}\n+\n+static int acx00_ephy_control_power_on(struct acx00_ephy_priv *priv)\n+{\n+\treturn priv-\u003econtrol-\u003epower_on(priv-\u003econtrol,\n+\t\t\t\t priv-\u003ephydev-\u003emdio.addr);\n+}\n+\n+static int acx00_ephy_control_power_off(struct acx00_ephy_priv *priv)\n+{\n+\treturn priv-\u003econtrol-\u003epower_off(priv-\u003econtrol);\n+}\n+\n+static int acx00_ephy_set_interface(struct phy_device *phydev)\n+{\n+\tstruct acx00_ephy_priv *priv = phydev-\u003epriv;\n+\n+\tif (phydev-\u003einterface == PHY_INTERFACE_MODE_NA)\n+\t\treturn 0;\n+\tif (phydev-\u003einterface != PHY_INTERFACE_MODE_MII \u0026\u0026\n+\t phydev-\u003einterface != PHY_INTERFACE_MODE_RMII)\n+\t\treturn -EINVAL;\n+\n+\treturn priv-\u003econtrol-\u003eset_interface(priv-\u003econtrol,\n+\t\t\t\t\t phydev-\u003einterface);\n+}\n+\n+static void acx00_ephy_control_release(void *data)\n+{\n+\tstruct acx00_ephy_priv *priv = data;\n+\tint ret;\n+\n+\tret = acx00_ephy_control_power_off(priv);\n+\tif (ret)\n+\t\tphydev_warn(priv-\u003ephydev,\n+\t\t\t \"failed to power off control block: %pe\\n\",\n+\t\t\t ERR_PTR(ret));\n+}\n+\n+static int acx00_ephy_read_nvmem_u16(struct device_node *node,\n+\t\t\t\t const char *name, u16 *value)\n+{\n+\tstruct nvmem_cell *cell;\n+\tsize_t i;\n+\tsize_t len;\n+\tu8 *buf;\n+\tu16 val = 0;\n+\n+\tcell = of_nvmem_cell_get(node, name);\n+\tif (IS_ERR(cell))\n+\t\treturn PTR_ERR(cell);\n+\n+\tbuf = nvmem_cell_read(cell, \u0026len);\n+\tnvmem_cell_put(cell);\n+\tif (IS_ERR(buf))\n+\t\treturn PTR_ERR(buf);\n+\tif (!len || len \u003e sizeof(*value)) {\n+\t\tkfree(buf);\n+\t\treturn len ? -ERANGE : -EINVAL;\n+\t}\n+\n+\tfor (i = 0; i \u003c len; i++)\n+\t\tval |= (u16)buf[i] \u003c\u003c (8 * i);\n+\tkfree(buf);\n+\t*value = val;\n+\n+\treturn 0;\n+}\n+\n+static int acx00_ephy_init_package(struct phy_device *phydev,\n+\t\t\t\t struct acx00_ephy_priv *priv)\n+{\n+\tstruct device *dev = \u0026phydev-\u003emdio.dev;\n+\tstruct device_node *package_node;\n+\tbool selectable;\n+\tbool fixed_ac300;\n+\tbool has_configuration;\n+\tu8 calibration;\n+\tu32 base_addr;\n+\tu16 configuration = 0;\n+\tint ret;\n+\n+\tpackage_node = of_get_parent(dev-\u003eof_node);\n+\tif (!package_node)\n+\t\treturn -EINVAL;\n+\tif (!of_node_name_eq(package_node, \"ethernet-phy-package\")) {\n+\t\tret = dev_err_probe(dev, -EINVAL,\n+\t\t\t\t \"PHY is not in an Ethernet PHY package\\n\");\n+\t\tgoto out_put_node;\n+\t}\n+\n+\tret = of_property_read_u32(package_node, \"reg\", \u0026base_addr);\n+\tif (ret || base_addr != phydev-\u003emdio.addr) {\n+\t\tret = dev_err_probe(dev, -EINVAL,\n+\t\t\t\t \"package and link PHY addresses differ\\n\");\n+\t\tgoto out_put_node;\n+\t}\n+\n+\tselectable = of_device_is_compatible(package_node,\n+\t\t\t\t\t \"x-powers,acx00-ephy-package\");\n+\tfixed_ac300 = of_device_is_compatible(package_node,\n+\t\t\t\t\t \"x-powers,ac300-ephy-package\");\n+\tif (!selectable \u0026\u0026 !fixed_ac300 \u0026\u0026\n+\t !of_device_is_compatible(package_node,\n+\t\t\t\t \"x-powers,ac200-ephy-package\")) {\n+\t\tret = dev_err_probe(dev, -EINVAL,\n+\t\t\t\t \"unsupported Ethernet PHY package\\n\");\n+\t\tgoto out_put_node;\n+\t}\n+\n+\tret = devm_of_phy_package_join(dev, phydev, 0);\n+\tif (ret)\n+\t\tgoto out_put_node;\n+\n+\thas_configuration =\n+\t\tof_property_match_string(package_node, \"nvmem-cell-names\",\n+\t\t\t\t\t \"configuration\") \u003e= 0;\n+\tif (has_configuration) {\n+\t\tret = acx00_ephy_read_nvmem_u16(package_node, \"configuration\",\n+\t\t\t\t\t\t\u0026configuration);\n+\t\tif (ret) {\n+\t\t\tret = dev_err_probe(dev, ret,\n+\t\t\t\t\t \"failed to read package configuration\\n\");\n+\t\t\tgoto out_put_node;\n+\t\t}\n+\t} else if (selectable || fixed_ac300) {\n+\t\tret = dev_err_probe(dev, -EINVAL,\n+\t\t\t\t \"package configuration is required\\n\");\n+\t\tgoto out_put_node;\n+\t}\n+\n+\tif (selectable) {\n+\t\tpriv-\u003eis_ac300 =\n+\t\t\tconfiguration \u0026 ACX00_EPHY_CONFIG_VARIANT_AC300;\n+\t} else {\n+\t\tpriv-\u003eis_ac300 = fixed_ac300;\n+\t\tif (has_configuration \u0026\u0026\n+\t\t !!(configuration \u0026 ACX00_EPHY_CONFIG_VARIANT_AC300) !=\n+\t\t priv-\u003eis_ac300) {\n+\t\t\tret = dev_err_probe(dev, -EINVAL,\n+\t\t\t\t\t \"configuration does not match package\\n\");\n+\t\t\tgoto out_put_node;\n+\t\t}\n+\t}\n+\n+\tcalibration = FIELD_GET(ACX00_EPHY_CONFIG_CALIBRATION_MASK,\n+\t\t\t\tconfiguration);\n+\n+\tpriv-\u003euse_low_calibration_tuning =\n+\t\tpriv-\u003eis_ac300 \u0026\u0026\n+\t\t!!(configuration \u0026 ACX00_EPHY_CONFIG_CALIBRATION_LOW);\n+\tpriv-\u003exmii_rx_clock_inverted =\n+\t\tof_property_read_bool(package_node,\n+\t\t\t\t \"x-powers,xmii-rx-clock-inverted\");\n+\n+\tif (priv-\u003eis_ac300)\n+\t\tpriv-\u003econtrol =\n+\t\t\tac300_ephy_ctl_create(phydev, package_node,\n+\t\t\t\t\t calibration);\n+\telse\n+\t\tpriv-\u003econtrol =\n+\t\t\tac200_ephy_ctl_create(phydev, package_node,\n+\t\t\t\t\t has_configuration,\n+\t\t\t\t\t calibration);\n+\tret = PTR_ERR_OR_ZERO(priv-\u003econtrol);\n+\n+out_put_node:\n+\tof_node_put(package_node);\n+\treturn ret;\n+}\n+\n+static int acx00_ephy_disable_autonomous_eee(struct phy_device *phydev)\n+{\n+\treturn phy_modify_paged(phydev, ACX00_PAGE_1,\n+\t\t\t\tACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG,\n+\t\t\t\tACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0);\n+}\n+\n+static int acx00_ephy_probe(struct phy_device *phydev)\n+{\n+\tstruct device *dev = \u0026phydev-\u003emdio.dev;\n+\tstruct acx00_ephy_priv *priv;\n+\tint ret;\n+\n+\tif (!dev-\u003eof_node)\n+\t\treturn -ENODEV;\n+\n+\tpriv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);\n+\tif (!priv)\n+\t\treturn -ENOMEM;\n+\n+\tpriv-\u003ephydev = phydev;\n+\tret = acx00_ephy_init_package(phydev, priv);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tphydev-\u003epriv = priv;\n+\tret = devm_add_action_or_reset(dev, acx00_ephy_control_release, priv);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = acx00_ephy_control_power_on(priv);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret,\n+\t\t\t\t \"failed to power on control block\\n\");\n+\n+\treturn 0;\n+}\n+\n+static int acx00_ephy_soft_reset(struct phy_device *phydev)\n+{\n+\tstruct acx00_ephy_priv *priv = phydev-\u003epriv;\n+\tint ret;\n+\n+\tret = acx00_ephy_set_interface(phydev);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = acx00_ephy_control_power_on(priv);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* ACx00 can acknowledge reset in power-down without restarting. */\n+\tret = genphy_resume(phydev);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn genphy_soft_reset(phydev);\n+}\n+\n+static int acx00_ephy_config_init(struct phy_device *phydev)\n+{\n+\tstruct acx00_ephy_priv *priv = phydev-\u003epriv;\n+\tu16 afe_eq_rx_detect = priv-\u003eis_ac300 ?\n+\t\tAC300_PAGE6_AFE_EQ_RX_DETECT_VALUE :\n+\t\tAC200_PAGE6_AFE_EQ_RX_DETECT_VALUE;\n+\tu16 tx_level_value = ACX00_PAGE6_TX_LEVEL_DEFAULT_VALUE;\n+\tu16 global_config;\n+\tint oldpage;\n+\tint ret;\n+\n+\tglobal_config = FIELD_PREP(ACX00_PAGE0_MDI_MODE_MASK,\n+\t\t\t\t ACX00_PAGE0_MDI_MODE_AUTO);\n+\tif (priv-\u003exmii_rx_clock_inverted)\n+\t\tglobal_config |= ACX00_PAGE0_XMII_RX_CLOCK_INVERT;\n+\n+\tret = phy_modify_paged(phydev, ACX00_PAGE_0,\n+\t\t\t ACX00_PAGE0_GLOBAL_CONFIG_REG,\n+\t\t\t ACX00_PAGE0_XMII_RX_CLOCK_INVERT |\n+\t\t\t ACX00_PAGE0_MDI_MODE_MASK, global_config);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (priv-\u003eis_ac300 \u0026\u0026 priv-\u003euse_low_calibration_tuning) {\n+\t\tafe_eq_rx_detect =\n+\t\t\tACX00_PAGE6_AFE_EQ_RX_DETECT_LOW_CAL_VALUE;\n+\t\ttx_level_value = ACX00_PAGE6_TX_LEVEL_LOW_CAL_VALUE;\n+\t}\n+\n+\toldpage = phy_select_page(phydev, ACX00_PAGE_1);\n+\tif (oldpage \u003c 0)\n+\t\tgoto out_restore_page;\n+\n+\tret = __phy_write(phydev, ACX00_PAGE1_APS_CONTROL_REG,\n+\t\t\t ACX00_PAGE1_APS_DISABLED_4S_VALUE);\n+\tif (ret)\n+\t\tgoto out_restore_page;\n+\tret = __phy_modify(phydev, ACX00_PAGE1_UAPS_CONTROL_REG,\n+\t\t\t ACX00_PAGE1_UAPS_ENABLE, 0);\n+\tif (ret)\n+\t\tgoto out_restore_page;\n+\tret = __phy_modify(phydev, ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG,\n+\t\t\t ACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0);\n+\tif (ret)\n+\t\tgoto out_restore_page;\n+\n+\tret = acx00_ephy_write_page(phydev, ACX00_PAGE_2);\n+\tif (ret)\n+\t\tgoto out_restore_page;\n+\tret = __phy_modify(phydev, ACX00_PAGE2_TX_DATA_CONTROL_REG,\n+\t\t\t ACX00_PAGE2_10BT_FIR_SELECT_MASK,\n+\t\t\t FIELD_PREP(ACX00_PAGE2_10BT_FIR_SELECT_MASK,\n+\t\t\t\t ACX00_PAGE2_10BT_FIR_SELECT_DEFAULT));\n+\tif (ret)\n+\t\tgoto out_restore_page;\n+\n+\tret = acx00_ephy_write_page(phydev, ACX00_PAGE_6);\n+\tif (ret)\n+\t\tgoto out_restore_page;\n+\tret = __phy_write(phydev, ACX00_PAGE6_AFE_EQ_RX_DETECT_CONTROL_REG,\n+\t\t\t afe_eq_rx_detect);\n+\tif (ret)\n+\t\tgoto out_restore_page;\n+\tret = __phy_write(phydev, ACX00_PAGE6_AFE_RX_CONTROL_REG,\n+\t\t\t ACX00_PAGE6_AFE_RX_CONTROL_VALUE);\n+\tif (ret)\n+\t\tgoto out_restore_page;\n+\tif (priv-\u003eis_ac300 \u0026\u0026 priv-\u003euse_low_calibration_tuning) {\n+\t\tret = __phy_write(phydev, ACX00_PAGE6_ADC_CONTROL_REG,\n+\t\t\t\t ACX00_PAGE6_ADC_CONTROL_LOW_CAL_VALUE);\n+\t\tif (ret)\n+\t\t\tgoto out_restore_page;\n+\t}\n+\tret = __phy_write(phydev, ACX00_PAGE6_TX_LEVEL_REG, tx_level_value);\n+\tif (ret)\n+\t\tgoto out_restore_page;\n+\n+\tret = acx00_ephy_write_page(phydev, ACX00_PAGE_8);\n+\tif (ret)\n+\t\tgoto out_restore_page;\n+\tif (priv-\u003eis_ac300 \u0026\u0026 priv-\u003euse_low_calibration_tuning) {\n+\t\tret = __phy_write(phydev, ACX00_PAGE8_AUTO_CAL_CONTROL_REG,\n+\t\t\t\t ACX00_PAGE8_AUTO_CAL_LOW_VALUE);\n+\t\tif (ret)\n+\t\t\tgoto out_restore_page;\n+\t}\n+\tret = __phy_write(phydev, ACX00_PAGE8_AFE_CONTROL_REG,\n+\t\t\t ACX00_PAGE8_AFE_CONTROL_VALUE);\n+\n+out_restore_page:\n+\tret = phy_restore_page(phydev, oldpage, ret);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Restore the standard EEE policy retained by phylib across resets. */\n+\treturn genphy_c45_an_config_eee_aneg(phydev);\n+}\n+\n+static int acx00_ephy_power_on_and_resume(struct phy_device *phydev)\n+{\n+\tstruct acx00_ephy_priv *priv = phydev-\u003epriv;\n+\tint ret;\n+\n+\tret = acx00_ephy_set_interface(phydev);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = acx00_ephy_control_power_on(priv);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = genphy_resume(phydev);\n+\tif (ret) {\n+\t\tacx00_ephy_control_power_off(priv);\n+\t\treturn ret;\n+\t}\n+\n+\t/* Powering off the control block loses the vendor-page state. */\n+\treturn acx00_ephy_config_init(phydev);\n+}\n+\n+static int acx00_ephy_resume(struct phy_device *phydev)\n+{\n+\treturn acx00_ephy_power_on_and_resume(phydev);\n+}\n+\n+static int acx00_ephy_suspend(struct phy_device *phydev)\n+{\n+\tstruct acx00_ephy_priv *priv = phydev-\u003epriv;\n+\tint resume_ret;\n+\tint ret;\n+\n+\tret = genphy_suspend(phydev);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = acx00_ephy_control_power_off(priv);\n+\tif (ret) {\n+\t\tresume_ret = acx00_ephy_power_on_and_resume(phydev);\n+\t\tif (resume_ret)\n+\t\t\tphydev_warn(phydev,\n+\t\t\t\t \"failed to recover from suspend error: %pe\\n\",\n+\t\t\t\t ERR_PTR(resume_ret));\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int acx00_ephy_match_phy_device(struct phy_device *phydev,\n+\t\t\t\t const struct phy_driver *phydrv)\n+{\n+\tstruct device_node *node = phydev-\u003emdio.dev.of_node;\n+\tstruct device_node *package_node;\n+\tbool match;\n+\n+\tif (!genphy_match_phy_device(phydev, phydrv) || !node)\n+\t\treturn 0;\n+\n+\t/* RK630 reports the same PHY ID, so also match the package identity. */\n+\tpackage_node = of_get_parent(node);\n+\tif (!package_node)\n+\t\treturn 0;\n+\tmatch = of_device_is_compatible(package_node,\n+\t\t\t\t\t\"x-powers,ac200-ephy-package\") ||\n+\t\tof_device_is_compatible(package_node,\n+\t\t\t\t\t\"x-powers,ac300-ephy-package\") ||\n+\t\tof_device_is_compatible(package_node,\n+\t\t\t\t\t\"x-powers,acx00-ephy-package\");\n+\tof_node_put(package_node);\n+\n+\treturn match;\n+}\n+\n+static struct phy_driver acx00_ephy_driver[] = {\n+\t{\n+\t\tPHY_ID_MATCH_MODEL(ACX00_EPHY_ID),\n+\t\t.name = \"X-Powers AC200/AC300 EPHY\",\n+\t\t.match_phy_device = acx00_ephy_match_phy_device,\n+\t\t.probe = acx00_ephy_probe,\n+\t\t.read_page = acx00_ephy_read_page,\n+\t\t.write_page = acx00_ephy_write_page,\n+\t\t.soft_reset = acx00_ephy_soft_reset,\n+\t\t.config_init = acx00_ephy_config_init,\n+\t\t.disable_autonomous_eee = acx00_ephy_disable_autonomous_eee,\n+\t\t.suspend = acx00_ephy_suspend,\n+\t\t.resume = acx00_ephy_resume,\n+\t},\n+};\n+module_phy_driver(acx00_ephy_driver);\n+\n+static const struct mdio_device_id __maybe_unused acx00_ephy_tbl[] = {\n+\t{ PHY_ID_MATCH_MODEL(ACX00_EPHY_ID) },\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(mdio, acx00_ephy_tbl);\n+\n+MODULE_AUTHOR(\"James Hilliard \u003cjames.hilliard1@gmail.com\u003e\");\n+MODULE_DESCRIPTION(\"X-Powers AC200/AC300 Ethernet PHY driver\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/net/phy/xpowers/acx00.h b/drivers/net/phy/xpowers/acx00.h\nnew file mode 100644\nindex 0000000000000..7df6084c7a772\n--- /dev/null\n+++ b/drivers/net/phy/xpowers/acx00.h\n@@ -0,0 +1,28 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+#ifndef __DRIVERS_NET_PHY_XPOWERS_ACX00_H\n+#define __DRIVERS_NET_PHY_XPOWERS_ACX00_H\n+\n+#include \u003clinux/err.h\u003e\n+#include \u003clinux/phy.h\u003e\n+\n+#define AC300_EPHY_CONTROL_ADDR_OFFSET\t16\n+\n+struct device_node;\n+\n+struct acx00_ephy_control {\n+\tint (*power_on)(struct acx00_ephy_control *control,\n+\t\t\tunsigned int phy_addr);\n+\tint (*power_off)(struct acx00_ephy_control *control);\n+\tint (*set_interface)(struct acx00_ephy_control *control,\n+\t\t\t phy_interface_t interface);\n+};\n+\n+struct acx00_ephy_control *\n+ac200_ephy_ctl_create(struct phy_device *phydev,\n+\t\t struct device_node *package_node,\n+\t\t bool has_calibration, u8 calibration);\n+struct acx00_ephy_control *\n+ac300_ephy_ctl_create(struct phy_device *phydev,\n+\t\t struct device_node *package_node, u8 calibration);\n+\n+#endif\ndiff --git a/include/linux/mfd/syscon.h b/include/linux/mfd/syscon.h\nindex aad9c6b504636..3e804fc1b03eb 100644\n--- a/include/linux/mfd/syscon.h\n+++ b/include/linux/mfd/syscon.h\n@@ -15,6 +15,7 @@\n #include \u003clinux/errno.h\u003e\n \n struct device_node;\n+struct device;\n \n #ifdef CONFIG_MFD_SYSCON\n struct regmap *device_node_to_regmap(struct device_node *np);\n@@ -30,6 +31,9 @@ struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np,\n \t\t\t\t\t\t\tconst char *property);\n int of_syscon_register_regmap(struct device_node *np,\n \t\t\t struct regmap *regmap);\n+int devm_of_syscon_register_regmap(struct device *dev,\n+\t\t\t\t struct device_node *np,\n+\t\t\t\t struct regmap *regmap);\n #else\n static inline struct regmap *device_node_to_regmap(struct device_node *np)\n {\n@@ -75,6 +79,14 @@ static inline int of_syscon_register_regmap(struct device_node *np,\n \treturn -EOPNOTSUPP;\n }\n \n+static inline int\n+devm_of_syscon_register_regmap(struct device *dev,\n+\t\t\t struct device_node *np,\n+\t\t\t struct regmap *regmap)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n #endif\n \n #endif /* __LINUX_MFD_SYSCON_H__ */\n"
} |
| 2/1 | 2026/08/09 20:38 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds drivers for X-Powers AC200 MFD and ACx00 Ethernet PHYs, which strictly require device tree nodes (of_node) to probe and function. Since syzkaller does not dynamically generate arbitrary device tree nodes, and these devices are not present in the default virtualized environments (like QEMU 'virt' or x86_64), the code is unreachable. The minor refactoring in syscon.c is only reachable via SoC-specific drivers (Exynos, Sunxi, Renesas) that are also not present in standard virtualized environments.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit d94a5a99c55e956a2ffa0ba0575afa00146a50c4
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Aug 9 20:38:40 2026 +0000
syz-cluster: applied patch under review
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 0000000000000..609831b15c806
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/x-powers,ac200.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: X-Powers AC200 mixed-signal IC
+
+maintainers:
+ - James Hilliard <james.hilliard1@gmail.com>
+
+description:
+ The AC200 is a mixed-signal companion IC containing audio, video, RTC and
+ Fast Ethernet PHY functions. Its control registers are accessed over I2C.
+
+properties:
+ compatible:
+ const: x-powers,ac200
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+ description:
+ AC200 input clock. When using the Ethernet PHY, its configured rate must
+ be 24 or 27 MHz, matching the rates encoded by the documented EPHY clock
+ selector.
+
+required:
+ - compatible
+ - reg
+ - clocks
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ mixed-signal@10 {
+ compatible = "x-powers,ac200";
+ reg = <0x10>;
+ clocks = <&pwm 5>;
+ };
+ };
+...
diff --git a/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml
new file mode 100644
index 0000000000000..ae0c5e980c39e
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml
@@ -0,0 +1,207 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/x-powers,acx00-ephy-package.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: X-Powers AC200 and AC300 Ethernet PHY packages
+
+maintainers:
+ - James Hilliard <james.hilliard1@gmail.com>
+
+description:
+ The AC200 and AC300 contain compatible Fast Ethernet PHY blocks which report
+ the same Clause 22 PHY ID and use the same link-side register layout. The
+ package control paths differ. AC200 control registers are part of an I2C MFD,
+ while AC300 control registers occupy a second Clause 22 address fixed at 16
+ plus the link PHY address.
+
+ Systems with a known package use the corresponding AC200 or AC300 compatible.
+ Systems which can contain either package use the ACx00 compatible and a
+ packed NVMEM configuration field to select the backend at runtime.
+
+ The link PHY cannot report its ID until the package control sequence has
+ completed, so its child node supplies the documented PHY ID. The package and
+ child use the same base address. The AC200 driver programs that address into
+ the package, while the AC300 driver derives its control address from it.
+
+select:
+ properties:
+ compatible:
+ enum:
+ - x-powers,ac200-ephy-package
+ - x-powers,ac300-ephy-package
+ - x-powers,acx00-ephy-package
+ required:
+ - compatible
+
+properties:
+ compatible:
+ enum:
+ - x-powers,ac200-ephy-package
+ - x-powers,ac300-ephy-package
+ - x-powers,acx00-ephy-package
+
+ reg:
+ maximum: 31
+
+ clocks:
+ maxItems: 1
+ description:
+ AC300 input clock, running at 24, 25, or 27 MHz. The AC200 input clock
+ belongs to the referenced AC200 MFD instead.
+
+ vcc-supply:
+ description:
+ 3.3 V package supply. This is connected to EPHY_VCC on AC200 and VCC1
+ on AC300.
+
+ nvmem-cells:
+ maxItems: 1
+ description:
+ Packed EPHY configuration field. Bits 3 through 0 contain the analog
+ calibration code, bits 7 through 4 are reserved, bit 8 selects AC300
+ when set and AC200 when clear, and bit 9 requests the AC300
+ low-calibration tuning sequence. A fixed AC200 package may provide only
+ the low calibration bits or omit the cell and use its internal eFuse
+ copy instead.
+
+ nvmem-cell-names:
+ const: configuration
+
+ x-powers,ac200:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: Reference to the AC200 MFD containing this Ethernet PHY
+
+ x-powers,xmii-rx-clock-inverted:
+ type: boolean
+ description:
+ Invert the xMII receive clock inside the PHY. This is required by some
+ SoC integrations, including the Allwinner H6 AC200 connection.
+
+patternProperties:
+ ^ethernet-phy@[a-f0-9]+$:
+ $ref: ethernet-phy.yaml#
+ properties:
+ compatible:
+ const: ethernet-phy-id0044.1400
+ required:
+ - compatible
+ unevaluatedProperties: false
+
+required:
+ - compatible
+ - vcc-supply
+
+dependentRequired:
+ nvmem-cells: [ nvmem-cell-names ]
+ nvmem-cell-names: [ nvmem-cells ]
+
+allOf:
+ - $ref: ethernet-phy-package.yaml#
+ - if:
+ properties:
+ compatible:
+ const: x-powers,ac200-ephy-package
+ then:
+ properties:
+ clocks: false
+ required:
+ - x-powers,ac200
+ - if:
+ properties:
+ compatible:
+ const: x-powers,ac300-ephy-package
+ then:
+ properties:
+ reg:
+ maximum: 7
+ x-powers,ac200: false
+ required:
+ - clocks
+ - nvmem-cells
+ - nvmem-cell-names
+ - if:
+ properties:
+ compatible:
+ const: x-powers,acx00-ephy-package
+ then:
+ properties:
+ reg:
+ maximum: 7
+ required:
+ - clocks
+ - nvmem-cells
+ - nvmem-cell-names
+ - x-powers,ac200
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-phy-package@0 {
+ compatible = "x-powers,acx00-ephy-package";
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&pwm 5>;
+ vcc-supply = <®_3v3>;
+ nvmem-cells = <&ephy_configuration>;
+ nvmem-cell-names = "configuration";
+ x-powers,ac200 = <&ac200>;
+
+ ethernet-phy@0 {
+ compatible = "ethernet-phy-id0044.1400";
+ reg = <0>;
+ };
+ };
+ };
+
+ - |
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-phy-package@0 {
+ compatible = "x-powers,ac300-ephy-package";
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&pwm 5>;
+ vcc-supply = <®_3v3>;
+ nvmem-cells = <&ephy_configuration>;
+ nvmem-cell-names = "configuration";
+
+ ethernet-phy@0 {
+ compatible = "ethernet-phy-id0044.1400";
+ reg = <0>;
+ };
+ };
+ };
+
+ - |
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-phy-package@1 {
+ compatible = "x-powers,ac200-ephy-package";
+ reg = <1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ vcc-supply = <®_3v3>;
+ x-powers,ac200 = <&ac200>;
+ nvmem-cells = <&ephy_configuration>;
+ nvmem-cell-names = "configuration";
+
+ ethernet-phy@1 {
+ compatible = "ethernet-phy-id0044.1400";
+ reg = <1>;
+ };
+ };
+ };
+...
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 763ce6a34782b..95e3c62742bb6 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -205,6 +205,18 @@ config MFD_AC100
This driver include only the core APIs. You have to select individual
components like codecs or RTC under the corresponding menus.
+config MFD_AC200
+ tristate "X-Powers AC200"
+ depends on I2C
+ depends on OF
+ select MFD_SYSCON
+ select REGMAP_I2C
+ help
+ Support for the X-Powers AC200 mixed-signal companion IC. The AC200
+ contains audio, video, RTC and Fast Ethernet PHY functions and is
+ co-packaged with some Allwinner H6 and H616 SoCs. This driver provides
+ the shared register access used by the individual function drivers.
+
config MFD_AXP20X
tristate
select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index dd4bb7e77c336..890e76a9ad00a 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 0000000000000..25066bb36ac76
--- /dev/null
+++ b/drivers/mfd/ac200.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MFD core driver for the X-Powers AC200
+ *
+ * Copyright (C) 2019 Jernej Skrabec <jernej.skrabec@gmail.com>
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ *
+ * Based on the AC100 driver:
+ * Copyright (C) 2016 Chen-Yu Tsai
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#define AC200_SYS_VERSION_REG 0x0000
+#define AC200_SYS_VERSION_PACKAGE_MASK GENMASK(15, 14)
+#define AC200_SYS_VERSION_CHIP_MASK GENMASK(11, 0)
+
+#define AC200_SYS_CONTROL_REG 0x0002
+#define AC200_SYS_CONTROL_CHIP_RESET_DEASSERT BIT(0)
+
+/* Interface register accessible from every register page. */
+#define AC200_TWI_REG_ADDR_H 0x00fe
+#define AC200_MAX_REG 0xa1f2
+
+struct ac200 {
+ struct regmap *regmap;
+};
+
+static const struct regmap_range_cfg ac200_range_cfg[] = {
+ {
+ .range_max = AC200_MAX_REG,
+ .selector_reg = AC200_TWI_REG_ADDR_H,
+ .selector_mask = 0xff,
+ .window_len = 256,
+ },
+};
+
+/*
+ * Each AC200 sub-block can reset independently, invalidating its register
+ * contents without regmap's knowledge. Cache only the common page selector;
+ * this avoids a selector read-modify-write for every access on the same page
+ * without ever returning stale functional-register values.
+ */
+static bool ac200_volatile_reg(struct device *dev, unsigned int reg)
+{
+ return reg != AC200_TWI_REG_ADDR_H;
+}
+
+static const struct regmap_config ac200_regmap_config = {
+ .name = "ac200",
+ .reg_bits = 8,
+ .reg_stride = 2,
+ .val_bits = 16,
+ .ranges = ac200_range_cfg,
+ .num_ranges = ARRAY_SIZE(ac200_range_cfg),
+ .max_register = AC200_MAX_REG,
+ .volatile_reg = ac200_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static void ac200_disable(void *data)
+{
+ struct ac200 *ddata = data;
+
+ regmap_write(ddata->regmap, AC200_SYS_CONTROL_REG, 0);
+}
+
+static int ac200_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ unsigned int version;
+ struct ac200 *ddata;
+ struct clk *clk;
+ int ret;
+
+ ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk),
+ "failed to enable input clock\n");
+
+ ret = devm_clk_rate_exclusive_get(dev, clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to lock clock rate\n");
+
+ ddata->regmap = devm_regmap_init_i2c(client, &ac200_regmap_config);
+ if (IS_ERR(ddata->regmap))
+ return dev_err_probe(dev, PTR_ERR(ddata->regmap),
+ "failed to initialize regmap\n");
+
+ i2c_set_clientdata(client, ddata);
+
+ /*
+ * No minimum delay is documented. Match the vendor driver's 40 ms delay
+ * before its first AC200 register access after enabling the input clock.
+ */
+ msleep(40);
+
+ ret = regmap_read(ddata->regmap, AC200_SYS_VERSION_REG, &version);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read chip version\n");
+
+ dev_info(dev, "AC200 revision %#lx in package %lu\n",
+ FIELD_GET(AC200_SYS_VERSION_CHIP_MASK, version),
+ FIELD_GET(AC200_SYS_VERSION_PACKAGE_MASK, version));
+
+ /* Reset the chip after dependent function drivers have unbound. */
+ ret = devm_add_action_or_reset(dev, ac200_disable, ddata);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ddata->regmap, AC200_SYS_CONTROL_REG, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ddata->regmap, AC200_SYS_CONTROL_REG,
+ AC200_SYS_CONTROL_CHIP_RESET_DEASSERT);
+ if (ret)
+ return ret;
+
+ ret = devm_of_syscon_register_regmap(dev, dev->of_node,
+ ddata->regmap);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to register regmap\n");
+
+ return 0;
+}
+
+static void ac200_shutdown(struct i2c_client *client)
+{
+ ac200_disable(i2c_get_clientdata(client));
+}
+
+static const struct of_device_id ac200_of_match[] = {
+ { .compatible = "x-powers,ac200" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ac200_of_match);
+
+static const struct i2c_device_id ac200_i2c_ids[] = {
+ { .name = "ac200" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ac200_i2c_ids);
+
+static struct i2c_driver ac200_driver = {
+ .driver = {
+ .name = "ac200",
+ .of_match_table = ac200_of_match,
+ },
+ .probe = ac200_probe,
+ .shutdown = ac200_shutdown,
+ .id_table = ac200_i2c_ids,
+};
+module_i2c_driver(ac200_driver);
+
+MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
+MODULE_DESCRIPTION("X-Powers AC200 MFD core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/syscon.c b/drivers/mfd/syscon.c
index 21a7fcdd27379..448422fe4f21c 100644
--- a/drivers/mfd/syscon.c
+++ b/drivers/mfd/syscon.c
@@ -10,6 +10,7 @@
#include <linux/cleanup.h>
#include <linux/clk.h>
+#include <linux/device.h>
#include <linux/err.h>
#include <linux/hwspinlock.h>
#include <linux/list.h>
@@ -193,53 +194,95 @@ static struct regmap *device_node_get_regmap(struct device_node *np,
return syscon->regmap;
}
-/**
- * of_syscon_register_regmap() - Register regmap for specified device node
- * @np: Device tree node
- * @regmap: Pointer to regmap object
- *
- * Register an externally created regmap object with syscon for the specified
- * device tree node. This regmap will then be returned to client drivers using
- * the syscon_regmap_lookup_by_phandle() API.
- *
- * Return: 0 on success, negative error code on failure.
- */
-int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)
+static struct syscon *
+of_syscon_register_regmap_internal(struct device_node *np,
+ struct regmap *regmap)
{
struct syscon *entry, *syscon = NULL;
- int ret;
if (!np || !regmap)
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
syscon = kzalloc_obj(*syscon);
if (!syscon)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
/* check if syscon entry already exists */
mutex_lock(&syscon_list_lock);
list_for_each_entry(entry, &syscon_list, list)
if (entry->np == np) {
- ret = -EEXIST;
- goto err_unlock;
+ mutex_unlock(&syscon_list_lock);
+ kfree(syscon);
+ return ERR_PTR(-EEXIST);
}
syscon->regmap = regmap;
- syscon->np = np;
+ syscon->np = of_node_get(np);
/* register the regmap in syscon list */
list_add_tail(&syscon->list, &syscon_list);
mutex_unlock(&syscon_list_lock);
- return 0;
+ return syscon;
+}
+
+/**
+ * of_syscon_register_regmap() - Register regmap for specified device node
+ * @np: Device tree node
+ * @regmap: Pointer to regmap object
+ *
+ * Register an externally created regmap object with syscon for the specified
+ * device tree node. This regmap will then be returned to client drivers using
+ * the syscon_regmap_lookup_by_phandle() API.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)
+{
+ return PTR_ERR_OR_ZERO(of_syscon_register_regmap_internal(np, regmap));
+}
+EXPORT_SYMBOL_GPL(of_syscon_register_regmap);
+
+static void devm_of_syscon_register_regmap_release(void *data)
+{
+ struct syscon *syscon = data;
-err_unlock:
+ mutex_lock(&syscon_list_lock);
+ list_del(&syscon->list);
mutex_unlock(&syscon_list_lock);
+
+ of_node_put(syscon->np);
kfree(syscon);
- return ret;
}
-EXPORT_SYMBOL_GPL(of_syscon_register_regmap);
+
+/**
+ * devm_of_syscon_register_regmap() - Register a managed external syscon regmap
+ * @dev: Device that owns the regmap
+ * @np: Device tree node associated with the regmap
+ * @regmap: Pointer to the externally created regmap
+ *
+ * Register an externally created regmap object with syscon and remove it when
+ * @dev is unbound. Consumers must stop using the regmap before the provider is
+ * unbound, for example by establishing a managed device link to @dev.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int devm_of_syscon_register_regmap(struct device *dev,
+ struct device_node *np,
+ struct regmap *regmap)
+{
+ struct syscon *syscon;
+
+ syscon = of_syscon_register_regmap_internal(np, regmap);
+ if (IS_ERR(syscon))
+ return PTR_ERR(syscon);
+
+ return devm_add_action_or_reset(dev,
+ devm_of_syscon_register_regmap_release,
+ syscon);
+}
+EXPORT_SYMBOL_GPL(devm_of_syscon_register_regmap);
/**
* device_node_to_regmap() - Get or create a regmap for specified device node
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index a29d3fed8a053..27b8ffe4b5390 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -475,6 +475,17 @@ config VITESSE_PHY
help
Currently supports the vsc8244
+config XPOWERS_ACX00_PHY
+ tristate "X-Powers AC200/AC300 Ethernet PHY"
+ depends on COMMON_CLK && NVMEM && OF_MDIO && REGULATOR
+ depends on I2C || !I2C
+ select PHY_PACKAGE
+ help
+ Enable the Fast Ethernet PHY driver shared by the X-Powers AC200
+ and AC300 companion ICs. The driver configures their common MDIO PHY
+ registers and the package-specific AC200 I2C or AC300 MDIO control
+ registers.
+
config XILINX_GMII2RGMII
tristate "Xilinx GMII2RGMII converter driver"
help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index e23df5e836e9e..ffb0efbc4cc4e 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -100,4 +100,5 @@ obj-$(CONFIG_SMSC_PHY) += smsc.o
obj-$(CONFIG_STE10XP) += ste10Xp.o
obj-$(CONFIG_TERANETICS_PHY) += teranetics.o
obj-$(CONFIG_VITESSE_PHY) += vitesse.o
+obj-$(CONFIG_XPOWERS_ACX00_PHY) += xpowers/
obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
diff --git a/drivers/net/phy/xpowers/Makefile b/drivers/net/phy/xpowers/Makefile
new file mode 100644
index 0000000000000..ae977a7958325
--- /dev/null
+++ b/drivers/net/phy/xpowers/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+xpowers-acx00-y := acx00.o ac200.o ac300.o
+obj-$(CONFIG_XPOWERS_ACX00_PHY) += xpowers-acx00.o
diff --git a/drivers/net/phy/xpowers/ac200.c b/drivers/net/phy/xpowers/ac200.c
new file mode 100644
index 0000000000000..681e013be401b
--- /dev/null
+++ b/drivers/net/phy/xpowers/ac200.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * X-Powers AC200 Ethernet PHY package backend
+ *
+ * Copyright (c) 2022 Arm Ltd. (Andre Przywara <andre.przywara@arm.com>)
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#include "acx00.h"
+
+#define AC200_EPHY_BPS_EFFUSE_OFFSET 3
+#define AC200_EPHY_CLK_RATE_24_MHZ 24000000
+#define AC200_EPHY_CLK_RATE_27_MHZ 27000000
+
+#define AC200_SYS_EPHY_CTL0_REG 0x0014
+#define AC200_EPHY_RESET_DEASSERT BIT(0)
+#define AC200_EPHY_SYSCLK_ENABLE BIT(1)
+
+#define AC200_SYS_EPHY_CTL1_REG 0x0016
+#define AC200_EPHY_MII_IO_ENABLE BIT(0)
+
+/* AC200-internal copy of the Ethernet PHY calibration eFuse. */
+#define AC200_EFUSE_EPHY_REG 0x8004
+
+#define AC200_EPHY_CTL_REG 0x6000
+#define AC200_EPHY_SHUTDOWN BIT(0)
+#define AC200_EPHY_CLK_SEL_24_MHZ BIT(2)
+#define AC200_EPHY_PHY_ADDR_MASK GENMASK(8, 4)
+#define AC200_EPHY_RMII_SEL BIT(11)
+#define AC200_EPHY_BPS_EFFUSE_MASK GENMASK(15, 12)
+
+struct ac200_ephy_ctl {
+ struct acx00_ephy_control control;
+ struct regmap *regmap;
+ struct regulator *vcc;
+ struct device *dev;
+ u16 ephy_ctl;
+ unsigned int phy_addr;
+ phy_interface_t interface;
+ bool supply_enabled;
+ bool powered;
+};
+
+static u16 ac200_ephy_ctl_config(const struct ac200_ephy_ctl *priv)
+{
+ return priv->ephy_ctl |
+ (priv->interface == PHY_INTERFACE_MODE_RMII ?
+ AC200_EPHY_RMII_SEL : 0) |
+ FIELD_PREP(AC200_EPHY_PHY_ADDR_MASK, priv->phy_addr);
+}
+
+static int ac200_ephy_ctl_write(struct ac200_ephy_ctl *priv,
+ unsigned int reg, u16 value)
+{
+ int ret;
+
+ ret = regmap_write(priv->regmap, reg, value);
+ if (ret)
+ dev_err(priv->dev, "failed to write register %#x: %pe\n",
+ reg, ERR_PTR(ret));
+
+ return ret;
+}
+
+static int ac200_ephy_ctl_disable(struct ac200_ephy_ctl *priv)
+{
+ int err;
+ int ret = 0;
+
+ if (priv->powered) {
+ ret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG,
+ ac200_ephy_ctl_config(priv) |
+ AC200_EPHY_SHUTDOWN);
+ err = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL1_REG, 0);
+ if (!ret)
+ ret = err;
+ err = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, 0);
+ if (!ret)
+ ret = err;
+
+ priv->powered = false;
+ }
+
+ if (priv->supply_enabled) {
+ err = regulator_disable(priv->vcc);
+ if (!err)
+ priv->supply_enabled = false;
+ else if (!ret)
+ ret = err;
+ }
+
+ return ret;
+}
+
+static int ac200_ephy_ctl_power_off(struct acx00_ephy_control *control)
+{
+ struct ac200_ephy_ctl *priv =
+ container_of(control, struct ac200_ephy_ctl, control);
+
+ return ac200_ephy_ctl_disable(priv);
+}
+
+static int
+ac200_ephy_ctl_set_interface(struct acx00_ephy_control *control,
+ phy_interface_t interface)
+{
+ struct ac200_ephy_ctl *priv =
+ container_of(control, struct ac200_ephy_ctl, control);
+ int ret = 0;
+
+ if (interface != PHY_INTERFACE_MODE_MII &&
+ interface != PHY_INTERFACE_MODE_RMII)
+ return -EINVAL;
+
+ if (priv->interface == interface)
+ return 0;
+
+ if (priv->powered)
+ ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL_REG,
+ AC200_EPHY_RMII_SEL,
+ interface == PHY_INTERFACE_MODE_RMII ?
+ AC200_EPHY_RMII_SEL : 0);
+ if (!ret)
+ priv->interface = interface;
+
+ return ret;
+}
+
+static int ac200_ephy_ctl_power_on(struct acx00_ephy_control *control,
+ unsigned int phy_addr)
+{
+ struct ac200_ephy_ctl *priv =
+ container_of(control, struct ac200_ephy_ctl, control);
+ u16 ephy_ctl;
+ int ret;
+
+ if (phy_addr > FIELD_MAX(AC200_EPHY_PHY_ADDR_MASK))
+ return -EINVAL;
+
+ if (priv->powered && priv->phy_addr == phy_addr)
+ return 0;
+
+ if (priv->powered) {
+ ret = ac200_ephy_ctl_disable(priv);
+ if (ret)
+ return ret;
+ }
+ priv->phy_addr = phy_addr;
+
+ if (!priv->supply_enabled) {
+ ret = regulator_enable(priv->vcc);
+ if (ret)
+ return ret;
+ priv->supply_enabled = true;
+ }
+
+ ephy_ctl = ac200_ephy_ctl_config(priv);
+
+ /* Start from a disabled state before applying the configuration. */
+ ret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, 0);
+ if (ret)
+ goto err_disable;
+
+ ret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL1_REG,
+ AC200_EPHY_MII_IO_ENABLE);
+ if (ret)
+ goto err_disable;
+
+ ret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG,
+ ephy_ctl | AC200_EPHY_SHUTDOWN);
+ if (ret)
+ goto err_disable;
+
+ ret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG,
+ AC200_EPHY_RESET_DEASSERT |
+ AC200_EPHY_SYSCLK_ENABLE);
+ if (ret)
+ goto err_disable;
+
+ ret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG, ephy_ctl);
+ if (ret)
+ goto err_disable;
+
+ priv->powered = true;
+ return 0;
+
+err_disable:
+ /* Attempt every step of the shutdown sequence after a partial start. */
+ priv->powered = true;
+ ac200_ephy_ctl_disable(priv);
+
+ return ret;
+}
+
+struct acx00_ephy_control *
+ac200_ephy_ctl_create(struct phy_device *phydev,
+ struct device_node *package_node,
+ bool has_calibration, u8 calibration)
+{
+ struct device *dev = &phydev->mdio.dev;
+ unsigned int internal_calibration;
+ struct device_node *ac200_node;
+ struct ac200_ephy_ctl *priv;
+ struct i2c_client *client;
+ struct device_link *link;
+ unsigned long clk_rate;
+ u8 bps_effuse_code;
+ struct clk *clk;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
+ priv->dev = dev;
+ priv->vcc = devm_of_regulator_get(dev, package_node, "vcc");
+ if (IS_ERR(priv->vcc))
+ return ERR_PTR(dev_err_probe(dev, PTR_ERR(priv->vcc),
+ "failed to get package supply\n"));
+
+ ac200_node = of_parse_phandle(package_node, "x-powers,ac200", 0);
+ if (!ac200_node)
+ return ERR_PTR(dev_err_probe(dev, -EINVAL,
+ "missing x-powers,ac200 reference\n"));
+ client = of_find_i2c_device_by_node(ac200_node);
+ of_node_put(ac200_node);
+ if (!client) {
+ ret = IS_ENABLED(CONFIG_I2C) ? -EPROBE_DEFER : -ENODEV;
+ return ERR_PTR(dev_err_probe(dev, ret,
+ "AC200 device is not registered\n"));
+ }
+
+ link = device_link_add(dev, &client->dev,
+ DL_FLAG_AUTOREMOVE_CONSUMER);
+ if (!link) {
+ ret = dev_err_probe(dev, -EINVAL,
+ "failed to link AC200 device\n");
+ goto out_put_client;
+ }
+
+ if (READ_ONCE(link->status) != DL_STATE_CONSUMER_PROBE) {
+ ret = dev_err_probe(dev, -EPROBE_DEFER,
+ "AC200 driver is not ready\n");
+ goto out_put_client;
+ }
+
+ priv->regmap = syscon_regmap_lookup_by_phandle(package_node,
+ "x-powers,ac200");
+ if (IS_ERR(priv->regmap)) {
+ ret = dev_err_probe(dev, PTR_ERR(priv->regmap),
+ "failed to get AC200 regmap\n");
+ goto out_error;
+ }
+
+ if (!has_calibration) {
+ ret = regmap_read(priv->regmap, AC200_EFUSE_EPHY_REG,
+ &internal_calibration);
+ if (ret)
+ goto out_error;
+ calibration = internal_calibration;
+ }
+
+ /* The vendor driver supplies no transfer function beyond this offset. */
+ bps_effuse_code = (calibration + AC200_EPHY_BPS_EFFUSE_OFFSET) &
+ FIELD_MAX(AC200_EPHY_BPS_EFFUSE_MASK);
+ priv->ephy_ctl =
+ FIELD_PREP(AC200_EPHY_BPS_EFFUSE_MASK, bps_effuse_code);
+ /* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */
+
+ clk = clk_get(&client->dev, NULL);
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ goto out_error;
+ }
+
+ clk_rate = clk_get_rate(clk);
+ clk_put(clk);
+
+ switch (clk_rate) {
+ case AC200_EPHY_CLK_RATE_24_MHZ:
+ priv->ephy_ctl |= AC200_EPHY_CLK_SEL_24_MHZ;
+ break;
+ case AC200_EPHY_CLK_RATE_27_MHZ:
+ break;
+ default:
+ ret = dev_err_probe(dev, -EINVAL,
+ "unsupported AC200 clock rate %lu Hz\n",
+ clk_rate);
+ goto out_put_client;
+ }
+
+ priv->control.power_on = ac200_ephy_ctl_power_on;
+ priv->control.power_off = ac200_ephy_ctl_power_off;
+ priv->control.set_interface = ac200_ephy_ctl_set_interface;
+ /* MII is the reset default used until the MAC supplies its interface. */
+ priv->interface = PHY_INTERFACE_MODE_MII;
+ put_device(&client->dev);
+
+ return &priv->control;
+
+out_error:
+ ret = dev_err_probe(dev, ret, "failed to initialize AC200 control\n");
+out_put_client:
+ put_device(&client->dev);
+ return ERR_PTR(ret);
+}
diff --git a/drivers/net/phy/xpowers/ac300.c b/drivers/net/phy/xpowers/ac300.c
new file mode 100644
index 0000000000000..aa560432a14c9
--- /dev/null
+++ b/drivers/net/phy/xpowers/ac300.c
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * X-Powers AC300 Ethernet PHY package backend
+ *
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/regulator/consumer.h>
+
+#include "../phylib.h"
+#include "acx00.h"
+
+#define AC300_EPHY_BGS_EFFUSE_OFFSET 3
+#define AC300_EPHY_CLK_RATE_24_MHZ 24000000
+#define AC300_EPHY_CLK_RATE_25_MHZ 25000000
+#define AC300_EPHY_CLK_RATE_27_MHZ 27000000
+#define AC300_SYS_CONTROL_REG 0x00
+#define AC300_PACKAGE_STATUS_MASK GENMASK(11, 8)
+#define AC300_EPHY_CLK_SEL_MASK GENMASK(7, 6)
+#define AC300_EPHY_CLK_SEL_25_MHZ FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 0)
+#define AC300_EPHY_CLK_SEL_27_MHZ FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 1)
+#define AC300_EPHY_CLK_SEL_24_MHZ FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 2)
+#define AC300_EFUSE_CLK_ENABLE BIT(5)
+#define AC300_EPHY_REG_CLK_ENABLE BIT(4)
+#define AC300_CLKIN_GATING_ENABLE BIT(2)
+#define AC300_EPHY_RESET_DEASSERT BIT(1)
+#define AC300_CHIP_RESET_DEASSERT BIT(0)
+
+#define AC300_PACKAGE_POR_INTERNAL_DLDO BIT(3)
+#define AC300_PACKAGE_PHY_ADDR_MASK GENMASK(2, 0)
+
+#define AC300_SYS_BIAS1_REG 0x02
+#define AC300_INTERNAL_DLDO_ENABLE BIT(15)
+
+#define AC300_SYS_IO_REG 0x05
+#define AC300_MDIO_DRV_MASK GENMASK(15, 14)
+#define AC300_MII_DRV_MASK GENMASK(11, 10)
+#define AC300_IO_DRV_LEVEL_2 2
+#define AC300_CLKIN_PAD_ENABLE BIT(4)
+#define AC300_EPHY_MII_IO_ENABLE BIT(0)
+
+#define AC300_EPHY_CONFIG_REG 0x06
+#define AC300_EPHY_BGS_EFFUSE_MASK GENMASK(15, 12)
+#define AC300_EPHY_RMII_SEL BIT(11)
+#define AC300_EPHY_SHUTDOWN BIT(0)
+
+#define AC300_SYS_CONTROL_ENABLE_BITS \
+ (AC300_EFUSE_CLK_ENABLE | AC300_EPHY_REG_CLK_ENABLE | \
+ AC300_CLKIN_GATING_ENABLE | AC300_EPHY_RESET_DEASSERT | \
+ AC300_CHIP_RESET_DEASSERT)
+
+#define AC300_SYS_IO_VALUE \
+ (FIELD_PREP(AC300_MDIO_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \
+ FIELD_PREP(AC300_MII_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \
+ AC300_CLKIN_PAD_ENABLE | AC300_EPHY_MII_IO_ENABLE)
+
+struct ac300_ephy_ctl {
+ struct acx00_ephy_control control;
+ struct phy_device *phydev;
+ struct regulator *vcc;
+ struct clk *clk;
+ u16 sys_control;
+ u16 ephy_config;
+ phy_interface_t interface;
+ bool package_known;
+ bool internal_dldo;
+ bool supply_enabled;
+ bool powered;
+};
+
+static unsigned int
+ac300_ephy_ctl_link_addr(const struct ac300_ephy_ctl *priv)
+{
+ return priv->phydev->mdio.addr;
+}
+
+static int ac300_ephy_ctl_read(struct ac300_ephy_ctl *priv, u32 regnum)
+{
+ int ret;
+
+ phy_lock_mdio_bus(priv->phydev);
+ ret = __phy_package_read(priv->phydev,
+ AC300_EPHY_CONTROL_ADDR_OFFSET, regnum);
+ phy_unlock_mdio_bus(priv->phydev);
+
+ return ret;
+}
+
+static int ac300_ephy_ctl_write(struct ac300_ephy_ctl *priv, u32 regnum,
+ u16 val)
+{
+ int ret;
+
+ phy_lock_mdio_bus(priv->phydev);
+ ret = __phy_package_write(priv->phydev,
+ AC300_EPHY_CONTROL_ADDR_OFFSET, regnum, val);
+ phy_unlock_mdio_bus(priv->phydev);
+
+ return ret;
+}
+
+static int ac300_ephy_ctl_modify(struct ac300_ephy_ctl *priv, u32 regnum,
+ u16 mask, u16 set)
+{
+ int ret;
+
+ phy_lock_mdio_bus(priv->phydev);
+ ret = __phy_package_read(priv->phydev,
+ AC300_EPHY_CONTROL_ADDR_OFFSET, regnum);
+ if (ret >= 0) {
+ u16 val = (ret & ~mask) | (set & mask);
+
+ ret = val == ret ? 0 :
+ __phy_package_write(priv->phydev,
+ AC300_EPHY_CONTROL_ADDR_OFFSET,
+ regnum, val);
+ }
+ phy_unlock_mdio_bus(priv->phydev);
+
+ return ret;
+}
+
+static u16 ac300_ephy_ctl_config(const struct ac300_ephy_ctl *priv)
+{
+ return priv->ephy_config |
+ (priv->interface == PHY_INTERFACE_MODE_RMII ?
+ AC300_EPHY_RMII_SEL : 0);
+}
+
+static u16 ac300_ephy_ctl_reset_value(const struct ac300_ephy_ctl *priv)
+{
+ /*
+ * A chip reset restores DLDOEN to one. Until PKG_STATUS has identified
+ * the supply arrangement, preserve any external-VDD setup left by the
+ * bootloader by asserting only the EPHY reset.
+ */
+ return !priv->package_known || !priv->internal_dldo ?
+ AC300_CHIP_RESET_DEASSERT : 0;
+}
+
+static int ac300_ephy_ctl_disable(struct ac300_ephy_ctl *priv)
+{
+ int err;
+ int ret = 0;
+
+ if (priv->powered) {
+ ret = ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG,
+ ac300_ephy_ctl_config(priv) |
+ AC300_EPHY_SHUTDOWN);
+ err = ac300_ephy_ctl_write(priv, AC300_SYS_IO_REG, 0);
+ if (!ret)
+ ret = err;
+ err = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG,
+ ac300_ephy_ctl_reset_value(priv));
+ if (!ret)
+ ret = err;
+
+ clk_disable_unprepare(priv->clk);
+ priv->powered = false;
+ }
+
+ if (priv->supply_enabled) {
+ err = regulator_disable(priv->vcc);
+ if (!err)
+ priv->supply_enabled = false;
+ else if (!ret)
+ ret = err;
+ }
+
+ return ret;
+}
+
+static int ac300_ephy_ctl_power_off(struct acx00_ephy_control *control)
+{
+ struct ac300_ephy_ctl *priv =
+ container_of(control, struct ac300_ephy_ctl, control);
+
+ return ac300_ephy_ctl_disable(priv);
+}
+
+static int
+ac300_ephy_ctl_set_interface(struct acx00_ephy_control *control,
+ phy_interface_t interface)
+{
+ struct ac300_ephy_ctl *priv =
+ container_of(control, struct ac300_ephy_ctl, control);
+ int ret = 0;
+
+ if (interface != PHY_INTERFACE_MODE_MII &&
+ interface != PHY_INTERFACE_MODE_RMII)
+ return -EINVAL;
+
+ if (priv->interface == interface)
+ return 0;
+
+ if (priv->powered)
+ ret = ac300_ephy_ctl_modify(priv, AC300_EPHY_CONFIG_REG,
+ AC300_EPHY_RMII_SEL,
+ interface == PHY_INTERFACE_MODE_RMII ?
+ AC300_EPHY_RMII_SEL : 0);
+ if (!ret)
+ priv->interface = interface;
+
+ return ret;
+}
+
+static int ac300_ephy_ctl_power_on(struct acx00_ephy_control *control,
+ unsigned int phy_addr)
+{
+ struct ac300_ephy_ctl *priv =
+ container_of(control, struct ac300_ephy_ctl, control);
+ u8 package_status;
+ u16 reset_value;
+ int sys_control;
+ int ret;
+
+ if (phy_addr != ac300_ephy_ctl_link_addr(priv))
+ return -EINVAL;
+
+ if (priv->powered)
+ return 0;
+
+ if (!priv->supply_enabled) {
+ ret = regulator_enable(priv->vcc);
+ if (ret)
+ return ret;
+ priv->supply_enabled = true;
+
+ /* Wait for the power-on reset interval specified by the manual. */
+ fsleep(10000);
+ }
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret)
+ goto err_power_off;
+ priv->powered = true;
+
+ /* Keep the external-supply configuration across chip resets. */
+ reset_value = ac300_ephy_ctl_reset_value(priv);
+ ret = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG, reset_value);
+ if (ret)
+ goto err_power_off;
+
+ /* The manual requires both resets to be released before the clocks. */
+ ret = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG,
+ AC300_EPHY_RESET_DEASSERT |
+ AC300_CHIP_RESET_DEASSERT);
+ if (ret)
+ goto err_power_off;
+
+ /* Retain the vendor clock-enable defaults, including the eFuse clock. */
+ ret = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG,
+ priv->sys_control);
+ if (ret)
+ goto err_power_off;
+
+ sys_control = ac300_ephy_ctl_read(priv, AC300_SYS_CONTROL_REG);
+ if (sys_control < 0) {
+ ret = sys_control;
+ goto err_power_off;
+ }
+
+ package_status = FIELD_GET(AC300_PACKAGE_STATUS_MASK, sys_control);
+ if ((~package_status & AC300_PACKAGE_PHY_ADDR_MASK) !=
+ ac300_ephy_ctl_link_addr(priv)) {
+ ret = -EINVAL;
+ goto err_power_off;
+ }
+
+ priv->internal_dldo = package_status & AC300_PACKAGE_POR_INTERNAL_DLDO;
+ priv->package_known = true;
+ ret = ac300_ephy_ctl_modify(priv, AC300_SYS_BIAS1_REG,
+ AC300_INTERNAL_DLDO_ENABLE,
+ priv->internal_dldo ?
+ AC300_INTERNAL_DLDO_ENABLE : 0);
+ if (ret)
+ goto err_power_off;
+
+ /* Keep the documented default drive level and leave the IRQ disabled. */
+ ret = ac300_ephy_ctl_write(priv, AC300_SYS_IO_REG,
+ AC300_SYS_IO_VALUE);
+ if (ret)
+ goto err_power_off;
+
+ fsleep(10000);
+
+ ret = ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG,
+ ac300_ephy_ctl_config(priv) |
+ AC300_EPHY_SHUTDOWN);
+ if (ret)
+ goto err_power_off;
+
+ fsleep(10000);
+
+ ret = ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG,
+ ac300_ephy_ctl_config(priv));
+ if (ret)
+ goto err_power_off;
+
+ return 0;
+
+err_power_off:
+ ac300_ephy_ctl_disable(priv);
+
+ return ret;
+}
+
+static void ac300_ephy_clk_put(void *data)
+{
+ clk_put(data);
+}
+
+struct acx00_ephy_control *
+ac300_ephy_ctl_create(struct phy_device *phydev,
+ struct device_node *package_node, u8 calibration)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct ac300_ephy_ctl *priv;
+ unsigned long clk_rate;
+ u8 bgs_effuse_code;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
+ if (phydev->mdio.addr > FIELD_MAX(AC300_PACKAGE_PHY_ADDR_MASK))
+ return ERR_PTR(dev_err_probe(dev, -EINVAL,
+ "link address is outside the package range\n"));
+ priv->phydev = phydev;
+
+ priv->vcc = devm_of_regulator_get(dev, package_node, "vcc");
+ if (IS_ERR(priv->vcc))
+ return ERR_PTR(dev_err_probe(dev, PTR_ERR(priv->vcc),
+ "failed to get package supply\n"));
+
+ priv->clk = of_clk_get(package_node, 0);
+ if (IS_ERR(priv->clk))
+ return ERR_PTR(dev_err_probe(dev, PTR_ERR(priv->clk),
+ "failed to get input clock\n"));
+
+ ret = devm_add_action_or_reset(dev, ac300_ephy_clk_put, priv->clk);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = devm_clk_rate_exclusive_get(dev, priv->clk);
+ if (ret)
+ return ERR_PTR(dev_err_probe(dev, ret,
+ "failed to lock clock rate\n"));
+
+ clk_rate = clk_get_rate(priv->clk);
+ switch (clk_rate) {
+ case AC300_EPHY_CLK_RATE_24_MHZ:
+ priv->sys_control = AC300_EPHY_CLK_SEL_24_MHZ;
+ break;
+ case AC300_EPHY_CLK_RATE_25_MHZ:
+ priv->sys_control = AC300_EPHY_CLK_SEL_25_MHZ;
+ break;
+ case AC300_EPHY_CLK_RATE_27_MHZ:
+ priv->sys_control = AC300_EPHY_CLK_SEL_27_MHZ;
+ break;
+ default:
+ return ERR_PTR(dev_err_probe(dev, -EINVAL,
+ "unsupported input clock rate %lu Hz\n",
+ clk_rate));
+ }
+ priv->sys_control |= AC300_SYS_CONTROL_ENABLE_BITS;
+
+ /* The vendor driver supplies no transfer function beyond this offset. */
+ bgs_effuse_code = (calibration + AC300_EPHY_BGS_EFFUSE_OFFSET) &
+ FIELD_MAX(AC300_EPHY_BGS_EFFUSE_MASK);
+ priv->ephy_config =
+ FIELD_PREP(AC300_EPHY_BGS_EFFUSE_MASK, bgs_effuse_code);
+ /* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */
+
+ priv->control.power_on = ac300_ephy_ctl_power_on;
+ priv->control.power_off = ac300_ephy_ctl_power_off;
+ priv->control.set_interface = ac300_ephy_ctl_set_interface;
+ /* MII is the reset default used until the MAC supplies its interface. */
+ priv->interface = PHY_INTERFACE_MODE_MII;
+
+ return &priv->control;
+}
diff --git a/drivers/net/phy/xpowers/acx00.c b/drivers/net/phy/xpowers/acx00.c
new file mode 100644
index 0000000000000..98f2b0b639096
--- /dev/null
+++ b/drivers/net/phy/xpowers/acx00.c
@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * X-Powers AC200/AC300 Ethernet PHY driver
+ *
+ * Copyright (C) 2019 Jernej Skrabec <jernej.skrabec@gmail.com>
+ * Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/slab.h>
+
+#include "../phylib.h"
+#include "acx00.h"
+
+#define ACX00_EPHY_ID 0x00441400
+
+#define ACX00_EPHY_CONFIG_CALIBRATION_MASK GENMASK(3, 0)
+#define ACX00_EPHY_CONFIG_VARIANT_AC300 BIT(8)
+#define ACX00_EPHY_CONFIG_CALIBRATION_LOW BIT(9)
+
+#define ACX00_PAGE_SELECT_REG 0x1f
+#define ACX00_PAGE_SELECT_MASK GENMASK(12, 8)
+#define ACX00_PAGE_0 0
+#define ACX00_PAGE_1 1
+#define ACX00_PAGE_2 2
+#define ACX00_PAGE_6 6
+#define ACX00_PAGE_8 8
+
+#define ACX00_PAGE0_GLOBAL_CONFIG_REG 0x13
+#define ACX00_PAGE0_XMII_RX_CLOCK_INVERT BIT(12)
+#define ACX00_PAGE0_MDI_MODE_MASK GENMASK(1, 0)
+#define ACX00_PAGE0_MDI_MODE_AUTO 2
+
+#define ACX00_PAGE1_APS_CONTROL_REG 0x12
+#define ACX00_PAGE1_APS_DISABLED_4S_VALUE 0x4824
+#define ACX00_PAGE1_UAPS_CONTROL_REG 0x13
+#define ACX00_PAGE1_UAPS_ENABLE BIT(15)
+#define ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG 0x17
+#define ACX00_PAGE1_INTELLIGENT_EEE_ENABLE BIT(3)
+
+#define ACX00_PAGE2_TX_DATA_CONTROL_REG 0x18
+#define ACX00_PAGE2_10BT_FIR_SELECT_MASK GENMASK(14, 12)
+#define ACX00_PAGE2_10BT_FIR_SELECT_DEFAULT 0
+
+#define ACX00_PAGE6_ADC_CONTROL_REG 0x10
+#define ACX00_PAGE6_ADC_CONTROL_LOW_CAL_VALUE 0x5523
+#define ACX00_PAGE6_AFE_RX_CONTROL_REG 0x13
+#define ACX00_PAGE6_AFE_RX_CONTROL_VALUE 0xf000
+#define ACX00_PAGE6_AFE_EQ_RX_DETECT_CONTROL_REG 0x14
+#define AC200_PAGE6_AFE_EQ_RX_DETECT_VALUE 0x708f
+#define AC300_PAGE6_AFE_EQ_RX_DETECT_VALUE 0x708b
+#define ACX00_PAGE6_AFE_EQ_RX_DETECT_LOW_CAL_VALUE 0x7809
+#define ACX00_PAGE6_TX_LEVEL_REG 0x15
+#define ACX00_PAGE6_TX_LEVEL_100M_MASK GENMASK(15, 8)
+#define ACX00_PAGE6_TX_LEVEL_10M_MASK GENMASK(7, 0)
+#define ACX00_PAGE6_TX_LEVEL_VALUE(_100m, _10m) \
+ (FIELD_PREP(ACX00_PAGE6_TX_LEVEL_100M_MASK, (_100m)) | \
+ FIELD_PREP(ACX00_PAGE6_TX_LEVEL_10M_MASK, (_10m)))
+#define ACX00_PAGE6_TX_LEVEL_DEFAULT_VALUE \
+ ACX00_PAGE6_TX_LEVEL_VALUE(0x15, 0x30)
+#define ACX00_PAGE6_TX_LEVEL_LOW_CAL_VALUE \
+ ACX00_PAGE6_TX_LEVEL_VALUE(0x35, 0x33)
+
+#define ACX00_PAGE8_AFE_CONTROL_REG 0x18
+#define ACX00_PAGE8_AFE_CONTROL_VALUE 0x00bc
+#define ACX00_PAGE8_AUTO_CAL_CONTROL_REG 0x1d
+#define ACX00_PAGE8_AUTO_CAL_TX_LEVEL_ADJUST_BYPASS BIT(11)
+#define ACX00_PAGE8_AUTO_CAL_LOW_CAL_OPAQUE_BITS 0x0044
+#define ACX00_PAGE8_AUTO_CAL_LOW_VALUE \
+ (ACX00_PAGE8_AUTO_CAL_TX_LEVEL_ADJUST_BYPASS | \
+ ACX00_PAGE8_AUTO_CAL_LOW_CAL_OPAQUE_BITS)
+
+/*
+ * Another integration of this exact-ID PHY documents its digital vendor
+ * register map, which also matches the observed ACx00 reset values. The ACx00
+ * analog-page field encodings remain unpublished, so keep those as opaque
+ * vendor initialization values instead of inventing bit definitions.
+ */
+
+struct acx00_ephy_priv {
+ struct phy_device *phydev;
+ struct acx00_ephy_control *control;
+ bool is_ac300;
+ bool use_low_calibration_tuning;
+ bool xmii_rx_clock_inverted;
+};
+
+static int acx00_ephy_read_page(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = __phy_read(phydev, ACX00_PAGE_SELECT_REG);
+ if (ret < 0)
+ return ret;
+
+ return FIELD_GET(ACX00_PAGE_SELECT_MASK, ret);
+}
+
+static int acx00_ephy_write_page(struct phy_device *phydev, int page)
+{
+ return __phy_write(phydev, ACX00_PAGE_SELECT_REG,
+ FIELD_PREP(ACX00_PAGE_SELECT_MASK, page));
+}
+
+static int acx00_ephy_control_power_on(struct acx00_ephy_priv *priv)
+{
+ return priv->control->power_on(priv->control,
+ priv->phydev->mdio.addr);
+}
+
+static int acx00_ephy_control_power_off(struct acx00_ephy_priv *priv)
+{
+ return priv->control->power_off(priv->control);
+}
+
+static int acx00_ephy_set_interface(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_NA)
+ return 0;
+ if (phydev->interface != PHY_INTERFACE_MODE_MII &&
+ phydev->interface != PHY_INTERFACE_MODE_RMII)
+ return -EINVAL;
+
+ return priv->control->set_interface(priv->control,
+ phydev->interface);
+}
+
+static void acx00_ephy_control_release(void *data)
+{
+ struct acx00_ephy_priv *priv = data;
+ int ret;
+
+ ret = acx00_ephy_control_power_off(priv);
+ if (ret)
+ phydev_warn(priv->phydev,
+ "failed to power off control block: %pe\n",
+ ERR_PTR(ret));
+}
+
+static int acx00_ephy_read_nvmem_u16(struct device_node *node,
+ const char *name, u16 *value)
+{
+ struct nvmem_cell *cell;
+ size_t i;
+ size_t len;
+ u8 *buf;
+ u16 val = 0;
+
+ cell = of_nvmem_cell_get(node, name);
+ if (IS_ERR(cell))
+ return PTR_ERR(cell);
+
+ buf = nvmem_cell_read(cell, &len);
+ nvmem_cell_put(cell);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+ if (!len || len > sizeof(*value)) {
+ kfree(buf);
+ return len ? -ERANGE : -EINVAL;
+ }
+
+ for (i = 0; i < len; i++)
+ val |= (u16)buf[i] << (8 * i);
+ kfree(buf);
+ *value = val;
+
+ return 0;
+}
+
+static int acx00_ephy_init_package(struct phy_device *phydev,
+ struct acx00_ephy_priv *priv)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct device_node *package_node;
+ bool selectable;
+ bool fixed_ac300;
+ bool has_configuration;
+ u8 calibration;
+ u32 base_addr;
+ u16 configuration = 0;
+ int ret;
+
+ package_node = of_get_parent(dev->of_node);
+ if (!package_node)
+ return -EINVAL;
+ if (!of_node_name_eq(package_node, "ethernet-phy-package")) {
+ ret = dev_err_probe(dev, -EINVAL,
+ "PHY is not in an Ethernet PHY package\n");
+ goto out_put_node;
+ }
+
+ ret = of_property_read_u32(package_node, "reg", &base_addr);
+ if (ret || base_addr != phydev->mdio.addr) {
+ ret = dev_err_probe(dev, -EINVAL,
+ "package and link PHY addresses differ\n");
+ goto out_put_node;
+ }
+
+ selectable = of_device_is_compatible(package_node,
+ "x-powers,acx00-ephy-package");
+ fixed_ac300 = of_device_is_compatible(package_node,
+ "x-powers,ac300-ephy-package");
+ if (!selectable && !fixed_ac300 &&
+ !of_device_is_compatible(package_node,
+ "x-powers,ac200-ephy-package")) {
+ ret = dev_err_probe(dev, -EINVAL,
+ "unsupported Ethernet PHY package\n");
+ goto out_put_node;
+ }
+
+ ret = devm_of_phy_package_join(dev, phydev, 0);
+ if (ret)
+ goto out_put_node;
+
+ has_configuration =
+ of_property_match_string(package_node, "nvmem-cell-names",
+ "configuration") >= 0;
+ if (has_configuration) {
+ ret = acx00_ephy_read_nvmem_u16(package_node, "configuration",
+ &configuration);
+ if (ret) {
+ ret = dev_err_probe(dev, ret,
+ "failed to read package configuration\n");
+ goto out_put_node;
+ }
+ } else if (selectable || fixed_ac300) {
+ ret = dev_err_probe(dev, -EINVAL,
+ "package configuration is required\n");
+ goto out_put_node;
+ }
+
+ if (selectable) {
+ priv->is_ac300 =
+ configuration & ACX00_EPHY_CONFIG_VARIANT_AC300;
+ } else {
+ priv->is_ac300 = fixed_ac300;
+ if (has_configuration &&
+ !!(configuration & ACX00_EPHY_CONFIG_VARIANT_AC300) !=
+ priv->is_ac300) {
+ ret = dev_err_probe(dev, -EINVAL,
+ "configuration does not match package\n");
+ goto out_put_node;
+ }
+ }
+
+ calibration = FIELD_GET(ACX00_EPHY_CONFIG_CALIBRATION_MASK,
+ configuration);
+
+ priv->use_low_calibration_tuning =
+ priv->is_ac300 &&
+ !!(configuration & ACX00_EPHY_CONFIG_CALIBRATION_LOW);
+ priv->xmii_rx_clock_inverted =
+ of_property_read_bool(package_node,
+ "x-powers,xmii-rx-clock-inverted");
+
+ if (priv->is_ac300)
+ priv->control =
+ ac300_ephy_ctl_create(phydev, package_node,
+ calibration);
+ else
+ priv->control =
+ ac200_ephy_ctl_create(phydev, package_node,
+ has_configuration,
+ calibration);
+ ret = PTR_ERR_OR_ZERO(priv->control);
+
+out_put_node:
+ of_node_put(package_node);
+ return ret;
+}
+
+static int acx00_ephy_disable_autonomous_eee(struct phy_device *phydev)
+{
+ return phy_modify_paged(phydev, ACX00_PAGE_1,
+ ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG,
+ ACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0);
+}
+
+static int acx00_ephy_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct acx00_ephy_priv *priv;
+ int ret;
+
+ if (!dev->of_node)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->phydev = phydev;
+ ret = acx00_ephy_init_package(phydev, priv);
+ if (ret)
+ return ret;
+
+ phydev->priv = priv;
+ ret = devm_add_action_or_reset(dev, acx00_ephy_control_release, priv);
+ if (ret)
+ return ret;
+
+ ret = acx00_ephy_control_power_on(priv);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to power on control block\n");
+
+ return 0;
+}
+
+static int acx00_ephy_soft_reset(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ int ret;
+
+ ret = acx00_ephy_set_interface(phydev);
+ if (ret)
+ return ret;
+
+ ret = acx00_ephy_control_power_on(priv);
+ if (ret)
+ return ret;
+
+ /* ACx00 can acknowledge reset in power-down without restarting. */
+ ret = genphy_resume(phydev);
+ if (ret)
+ return ret;
+
+ return genphy_soft_reset(phydev);
+}
+
+static int acx00_ephy_config_init(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ u16 afe_eq_rx_detect = priv->is_ac300 ?
+ AC300_PAGE6_AFE_EQ_RX_DETECT_VALUE :
+ AC200_PAGE6_AFE_EQ_RX_DETECT_VALUE;
+ u16 tx_level_value = ACX00_PAGE6_TX_LEVEL_DEFAULT_VALUE;
+ u16 global_config;
+ int oldpage;
+ int ret;
+
+ global_config = FIELD_PREP(ACX00_PAGE0_MDI_MODE_MASK,
+ ACX00_PAGE0_MDI_MODE_AUTO);
+ if (priv->xmii_rx_clock_inverted)
+ global_config |= ACX00_PAGE0_XMII_RX_CLOCK_INVERT;
+
+ ret = phy_modify_paged(phydev, ACX00_PAGE_0,
+ ACX00_PAGE0_GLOBAL_CONFIG_REG,
+ ACX00_PAGE0_XMII_RX_CLOCK_INVERT |
+ ACX00_PAGE0_MDI_MODE_MASK, global_config);
+ if (ret)
+ return ret;
+
+ if (priv->is_ac300 && priv->use_low_calibration_tuning) {
+ afe_eq_rx_detect =
+ ACX00_PAGE6_AFE_EQ_RX_DETECT_LOW_CAL_VALUE;
+ tx_level_value = ACX00_PAGE6_TX_LEVEL_LOW_CAL_VALUE;
+ }
+
+ oldpage = phy_select_page(phydev, ACX00_PAGE_1);
+ if (oldpage < 0)
+ goto out_restore_page;
+
+ ret = __phy_write(phydev, ACX00_PAGE1_APS_CONTROL_REG,
+ ACX00_PAGE1_APS_DISABLED_4S_VALUE);
+ if (ret)
+ goto out_restore_page;
+ ret = __phy_modify(phydev, ACX00_PAGE1_UAPS_CONTROL_REG,
+ ACX00_PAGE1_UAPS_ENABLE, 0);
+ if (ret)
+ goto out_restore_page;
+ ret = __phy_modify(phydev, ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG,
+ ACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0);
+ if (ret)
+ goto out_restore_page;
+
+ ret = acx00_ephy_write_page(phydev, ACX00_PAGE_2);
+ if (ret)
+ goto out_restore_page;
+ ret = __phy_modify(phydev, ACX00_PAGE2_TX_DATA_CONTROL_REG,
+ ACX00_PAGE2_10BT_FIR_SELECT_MASK,
+ FIELD_PREP(ACX00_PAGE2_10BT_FIR_SELECT_MASK,
+ ACX00_PAGE2_10BT_FIR_SELECT_DEFAULT));
+ if (ret)
+ goto out_restore_page;
+
+ ret = acx00_ephy_write_page(phydev, ACX00_PAGE_6);
+ if (ret)
+ goto out_restore_page;
+ ret = __phy_write(phydev, ACX00_PAGE6_AFE_EQ_RX_DETECT_CONTROL_REG,
+ afe_eq_rx_detect);
+ if (ret)
+ goto out_restore_page;
+ ret = __phy_write(phydev, ACX00_PAGE6_AFE_RX_CONTROL_REG,
+ ACX00_PAGE6_AFE_RX_CONTROL_VALUE);
+ if (ret)
+ goto out_restore_page;
+ if (priv->is_ac300 && priv->use_low_calibration_tuning) {
+ ret = __phy_write(phydev, ACX00_PAGE6_ADC_CONTROL_REG,
+ ACX00_PAGE6_ADC_CONTROL_LOW_CAL_VALUE);
+ if (ret)
+ goto out_restore_page;
+ }
+ ret = __phy_write(phydev, ACX00_PAGE6_TX_LEVEL_REG, tx_level_value);
+ if (ret)
+ goto out_restore_page;
+
+ ret = acx00_ephy_write_page(phydev, ACX00_PAGE_8);
+ if (ret)
+ goto out_restore_page;
+ if (priv->is_ac300 && priv->use_low_calibration_tuning) {
+ ret = __phy_write(phydev, ACX00_PAGE8_AUTO_CAL_CONTROL_REG,
+ ACX00_PAGE8_AUTO_CAL_LOW_VALUE);
+ if (ret)
+ goto out_restore_page;
+ }
+ ret = __phy_write(phydev, ACX00_PAGE8_AFE_CONTROL_REG,
+ ACX00_PAGE8_AFE_CONTROL_VALUE);
+
+out_restore_page:
+ ret = phy_restore_page(phydev, oldpage, ret);
+ if (ret)
+ return ret;
+
+ /* Restore the standard EEE policy retained by phylib across resets. */
+ return genphy_c45_an_config_eee_aneg(phydev);
+}
+
+static int acx00_ephy_power_on_and_resume(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ int ret;
+
+ ret = acx00_ephy_set_interface(phydev);
+ if (ret)
+ return ret;
+
+ ret = acx00_ephy_control_power_on(priv);
+ if (ret)
+ return ret;
+
+ ret = genphy_resume(phydev);
+ if (ret) {
+ acx00_ephy_control_power_off(priv);
+ return ret;
+ }
+
+ /* Powering off the control block loses the vendor-page state. */
+ return acx00_ephy_config_init(phydev);
+}
+
+static int acx00_ephy_resume(struct phy_device *phydev)
+{
+ return acx00_ephy_power_on_and_resume(phydev);
+}
+
+static int acx00_ephy_suspend(struct phy_device *phydev)
+{
+ struct acx00_ephy_priv *priv = phydev->priv;
+ int resume_ret;
+ int ret;
+
+ ret = genphy_suspend(phydev);
+ if (ret)
+ return ret;
+
+ ret = acx00_ephy_control_power_off(priv);
+ if (ret) {
+ resume_ret = acx00_ephy_power_on_and_resume(phydev);
+ if (resume_ret)
+ phydev_warn(phydev,
+ "failed to recover from suspend error: %pe\n",
+ ERR_PTR(resume_ret));
+ }
+
+ return ret;
+}
+
+static int acx00_ephy_match_phy_device(struct phy_device *phydev,
+ const struct phy_driver *phydrv)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ struct device_node *package_node;
+ bool match;
+
+ if (!genphy_match_phy_device(phydev, phydrv) || !node)
+ return 0;
+
+ /* RK630 reports the same PHY ID, so also match the package identity. */
+ package_node = of_get_parent(node);
+ if (!package_node)
+ return 0;
+ match = of_device_is_compatible(package_node,
+ "x-powers,ac200-ephy-package") ||
+ of_device_is_compatible(package_node,
+ "x-powers,ac300-ephy-package") ||
+ of_device_is_compatible(package_node,
+ "x-powers,acx00-ephy-package");
+ of_node_put(package_node);
+
+ return match;
+}
+
+static struct phy_driver acx00_ephy_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(ACX00_EPHY_ID),
+ .name = "X-Powers AC200/AC300 EPHY",
+ .match_phy_device = acx00_ephy_match_phy_device,
+ .probe = acx00_ephy_probe,
+ .read_page = acx00_ephy_read_page,
+ .write_page = acx00_ephy_write_page,
+ .soft_reset = acx00_ephy_soft_reset,
+ .config_init = acx00_ephy_config_init,
+ .disable_autonomous_eee = acx00_ephy_disable_autonomous_eee,
+ .suspend = acx00_ephy_suspend,
+ .resume = acx00_ephy_resume,
+ },
+};
+module_phy_driver(acx00_ephy_driver);
+
+static const struct mdio_device_id __maybe_unused acx00_ephy_tbl[] = {
+ { PHY_ID_MATCH_MODEL(ACX00_EPHY_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE(mdio, acx00_ephy_tbl);
+
+MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
+MODULE_DESCRIPTION("X-Powers AC200/AC300 Ethernet PHY driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/xpowers/acx00.h b/drivers/net/phy/xpowers/acx00.h
new file mode 100644
index 0000000000000..7df6084c7a772
--- /dev/null
+++ b/drivers/net/phy/xpowers/acx00.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __DRIVERS_NET_PHY_XPOWERS_ACX00_H
+#define __DRIVERS_NET_PHY_XPOWERS_ACX00_H
+
+#include <linux/err.h>
+#include <linux/phy.h>
+
+#define AC300_EPHY_CONTROL_ADDR_OFFSET 16
+
+struct device_node;
+
+struct acx00_ephy_control {
+ int (*power_on)(struct acx00_ephy_control *control,
+ unsigned int phy_addr);
+ int (*power_off)(struct acx00_ephy_control *control);
+ int (*set_interface)(struct acx00_ephy_control *control,
+ phy_interface_t interface);
+};
+
+struct acx00_ephy_control *
+ac200_ephy_ctl_create(struct phy_device *phydev,
+ struct device_node *package_node,
+ bool has_calibration, u8 calibration);
+struct acx00_ephy_control *
+ac300_ephy_ctl_create(struct phy_device *phydev,
+ struct device_node *package_node, u8 calibration);
+
+#endif
diff --git a/include/linux/mfd/syscon.h b/include/linux/mfd/syscon.h
index aad9c6b504636..3e804fc1b03eb 100644
--- a/include/linux/mfd/syscon.h
+++ b/include/linux/mfd/syscon.h
@@ -15,6 +15,7 @@
#include <linux/errno.h>
struct device_node;
+struct device;
#ifdef CONFIG_MFD_SYSCON
struct regmap *device_node_to_regmap(struct device_node *np);
@@ -30,6 +31,9 @@ struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np,
const char *property);
int of_syscon_register_regmap(struct device_node *np,
struct regmap *regmap);
+int devm_of_syscon_register_regmap(struct device *dev,
+ struct device_node *np,
+ struct regmap *regmap);
#else
static inline struct regmap *device_node_to_regmap(struct device_node *np)
{
@@ -75,6 +79,14 @@ static inline int of_syscon_register_regmap(struct device_node *np,
return -EOPNOTSUPP;
}
+static inline int
+devm_of_syscon_register_regmap(struct device *dev,
+ struct device_node *np,
+ struct regmap *regmap)
+{
+ return -EOPNOTSUPP;
+}
+
#endif
#endif /* __LINUX_MFD_SYSCON_H__ */
|
| 3/2 | 2026/08/09 20:38 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26121 output: 23 thoughts: 2518 Thoughts: **Analysis of the X-Powers AC200/AC300 Ethernet PHY Patch for Fuzzing Reachability** Okay, here's the breakdown. This patch introduces support for new Ethernet PHY packages from X-Powers, specifically the AC200 and AC300. It's touching the usual suspects: device tree bindings, Kconfig/Makefiles, and new driver files for the Multi-Function Device (MFD) and the PHY itself. My concern, as always, is whether syzkaller, or any fuzzing setup, can actually reach this new code, especially considering the reliance on device tree nodes. I've got an MFD driver (`ac200.c`) and a PHY driver (`acx00.c` and associated files). The `syscon.c` file is being modified to add a `devm_of_syscon_register_regmap` function, which is interesting because that's core kernel logic, a common entry point for potential fuzzing. This is the first thing to probe for reachability. Initially, I thought about instantiation methods. Can I instantiate these devices through I2C or MDIO sysfs entries, or even virtual device tree manipulation? The answer, at least straightforwardly, is no. The PHY driver (`acx00_ephy_probe`) explicitly checks for a `dev->of_node` and returns `-ENODEV` if it's not present. The MFD driver (`ac200_probe`) also uses `dev->of_node` in the critical `devm_of_syscon_register_regmap` call. If I try to instantiate these devices with sysfs `new_device`, there's no way to attach a DT node. Syzkaller does have limited I2C/MDIO support, typically used via `/dev/i2c-*` devices. However, it's not going to automatically *probe* drivers unless the device is properly instantiated, and certainly isn't going to inject device tree nodes on demand. In a virtualized environment, it's difficult to get that node set up dynamically, and both probes will fail. The critical piece here is the use of `devm_of_syscon_register_regmap`. It *is* a core kernel function, but its only explicit use in the patch is within the `ac200.c` file. While the modification *seems* impactful to core code, if `ac200_probe` is never called, then the function will not be tested. I thought about the potential to instantiate the I2C device via sysfs - as in, echo the device and address, and try to have it bind. This would call `ac200_probe`. And it *would*, but then the `devm_clk_get_enabled` function would fail with `-ENOENT` because the clk node is also missing from the device tree and the MFD code requires it to be present. So, realistically, *neither* driver is likely to execute any significant code path without a *well-defined* device tree node. Therefore, the refactored syscon API is unlikely to be tested, too. However, the refactoring itself is a change to core `syscon.c` code. The function name `of_syscon_register_regmap` already existed and only the internal function was modified to use `of_syscon_register_regmap_internal`. This function is likely used in other parts of the kernel, so this change *could* be reachable if there is another call to it. The bottom line is that while I can see that the changes are adding support, that code is unreachable without crafting a specific device tree, which is not something syzkaller typically does automatically for arbitrary drivers and it is unlikely to test the drivers if I just declare that code is worth fuzzing. |
| 4/2 | 2026/08/09 20:39 | tool | grepper |
0mArgs:{
"Expression": "of_syscon_register_regmap"
}Results: {
"Output": "drivers/mfd/ac200.c=74=static int ac200_probe(struct i2c_client *client)\n--\ndrivers/mfd/ac200.c-130-\ndrivers/mfd/ac200.c:131:\tret = devm_of_syscon_register_regmap(dev, dev-\u003eof_node,\ndrivers/mfd/ac200.c-132-\t\t\t\t\t ddata-\u003eregmap);\n--\ndrivers/mfd/syscon.c=197=static struct syscon *\ndrivers/mfd/syscon.c:198:of_syscon_register_regmap_internal(struct device_node *np,\ndrivers/mfd/syscon.c-199-\t\t\t\t struct regmap *regmap)\n--\ndrivers/mfd/syscon.c-230-/**\ndrivers/mfd/syscon.c:231: * of_syscon_register_regmap() - Register regmap for specified device node\ndrivers/mfd/syscon.c-232- * @np: Device tree node\n--\ndrivers/mfd/syscon.c-240- */\ndrivers/mfd/syscon.c:241:int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)\ndrivers/mfd/syscon.c-242-{\ndrivers/mfd/syscon.c:243:\treturn PTR_ERR_OR_ZERO(of_syscon_register_regmap_internal(np, regmap));\ndrivers/mfd/syscon.c-244-}\ndrivers/mfd/syscon.c:245:EXPORT_SYMBOL_GPL(of_syscon_register_regmap);\ndrivers/mfd/syscon.c-246-\ndrivers/mfd/syscon.c:247:static void devm_of_syscon_register_regmap_release(void *data)\ndrivers/mfd/syscon.c-248-{\n--\ndrivers/mfd/syscon.c-259-/**\ndrivers/mfd/syscon.c:260: * devm_of_syscon_register_regmap() - Register a managed external syscon regmap\ndrivers/mfd/syscon.c-261- * @dev: Device that owns the regmap\n--\ndrivers/mfd/syscon.c-270- */\ndrivers/mfd/syscon.c:271:int devm_of_syscon_register_regmap(struct device *dev,\ndrivers/mfd/syscon.c-272-\t\t\t\t struct device_node *np,\n--\ndrivers/mfd/syscon.c-276-\ndrivers/mfd/syscon.c:277:\tsyscon = of_syscon_register_regmap_internal(np, regmap);\ndrivers/mfd/syscon.c-278-\tif (IS_ERR(syscon))\n--\ndrivers/mfd/syscon.c-281-\treturn devm_add_action_or_reset(dev,\ndrivers/mfd/syscon.c:282:\t\t\t\t\tdevm_of_syscon_register_regmap_release,\ndrivers/mfd/syscon.c-283-\t\t\t\t\tsyscon);\ndrivers/mfd/syscon.c-284-}\ndrivers/mfd/syscon.c:285:EXPORT_SYMBOL_GPL(devm_of_syscon_register_regmap);\ndrivers/mfd/syscon.c-286-\n--\ndrivers/mfd/syscon.c=302=EXPORT_SYMBOL_GPL(device_node_to_regmap);\n--\ndrivers/mfd/syscon.c-310- * function is safe to use for a syscon registered with\ndrivers/mfd/syscon.c:311: * of_syscon_register_regmap().\ndrivers/mfd/syscon.c-312- *\n--\ndrivers/soc/renesas/rz-sysc.c=107=static int rz_sysc_probe(struct platform_device *pdev)\n--\ndrivers/soc/renesas/rz-sysc.c-151-\ndrivers/soc/renesas/rz-sysc.c:152:\treturn of_syscon_register_regmap(dev-\u003eof_node, regmap);\ndrivers/soc/renesas/rz-sysc.c-153-}\n--\ndrivers/soc/samsung/exynos-pmu.c=410=static int setup_cpuhp_and_cpuidle(struct device *dev)\n--\ndrivers/soc/samsung/exynos-pmu.c-448-\t/* register custom mmio regmap with syscon */\ndrivers/soc/samsung/exynos-pmu.c:449:\tret = of_syscon_register_regmap(intr_gen_node,\ndrivers/soc/samsung/exynos-pmu.c-450-\t\t\t\t\tpmu_context-\u003epmuintrgen);\n--\ndrivers/soc/samsung/exynos-pmu.c=476=static int exynos_pmu_probe(struct platform_device *pdev)\n--\ndrivers/soc/samsung/exynos-pmu.c-516-\ndrivers/soc/samsung/exynos-pmu.c:517:\t\tret = of_syscon_register_regmap(dev-\u003eof_node, regmap);\ndrivers/soc/samsung/exynos-pmu.c-518-\t\tif (ret)\n--\ndrivers/soc/sunxi/sunxi_sram.c=408=static int __init sunxi_sram_probe(struct platform_device *pdev)\n--\ndrivers/soc/sunxi/sunxi_sram.c-431-\ndrivers/soc/sunxi/sunxi_sram.c:432:\t\tret = of_syscon_register_regmap(dev-\u003eof_node, regmap);\ndrivers/soc/sunxi/sunxi_sram.c-433-\t\tif (ret)\n--\ninclude/linux/mfd/syscon.h=30=struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np,\ninclude/linux/mfd/syscon.h-31-\t\t\t\t\t\t\tconst char *property);\ninclude/linux/mfd/syscon.h:32:int of_syscon_register_regmap(struct device_node *np,\ninclude/linux/mfd/syscon.h-33-\t\t\t struct regmap *regmap);\ninclude/linux/mfd/syscon.h:34:int devm_of_syscon_register_regmap(struct device *dev,\ninclude/linux/mfd/syscon.h-35-\t\t\t\t struct device_node *np,\n--\ninclude/linux/mfd/syscon.h=69=static inline struct regmap *syscon_regmap_lookup_by_phandle_optional(\n--\ninclude/linux/mfd/syscon.h-75-\ninclude/linux/mfd/syscon.h:76:static inline int of_syscon_register_regmap(struct device_node *np,\ninclude/linux/mfd/syscon.h-77-\t\t\t\t\tstruct regmap *regmap)\n--\ninclude/linux/mfd/syscon.h=82=static inline int\ninclude/linux/mfd/syscon.h:83:devm_of_syscon_register_regmap(struct device *dev,\ninclude/linux/mfd/syscon.h-84-\t\t\t struct device_node *np,\n"
} |
| 5/2 | 2026/08/09 20:39 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31404 output: 154 thoughts: 4007 Thoughts: **Analysis of Kernel Patch for Fuzzing Suitability** Okay, so I've got this patch that refactors `of_syscon_register_regmap` and adds `devm_of_syscon_register_regmap`. I need to figure out if it's worth fuzzing. The original `of_syscon_register_regmap` is used by a few SoC-specific drivers (`exynos-pmu.c`, `rz-sysc.c`, and `sunxi_sram.c`). The patch itself modifies some core syscon code, specifically the internal registration function. The crucial change is that it now uses `of_node_get()` to take a reference to the device node, fixing a potential memory issue, which is good. There's also some minor difference in memory allocation. I also see that there's a new `ac200` MFD driver and `acx00` PHY driver included in this patch. My initial thought is that I need to determine if these code paths are reachable by syzkaller's standard test environments, primarily x86_64 and ARM64 `virt`. I know syzkaller doesn't typically run these specific SoCs, and so, the SoC drivers that use `of_syscon_register_regmap` are likely unreachable. But, is the core function affected by the patch reachable? Let's look more closely at those new drivers. `ac200_probe` is an I2C probe function, and the `acx00_ephy_probe` is a MDIO PHY probe function. For both of them, the device tree node (`of_node`) is critical. In the `ac200` case, I noticed the function calls `devm_clk_get_enabled`. If no clock is available for the given node, it returns an error and the probe fails. For `acx00`, the probe returns `-ENODEV` if the device doesn't have an `of_node`. I also see that the PHY driver uses the generic function for device matching `genphy_match_phy_device`, which fails if `of_node` is `NULL`. Can syzkaller create a situation where these drivers' probes get called? I can try to create an I2C or MDIO device via sysfs in a virtualized QEMU/GCE environment. But the crucial issue is the device tree node. Syzkaller doesn't currently inject DT overlays, and without the DT node, the drivers will not initialize properly. Without a valid `of_node`, these drivers won't execute any significant code paths, returning early or failing at `devm_clk_get_enabled`. The `acx00` driver will also return immediately if there is no node available. So, I'm thinking, are there any other ways to reach the modified `of_syscon_register_regmap_internal` or the `devm_of_syscon_register_regmap`? From a code search, I see that this is not possible; these functions are directly called by the driver probes and those are the only places. Okay, let me review the instructions again: the patch has some code paths that are impossible to reach in virtualized environments like GCE or QEMU. That is my conclusion. Therefore, because this refactored code path is not easily reachable in the syzkaller setup, and the drivers depend on device tree nodes that are not easily provided, the patch is unlikely to uncover any meaningful new fuzzing targets in my typical test environment, I'm going to return `WorthFuzzing=false`. |
| 6/2 | 2026/08/09 20:39 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch adds drivers for X-Powers AC200 MFD and ACx00 Ethernet PHYs, which strictly require device tree nodes (of_node) to probe and function. Since syzkaller does not dynamically generate arbitrary device tree nodes, and these devices are not present in the default virtualized environments (like QEMU 'virt' or x86_64), the code is unreachable. The minor refactoring in syscon.c is only reachable via SoC-specific drivers (Exynos, Sunxi, Renesas) that are also not present in standard virtualized environments.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch adds drivers for X-Powers AC200 MFD and ACx00 Ethernet PHYs, which strictly require device tree nodes (of_node) to probe and function. Since syzkaller does not dynamically generate arbitrary device tree nodes, and these devices are not present in the default virtualized environments (like QEMU 'virt' or x86_64), the code is unreachable. The minor refactoring in syscon.c is only reachable via SoC-specific drivers (Exynos, Sunxi, Renesas) that are also not present in standard virtualized environments.",
"WorthFuzzing": false
} |
| 7/1 | 2026/08/09 20:39 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|