mirror of
https://github.com/openwrt/openwrt.git
synced 2026-10-14 13:08:30 +04:00
generic: pse-pd: add fault LED trigger and Si3474 IRQ
Add a per-PI fault LED trigger to the PSE core, an interrupts property to the Si3474 binding, and INTb handling to the Si3474 driver. Over-current events latch a fault state that the driver reports as FAULT, which lights the fault trigger. Power good and power enable changes update the delivering and enabled triggers without waiting for a regulator operation. Signed-off-by: Daniel Golle <daniel@makrotopia.org>
This commit is contained in:
1 parent
6e55a3c331
commit
b2a6ce658d
3 files changed
+457
No files matched your search
@@ -0,0 +1,142 @@
|
||||
From 631a975306fb701e2fe656883f4df6e3c06d7d85 Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Golle <daniel@makrotopia.org>
|
||||
Date: Sun, 4 Oct 2026 08:27:31 +0100
|
||||
Subject: [PATCH 1/3] net: pse-pd: add fault LED trigger
|
||||
|
||||
Add a per-PI "fault" LED trigger next to the existing "delivering"
|
||||
and "enabled" ones. It is lit while the controller reports the PI's
|
||||
power status as fault or otherfault, or error for PoDL, so a board
|
||||
can show an over-current or short-circuit shutdown on a dedicated
|
||||
LED.
|
||||
|
||||
The state is refreshed from the places that already update the other
|
||||
triggers: the event path and the regulator enable and disable paths.
|
||||
|
||||
Assisted-by: LLM
|
||||
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||
---
|
||||
drivers/net/pse-pd/pse_core.c | 39 +++++++++++++++++++++++++++++++----
|
||||
include/linux/pse-pd/pse.h | 4 ++++
|
||||
2 files changed, 39 insertions(+), 4 deletions(-)
|
||||
|
||||
--- a/drivers/net/pse-pd/pse_core.c
|
||||
+++ b/drivers/net/pse-pd/pse_core.c
|
||||
@@ -753,18 +753,19 @@ static int _pse_pi_delivery_power_sw_pw_
|
||||
|
||||
#if IS_ENABLED(CONFIG_LEDS_TRIGGERS)
|
||||
/**
|
||||
- * pse_pi_get_states - Fetch current delivering/enabled state for a PI
|
||||
+ * pse_pi_get_states - Fetch current delivering/enabled/fault state for a PI
|
||||
* @pcdev: PSE controller device
|
||||
* @id: PI index
|
||||
* @delivering: out, set to true if PI is currently delivering power
|
||||
* @enabled: out, set to true if PI is administratively enabled
|
||||
+ * @fault: out, set to true if PI reports a power fault
|
||||
*
|
||||
* Queries hardware via the controller ops. Caller must hold pcdev->lock.
|
||||
*
|
||||
* Return: 0 on success, negative errno on failure.
|
||||
*/
|
||||
static int pse_pi_get_states(struct pse_controller_dev *pcdev, int id,
|
||||
- bool *delivering, bool *enabled)
|
||||
+ bool *delivering, bool *enabled, bool *fault)
|
||||
{
|
||||
struct pse_pw_status pw_status = {};
|
||||
struct pse_admin_state admin_state = {};
|
||||
@@ -785,6 +786,10 @@ static int pse_pi_get_states(struct pse_
|
||||
ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED ||
|
||||
admin_state.podl_admin_state ==
|
||||
ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED;
|
||||
+ *fault = pw_status.c33_pw_status == ETHTOOL_C33_PSE_PW_D_STATUS_FAULT ||
|
||||
+ pw_status.c33_pw_status ==
|
||||
+ ETHTOOL_C33_PSE_PW_D_STATUS_OTHERFAULT ||
|
||||
+ pw_status.podl_pw_status == ETHTOOL_PODL_PSE_PW_D_STATUS_ERROR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -803,7 +808,7 @@ static int pse_pi_get_states(struct pse_
|
||||
static void pse_led_update(struct pse_controller_dev *pcdev, int id)
|
||||
{
|
||||
struct pse_pi_led_triggers *trigs;
|
||||
- bool delivering, enabled;
|
||||
+ bool delivering, enabled, fault;
|
||||
|
||||
if (!pcdev->pi_led_trigs)
|
||||
return;
|
||||
@@ -812,7 +817,7 @@ static void pse_led_update(struct pse_co
|
||||
if (!trigs->delivering.name)
|
||||
return;
|
||||
|
||||
- if (pse_pi_get_states(pcdev, id, &delivering, &enabled))
|
||||
+ if (pse_pi_get_states(pcdev, id, &delivering, &enabled, &fault))
|
||||
return;
|
||||
|
||||
if (trigs->last_delivering != delivering) {
|
||||
@@ -826,6 +831,11 @@ static void pse_led_update(struct pse_co
|
||||
led_trigger_event(&trigs->enabled,
|
||||
enabled ? LED_FULL : LED_OFF);
|
||||
}
|
||||
+
|
||||
+ if (trigs->last_fault != fault) {
|
||||
+ trigs->last_fault = fault;
|
||||
+ led_trigger_event(&trigs->fault, fault ? LED_FULL : LED_OFF);
|
||||
+ }
|
||||
}
|
||||
|
||||
/* Sync a freshly-bound LED to the cached trigger state. Without these
|
||||
@@ -855,6 +865,15 @@ static int pse_led_enabled_activate(stru
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static int pse_led_fault_activate(struct led_classdev *led_cdev)
|
||||
+{
|
||||
+ struct pse_pi_led_triggers *trigs = container_of(
|
||||
+ led_cdev->trigger, struct pse_pi_led_triggers, fault);
|
||||
+
|
||||
+ led_set_brightness(led_cdev, trigs->last_fault ? LED_FULL : LED_OFF);
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static int pse_led_triggers_register(struct pse_controller_dev *pcdev)
|
||||
{
|
||||
struct device *dev = pcdev->dev;
|
||||
@@ -901,6 +920,18 @@ static int pse_led_triggers_register(str
|
||||
trigs->enabled.name = NULL;
|
||||
return ret;
|
||||
}
|
||||
+
|
||||
+ trigs->fault.name = devm_kasprintf(
|
||||
+ dev, GFP_KERNEL, "pse-%s:port%d:fault", dev_id, i);
|
||||
+ if (!trigs->fault.name)
|
||||
+ return -ENOMEM;
|
||||
+ trigs->fault.activate = pse_led_fault_activate;
|
||||
+
|
||||
+ ret = devm_led_trigger_register(dev, &trigs->fault);
|
||||
+ if (ret) {
|
||||
+ trigs->fault.name = NULL;
|
||||
+ return ret;
|
||||
+ }
|
||||
}
|
||||
|
||||
return 0;
|
||||
--- a/include/linux/pse-pd/pse.h
|
||||
+++ b/include/linux/pse-pd/pse.h
|
||||
@@ -273,14 +273,18 @@ struct pse_pi {
|
||||
*
|
||||
* @delivering: LED trigger for power delivering state
|
||||
* @enabled: LED trigger for admin enabled state
|
||||
+ * @fault: LED trigger for power fault state
|
||||
* @last_delivering: cached delivering state for change detection
|
||||
* @last_enabled: cached enabled state for change detection
|
||||
+ * @last_fault: cached fault state for change detection
|
||||
*/
|
||||
struct pse_pi_led_triggers {
|
||||
struct led_trigger delivering;
|
||||
struct led_trigger enabled;
|
||||
+ struct led_trigger fault;
|
||||
bool last_delivering;
|
||||
bool last_enabled;
|
||||
+ bool last_fault;
|
||||
};
|
||||
|
||||
/**
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
From 3b57fcfd2ba85f59786904da3ea8a8416dfa2d2e Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Golle <daniel@makrotopia.org>
|
||||
Date: Sun, 4 Oct 2026 08:27:35 +0100
|
||||
Subject: [PATCH 2/3] dt-bindings: net: pse-pd: skyworks,si3474: add interrupts
|
||||
|
||||
The Si3474 has a single open-drain INTb output that signals events
|
||||
from either of its two quads. Describe it so a board can wire it to
|
||||
a GPIO and let the driver react to over-current faults and power
|
||||
state changes.
|
||||
|
||||
Assisted-by: LLM
|
||||
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||
---
|
||||
.../devicetree/bindings/net/pse-pd/skyworks,si3474.yaml | 5 +++++
|
||||
1 file changed, 5 insertions(+)
|
||||
|
||||
--- a/Documentation/devicetree/bindings/net/pse-pd/skyworks,si3474.yaml
|
||||
+++ b/Documentation/devicetree/bindings/net/pse-pd/skyworks,si3474.yaml
|
||||
@@ -25,6 +25,11 @@ properties:
|
||||
- const: main
|
||||
- const: secondary
|
||||
|
||||
+ interrupts:
|
||||
+ maxItems: 1
|
||||
+ description: INTb output, asserted low by an unserved event in either
|
||||
+ quad.
|
||||
+
|
||||
channels:
|
||||
description: The Si3474 is a single-chip PoE PSE controller managing
|
||||
8 physical power delivery channels. Internally, it's structured
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
From 3a0bb2b94a94d304890077d72c48340ca8ffd05f Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Golle <daniel@makrotopia.org>
|
||||
Date: Sun, 4 Oct 2026 08:27:36 +0100
|
||||
Subject: [PATCH 3/3] net: pse-pd: si3474: report over-current faults via INTb
|
||||
|
||||
When an interrupt is described, enable the START_EVENT, P_I_FAULT,
|
||||
POWER_GOOD_CHANGE and POWER_ENABLE_CHANGE sources and service them
|
||||
through the PSE interrupt helper. On every interrupt the event
|
||||
registers of both quads are read, which clears the events and
|
||||
negates INTb.
|
||||
|
||||
START_FAULT, ILIM_FAULT and the 2-pair and 4-pair PCUT_FAULT events
|
||||
raise ETHTOOL_PSE_EVENT_OVER_CURRENT for the affected PI and latch a
|
||||
fault state. pi_get_pw_status() reports that latch as FAULT until the
|
||||
PI delivers power again or is enabled or disabled, which drives the
|
||||
fault LED trigger. Power good and power enable changes are mapped to
|
||||
their PI so the delivering and enabled LED triggers follow the
|
||||
hardware without waiting for a regulator operation.
|
||||
|
||||
Assisted-by: LLM
|
||||
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||
---
|
||||
drivers/net/pse-pd/si3474.c | 179 +++++++++++++++++++++++++++++++++++-
|
||||
1 file changed, 175 insertions(+), 4 deletions(-)
|
||||
|
||||
--- a/drivers/net/pse-pd/si3474.c
|
||||
+++ b/drivers/net/pse-pd/si3474.c
|
||||
@@ -86,6 +86,8 @@
|
||||
* (Note: Actual mapping depends on Device Tree and PORT_REMAP config)
|
||||
*/
|
||||
|
||||
+#include <linux/bitmap.h>
|
||||
+#include <linux/bitops.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
@@ -104,6 +106,25 @@
|
||||
#define FIRMWARE_REVISION_REG 0x41
|
||||
#define CHIP_REVISION_REG 0x43
|
||||
|
||||
+/* Interrupt registers */
|
||||
+#define INTERRUPT_REG 0x00
|
||||
+#define INTERRUPT_MASK_REG 0x01
|
||||
+#define POWER_EVENT_COR_REG 0x03
|
||||
+#define DISCONNECT_PCUT_FAULT_COR_REG 0x07
|
||||
+#define ILIM_START_FAULT_COR_REG 0x09
|
||||
+#define SUPPLY_EVENT_COR_REG 0x0B
|
||||
+#define POWER_ON_FAULT_COR_REG 0x25
|
||||
+
|
||||
+/* INTERRUPT and INTERRUPT_MASK bits */
|
||||
+#define INT_START_EVENT BIT(6)
|
||||
+#define INT_P_I_FAULT BIT(5)
|
||||
+#define INT_POWER_GOOD_CHANGE BIT(1)
|
||||
+#define INT_POWER_ENABLE_CHANGE BIT(0)
|
||||
+
|
||||
+#define SI3474_INT_MASK \
|
||||
+ (INT_START_EVENT | INT_P_I_FAULT | INT_POWER_GOOD_CHANGE | \
|
||||
+ INT_POWER_ENABLE_CHANGE)
|
||||
+
|
||||
/* Main status registers */
|
||||
#define POWER_STATUS_REG 0x10
|
||||
#define PORT_MODE_REG 0x12
|
||||
@@ -135,16 +156,28 @@
|
||||
#define CHAN_MASK(chan) (0x03U << (2 * CHAN_IDX(chan)))
|
||||
#define CHAN_REG(base, chan) ((base) + (CHAN_IDX(chan) * 4))
|
||||
|
||||
+/* PORT12 / PORT34 PCUT_FAULT_4P bits of SUPPLY_EVENT */
|
||||
+#define CHAN_PCUT_FAULT_4P_BIT(chan) BIT(2 + CHAN_IDX(chan) / 2)
|
||||
+
|
||||
struct si3474_pi_desc {
|
||||
u8 chan[2];
|
||||
bool is_4p;
|
||||
};
|
||||
|
||||
+/* Per-quad snapshot of the event registers, read clear-on-read */
|
||||
+struct si3474_events {
|
||||
+ u8 power;
|
||||
+ u8 pcut;
|
||||
+ u8 ilim_start;
|
||||
+ u8 supply;
|
||||
+};
|
||||
+
|
||||
struct si3474_priv {
|
||||
struct i2c_client *client[2];
|
||||
struct pse_controller_dev pcdev;
|
||||
struct device_node *np;
|
||||
struct si3474_pi_desc pi[SI3474_MAX_CHANS];
|
||||
+ unsigned long fault_pis;
|
||||
};
|
||||
|
||||
static struct si3474_priv *to_si3474_priv(struct pse_controller_dev *pcdev)
|
||||
@@ -159,10 +192,15 @@ static void si3474_get_channels(struct s
|
||||
*chan1 = priv->pi[id].chan[1];
|
||||
}
|
||||
|
||||
+static unsigned int si3474_chan_quad(u8 chan)
|
||||
+{
|
||||
+ return chan < 4 ? 0 : 1;
|
||||
+}
|
||||
+
|
||||
static struct i2c_client *si3474_get_chan_client(struct si3474_priv *priv,
|
||||
u8 chan)
|
||||
{
|
||||
- return (chan < 4) ? priv->client[0] : priv->client[1];
|
||||
+ return priv->client[si3474_chan_quad(chan)];
|
||||
}
|
||||
|
||||
static int si3474_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
|
||||
@@ -216,11 +254,15 @@ static int si3474_pi_get_pw_status(struc
|
||||
|
||||
delivering = ret & (CHAN_UPPER_BIT(chan0) | CHAN_UPPER_BIT(chan1));
|
||||
|
||||
- if (delivering)
|
||||
+ if (delivering) {
|
||||
+ clear_bit(id, &priv->fault_pis);
|
||||
pw_status->c33_pw_status =
|
||||
ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
|
||||
- else
|
||||
+ } else if (test_bit(id, &priv->fault_pis)) {
|
||||
+ pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_FAULT;
|
||||
+ } else {
|
||||
pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
|
||||
+ }
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -309,6 +351,8 @@ static int si3474_pi_enable(struct pse_c
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
+ clear_bit(id, &priv->fault_pis);
|
||||
+
|
||||
/* DETECT_CLASS_ENABLE must be set when using AUTO mode,
|
||||
* otherwise PI does not power up - datasheet section 2.10.2
|
||||
*/
|
||||
@@ -346,6 +390,8 @@ static int si3474_pi_disable(struct pse_
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
+ clear_bit(id, &priv->fault_pis);
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -453,6 +499,128 @@ static int si3474_pi_get_actual_pw(struc
|
||||
return DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000);
|
||||
}
|
||||
|
||||
+static int si3474_events_read(struct i2c_client *client,
|
||||
+ struct si3474_events *ev)
|
||||
+{
|
||||
+ s32 ret;
|
||||
+
|
||||
+ ret = i2c_smbus_read_byte_data(client, INTERRUPT_REG);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (!(ret & SI3474_INT_MASK))
|
||||
+ return 0;
|
||||
+
|
||||
+ ret = i2c_smbus_read_byte_data(client, POWER_EVENT_COR_REG);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+ ev->power = ret;
|
||||
+
|
||||
+ ret = i2c_smbus_read_byte_data(client, DISCONNECT_PCUT_FAULT_COR_REG);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+ ev->pcut = ret;
|
||||
+
|
||||
+ ret = i2c_smbus_read_byte_data(client, ILIM_START_FAULT_COR_REG);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+ ev->ilim_start = ret;
|
||||
+
|
||||
+ ret = i2c_smbus_read_byte_data(client, SUPPLY_EVENT_COR_REG);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+ ev->supply = ret;
|
||||
+
|
||||
+ /* START_EVENT stays set until POWER_ON_FAULT is read as well */
|
||||
+ ret = i2c_smbus_read_byte_data(client, POWER_ON_FAULT_COR_REG);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static bool si3474_chan_over_current(const struct si3474_events *ev, u8 chan)
|
||||
+{
|
||||
+ return (ev->pcut & CHAN_BIT(chan)) ||
|
||||
+ (ev->ilim_start & (CHAN_BIT(chan) | CHAN_UPPER_BIT(chan))) ||
|
||||
+ (ev->supply & CHAN_PCUT_FAULT_4P_BIT(chan));
|
||||
+}
|
||||
+
|
||||
+static void si3474_pi_map_event(struct si3474_priv *priv, int id,
|
||||
+ const struct si3474_events *ev,
|
||||
+ unsigned long *notifs,
|
||||
+ unsigned long *notifs_mask)
|
||||
+{
|
||||
+ bool over_current = false, power_change = false;
|
||||
+ const struct si3474_events *quad_ev;
|
||||
+ u8 chan[2];
|
||||
+ int i;
|
||||
+
|
||||
+ if (!priv->pi[id].is_4p)
|
||||
+ return;
|
||||
+
|
||||
+ si3474_get_channels(priv, id, &chan[0], &chan[1]);
|
||||
+
|
||||
+ for (i = 0; i < 2; i++) {
|
||||
+ quad_ev = &ev[si3474_chan_quad(chan[i])];
|
||||
+ over_current |= si3474_chan_over_current(quad_ev, chan[i]);
|
||||
+ power_change |= quad_ev->power &
|
||||
+ (CHAN_BIT(chan[i]) | CHAN_UPPER_BIT(chan[i]));
|
||||
+ }
|
||||
+
|
||||
+ if (over_current) {
|
||||
+ set_bit(id, &priv->fault_pis);
|
||||
+ notifs[id] |= ETHTOOL_PSE_EVENT_OVER_CURRENT;
|
||||
+ }
|
||||
+
|
||||
+ if (over_current || power_change)
|
||||
+ set_bit(id, notifs_mask);
|
||||
+}
|
||||
+
|
||||
+static int si3474_map_event(int irq, struct pse_controller_dev *pcdev,
|
||||
+ unsigned long *notifs, unsigned long *notifs_mask)
|
||||
+{
|
||||
+ struct si3474_priv *priv = to_si3474_priv(pcdev);
|
||||
+ struct si3474_events ev[2] = {};
|
||||
+ int quad, id, ret = 0, err;
|
||||
+
|
||||
+ /* Events of a quad that was read are consumed even if the other fails */
|
||||
+ for (quad = 0; quad < 2; quad++) {
|
||||
+ err = si3474_events_read(priv->client[quad], &ev[quad]);
|
||||
+ if (err)
|
||||
+ ret = err;
|
||||
+ }
|
||||
+
|
||||
+ for (id = 0; id < SI3474_MAX_CHANS; id++)
|
||||
+ si3474_pi_map_event(priv, id, ev, notifs, notifs_mask);
|
||||
+
|
||||
+ return bitmap_empty(notifs_mask, pcdev->nr_lines) ? ret : 0;
|
||||
+}
|
||||
+
|
||||
+static int si3474_setup_irq(struct si3474_priv *priv, int irq)
|
||||
+{
|
||||
+ struct pse_irq_desc irq_desc = {
|
||||
+ .name = "si3474-irq",
|
||||
+ .map_event = si3474_map_event,
|
||||
+ };
|
||||
+ struct si3474_events ev;
|
||||
+ int quad, ret;
|
||||
+
|
||||
+ for (quad = 0; quad < 2; quad++) {
|
||||
+ ret = si3474_events_read(priv->client[quad], &ev);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = i2c_smbus_write_byte_data(priv->client[quad],
|
||||
+ INTERRUPT_MASK_REG,
|
||||
+ SI3474_INT_MASK);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc);
|
||||
+}
|
||||
+
|
||||
static const struct pse_controller_ops si3474_ops = {
|
||||
.setup_pi_matrix = si3474_setup_pi_matrix,
|
||||
.pi_enable = si3474_pi_enable,
|
||||
@@ -546,7 +714,10 @@ static int si3474_i2c_probe(struct i2c_c
|
||||
return ret;
|
||||
}
|
||||
|
||||
- return 0;
|
||||
+ if (client->irq <= 0)
|
||||
+ return 0;
|
||||
+
|
||||
+ return si3474_setup_irq(priv, client->irq);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id si3474_id[] = {
|
||||
Reference in new issue
Block a user