Add per-PI "delivering" and "enabled" LED triggers to the PSE core
subsystem. Instead of polling from LED-specific code, LED state is
updated from the shared pse_handle_events() function whenever the
IRQ or poll path detects a state change.
This ensures LED triggers react to the same events that drive netlink
notifications and regulator callbacks, without duplicating the polling
logic.
Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
---
drivers/net/pse-pd/pse_core.c | 119 +++++++++++++++++++++++++++++++++-
include/linux/pse-pd/pse.h | 22 +++++++
2 files changed, 140 insertions(+), 1 deletion(-)
diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c
index 3202c19ef602..0e96c22493a4 100644
--- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c
@@ -12,6 +12,7 @@
#include <linux/phy.h>
#include <linux/pse-pd/pse.h>
#include <linux/regulator/driver.h>
+#include <linux/leds.h>
#include <linux/regulator/machine.h>
#include <linux/rtnetlink.h>
#include <net/net_trackers.h>
@@ -1037,6 +1038,107 @@ static void pse_send_ntf_worker(struct work_struct *work)
}
}
+#if IS_ENABLED(CONFIG_LEDS_TRIGGERS)
+/**
+ * pse_led_update - Update LED triggers for a PI based on current state
+ * @pcdev: PSE controller device
+ * @id: PI index
+ *
+ * Queries the current power status and admin state of the PI and
+ * fires LED trigger events on state changes. Called from the
+ * notification path whenever any event occurs for this PI.
+ *
+ * Must be called with pcdev->lock held.
+ */
+static void pse_led_update(struct pse_controller_dev *pcdev, int id)
+{
+ struct pse_pi_led_triggers *trigs;
+ struct pse_pw_status pw_status = {};
+ struct pse_admin_state admin_state = {};
+ bool delivering, enabled;
+
+ if (!pcdev->pi_led_trigs)
+ return;
+
+ trigs = &pcdev->pi_led_trigs[id];
+ if (!trigs->delivering.name)
+ return;
+
+ if (pcdev->ops->pi_get_pw_status(pcdev, id, &pw_status))
+ return;
+ if (pcdev->ops->pi_get_admin_state(pcdev, id, &admin_state))
+ return;
+
+ delivering = pw_status.c33_pw_status ==
+ ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+ enabled = admin_state.c33_admin_state ==
+ ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
+
+ if (trigs->last_delivering != delivering) {
+ trigs->last_delivering = delivering;
+ led_trigger_event(&trigs->delivering,
+ delivering ? LED_FULL : LED_OFF);
+ }
+
+ if (trigs->last_enabled != enabled) {
+ trigs->last_enabled = enabled;
+ led_trigger_event(&trigs->enabled,
+ enabled ? LED_FULL : LED_OFF);
+ }
+}
+
+static int pse_led_triggers_register(struct pse_controller_dev *pcdev)
+{
+ struct device *dev = pcdev->dev;
+ const char *node_name;
+ int i, ret;
+
+ node_name = dev->of_node ? dev->of_node->name : dev_name(dev);
+
+ pcdev->pi_led_trigs = devm_kcalloc(dev, pcdev->nr_lines,
+ sizeof(*pcdev->pi_led_trigs),
+ GFP_KERNEL);
+ if (!pcdev->pi_led_trigs)
+ return -ENOMEM;
+
+ for (i = 0; i < pcdev->nr_lines; i++) {
+ struct pse_pi_led_triggers *trigs = &pcdev->pi_led_trigs[i];
+
+ /* Skip PIs not described in device tree */
+ if (!pcdev->no_of_pse_pi && !pcdev->pi[i].np)
+ continue;
+
+ trigs->delivering.name = devm_kasprintf(dev, GFP_KERNEL,
+ "%s:port%d:delivering",
+ node_name, i);
+ if (!trigs->delivering.name)
+ return -ENOMEM;
+
+ ret = devm_led_trigger_register(dev, &trigs->delivering);
+ if (ret)
+ return ret;
+
+ trigs->enabled.name = devm_kasprintf(dev, GFP_KERNEL,
+ "%s:port%d:enabled",
+ node_name, i);
+ if (!trigs->enabled.name)
+ return -ENOMEM;
+
+ ret = devm_led_trigger_register(dev, &trigs->enabled);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+#else
+static inline void pse_led_update(struct pse_controller_dev *pcdev, int id) {}
+static int pse_led_triggers_register(struct pse_controller_dev *pcdev)
+{
+ return 0;
+}
+#endif /* CONFIG_LEDS_TRIGGERS */
+
/**
* pse_controller_register - register a PSE controller device
* @pcdev: a pointer to the initialized PSE controller device
@@ -1111,6 +1213,14 @@ int pse_controller_register(struct pse_controller_dev *pcdev)
list_add(&pcdev->list, &pse_controller_list);
mutex_unlock(&pse_list_mutex);
+ ret = pse_led_triggers_register(pcdev);
+ if (ret) {
+ dev_warn(pcdev->dev, "Failed to register LED triggers: %d\n",
+ ret);
+ /* Ensure pse_led_update() is a no-op on partial failure */
+ pcdev->pi_led_trigs = NULL;
+ }
+
return 0;
}
EXPORT_SYMBOL_GPL(pse_controller_register);
@@ -1266,7 +1376,14 @@ static void pse_handle_events(struct pse_controller_dev *pcdev,
struct pse_ntf ntf = {};
int ret;
- /* Do nothing PI not described */
+ /* Update LEDs for described PIs regardless of consumer state.
+ * LED triggers are registered at controller init, before any
+ * PHY claims a PSE control, so rdev may still be NULL here.
+ */
+ if (pcdev->no_of_pse_pi || pcdev->pi[i].np)
+ pse_led_update(pcdev, i);
+
+ /* Skip regulator/netlink path for PIs without consumers */
if (!pcdev->pi[i].rdev)
continue;
diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h
index 44d5d10e239d..0058636a6299 100644
--- a/include/linux/pse-pd/pse.h
+++ b/include/linux/pse-pd/pse.h
@@ -10,6 +10,7 @@
#include <linux/kfifo.h>
#include <uapi/linux/ethtool.h>
#include <uapi/linux/ethtool_netlink_generated.h>
+#include <linux/leds.h>
#include <linux/regulator/driver.h>
/* Maximum current in uA according to IEEE 802.3-2022 Table 145-1 */
@@ -266,6 +267,23 @@ struct pse_pi {
int pw_allocated_mW;
};
+#if IS_ENABLED(CONFIG_LEDS_TRIGGERS)
+/**
+ * struct pse_pi_led_triggers - LED trigger state for a PSE PI
+ *
+ * @delivering: LED trigger for power delivering state
+ * @enabled: LED trigger for admin enabled state
+ * @last_delivering: cached delivering state for change detection
+ * @last_enabled: cached enabled state for change detection
+ */
+struct pse_pi_led_triggers {
+ struct led_trigger delivering;
+ struct led_trigger enabled;
+ bool last_delivering;
+ bool last_enabled;
+};
+#endif
+
/**
* struct pse_ntf - PSE notification element
*
@@ -303,6 +321,7 @@ struct pse_ntf {
* @ntf_work: workqueue for PSE notification management
* @ntf_fifo: PSE notifications FIFO
* @ntf_fifo_lock: protect @ntf_fifo writer
+ * @pi_led_trigs: per-PI LED trigger state array
*/
struct pse_controller_dev {
const struct pse_controller_ops *ops;
@@ -327,6 +346,9 @@ struct pse_controller_dev {
struct work_struct ntf_work;
DECLARE_KFIFO_PTR(ntf_fifo, struct pse_ntf);
spinlock_t ntf_fifo_lock; /* Protect @ntf_fifo writer */
+#if IS_ENABLED(CONFIG_LEDS_TRIGGERS)
+ struct pse_pi_led_triggers *pi_led_trigs;
+#endif
};
/**
--
2.43.0
Hi Carlo,
kernel test robot noticed the following build errors:
[auto build test ERROR on robh/for-next]
[also build test ERROR on net-next/main net/main linus/master v7.0-rc5 next-20260324]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Carlo-Szelinsky/dt-bindings-net-pse-pd-add-poll-interval-ms-property/20260325-040935
base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
patch link: https://lore.kernel.org/r/20260323201225.1836561-4-github%40szelinsky.de
patch subject: [PATCH v2 3/3] net: pse-pd: add LED trigger support via notification path
config: sh-randconfig-002-20260325 (https://download.01.org/0day-ci/archive/20260325/202603251250.cuMCk5Yv-lkp@intel.com/config)
compiler: sh4-linux-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260325/202603251250.cuMCk5Yv-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603251250.cuMCk5Yv-lkp@intel.com/
All errors (new ones prefixed by >>):
drivers/net/pse-pd/pse_core.c: In function 'pse_controller_register':
>> drivers/net/pse-pd/pse_core.c:1221:22: error: 'struct pse_controller_dev' has no member named 'pi_led_trigs'
1221 | pcdev->pi_led_trigs = NULL;
| ^~
vim +1221 drivers/net/pse-pd/pse_core.c
1141
1142 /**
1143 * pse_controller_register - register a PSE controller device
1144 * @pcdev: a pointer to the initialized PSE controller device
1145 *
1146 * Return: 0 on success and failure value on error
1147 */
1148 int pse_controller_register(struct pse_controller_dev *pcdev)
1149 {
1150 size_t reg_name_len;
1151 int ret, i;
1152
1153 mutex_init(&pcdev->lock);
1154 INIT_LIST_HEAD(&pcdev->pse_control_head);
1155 spin_lock_init(&pcdev->ntf_fifo_lock);
1156 ret = kfifo_alloc(&pcdev->ntf_fifo, pcdev->nr_lines, GFP_KERNEL);
1157 if (ret) {
1158 dev_err(pcdev->dev, "failed to allocate kfifo notifications\n");
1159 return ret;
1160 }
1161 INIT_WORK(&pcdev->ntf_work, pse_send_ntf_worker);
1162
1163 if (!pcdev->nr_lines)
1164 pcdev->nr_lines = 1;
1165
1166 if (!pcdev->ops->pi_get_admin_state ||
1167 !pcdev->ops->pi_get_pw_status) {
1168 dev_err(pcdev->dev,
1169 "Mandatory status report callbacks are missing");
1170 return -EINVAL;
1171 }
1172
1173 ret = of_load_pse_pis(pcdev);
1174 if (ret)
1175 return ret;
1176
1177 if (pcdev->ops->setup_pi_matrix) {
1178 ret = pcdev->ops->setup_pi_matrix(pcdev);
1179 if (ret)
1180 return ret;
1181 }
1182
1183 /* Each regulator name len is pcdev dev name + 7 char +
1184 * int max digit number (10) + 1
1185 */
1186 reg_name_len = strlen(dev_name(pcdev->dev)) + 18;
1187
1188 /* Register PI regulators */
1189 for (i = 0; i < pcdev->nr_lines; i++) {
1190 char *reg_name;
1191
1192 /* Do not register regulator for PIs not described */
1193 if (!pcdev->no_of_pse_pi && !pcdev->pi[i].np)
1194 continue;
1195
1196 reg_name = devm_kzalloc(pcdev->dev, reg_name_len, GFP_KERNEL);
1197 if (!reg_name)
1198 return -ENOMEM;
1199
1200 snprintf(reg_name, reg_name_len, "pse-%s_pi%d",
1201 dev_name(pcdev->dev), i);
1202
1203 ret = devm_pse_pi_regulator_register(pcdev, reg_name, i);
1204 if (ret)
1205 return ret;
1206 }
1207
1208 ret = pse_register_pw_ds(pcdev);
1209 if (ret)
1210 return ret;
1211
1212 mutex_lock(&pse_list_mutex);
1213 list_add(&pcdev->list, &pse_controller_list);
1214 mutex_unlock(&pse_list_mutex);
1215
1216 ret = pse_led_triggers_register(pcdev);
1217 if (ret) {
1218 dev_warn(pcdev->dev, "Failed to register LED triggers: %d\n",
1219 ret);
1220 /* Ensure pse_led_update() is a no-op on partial failure */
> 1221 pcdev->pi_led_trigs = NULL;
1222 }
1223
1224 return 0;
1225 }
1226 EXPORT_SYMBOL_GPL(pse_controller_register);
1227
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
Hi Carlo,
kernel test robot noticed the following build errors:
[auto build test ERROR on robh/for-next]
[also build test ERROR on net-next/main net/main linus/master v7.0-rc5 next-20260324]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Carlo-Szelinsky/dt-bindings-net-pse-pd-add-poll-interval-ms-property/20260325-040935
base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
patch link: https://lore.kernel.org/r/20260323201225.1836561-4-github%40szelinsky.de
patch subject: [PATCH v2 3/3] net: pse-pd: add LED trigger support via notification path
config: hexagon-randconfig-001-20260325 (https://download.01.org/0day-ci/archive/20260325/202603251254.o5PqMBRU-lkp@intel.com/config)
compiler: clang version 19.1.7 (https://github.com/llvm/llvm-project cd708029e0b2869e80abe31ddb175f7c35361f90)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260325/202603251254.o5PqMBRU-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603251254.o5PqMBRU-lkp@intel.com/
All errors (new ones prefixed by >>):
>> drivers/net/pse-pd/pse_core.c:1221:10: error: no member named 'pi_led_trigs' in 'struct pse_controller_dev'
1221 | pcdev->pi_led_trigs = NULL;
| ~~~~~ ^
1 error generated.
vim +1221 drivers/net/pse-pd/pse_core.c
1141
1142 /**
1143 * pse_controller_register - register a PSE controller device
1144 * @pcdev: a pointer to the initialized PSE controller device
1145 *
1146 * Return: 0 on success and failure value on error
1147 */
1148 int pse_controller_register(struct pse_controller_dev *pcdev)
1149 {
1150 size_t reg_name_len;
1151 int ret, i;
1152
1153 mutex_init(&pcdev->lock);
1154 INIT_LIST_HEAD(&pcdev->pse_control_head);
1155 spin_lock_init(&pcdev->ntf_fifo_lock);
1156 ret = kfifo_alloc(&pcdev->ntf_fifo, pcdev->nr_lines, GFP_KERNEL);
1157 if (ret) {
1158 dev_err(pcdev->dev, "failed to allocate kfifo notifications\n");
1159 return ret;
1160 }
1161 INIT_WORK(&pcdev->ntf_work, pse_send_ntf_worker);
1162
1163 if (!pcdev->nr_lines)
1164 pcdev->nr_lines = 1;
1165
1166 if (!pcdev->ops->pi_get_admin_state ||
1167 !pcdev->ops->pi_get_pw_status) {
1168 dev_err(pcdev->dev,
1169 "Mandatory status report callbacks are missing");
1170 return -EINVAL;
1171 }
1172
1173 ret = of_load_pse_pis(pcdev);
1174 if (ret)
1175 return ret;
1176
1177 if (pcdev->ops->setup_pi_matrix) {
1178 ret = pcdev->ops->setup_pi_matrix(pcdev);
1179 if (ret)
1180 return ret;
1181 }
1182
1183 /* Each regulator name len is pcdev dev name + 7 char +
1184 * int max digit number (10) + 1
1185 */
1186 reg_name_len = strlen(dev_name(pcdev->dev)) + 18;
1187
1188 /* Register PI regulators */
1189 for (i = 0; i < pcdev->nr_lines; i++) {
1190 char *reg_name;
1191
1192 /* Do not register regulator for PIs not described */
1193 if (!pcdev->no_of_pse_pi && !pcdev->pi[i].np)
1194 continue;
1195
1196 reg_name = devm_kzalloc(pcdev->dev, reg_name_len, GFP_KERNEL);
1197 if (!reg_name)
1198 return -ENOMEM;
1199
1200 snprintf(reg_name, reg_name_len, "pse-%s_pi%d",
1201 dev_name(pcdev->dev), i);
1202
1203 ret = devm_pse_pi_regulator_register(pcdev, reg_name, i);
1204 if (ret)
1205 return ret;
1206 }
1207
1208 ret = pse_register_pw_ds(pcdev);
1209 if (ret)
1210 return ret;
1211
1212 mutex_lock(&pse_list_mutex);
1213 list_add(&pcdev->list, &pse_controller_list);
1214 mutex_unlock(&pse_list_mutex);
1215
1216 ret = pse_led_triggers_register(pcdev);
1217 if (ret) {
1218 dev_warn(pcdev->dev, "Failed to register LED triggers: %d\n",
1219 ret);
1220 /* Ensure pse_led_update() is a no-op on partial failure */
> 1221 pcdev->pi_led_trigs = NULL;
1222 }
1223
1224 return 0;
1225 }
1226 EXPORT_SYMBOL_GPL(pse_controller_register);
1227
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
© 2016 - 2026 Red Hat, Inc.