]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net: pse-pd: tps23881: Add support for PSE events and interrupts
authorKory Maincent (Dent Project) <kory.maincent@bootlin.com>
Tue, 17 Jun 2025 12:12:02 +0000 (14:12 +0200)
committerJakub Kicinski <kuba@kernel.org>
Thu, 19 Jun 2025 02:00:16 +0000 (19:00 -0700)
Add support for PSE event reporting through interrupts. Set up the newly
introduced devm_pse_irq_helper helper to register the interrupt. Events are
reported for over-current and over-temperature conditions.

Signed-off-by: Kory Maincent (Dent Project) <kory.maincent@bootlin.com>
Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-3-78a1a645e2ee@bootlin.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/pse-pd/tps23881.c

index 5e9dda2c0eac7c1847953c574a3b331fab1ec93e..7a9a5dbe0cb1df9fa3cd8323e76dd0c1b8b71edb 100644 (file)
 #include <linux/pse-pd/pse.h>
 
 #define TPS23881_MAX_CHANS 8
-
+#define TPS23881_MAX_IRQ_RETRIES 10
+
+#define TPS23881_REG_IT                0x0
+#define TPS23881_REG_IT_MASK   0x1
+#define TPS23881_REG_IT_IFAULT BIT(5)
+#define TPS23881_REG_IT_SUPF   BIT(7)
+#define TPS23881_REG_FAULT     0x7
+#define TPS23881_REG_SUPF_EVENT        0xb
+#define TPS23881_REG_TSD       BIT(7)
 #define TPS23881_REG_PW_STATUS 0x10
 #define TPS23881_REG_OP_MODE   0x12
 #define TPS23881_OP_MODE_SEMIAUTO      0xaaaa
@@ -24,6 +32,7 @@
 #define TPS23881_REG_DET_CLA_EN        0x14
 #define TPS23881_REG_GEN_MASK  0x17
 #define TPS23881_REG_NBITACC   BIT(5)
+#define TPS23881_REG_INTEN     BIT(7)
 #define TPS23881_REG_PW_EN     0x19
 #define TPS23881_REG_2PAIR_POL1        0x1e
 #define TPS23881_REG_PORT_MAP  0x26
@@ -51,6 +60,7 @@ struct tps23881_port_desc {
        u8 chan[2];
        bool is_4p;
        int pw_pol;
+       bool exist;
 };
 
 struct tps23881_priv {
@@ -782,8 +792,10 @@ tps23881_write_port_matrix(struct tps23881_priv *priv,
                hw_chan = port_matrix[i].hw_chan[0] % 4;
 
                /* Set software port matrix for existing ports */
-               if (port_matrix[i].exist)
+               if (port_matrix[i].exist) {
                        priv->port[pi_id].chan[0] = lgcl_chan;
+                       priv->port[pi_id].exist = true;
+               }
 
                /* Initialize power policy internal value */
                priv->port[pi_id].pw_pol = -1;
@@ -1017,6 +1029,173 @@ static int tps23881_flash_sram_fw(struct i2c_client *client)
        return 0;
 }
 
+/* Convert interrupt events to 0xff to be aligned with the chan
+ * number.
+ */
+static u8 tps23881_irq_export_chans_helper(u16 reg_val, u8 field_offset)
+{
+       u8 val;
+
+       val = (reg_val >> (4 + field_offset) & 0xf0) |
+             (reg_val >> field_offset & 0x0f);
+
+       return val;
+}
+
+/* Convert chan number to port number */
+static void tps23881_set_notifs_helper(struct tps23881_priv *priv,
+                                      u8 chans,
+                                      unsigned long *notifs,
+                                      unsigned long *notifs_mask,
+                                      enum ethtool_pse_event event)
+{
+       u8 chan;
+       int i;
+
+       if (!chans)
+               return;
+
+       for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+               if (!priv->port[i].exist)
+                       continue;
+               /* No need to look at the 2nd channel in case of PoE4 as
+                * both registers are set.
+                */
+               chan = priv->port[i].chan[0];
+
+               if (BIT(chan) & chans) {
+                       *notifs_mask |= BIT(i);
+                       notifs[i] |= event;
+               }
+       }
+}
+
+static void tps23881_irq_event_over_temp(struct tps23881_priv *priv,
+                                        u16 reg_val,
+                                        unsigned long *notifs,
+                                        unsigned long *notifs_mask)
+{
+       int i;
+
+       if (reg_val & TPS23881_REG_TSD) {
+               for (i = 0; i < TPS23881_MAX_CHANS; i++) {
+                       if (!priv->port[i].exist)
+                               continue;
+
+                       *notifs_mask |= BIT(i);
+                       notifs[i] |= ETHTOOL_PSE_EVENT_OVER_TEMP;
+               }
+       }
+}
+
+static void tps23881_irq_event_over_current(struct tps23881_priv *priv,
+                                           u16 reg_val,
+                                           unsigned long *notifs,
+                                           unsigned long *notifs_mask)
+{
+       u8 chans;
+
+       chans = tps23881_irq_export_chans_helper(reg_val, 0);
+       if (chans)
+               tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
+                                          ETHTOOL_PSE_EVENT_OVER_CURRENT);
+}
+
+static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg,
+                                     unsigned long *notifs,
+                                     unsigned long *notifs_mask)
+{
+       struct i2c_client *client = priv->client;
+       int ret;
+
+       /* The Supply event bit is repeated twice so we only need to read
+        * the one from the first byte.
+        */
+       if (reg & TPS23881_REG_IT_SUPF) {
+               ret = i2c_smbus_read_word_data(client, TPS23881_REG_SUPF_EVENT);
+               if (ret < 0)
+                       return ret;
+               tps23881_irq_event_over_temp(priv, ret, notifs, notifs_mask);
+       }
+
+       if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8)) {
+               ret = i2c_smbus_read_word_data(client, TPS23881_REG_FAULT);
+               if (ret < 0)
+                       return ret;
+               tps23881_irq_event_over_current(priv, ret, notifs, notifs_mask);
+       }
+
+       return 0;
+}
+
+static int tps23881_irq_handler(int irq, struct pse_controller_dev *pcdev,
+                               unsigned long *notifs,
+                               unsigned long *notifs_mask)
+{
+       struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+       struct i2c_client *client = priv->client;
+       int ret, it_mask, retry;
+
+       /* Get interruption mask */
+       ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT_MASK);
+       if (ret < 0)
+               return ret;
+       it_mask = ret;
+
+       /* Read interrupt register until it frees the interruption pin. */
+       retry = 0;
+       while (true) {
+               if (retry > TPS23881_MAX_IRQ_RETRIES) {
+                       dev_err(&client->dev, "interrupt never freed");
+                       return -ETIMEDOUT;
+               }
+
+               ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT);
+               if (ret < 0)
+                       return ret;
+
+               /* No more relevant interruption */
+               if (!(ret & it_mask))
+                       return 0;
+
+               ret = tps23881_irq_event_handler(priv, (u16)ret, notifs,
+                                                notifs_mask);
+               if (ret)
+                       return ret;
+
+               retry++;
+       }
+       return 0;
+}
+
+static int tps23881_setup_irq(struct tps23881_priv *priv, int irq)
+{
+       struct i2c_client *client = priv->client;
+       struct pse_irq_desc irq_desc = {
+               .name = "tps23881-irq",
+               .map_event = tps23881_irq_handler,
+       };
+       int ret;
+       u16 val;
+
+       val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF;
+       val |= val << 8;
+       ret = i2c_smbus_write_word_data(client, TPS23881_REG_IT_MASK, val);
+       if (ret)
+               return ret;
+
+       ret = i2c_smbus_read_word_data(client, TPS23881_REG_GEN_MASK);
+       if (ret < 0)
+               return ret;
+
+       val = (u16)(ret | TPS23881_REG_INTEN | TPS23881_REG_INTEN << 8);
+       ret = i2c_smbus_write_word_data(client, TPS23881_REG_GEN_MASK, val);
+       if (ret < 0)
+               return ret;
+
+       return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc);
+}
+
 static int tps23881_i2c_probe(struct i2c_client *client)
 {
        struct device *dev = &client->dev;
@@ -1097,6 +1276,12 @@ static int tps23881_i2c_probe(struct i2c_client *client)
                                     "failed to register PSE controller\n");
        }
 
+       if (client->irq) {
+               ret = tps23881_setup_irq(priv, client->irq);
+               if (ret)
+                       return ret;
+       }
+
        return ret;
 }