RE: [PATCH net-next v2 6/8] net: phy: add X-Powers AC300 EPHY control driver
"Jagielski, Jedrzej" <[email protected]> Thu, 6 Aug 2026 09:20:16 +0000
| Newsgroups | dev.linux.lists.mfd,org.infradead.lists.linux-arm-kernel,org.infradead.lists.linux-rockchip,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.netdev |
|---|---|
| Message-ID | <PH0PR11MB5902224A6A8C65CC83AD6AE2F0D22@PH0PR11MB5902.namprd11.prod.outlook.com> |
From: James Hilliard <[email protected]> Sent: Wednesday, August 5, 2026 4:27 AM >The AC300 Fast Ethernet PHY needs a separate non-PHY Clause 22 endpoint >to complete package setup before the link endpoint becomes usable. > >Enable VCC1 and the input clock, select its 24, 25 or 27 MHz rate, apply >the SoC SID calibration value and vendor offset, configure the documented >I/O drive strength, and perform the reset-before-clock power-up sequence. >Validate the control and link address relationship against the package >straps, clear a latched MDIO error, and configure the internal digital >LDO only for the package variant which uses it. > >Early PHY creation uses reset-default MII. A separate operation later >applies phydev->interface and, if already powered, changes only RMII_SEL >before the normal PHY soft reset. > >Expose serialized, idempotent power and interface operations to the >common PHY driver. Enable only the MDIO, MII and input-clock pads in this >basic driver; dedicated LED outputs remain disabled for a later patch. > >Signed-off-by: James Hilliard <[email protected]> >--- > drivers/net/phy/Kconfig | 9 + > drivers/net/phy/Makefile | 1 + > drivers/net/phy/xpowers-ac300-ctl.c | 423 ++++++++++++++++++++++++++++++++++++ > drivers/net/phy/xpowers-acx00.h | 2 + > 4 files changed, 435 insertions(+) > >diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig >index 6119f4de880d..7535db7f3471 100644 >--- a/drivers/net/phy/Kconfig >+++ b/drivers/net/phy/Kconfig >@@ -484,6 +484,15 @@ config XPOWERS_AC200_PHY_CTL > address, interface mode, calibration and I/O controls through the > parent AC200 I2C regmap. > >+config XPOWERS_AC300_PHY_CTL >+ tristate "X-Powers AC300 Ethernet PHY control" >+ depends on COMMON_CLK && NVMEM && OF_MDIO >+ help >+ Enable the MDIO control driver for the Fast Ethernet PHY in the >+ X-Powers AC300 companion IC. It programs the PHY calibration, >+ interface mode, clock and I/O controls through the separate MDIO >+ control address. >+ > config XILINX_GMII2RGMII > tristate "Xilinx GMII2RGMII converter driver" > help >diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile >index f81854fc9f12..0c4e40c9a28a 100644 >--- a/drivers/net/phy/Makefile >+++ b/drivers/net/phy/Makefile >@@ -101,4 +101,5 @@ obj-$(CONFIG_STE10XP) += ste10Xp.o > obj-$(CONFIG_TERANETICS_PHY) += teranetics.o > obj-$(CONFIG_VITESSE_PHY) += vitesse.o > obj-$(CONFIG_XPOWERS_AC200_PHY_CTL) += xpowers-ac200-ctl.o >+obj-$(CONFIG_XPOWERS_AC300_PHY_CTL) += xpowers-ac300-ctl.o > obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o >diff --git a/drivers/net/phy/xpowers-ac300-ctl.c b/drivers/net/phy/xpowers-ac300-ctl.c >new file mode 100644 >index 000000000000..d3ca4df8a9a3 >--- /dev/null >+++ b/drivers/net/phy/xpowers-ac300-ctl.c >@@ -0,0 +1,423 @@ >+// SPDX-License-Identifier: GPL-2.0-only >+/* >+ * X-Powers AC300 Ethernet PHY control driver >+ * >+ * Copyright (C) 2026 James Hilliard <[email protected]> >+ */ >+ >+#include <linux/bitfield.h> >+#include <linux/clk.h> >+#include <linux/delay.h> >+#include <linux/mdio.h> >+#include <linux/module.h> >+#include <linux/mutex.h> >+#include <linux/nvmem-consumer.h> >+#include <linux/phy.h> >+#include <linux/property.h> >+#include <linux/regulator/consumer.h> >+ >+#include "xpowers-acx00.h" >+ >+#define AC300_EPHY_BGS_EFFUSE_OFFSET 3 >+#define AC300_SYS_CONTROL_REG 0x00 >+#define AC300_CHIP_VERSION_MASK GENMASK(15, 12) >+#define AC300_PACKAGE_STATUS_MASK GENMASK(11, 8) >+#define AC300_EPHY_CLK_SEL_MASK GENMASK(7, 6) >+#define AC300_EPHY_CLK_SEL_25_MHZ FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 0) >+#define AC300_EPHY_CLK_SEL_27_MHZ FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 1) >+#define AC300_EPHY_CLK_SEL_24_MHZ FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 2) >+#define AC300_EFUSE_CLK_ENABLE BIT(5) >+#define AC300_EPHY_REG_CLK_ENABLE BIT(4) >+#define AC300_MDIO_ERROR BIT(3) >+#define AC300_CLKIN_GATING_ENABLE BIT(2) >+#define AC300_EPHY_RESET_DEASSERT BIT(1) >+#define AC300_CHIP_RESET_DEASSERT BIT(0) >+ >+#define AC300_MASK_VERSION_REG 0x04 >+#define AC300_MASK_VERSION_MASK GENMASK(2, 0) >+ >+#define AC300_PACKAGE_POR_INTERNAL_DLDO BIT(3) >+#define AC300_PACKAGE_PHY_ADDR_MASK GENMASK(2, 0) >+ >+#define AC300_SYS_BIAS1_REG 0x02 >+#define AC300_INTERNAL_DLDO_ENABLE BIT(15) >+ >+#define AC300_SYS_IO_REG 0x05 >+#define AC300_MDIO_DRV_MASK GENMASK(15, 14) >+#define AC300_MII_DRV_MASK GENMASK(11, 10) >+#define AC300_IO_DRV_LEVEL_2 2 >+#define AC300_CLKIN_PAD_ENABLE BIT(4) >+#define AC300_EPHY_MII_IO_ENABLE BIT(0) >+ >+#define AC300_EPHY_CONFIG_REG 0x06 >+#define AC300_EPHY_BGS_EFFUSE_MASK GENMASK(15, 12) >+#define AC300_EPHY_RMII_SEL BIT(11) >+#define AC300_EPHY_SHUTDOWN BIT(0) >+ >+#define AC300_SYS_CONTROL_ENABLE_BITS \ >+ (AC300_EFUSE_CLK_ENABLE | AC300_EPHY_REG_CLK_ENABLE | \ >+ AC300_CLKIN_GATING_ENABLE | AC300_EPHY_RESET_DEASSERT | \ >+ AC300_CHIP_RESET_DEASSERT) >+ >+#define AC300_SYS_IO_VALUE \ >+ (FIELD_PREP(AC300_MDIO_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \ >+ FIELD_PREP(AC300_MII_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \ >+ AC300_CLKIN_PAD_ENABLE | AC300_EPHY_MII_IO_ENABLE) >+ >+struct ac300_ephy_ctl { >+ struct acx00_ephy_control control; >+ struct mdio_device *mdiodev; >+ struct clk *clk; >+ struct mutex lock; /* Serializes power sequencing and state. */ >+ u16 sys_control; >+ u16 ephy_config; >+ phy_interface_t interface; >+ bool package_known; >+ bool internal_dldo; >+ bool powered; >+}; >+ >+static unsigned int >+ac300_ephy_ctl_link_addr(const struct ac300_ephy_ctl *priv) >+{ >+ return priv->mdiodev->addr - AC300_EPHY_CONTROL_ADDR_OFFSET; >+} >+ >+static u16 ac300_ephy_ctl_config(const struct ac300_ephy_ctl *priv) >+{ >+ return priv->ephy_config | >+ (priv->interface == PHY_INTERFACE_MODE_RMII ? >+ AC300_EPHY_RMII_SEL : 0); >+} >+ >+static int ac300_ephy_ctl_power_off_locked(struct ac300_ephy_ctl *priv) >+{ >+ int err; >+ int ret; >+ >+ if (!priv->powered) >+ return 0; >+ >+ ret = mdiodev_write(priv->mdiodev, AC300_EPHY_CONFIG_REG, >+ ac300_ephy_ctl_config(priv) | AC300_EPHY_SHUTDOWN); >+ err = mdiodev_write(priv->mdiodev, AC300_SYS_IO_REG, 0); >+ if (!ret) >+ ret = err; >+ err = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG, >+ priv->package_known && !priv->internal_dldo ? >+ AC300_CHIP_RESET_DEASSERT : 0); >+ if (!ret) >+ ret = err; >+ >+ clk_disable_unprepare(priv->clk); >+ priv->powered = false; >+ >+ return ret; >+} >+ >+static int ac300_ephy_ctl_power_off(struct acx00_ephy_control *control) >+{ >+ struct ac300_ephy_ctl *priv = >+ container_of(control, struct ac300_ephy_ctl, control); >+ int ret; >+ >+ mutex_lock(&priv->lock); >+ ret = ac300_ephy_ctl_power_off_locked(priv); >+ mutex_unlock(&priv->lock); >+ >+ return ret; >+} >+ >+static int >+ac300_ephy_ctl_set_interface(struct acx00_ephy_control *control, >+ phy_interface_t interface) >+{ >+ struct ac300_ephy_ctl *priv = >+ container_of(control, struct ac300_ephy_ctl, control); >+ u16 value; >+ int ret = 0; RCT >+ >+ switch (interface) { >+ case PHY_INTERFACE_MODE_MII: >+ value = 0; >+ break; >+ case PHY_INTERFACE_MODE_RMII: >+ value = AC300_EPHY_RMII_SEL; >+ break; >+ default: >+ return -EINVAL; >+ } >+ >+ mutex_lock(&priv->lock); >+ if (priv->interface == interface) >+ goto out_unlock; >+ >+ if (priv->powered) >+ ret = mdiodev_modify(priv->mdiodev, AC300_EPHY_CONFIG_REG, >+ AC300_EPHY_RMII_SEL, value); value can be moved, same like for previous patch >+ if (!ret) >+ priv->interface = interface; >+ >+out_unlock: >+ mutex_unlock(&priv->lock); >+ >+ return ret; >+} >+ >+static int ac300_ephy_ctl_power_on(struct acx00_ephy_control *control, >+ unsigned int phy_addr) >+{ >+ struct ac300_ephy_ctl *priv = >+ container_of(control, struct ac300_ephy_ctl, control); >+ u8 package_status; >+ u16 reset_value; >+ int sys_control; >+ int ret; >+ >+ if (phy_addr != ac300_ephy_ctl_link_addr(priv)) >+ return -EINVAL; >+ >+ mutex_lock(&priv->lock); >+ if (priv->powered) { >+ ret = 0; >+ goto out_unlock; >+ } >+ >+ ret = clk_prepare_enable(priv->clk); >+ if (ret) >+ goto out_unlock; >+ priv->powered = true; >+ >+ /* Keep the external-supply configuration across subsequent resets. */ >+ reset_value = priv->package_known && !priv->internal_dldo ? >+ AC300_CHIP_RESET_DEASSERT : 0; >+ ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG, reset_value); >+ if (ret) >+ goto err_power_off; >+ >+ /* The manual requires both resets to be released before the clocks. */ >+ ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG, >+ AC300_EPHY_RESET_DEASSERT | >+ AC300_CHIP_RESET_DEASSERT); >+ if (ret) >+ goto err_power_off; >+ >+ /* Retain the vendor clock-enable defaults, including the eFuse clock. */ >+ ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG, >+ priv->sys_control); >+ if (ret) >+ goto err_power_off; >+ >+ sys_control = mdiodev_read(priv->mdiodev, AC300_SYS_CONTROL_REG); >+ if (sys_control < 0) { >+ ret = sys_control; >+ goto err_power_off; >+ } >+ if (sys_control & AC300_MDIO_ERROR) { sys_control is checked for both and both ifs are contradictory if else? >+ ret = mdiodev_write(priv->mdiodev, AC300_SYS_CONTROL_REG, >+ priv->sys_control | AC300_MDIO_ERROR); >+ if (ret) >+ goto err_power_off; >+ >+ sys_control = mdiodev_read(priv->mdiodev, AC300_SYS_CONTROL_REG); >+ if (sys_control < 0) { >+ ret = sys_control; >+ goto err_power_off; >+ } >+ if (sys_control & AC300_MDIO_ERROR) { >+ ret = -EIO; >+ goto err_power_off; >+ } >+ } somehow that doubling instructions with only AC300_MDIO_ERROR changed doesn't appeal to me, could be done with goto saving some lines purely cosmetic nothing critical >+ >+ package_status = FIELD_GET(AC300_PACKAGE_STATUS_MASK, sys_control); >+ if ((~package_status & AC300_PACKAGE_PHY_ADDR_MASK) != >+ ac300_ephy_ctl_link_addr(priv)) { >+ ret = -EINVAL; >+ goto err_power_off; >+ } >+ >+ priv->internal_dldo = package_status & AC300_PACKAGE_POR_INTERNAL_DLDO; >+ priv->package_known = true; >+ ret = mdiodev_modify(priv->mdiodev, AC300_SYS_BIAS1_REG, >+ AC300_INTERNAL_DLDO_ENABLE, >+ priv->internal_dldo ? >+ AC300_INTERNAL_DLDO_ENABLE : 0); >+ if (ret) >+ goto err_power_off; >+ >+ /* Keep the documented default drive level and leave the IRQ disabled. */ >+ ret = mdiodev_write(priv->mdiodev, AC300_SYS_IO_REG, >+ AC300_SYS_IO_VALUE); >+ if (ret) >+ goto err_power_off; >+ >+ fsleep(10000); >+ >+ ret = mdiodev_write(priv->mdiodev, AC300_EPHY_CONFIG_REG, >+ ac300_ephy_ctl_config(priv) | AC300_EPHY_SHUTDOWN); >+ if (ret) >+ goto err_power_off; >+ >+ fsleep(10000); >+ >+ ret = mdiodev_write(priv->mdiodev, AC300_EPHY_CONFIG_REG, >+ ac300_ephy_ctl_config(priv)); >+ if (ret) >+ goto err_power_off; >+ >+ goto out_unlock; >+ >+err_power_off: >+ ac300_ephy_ctl_power_off_locked(priv); >+out_unlock: >+ mutex_unlock(&priv->lock); >+ >+ return ret; >+} >+ >+static int ac300_ephy_ctl_probe(struct mdio_device *mdiodev) >+{ >+ struct device *dev = &mdiodev->dev; >+ struct ac300_ephy_ctl *priv; >+ unsigned long clk_rate; >+ unsigned int phy_addr; >+ u8 calibration; >+ u8 bgs_effuse_code; >+ u8 package_status; >+ int mask_version; >+ int sys_control; >+ int ret; RCT >+ >+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); >+ if (!priv) >+ return -ENOMEM; >+ if (mdiodev->addr < AC300_EPHY_CONTROL_ADDR_OFFSET || >+ mdiodev->addr > AC300_EPHY_CONTROL_ADDR_OFFSET + >+ FIELD_MAX(AC300_PACKAGE_PHY_ADDR_MASK)) >+ return dev_err_probe(dev, -EINVAL, >+ "control address is outside the package range\n"); >+ priv->mdiodev = mdiodev; >+ mutex_init(&priv->lock); >+ >+ ret = devm_regulator_get_enable(dev, "vcc1"); >+ if (ret) >+ return dev_err_probe(dev, ret, >+ "failed to enable VCC1 supply\n"); >+ >+ /* Wait for the power-on reset interval specified by the manual. */ >+ fsleep(10000); >+ >+ priv->clk = devm_clk_get(dev, NULL); >+ if (IS_ERR(priv->clk)) >+ return dev_err_probe(dev, PTR_ERR(priv->clk), >+ "failed to get input clock\n"); >+ >+ ret = devm_clk_rate_exclusive_get(dev, priv->clk); >+ if (ret) >+ return dev_err_probe(dev, ret, "failed to lock clock rate\n"); >+ >+ clk_rate = clk_get_rate(priv->clk); >+ switch (clk_rate) { >+ case 24000000: why do not provide defines also for these? >+ priv->sys_control = AC300_EPHY_CLK_SEL_24_MHZ; >+ break; >+ case 25000000: >+ priv->sys_control = AC300_EPHY_CLK_SEL_25_MHZ; >+ break; >+ case 27000000: >+ priv->sys_control = AC300_EPHY_CLK_SEL_27_MHZ; >+ break; >+ default: >+ return dev_err_probe(dev, -EINVAL, >+ "unsupported input clock rate %lu Hz\n", >+ clk_rate); >+ } >+ priv->sys_control |= AC300_SYS_CONTROL_ENABLE_BITS; >+ >+ ret = nvmem_cell_read_u8(dev, "calibration", &calibration); >+ if (ret) >+ return dev_err_probe(dev, ret, >+ "failed to read calibration data\n"); >+ >+ /* The vendor driver supplies no transfer function beyond this offset. */ >+ bgs_effuse_code = (calibration + AC300_EPHY_BGS_EFFUSE_OFFSET) & >+ FIELD_MAX(AC300_EPHY_BGS_EFFUSE_MASK); >+ priv->ephy_config = >+ FIELD_PREP(AC300_EPHY_BGS_EFFUSE_MASK, bgs_effuse_code); >+ /* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */ >+ >+ priv->control.power_on = ac300_ephy_ctl_power_on; >+ priv->control.power_off = ac300_ephy_ctl_power_off; >+ priv->control.set_interface = ac300_ephy_ctl_set_interface; >+ /* MII is the reset default used until the MAC supplies its interface. */ >+ priv->interface = PHY_INTERFACE_MODE_MII; >+ mdiodev_set_drvdata(mdiodev, &priv->control); >+ >+ /* Validate the package while the control endpoint is known to respond. */ >+ phy_addr = ac300_ephy_ctl_link_addr(priv); >+ ret = ac300_ephy_ctl_power_on(&priv->control, phy_addr); >+ if (ret) >+ return ret; >+ >+ sys_control = mdiodev_read(mdiodev, AC300_SYS_CONTROL_REG); >+ if (sys_control < 0) { >+ ret = sys_control; >+ goto err_disable; >+ } >+ package_status = FIELD_GET(AC300_PACKAGE_STATUS_MASK, sys_control); >+ >+ mask_version = mdiodev_read(mdiodev, AC300_MASK_VERSION_REG); >+ if (mask_version < 0) { >+ ret = mask_version; >+ goto err_disable; >+ } >+ >+ dev_info(dev, "chip version %u, mask version %u, package %#x, %s supplies, PHY %u, %lu Hz clock\n", >+ (unsigned int)FIELD_GET(AC300_CHIP_VERSION_MASK, sys_control), >+ (unsigned int)FIELD_GET(AC300_MASK_VERSION_MASK, mask_version), >+ package_status, >+ package_status & AC300_PACKAGE_POR_INTERNAL_DLDO ? >+ "POR/internal DLDO" : "reset pin/external VDD", >+ ac300_ephy_ctl_link_addr(priv), clk_rate); >+ >+ ret = ac300_ephy_ctl_power_off(&priv->control); >+ if (ret) >+ return dev_err_probe(dev, ret, >+ "failed to quiesce control block\n"); >+ >+ return 0; >+ >+err_disable: >+ ac300_ephy_ctl_power_off(&priv->control); >+ return ret; >+} >+ >+static void ac300_ephy_ctl_remove(struct mdio_device *mdiodev) >+{ >+ struct acx00_ephy_control *control = mdiodev_get_drvdata(mdiodev); >+ >+ control->power_off(control); >+} >+ >+static const struct of_device_id ac300_ephy_ctl_of_match[] = { >+ { .compatible = "x-powers,ac300-ephy-ctl" }, >+ { } >+}; >+MODULE_DEVICE_TABLE(of, ac300_ephy_ctl_of_match); >+ >+static struct mdio_driver ac300_ephy_ctl_driver = { >+ .probe = ac300_ephy_ctl_probe, >+ .remove = ac300_ephy_ctl_remove, >+ .shutdown = ac300_ephy_ctl_remove, >+ .mdiodrv.driver = { >+ .name = "ac300-ephy-ctl", >+ .of_match_table = ac300_ephy_ctl_of_match, >+ }, >+}; >+ >+mdio_module_driver(ac300_ephy_ctl_driver); >+ >+MODULE_AUTHOR("James Hilliard <[email protected]>"); >+MODULE_DESCRIPTION("X-Powers AC300 Ethernet PHY control driver"); >+MODULE_LICENSE("GPL"); >diff --git a/drivers/net/phy/xpowers-acx00.h b/drivers/net/phy/xpowers-acx00.h >index 482ba6faba6e..0af8895e4cae 100644 >--- a/drivers/net/phy/xpowers-acx00.h >+++ b/drivers/net/phy/xpowers-acx00.h >@@ -4,6 +4,8 @@ > > #include <linux/phy.h> > >+#define AC300_EPHY_CONTROL_ADDR_OFFSET 16 >+ > struct acx00_ephy_control { > int (*power_on)(struct acx00_ephy_control *control, > unsigned int phy_addr); > >-- >2.53.0