When a PSE controller driver is loaded as a module, its PI regulators
are registered before any consumer (PHY) acquires the corresponding PSE
control via of_pse_control_get(). The regulator framework's
regulator_late_cleanup then calls pse_pi_is_enabled(), which queries
hardware and sees the PI is enabled. Since no consumer holds it
(use_count == 0), regulator_late_cleanup disables it, killing PoE.
Add an admin_state_synced flag to struct pse_pi that is set when a
consumer first acquires the PSE control and syncs admin_state_enabled
from hardware. In pse_pi_is_enabled(), report unsynchronized PIs as
disabled so regulator_late_cleanup skips them.
This preserves the existing dual-path behavior: software-tracked state
for software-controlled power domains, and hardware queries for
hardware-controlled domains. The admin_state_synced flag is only false
before the first consumer acquisition, which is the exact window where
regulator_late_cleanup could incorrectly disable the PI.
Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
---
drivers/net/pse-pd/pse_core.c | 13 +++++++++++++
include/linux/pse-pd/pse.h | 1 +
2 files changed, 14 insertions(+)
diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c
index 3beaaaeec9e1..566b07c336bf 100644
--- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c
@@ -421,6 +421,18 @@ static int pse_pi_is_enabled(struct regulator_dev *rdev)
id = rdev_get_id(rdev);
mutex_lock(&pcdev->lock);
+
+ /*
+ * Report the PI as disabled until a consumer has acquired it
+ * and synced admin_state_enabled from hardware. This prevents
+ * regulator_late_cleanup from disabling unclaimed PSE PIs
+ * when the PSE controller driver loads as a module.
+ */
+ if (!pcdev->pi[id].admin_state_synced) {
+ ret = 0;
+ goto out;
+ }
+
if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
ret = pcdev->pi[id].admin_state_enabled;
goto out;
@@ -1431,6 +1443,7 @@ pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index,
goto free_psec;
pcdev->pi[index].admin_state_enabled = ret;
+ pcdev->pi[index].admin_state_synced = true;
psec->ps = devm_regulator_get_exclusive(pcdev->dev,
rdev_get_name(pcdev->pi[index].rdev));
if (IS_ERR(psec->ps)) {
diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h
index 4e5696cfade7..b86cce740551 100644
--- a/include/linux/pse-pd/pse.h
+++ b/include/linux/pse-pd/pse.h
@@ -260,6 +260,7 @@ struct pse_pi {
struct device_node *np;
struct regulator_dev *rdev;
bool admin_state_enabled;
+ bool admin_state_synced;
struct pse_power_domain *pw_d;
int prio;
bool isr_pd_detected;
--
2.43.0
On Sun, 29 Mar 2026 18:10:13 +0200 Carlo Szelinsky <github@szelinsky.de> wrote: > When a PSE controller driver is loaded as a module, its PI regulators > are registered before any consumer (PHY) acquires the corresponding PSE > control via of_pse_control_get(). The regulator framework's > regulator_late_cleanup then calls pse_pi_is_enabled(), which queries > hardware and sees the PI is enabled. Since no consumer holds it > (use_count == 0), regulator_late_cleanup disables it, killing PoE. > > Add an admin_state_synced flag to struct pse_pi that is set when a > consumer first acquires the PSE control and syncs admin_state_enabled > from hardware. In pse_pi_is_enabled(), report unsynchronized PIs as > disabled so regulator_late_cleanup skips them. > > This preserves the existing dual-path behavior: software-tracked state > for software-controlled power domains, and hardware queries for > hardware-controlled domains. The admin_state_synced flag is only false > before the first consumer acquisition, which is the exact window where > regulator_late_cleanup could incorrectly disable the PI. Acked-by: Kory Maincent <kory.maincent@bootlin.com> Thank you! -- Köry Maincent, Bootlin Embedded Linux and kernel engineering https://bootlin.com
© 2016 - 2026 Red Hat, Inc.