From: Aleksei Sviridkin <f@lex.la>
To: andrew@lunn.ch, andrew+netdev@lunn.ch, hkallweit1@gmail.com,
linux@armlinux.org.uk, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, horms@kernel.org,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org
Cc: ericwouds@gmail.com, netdev@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
Aleksei Sviridkin <f@lex.la>
Subject: [RFC PATCH net-next v3 6/6] net: mdio: en8811h: add the nested bus
Date: Sat, 12 Sep 2026 16:04:30 +0300 [thread overview]
Message-ID: <20260912130430.2246285-7-f@lex.la> (raw)
In-Reply-To: <20260912130430.2246285-1-f@lex.la>
Registering the bus is what publishes the PHY, so it can only happen
once the MD32 runs its firmware. Giving the PHY a bus of its own rather
than the parent lets the device tree describe it normally, interrupt
included, and leaves the reset line owned by the MCU, where the PHY
cannot assert it. Only the address the MD32 answers on is passed
through.
That split puts the PHY driver and this one on two mutexes: the PHY
holds the child bus lock across a whole paged sequence while each frame
under it takes only the parent, so a chip access holding the parent
alone would land between two of those frames and step on the state the
PHY left mid-sequence - the buckpbus mode and address registers it had
already written. The library therefore holds the child lock across the
whole of its own chip access, and the caller hands it the bus to take or
nothing when there is none. Only the firmware read stays outside: those
files may have to come from a filesystem reached over that very PHY.
Registering the bus can fail on its own, so it becomes a second retry
phase. Where the wait for firmware files never gives up, this one does
once its minute is spent - except on a deferred probe, which nothing but
a retry resolves. A failed reload on resume goes back to that poller,
which restores the chip and not the port.
The sysfs bind attributes are suppressed, because unregistering this bus
while its PHY is attached leaves the consumer holding a phy_device whose
driver is gone, and this driver cannot tell a consumer it does not know
to let go. Module unload needs no such guard, since attaching a PHY
takes a reference on the child bus owner, which is this module. Removing
the parent MDIO bus still reaches this driver's remove and is not
covered; that wants a way for a bus to tell its PHYs' consumers to let
go.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
---
Notes:
Why the registration phase gives up where the wait for firmware does
not: every failed registration repeats a bus creation, a message from
the MDIO core and a pair of uevents. Firmware files are the opposite
case, since the package carrying them can be installed at any time.
Why the deferral is exempt, since the default configuration hides the
reason. fwnode_mdiobus_phy_device_register() runs fwnode_irq_get()'s
deferral through driver_deferred_probe_check_state(), which answers
-ETIMEDOUT once deferred_probe_timeout has expired, or -ENODEV when the
kernel is built without modules; neither escapes, and the PHY is
registered with the bus default that mdiobus_alloc() filled with
PHY_POLL. So a PHY that asks for an interrupt gets one only while its
controller can still defer the registration. With the timeout at its
default of ten seconds this driver, which registers only once firmware
files can be read, is always past that point and never sees a deferral
at all. But the timeout is a kernel parameter: a negative value is
documented as infinite, the timer is then never armed, and every
successful driver registration restarts it while it is pending. Under
either setting the deferral persists until the dependency shows up, and
a driver that stopped retrying after a minute would leave the PHY absent
until reboot, with fw_running still true so neither the poller nor
resume tries again.
Why a failed reload on resume restores the chip and not the port: the
PHY below resumes next, its phy_init_hw() runs against a chip still in
its bootloader and fails, and when the poller later gets the firmware
back nothing runs that resume again.
Both lock helpers in the library take mdio_lock directly rather than
through phylib's phy_lock_mdio_bus(), which keys off a phy_device: this
code runs before one exists for the chip, and the child bus never has
one at all.
drivers/net/mdio/mdio-airoha-en8811h.c | 203 +++++++++++++++++++++--
drivers/net/phy/air_en8811h.c | 7 +-
drivers/net/phy/air_phy_lib.c | 89 +++++++---
drivers/net/phy/air_phy_lib.h | 5 +-
include/linux/mdio/mdio-airoha-en8811h.h | 11 +-
5 files changed, 271 insertions(+), 44 deletions(-)
diff --git a/drivers/net/mdio/mdio-airoha-en8811h.c b/drivers/net/mdio/mdio-airoha-en8811h.c
index b88e3c014d15..7785525c57a6 100644
--- a/drivers/net/mdio/mdio-airoha-en8811h.c
+++ b/drivers/net/mdio/mdio-airoha-en8811h.c
@@ -14,6 +14,8 @@
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
#include <linux/pm.h>
#include <linux/property.h>
#include <linux/workqueue.h>
@@ -28,12 +30,109 @@ struct en8811h_mcu {
struct mdio_device *mdiodev;
struct gpio_desc *reset_gpio;
struct delayed_work fw_poll;
+ struct mii_bus *bus;
unsigned int poll_ms;
unsigned int waited_ms;
u32 fw_version;
bool warned;
+ bool fw_running;
};
+static int en8811h_mcu_read(struct mii_bus *bus, int addr, int regnum)
+{
+ struct en8811h_mcu *mcu = bus->priv;
+
+ if (addr != mcu->mdiodev->addr)
+ return -ENODEV;
+
+ return mdiobus_read_nested(mcu->mdiodev->bus, addr, regnum);
+}
+
+static int en8811h_mcu_write(struct mii_bus *bus, int addr, int regnum, u16 val)
+{
+ struct en8811h_mcu *mcu = bus->priv;
+
+ if (addr != mcu->mdiodev->addr)
+ return -ENODEV;
+
+ return mdiobus_write_nested(mcu->mdiodev->bus, addr, regnum, val);
+}
+
+static int en8811h_mcu_read_c45(struct mii_bus *bus, int addr, int devad,
+ int regnum)
+{
+ struct en8811h_mcu *mcu = bus->priv;
+
+ if (addr != mcu->mdiodev->addr)
+ return -ENODEV;
+
+ return mdiobus_c45_read_nested(mcu->mdiodev->bus, addr, devad, regnum);
+}
+
+static int en8811h_mcu_write_c45(struct mii_bus *bus, int addr, int devad,
+ int regnum, u16 val)
+{
+ struct en8811h_mcu *mcu = bus->priv;
+
+ if (addr != mcu->mdiodev->addr)
+ return -ENODEV;
+
+ return mdiobus_c45_write_nested(mcu->mdiodev->bus, addr, devad, regnum,
+ val);
+}
+
+static int en8811h_mcu_bus_register(struct en8811h_mcu *mcu)
+{
+ struct mii_bus *parent = mcu->mdiodev->bus;
+ struct device *dev = &mcu->mdiodev->dev;
+ struct device_node *np;
+ struct mii_bus *bus;
+ int ret;
+
+ np = of_get_child_by_name(dev->of_node, "mdio");
+ if (!np)
+ return -ENODEV;
+
+ /* Not devm: this is retried, and a devm bus would only be freed at
+ * detach.
+ */
+ bus = mdiobus_alloc();
+ if (!bus) {
+ of_node_put(np);
+ return -ENOMEM;
+ }
+
+ bus->name = "airoha-en8811h";
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
+ bus->priv = mcu;
+ bus->parent = dev;
+ if (parent->read && parent->write) {
+ bus->read = en8811h_mcu_read;
+ bus->write = en8811h_mcu_write;
+ }
+ if (parent->read_c45 && parent->write_c45) {
+ bus->read_c45 = en8811h_mcu_read_c45;
+ bus->write_c45 = en8811h_mcu_write_c45;
+ }
+
+ ret = of_mdiobus_register(bus, np);
+ of_node_put(np);
+ if (!ret && !mdiobus_get_phy(bus, mcu->mdiodev->addr)) {
+ /* An ID read that failed leaves the bus registered and the
+ * PHY absent; of_mdiobus_register() returns 0 either way.
+ */
+ mdiobus_unregister(bus);
+ ret = -ENODEV;
+ }
+ if (ret) {
+ mdiobus_free(bus);
+ return ret;
+ }
+
+ mcu->bus = bus;
+ return 0;
+}
+
static void en8811h_mcu_fw_poll(struct work_struct *work)
{
struct en8811h_mcu *mcu = container_of(to_delayed_work(work),
@@ -41,8 +140,12 @@ static void en8811h_mcu_fw_poll(struct work_struct *work)
struct device *dev = &mcu->mdiodev->dev;
int cached, ret;
- ret = air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version);
- if (ret >= 0) {
+ if (!mcu->fw_running) {
+ ret = air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version,
+ mcu->bus);
+ if (ret < 0)
+ goto retry;
+
dev_dbg(dev, "firmware %08x running after %ums\n",
mcu->fw_version, mcu->waited_ms);
cached = firmware_request_cache(dev, EN8811H_MD32_DM);
@@ -50,19 +153,38 @@ static void en8811h_mcu_fw_poll(struct work_struct *work)
if (ret)
dev_warn(dev, "not cached, resume will read the files off a filesystem: %pe\n",
ERR_PTR(ret));
- return;
+ mcu->fw_running = true;
+ mcu->poll_ms = EN8811H_FW_POLL_MIN_MS;
+ mcu->waited_ms = 0;
+ mcu->warned = false;
}
+ /* Resume re-runs the download, so the bus can already be here. */
+ ret = mcu->bus ? 0 : en8811h_mcu_bus_register(mcu);
+ if (!ret)
+ return;
+
+retry:
if (!mcu->warned && mcu->waited_ms >= EN8811H_FW_WARN_MS) {
- if (ret == -ENOENT)
- dev_warn(dev, "still waiting for %s and %s\n",
- EN8811H_MD32_DM, EN8811H_MD32_DSP);
+ if (!mcu->fw_running)
+ dev_warn(dev, "no firmware after %ums of waiting for %s and %s: %pe\n",
+ mcu->waited_ms, EN8811H_MD32_DM,
+ EN8811H_MD32_DSP, ERR_PTR(ret));
else
- dev_warn(dev, "firmware download keeps failing: %pe\n",
+ dev_warn(dev, "still no PHY at address %d, %ums after the firmware started: %pe\n",
+ mcu->mdiodev->addr, mcu->waited_ms,
ERR_PTR(ret));
mcu->warned = true;
}
+ /* Registering again repeats the MDIO core's message and its uevents,
+ * so this phase gives up - except on a deferral, which only a retry
+ * resolves and which deferred_probe_timeout can make permanent.
+ */
+ if (mcu->fw_running && ret != -EPROBE_DEFER &&
+ mcu->waited_ms >= EN8811H_FW_WARN_MS)
+ return;
+
/* Count the sleep ahead: the first run was immediate. */
mcu->waited_ms += mcu->poll_ms;
queue_delayed_work(system_freezable_wq, &mcu->fw_poll,
@@ -78,7 +200,7 @@ static void en8811h_mcu_reset_unless_running(struct en8811h_mcu *mcu)
u32 assert_us = 0, deassert_us = 0;
int ret;
- ret = air_en8811h_mcu_running(mdiodev);
+ ret = air_en8811h_mcu_running(mdiodev, mcu->bus);
if (ret > 0) {
dev_dbg(dev, "MD32 already running, leaving reset alone\n");
return;
@@ -117,6 +239,8 @@ static int en8811h_mcu_probe(struct mdio_device *mdiodev)
{
struct device *dev = &mdiodev->dev;
struct en8811h_mcu *mcu;
+ struct device_node *np;
+ unsigned int phys = 0;
mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL);
if (!mcu)
@@ -125,6 +249,43 @@ static int en8811h_mcu_probe(struct mdio_device *mdiodev)
mcu->mdiodev = mdiodev;
mdiodev_set_drvdata(mdiodev, mcu);
+ /* Registration needs this only once the firmware runs, but a DT
+ * hole should fail the bind now, not as a work-item error later.
+ */
+ np = of_get_child_by_name(dev->of_node, "mdio");
+ if (!np || !of_device_is_available(np)) {
+ of_node_put(np);
+ return dev_err_probe(dev, -ENODEV,
+ "no usable mdio node describing the PHY\n");
+ }
+
+ /* Only the address this core occupies on the parent bus is passed
+ * through, so a node at any other one will never respond.
+ */
+ for_each_available_child_of_node_scoped(np, child) {
+ u32 addr;
+
+ if (of_property_read_u32(child, "reg", &addr))
+ continue;
+ if (addr != mdiodev->addr) {
+ dev_err(dev, "%pOF: address %u is not answered here, only %u\n",
+ child, addr, mdiodev->addr);
+ of_node_put(np);
+ return -EINVAL;
+ }
+ phys++;
+ }
+ of_node_put(np);
+
+ if (!phys)
+ return dev_err_probe(dev, -ENODEV,
+ "mdio node describes no PHY\n");
+
+ /* A truncated bus name loses the address that makes it unique. */
+ if (strlen(dev_name(dev)) >= MII_BUS_ID_SIZE)
+ return dev_err_probe(dev, -ENAMETOOLONG,
+ "name does not fit an MDIO bus id\n");
+
/* The core claims reset-gpios only for devices flagged as PHYs. */
mcu->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS);
if (IS_ERR(mcu->reset_gpio))
@@ -151,6 +312,10 @@ static void en8811h_mcu_remove(struct mdio_device *mdiodev)
struct en8811h_mcu *mcu = mdiodev_get_drvdata(mdiodev);
cancel_delayed_work_sync(&mcu->fw_poll);
+ if (mcu->bus) {
+ mdiobus_unregister(mcu->bus);
+ mdiobus_free(mcu->bus);
+ }
}
static int en8811h_mcu_resume(struct device *dev)
@@ -158,13 +323,27 @@ static int en8811h_mcu_resume(struct device *dev)
struct en8811h_mcu *mcu = dev_get_drvdata(dev);
int ret;
+ /* Nothing to redo: the poll is armed and thaws with everything else. */
+ if (!mcu->fw_running)
+ return 0;
+
/* Synchronous: the poll's workqueue is freezable and thaws only
* after the resume callbacks have run.
*/
en8811h_mcu_reset_unless_running(mcu);
- ret = air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version);
- if (ret < 0)
- dev_err(dev, "firmware not restored: %pe\n", ERR_PTR(ret));
+ ret = air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version, mcu->bus);
+ if (ret < 0) {
+ dev_err(dev, "firmware not restored, reloading: %pe\n",
+ ERR_PTR(ret));
+ mcu->fw_running = false;
+ mcu->poll_ms = EN8811H_FW_POLL_MIN_MS;
+ mcu->waited_ms = 0;
+ mcu->warned = false;
+ /* The poll may already be pending on a long backoff, and
+ * queue_delayed_work() would leave that timer alone.
+ */
+ mod_delayed_work(system_freezable_wq, &mcu->fw_poll, 0);
+ }
return 0;
}
@@ -184,6 +363,8 @@ static struct mdio_driver en8811h_mcu_driver = {
.name = "airoha-en8811h-mcu",
.of_match_table = en8811h_mcu_of_match,
.pm = pm_sleep_ptr(&en8811h_mcu_pm_ops),
+ /* No safe teardown under an attached PHY. */
+ .suppress_bind_attrs = true,
},
};
diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c
index c9b46cfc854f..940379e2737b 100644
--- a/drivers/net/phy/air_en8811h.c
+++ b/drivers/net/phy/air_en8811h.c
@@ -258,7 +258,7 @@ static int en8811h_wait_mcu_ready(struct phy_device *phydev)
{
int ret;
- ret = air_en8811h_wait_mcu_ready(&phydev->mdio);
+ ret = air_en8811h_wait_mcu_ready(&phydev->mdio, false);
if (ret < 0)
phydev_err(phydev, "MCU not ready: %pe\n", ERR_PTR(ret));
@@ -324,7 +324,7 @@ static int an8811hb_load_file(struct phy_device *phydev, const char *name,
if (ret < 0)
return ret;
- ret = air_fw_write_buf(&phydev->mdio, address, fw);
+ ret = air_fw_write_buf(&phydev->mdio, address, fw, false);
release_firmware(fw);
return ret;
}
@@ -423,7 +423,8 @@ static int en8811h_load_firmware(struct phy_device *phydev)
struct en8811h_priv *priv = phydev->priv;
int ret;
- ret = air_en8811h_fw_download(&phydev->mdio, &priv->firmware_version);
+ ret = air_en8811h_fw_download(&phydev->mdio, &priv->firmware_version,
+ NULL);
if (ret < 0)
phydev_err(phydev, "Load firmware failed: %d\n", ret);
else
diff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c
index c71322f16c21..5902a6ba3643 100644
--- a/drivers/net/phy/air_phy_lib.c
+++ b/drivers/net/phy/air_phy_lib.c
@@ -272,8 +272,31 @@ static int __air_mdio_restore_page(struct mdio_device *mdiodev,
return ret;
}
+static void air_mdiodev_lock(struct mdio_device *mdiodev, bool nested)
+{
+ if (nested)
+ mutex_lock_nested(&mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
+ else
+ mutex_lock(&mdiodev->bus->mdio_lock);
+}
+
+/* The PHY below reaches these registers under this lock through
+ * phy_select_page(); the firmware read must stay outside it.
+ */
+static void air_child_bus_lock(struct mii_bus *child_bus)
+{
+ if (child_bus)
+ mutex_lock(&child_bus->mdio_lock);
+}
+
+static void air_child_bus_unlock(struct mii_bus *child_bus)
+{
+ if (child_bus)
+ mutex_unlock(&child_bus->mdio_lock);
+}
+
int air_fw_write_buf(struct mdio_device *mdiodev, u32 address,
- const struct firmware *fw)
+ const struct firmware *fw, bool nested)
{
size_t chunk, done = 0;
int saved_page, ret;
@@ -287,7 +310,7 @@ int air_fw_write_buf(struct mdio_device *mdiodev, u32 address,
while (done < fw->size) {
chunk = min_t(size_t, fw->size - done, AIR_FW_CHUNK_BYTES);
- mutex_lock(&mdiodev->bus->mdio_lock);
+ air_mdiodev_lock(mdiodev, nested);
saved_page = __air_mdio_select_page(mdiodev,
AIR_PHY_PAGE_EXTENDED_4);
@@ -313,11 +336,12 @@ int air_fw_write_buf(struct mdio_device *mdiodev, u32 address,
EXPORT_SYMBOL_GPL(air_fw_write_buf);
static int air_mdio_buckpbus_reg_read(struct mdio_device *mdiodev,
- u32 pbus_address, u32 *pbus_data)
+ u32 pbus_address, u32 *pbus_data,
+ bool nested)
{
int saved_page, ret;
- mutex_lock(&mdiodev->bus->mdio_lock);
+ air_mdiodev_lock(mdiodev, nested);
saved_page = __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4);
if (saved_page < 0) {
@@ -333,11 +357,12 @@ static int air_mdio_buckpbus_reg_read(struct mdio_device *mdiodev,
}
static int air_mdio_buckpbus_reg_write(struct mdio_device *mdiodev,
- u32 pbus_address, u32 pbus_data)
+ u32 pbus_address, u32 pbus_data,
+ bool nested)
{
int saved_page, ret;
- mutex_lock(&mdiodev->bus->mdio_lock);
+ air_mdiodev_lock(mdiodev, nested);
saved_page = __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4);
if (saved_page < 0) {
@@ -354,11 +379,12 @@ static int air_mdio_buckpbus_reg_write(struct mdio_device *mdiodev,
}
static int air_mdio_buckpbus_reg_modify(struct mdio_device *mdiodev,
- u32 pbus_address, u32 mask, u32 set)
+ u32 pbus_address, u32 mask, u32 set,
+ bool nested)
{
int saved_page, ret;
- mutex_lock(&mdiodev->bus->mdio_lock);
+ air_mdiodev_lock(mdiodev, nested);
saved_page = __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4);
if (saved_page < 0) {
@@ -399,20 +425,25 @@ static int __air_mmd_read(struct mdio_device *mdiodev, u16 devad, u16 regnum)
return __mdiobus_read(bus, addr, MII_MMD_DATA);
}
-static int air_mmd_status_read(struct mdio_device *mdiodev)
+static int air_mmd_status_read(struct mdio_device *mdiodev, bool nested)
{
int ret;
- mutex_lock(&mdiodev->bus->mdio_lock);
+ air_mdiodev_lock(mdiodev, nested);
ret = __air_mmd_read(mdiodev, MDIO_MMD_VEND1, EN8811H_PHY_FW_STATUS);
mutex_unlock(&mdiodev->bus->mdio_lock);
return ret;
}
-int air_en8811h_mcu_running(struct mdio_device *mdiodev)
+int air_en8811h_mcu_running(struct mdio_device *mdiodev,
+ struct mii_bus *child_bus)
{
- int ret = air_mmd_status_read(mdiodev);
+ int ret;
+
+ air_child_bus_lock(child_bus);
+ ret = air_mmd_status_read(mdiodev, !!child_bus);
+ air_child_bus_unlock(child_bus);
if (ret < 0)
return ret;
@@ -421,12 +452,12 @@ int air_en8811h_mcu_running(struct mdio_device *mdiodev)
}
EXPORT_SYMBOL_GPL(air_en8811h_mcu_running);
-int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev)
+int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev, bool nested)
{
int ret, reg_value;
ret = air_mdio_buckpbus_reg_write(mdiodev, EN8811H_FW_CTRL_1,
- EN8811H_FW_CTRL_1_FINISH);
+ EN8811H_FW_CTRL_1_FINISH, nested);
if (ret)
return ret;
@@ -436,7 +467,7 @@ int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev)
ret = read_poll_timeout(air_mmd_status_read, reg_value,
reg_value < 0 ||
reg_value == EN8811H_PHY_READY,
- 20000, 7500000, true, mdiodev);
+ 20000, 7500000, true, mdiodev, nested);
if (reg_value < 0)
return reg_value;
if (ret) {
@@ -448,19 +479,23 @@ int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev)
}
EXPORT_SYMBOL_GPL(air_en8811h_wait_mcu_ready);
-int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version)
+int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version,
+ struct mii_bus *child_bus)
{
struct device *dev = &mdiodev->dev;
const struct firmware *fw1, *fw2;
+ bool nested = !!child_bus;
int ret;
- ret = air_en8811h_mcu_running(mdiodev);
+ ret = air_en8811h_mcu_running(mdiodev, child_bus);
if (ret < 0)
return ret;
if (ret) {
+ air_child_bus_lock(child_bus);
ret = air_mdio_buckpbus_reg_read(mdiodev, EN8811H_FW_VERSION,
- fw_version);
+ fw_version, nested);
+ air_child_bus_unlock(child_bus);
if (ret < 0)
return ret;
@@ -475,38 +510,42 @@ int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version)
if (ret < 0)
goto air_fw_download_rel1;
+ air_child_bus_lock(child_bus);
+
ret = air_mdio_buckpbus_reg_write(mdiodev, EN8811H_FW_CTRL_1,
- EN8811H_FW_CTRL_1_START);
+ EN8811H_FW_CTRL_1_START, nested);
if (ret < 0)
goto air_fw_download_out;
ret = air_mdio_buckpbus_reg_modify(mdiodev, EN8811H_FW_CTRL_2,
EN8811H_FW_CTRL_2_LOADING,
- EN8811H_FW_CTRL_2_LOADING);
+ EN8811H_FW_CTRL_2_LOADING, nested);
if (ret < 0)
goto air_fw_download_out;
- ret = air_fw_write_buf(mdiodev, AIR_FW_ADDR_DM, fw1);
+ ret = air_fw_write_buf(mdiodev, AIR_FW_ADDR_DM, fw1, nested);
if (ret < 0)
goto air_fw_download_out;
- ret = air_fw_write_buf(mdiodev, AIR_FW_ADDR_DSP, fw2);
+ ret = air_fw_write_buf(mdiodev, AIR_FW_ADDR_DSP, fw2, nested);
if (ret < 0)
goto air_fw_download_out;
ret = air_mdio_buckpbus_reg_modify(mdiodev, EN8811H_FW_CTRL_2,
- EN8811H_FW_CTRL_2_LOADING, 0);
+ EN8811H_FW_CTRL_2_LOADING, 0,
+ nested);
if (ret < 0)
goto air_fw_download_out;
- ret = air_en8811h_wait_mcu_ready(mdiodev);
+ ret = air_en8811h_wait_mcu_ready(mdiodev, nested);
if (ret < 0)
goto air_fw_download_out;
ret = air_mdio_buckpbus_reg_read(mdiodev, EN8811H_FW_VERSION,
- fw_version);
+ fw_version, nested);
air_fw_download_out:
+ air_child_bus_unlock(child_bus);
release_firmware(fw2);
air_fw_download_rel1:
diff --git a/drivers/net/phy/air_phy_lib.h b/drivers/net/phy/air_phy_lib.h
index 764f832a02a7..5f6f09446008 100644
--- a/drivers/net/phy/air_phy_lib.h
+++ b/drivers/net/phy/air_phy_lib.h
@@ -60,8 +60,9 @@ int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address,
int air_phy_read_page(struct phy_device *phydev);
int air_phy_write_page(struct phy_device *phydev, int page);
+/* nested: the caller already holds the child bus this chip publishes. */
int air_fw_write_buf(struct mdio_device *mdiodev, u32 address,
- const struct firmware *fw);
-int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev);
+ const struct firmware *fw, bool nested);
+int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev, bool nested);
#endif /* __AIR_PHY_LIB_H */
diff --git a/include/linux/mdio/mdio-airoha-en8811h.h b/include/linux/mdio/mdio-airoha-en8811h.h
index 8a073a1b7ee6..383ff4a7f6a1 100644
--- a/include/linux/mdio/mdio-airoha-en8811h.h
+++ b/include/linux/mdio/mdio-airoha-en8811h.h
@@ -10,15 +10,20 @@
#include <linux/types.h>
struct mdio_device;
+struct mii_bus;
#define EN8811H_MD32_DM "airoha/EthMD32.dm.bin"
#define EN8811H_MD32_DSP "airoha/EthMD32.DSP.bin"
-/* Returns 1 when the firmware runs, 0 when the MD32 is still in its
+/* child_bus is the bus this chip publishes below mdiodev, or NULL.
+ *
+ * Returns 1 when the firmware runs, 0 when the MD32 is still in its
* bootloader, and negative on a failed status read.
*/
-int air_en8811h_mcu_running(struct mdio_device *mdiodev);
+int air_en8811h_mcu_running(struct mdio_device *mdiodev,
+ struct mii_bus *child_bus);
/* Returns 1 when firmware was already running and was left in place. */
-int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version);
+int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version,
+ struct mii_bus *child_bus);
#endif /* __LINUX_MDIO_AIROHA_EN8811H_H */
--
2.53.0
prev parent reply other threads:[~2026-09-12 13:04 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-12 13:04 [RFC PATCH net-next v3 0/6] net: mdio: an MDIO device driver for the Airoha EN8811H Aleksei Sviridkin
2026-09-12 13:04 ` [RFC PATCH net-next v3 1/6] dt-bindings: net: add Airoha EN8811H PHY MCU Aleksei Sviridkin
2026-09-12 13:04 ` [RFC PATCH net-next v3 2/6] net: phy: air: type the buckpbus core on the mdio device Aleksei Sviridkin
2026-09-12 13:04 ` [RFC PATCH net-next v3 3/6] net: phy: air: move the EN8811H firmware download into the library Aleksei Sviridkin
2026-09-12 13:04 ` [RFC PATCH net-next v3 4/6] net: phy: air: skip the download when the MD32 is already running Aleksei Sviridkin
2026-09-12 13:04 ` [RFC PATCH net-next v3 5/6] net: mdio: add Airoha EN8811H MDIO device driver Aleksei Sviridkin
2026-09-12 13:04 ` Aleksei Sviridkin [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260912130430.2246285-7-f@lex.la \
--to=f@lex.la \
--cc=andrew+netdev@lunn.ch \
--cc=andrew@lunn.ch \
--cc=conor+dt@kernel.org \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@google.com \
--cc=ericwouds@gmail.com \
--cc=hkallweit1@gmail.com \
--cc=horms@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=robh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox