From: Sascha Hauer <s.hauer@pengutronix.de>
To: Barebox List <barebox@lists.infradead.org>
Subject: [PATCH] net: phy: defer PHY reset settle time off the boot path
Date: Fri, 10 Jul 2026 12:26:38 +0200 [thread overview]
Message-ID: <20260710102638.1640479-1-s.hauer@pengutronix.de> (raw)
of_mdiobus_register() reset each PHY by asserting its reset GPIO, waiting
the full reset-assert and reset-deassert delays inline, and only then
registering the PHY (which reads its ID over MDIO). Waiting for the
reset deassert adds to the startup time, so do it asynchronously
Split the reset so the (usually short) assert pulse is still done inline,
but the potentially long deassert settle time is not waited for at reset
time. of_mdiobus_reset_phy() now returns the point in time at which the
PHY becomes usable and the PHY is queued on a per-bus deferred list
instead of being registered immediately. The actual registration, and
with it the wait for the remaining settle time, happens on first access
to the bus via mdiobus_flush_deferred(), hooked into mdiobus_scan() and
eth_open_all(). This lets the settle time elapse in the background while
the rest of boot proceeds; by the time the PHY is first accessed the wait
is usually already over.
PHYs without a reset GPIO have nothing to wait for and are registered
immediately as before. The PHY ID is still read for real at registration
time, so vendor driver binding and absence detection are unchanged.
Assisted-by: Claude Opus 4.8
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/net/phy/mdio_bus.c | 85 ++++++++++++++++++++++++++++++++++----
include/linux/phy.h | 4 ++
net/eth.c | 9 ++++
3 files changed, 91 insertions(+), 7 deletions(-)
diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
index 1381f70f1c..228f59844f 100644
--- a/drivers/net/phy/mdio_bus.c
+++ b/drivers/net/phy/mdio_bus.c
@@ -18,6 +18,7 @@
#include <gpio.h>
#include <clock.h>
#include <net.h>
+#include <sched.h>
#include <errno.h>
#include <linux/mdio.h>
#include <linux/phy.h>
@@ -30,6 +31,18 @@
DEFINE_DEV_CLASS(mii_class, "mii");
+/*
+ * A PHY whose reset GPIO has been deasserted but which has not been probed
+ * yet. The reset settle time elapses in the background; the PHY is only
+ * registered (and its ID read over MDIO) once the bus is first accessed.
+ */
+struct mdio_deferred_phy {
+ struct list_head list;
+ struct device_node *child;
+ u32 addr;
+ u64 settle_deadline; /* get_time_ns() value, 0 if no reset GPIO */
+};
+
static struct phy_device *mdio_device_create(struct mii_bus *bus, int addr)
{
struct phy_device *phydev;
@@ -195,9 +208,14 @@ static bool of_mdiobus_child_is_phy(struct device_node *np)
* Reset the PHY, based on DT info
*
* If np is a phy node, and the phy node contains a reset-gpios property
- * then reset the phy.
+ * then reset the phy. The (usually short) assert pulse is done inline,
+ * but the potentially long deassert settle time is not waited for here:
+ * instead the time at which the PHY becomes usable is returned so the
+ * caller can let it elapse in the background and only wait for the
+ * remainder right before the first access. Returns 0 if there is no
+ * reset GPIO.
*/
-static void of_mdiobus_reset_phy(struct mii_bus *bus, struct device_node *np)
+static u64 of_mdiobus_reset_phy(struct mii_bus *bus, struct device_node *np)
{
enum of_gpio_flags of_flags;
u32 reset_deassert_delay;
@@ -208,7 +226,7 @@ static void of_mdiobus_reset_phy(struct mii_bus *bus, struct device_node *np)
gpio = of_get_named_gpio_flags(np, "reset-gpios", 0, &of_flags);
if (!gpio_is_valid(gpio))
- return;
+ return 0;
flags = GPIOF_OUT_INIT_INACTIVE;
if (of_flags & OF_GPIO_ACTIVE_LOW)
@@ -218,7 +236,7 @@ static void of_mdiobus_reset_phy(struct mii_bus *bus, struct device_node *np)
if (ret) {
dev_err(&bus->dev, "failed to request reset gpio for: %s\n",
np->name);
- return;
+ return 0;
}
reset_assert_delay = DEFAULT_GPIO_RESET_ASSERT;
@@ -232,7 +250,8 @@ static void of_mdiobus_reset_phy(struct mii_bus *bus, struct device_node *np)
gpio_set_active(gpio, 1);
udelay(reset_assert_delay);
gpio_set_active(gpio, 0);
- udelay(reset_deassert_delay);
+
+ return get_time_ns() + (u64)reset_deassert_delay * 1000;
}
/**
@@ -267,8 +286,28 @@ static int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
of_pinctrl_select_state_default(child);
if (of_mdiobus_child_is_phy(child)) {
- of_mdiobus_reset_phy(mdio, child);
- of_mdiobus_register_phy(mdio, child, addr);
+ struct mdio_deferred_phy *dp;
+ u64 settle_deadline;
+
+ /* Assert/deassert the reset now. */
+ settle_deadline = of_mdiobus_reset_phy(mdio, child);
+ if (!settle_deadline) {
+ /* No reset GPIO, nothing to wait for. */
+ of_mdiobus_register_phy(mdio, child, addr);
+ continue;
+ }
+
+ /*
+ * Defer the settle wait and the actual registration
+ * (which reads the PHY ID over MDIO) until the bus is
+ * first accessed. This keeps the reset settle time off
+ * the boot path.
+ */
+ dp = xzalloc(sizeof(*dp));
+ dp->child = child;
+ dp->addr = addr;
+ dp->settle_deadline = settle_deadline;
+ list_add_tail(&dp->list, &mdio->deferred_phys);
} else {
of_mdiobus_register_device(mdio, child, addr);
}
@@ -277,6 +316,33 @@ static int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
return 0;
}
+/**
+ * mdiobus_flush_deferred - register PHYs whose reset was deferred
+ * @bus: target mii_bus
+ *
+ * PHYs described in the device tree have their reset asserted during
+ * of_mdiobus_register(), but their registration (and the MDIO ID read that
+ * comes with it) is deferred until the bus is actually used. Wait for any
+ * outstanding reset settle time to elapse and register them now.
+ */
+void mdiobus_flush_deferred(struct mii_bus *bus)
+{
+ struct mdio_deferred_phy *dp, *tmp;
+
+ list_for_each_entry_safe(dp, tmp, &bus->deferred_phys, list) {
+ /* remove before registering so a reentrant flush is a no-op */
+ list_del(&dp->list);
+
+ /* usually already elapsed, so this does not wait at all */
+ while (!is_timeout_non_interruptible(dp->settle_deadline, 0))
+ resched();
+
+ of_mdiobus_register_phy(bus, dp->child, dp->addr);
+ free(dp);
+ }
+}
+EXPORT_SYMBOL(mdiobus_flush_deferred);
+
/**
* mdiobus_register - bring up all the PHYs on a given bus and attach them to bus
* @bus: target mii_bus
@@ -295,6 +361,8 @@ int mdiobus_register(struct mii_bus *bus)
NULL == bus->write)
return -EINVAL;
+ INIT_LIST_HEAD(&bus->deferred_phys);
+
bus->dev.priv = bus;
bus->dev.id = DEVICE_ID_DYNAMIC;
dev_set_name(&bus->dev, "miibus");
@@ -350,6 +418,9 @@ struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr)
{
struct phy_device *phydev;
+ /* deferred PHYs must be registered before we can find them */
+ mdiobus_flush_deferred(bus);
+
if (bus->phy_map[addr])
return bus->phy_map[addr];
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 26b1136b8a..5685ec5eb1 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -244,6 +244,9 @@ struct mii_bus {
bool is_multiplexed;
struct slice slice;
+
+ /* PHYs whose reset was asserted but which are not registered yet */
+ struct list_head deferred_phys;
};
#define to_mii_bus(d) container_of(d, struct mii_bus, dev)
@@ -269,6 +272,7 @@ struct mii_bus *of_mdio_find_bus(struct device_node *mdio_bus_np);
int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum);
int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val);
+void mdiobus_flush_deferred(struct mii_bus *bus);
/* phy_device: An instance of a PHY
*
diff --git a/net/eth.c b/net/eth.c
index 37f55e0b36..ee90ad5df6 100644
--- a/net/eth.c
+++ b/net/eth.c
@@ -575,6 +575,15 @@ EXPORT_SYMBOL(of_find_eth_device_by_node);
void eth_open_all(void)
{
struct eth_device *edev;
+ struct mii_bus *mii;
+
+ /*
+ * PHY registration is deferred until an MDIO bus is first used. Flush
+ * any pending registrations now so the PHY devices are present when
+ * entering interactive mode, even for buses no netdev connects to.
+ */
+ for_each_mii_bus(mii)
+ mdiobus_flush_deferred(mii);
for_each_netdev(edev) {
if (edev->global_mode == ETH_MODE_DISABLED)
--
2.47.3
reply other threads:[~2026-07-10 10:28 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
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=20260710102638.1640479-1-s.hauer@pengutronix.de \
--to=s.hauer@pengutronix.de \
--cc=barebox@lists.infradead.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