]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net: pse-pd: tps23881: Add support for static port priority feature
authorKory Maincent (Dent Project) <kory.maincent@bootlin.com>
Tue, 17 Jun 2025 12:12:11 +0000 (14:12 +0200)
committerJakub Kicinski <kuba@kernel.org>
Thu, 19 Jun 2025 02:00:18 +0000 (19:00 -0700)
This patch enhances PSE callbacks by introducing support for the static
port priority feature. It extends interrupt management to handle and report
detection, classification, and disconnection events. Additionally, it
introduces the pi_get_pw_req() callback, which provides information about
the power requested by the Powered Devices.

Interrupt support is essential for the proper functioning of the TPS23881
controller. Without it, after a power-on (PWON), the controller will
no longer perform detection and classification. This could lead to
potential hazards, such as connecting a non-PoE device after a PoE device,
which might result in magic smoke.

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-12-78a1a645e2ee@bootlin.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/pse-pd/tps23881.c

index 7a9a5dbe0cb1df9fa3cd8323e76dd0c1b8b71edb..63f8f43062bce6f369bdfd3c9b4ff42b992632f0 100644 (file)
 
 #define TPS23881_REG_IT                0x0
 #define TPS23881_REG_IT_MASK   0x1
+#define TPS23881_REG_IT_DISF   BIT(2)
+#define TPS23881_REG_IT_DETC   BIT(3)
+#define TPS23881_REG_IT_CLASC  BIT(4)
 #define TPS23881_REG_IT_IFAULT BIT(5)
 #define TPS23881_REG_IT_SUPF   BIT(7)
+#define TPS23881_REG_DET_EVENT 0x5
 #define TPS23881_REG_FAULT     0x7
 #define TPS23881_REG_SUPF_EVENT        0xb
 #define TPS23881_REG_TSD       BIT(7)
+#define TPS23881_REG_DISC      0xc
 #define TPS23881_REG_PW_STATUS 0x10
 #define TPS23881_REG_OP_MODE   0x12
+#define TPS23881_REG_DISC_EN   0x13
 #define TPS23881_OP_MODE_SEMIAUTO      0xaaaa
 #define TPS23881_REG_DIS_EN    0x13
 #define TPS23881_REG_DET_CLA_EN        0x14
 #define TPS23881_REG_GEN_MASK  0x17
+#define TPS23881_REG_CLCHE     BIT(2)
+#define TPS23881_REG_DECHE     BIT(3)
 #define TPS23881_REG_NBITACC   BIT(5)
 #define TPS23881_REG_INTEN     BIT(7)
 #define TPS23881_REG_PW_EN     0x19
+#define TPS23881_REG_RESET     0x1a
+#define TPS23881_REG_CLRAIN    BIT(7)
 #define TPS23881_REG_2PAIR_POL1        0x1e
 #define TPS23881_REG_PORT_MAP  0x26
 #define TPS23881_REG_PORT_POWER        0x29
@@ -178,6 +188,7 @@ static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id)
        struct i2c_client *client = priv->client;
        u8 chan;
        u16 val;
+       int ret;
 
        if (id >= TPS23881_MAX_CHANS)
                return -ERANGE;
@@ -191,7 +202,22 @@ static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id)
                                       BIT(chan % 4));
        }
 
-       return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+       ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+       if (ret)
+               return ret;
+
+       /* Enable DC disconnect*/
+       chan = priv->port[id].chan[0];
+       ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN);
+       if (ret < 0)
+               return ret;
+
+       val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4));
+       ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val);
+       if (ret)
+               return ret;
+
+       return 0;
 }
 
 static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id)
@@ -224,6 +250,17 @@ static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id)
         */
        mdelay(5);
 
+       /* Disable DC disconnect*/
+       chan = priv->port[id].chan[0];
+       ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN);
+       if (ret < 0)
+               return ret;
+
+       val = tps23881_set_val(ret, chan, 0, 0, BIT(chan % 4));
+       ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val);
+       if (ret)
+               return ret;
+
        /* Enable detection and classification */
        ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_CLA_EN);
        if (ret < 0)
@@ -919,6 +956,47 @@ static int tps23881_setup_pi_matrix(struct pse_controller_dev *pcdev)
        return ret;
 }
 
+static int tps23881_power_class_table[] = {
+       -ERANGE,
+       4000,
+       7000,
+       15500,
+       30000,
+       15500,
+       15500,
+       -ERANGE,
+       45000,
+       60000,
+       75000,
+       90000,
+       15500,
+       45000,
+       -ERANGE,
+       -ERANGE,
+};
+
+static int tps23881_pi_get_pw_req(struct pse_controller_dev *pcdev, int id)
+{
+       struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+       struct i2c_client *client = priv->client;
+       u8 reg, chan;
+       int ret;
+       u16 val;
+
+       /* For a 4-pair the classification need 5ms to be completed */
+       if (priv->port[id].is_4p)
+               mdelay(5);
+
+       chan = priv->port[id].chan[0];
+       reg = TPS23881_REG_DISC + (chan % 4);
+       ret = i2c_smbus_read_word_data(client, reg);
+       if (ret < 0)
+               return ret;
+
+       val = tps23881_calc_val(ret, chan, 4, 0xf);
+       return tps23881_power_class_table[val];
+}
+
 static const struct pse_controller_ops tps23881_ops = {
        .setup_pi_matrix = tps23881_setup_pi_matrix,
        .pi_enable = tps23881_pi_enable,
@@ -931,6 +1009,7 @@ static const struct pse_controller_ops tps23881_ops = {
        .pi_get_pw_limit = tps23881_pi_get_pw_limit,
        .pi_set_pw_limit = tps23881_pi_set_pw_limit,
        .pi_get_pw_limit_ranges = tps23881_pi_get_pw_limit_ranges,
+       .pi_get_pw_req = tps23881_pi_get_pw_req,
 };
 
 static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin";
@@ -1088,17 +1167,113 @@ static void tps23881_irq_event_over_temp(struct tps23881_priv *priv,
        }
 }
 
-static void tps23881_irq_event_over_current(struct tps23881_priv *priv,
-                                           u16 reg_val,
-                                           unsigned long *notifs,
-                                           unsigned long *notifs_mask)
+static int tps23881_irq_event_over_current(struct tps23881_priv *priv,
+                                          u16 reg_val,
+                                          unsigned long *notifs,
+                                          unsigned long *notifs_mask)
 {
+       int i, ret;
        u8 chans;
 
        chans = tps23881_irq_export_chans_helper(reg_val, 0);
+       if (!chans)
+               return 0;
+
+       tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
+                                  ETHTOOL_PSE_EVENT_OVER_CURRENT |
+                                  ETHTOOL_C33_PSE_EVENT_DISCONNECTION);
+
+       /* Over Current event resets the power limit registers so we need
+        * to configured it again.
+        */
+       for_each_set_bit(i, notifs_mask, priv->pcdev.nr_lines) {
+               if (priv->port[i].pw_pol < 0)
+                       continue;
+
+               ret = tps23881_pi_enable_manual_pol(priv, i);
+               if (ret < 0)
+                       return ret;
+
+               /* Set power policy */
+               ret = tps23881_pi_set_pw_pol_limit(priv, i,
+                                                  priv->port[i].pw_pol,
+                                                  priv->port[i].is_4p);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static void tps23881_irq_event_disconnection(struct tps23881_priv *priv,
+                                            u16 reg_val,
+                                            unsigned long *notifs,
+                                            unsigned long *notifs_mask)
+{
+       u8 chans;
+
+       chans = tps23881_irq_export_chans_helper(reg_val, 4);
        if (chans)
                tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
-                                          ETHTOOL_PSE_EVENT_OVER_CURRENT);
+                                          ETHTOOL_C33_PSE_EVENT_DISCONNECTION);
+}
+
+static int tps23881_irq_event_detection(struct tps23881_priv *priv,
+                                       u16 reg_val,
+                                       unsigned long *notifs,
+                                       unsigned long *notifs_mask)
+{
+       enum ethtool_pse_event event;
+       int reg, ret, i, val;
+       unsigned long chans;
+
+       chans = tps23881_irq_export_chans_helper(reg_val, 0);
+       for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) {
+               reg = TPS23881_REG_DISC + (i % 4);
+               ret = i2c_smbus_read_word_data(priv->client, reg);
+               if (ret < 0)
+                       return ret;
+
+               val = tps23881_calc_val(ret, i, 0, 0xf);
+               /* If detection valid */
+               if (val == 0x4)
+                       event = ETHTOOL_C33_PSE_EVENT_DETECTION;
+               else
+                       event = ETHTOOL_C33_PSE_EVENT_DISCONNECTION;
+
+               tps23881_set_notifs_helper(priv, BIT(i), notifs,
+                                          notifs_mask, event);
+       }
+
+       return 0;
+}
+
+static int tps23881_irq_event_classification(struct tps23881_priv *priv,
+                                            u16 reg_val,
+                                            unsigned long *notifs,
+                                            unsigned long *notifs_mask)
+{
+       int reg, ret, val, i;
+       unsigned long chans;
+
+       chans = tps23881_irq_export_chans_helper(reg_val, 4);
+       for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) {
+               reg = TPS23881_REG_DISC + (i % 4);
+               ret = i2c_smbus_read_word_data(priv->client, reg);
+               if (ret < 0)
+                       return ret;
+
+               val = tps23881_calc_val(ret, i, 4, 0xf);
+               /* Do not report classification event for unknown class */
+               if (!val || val == 0x8 || val == 0xf)
+                       continue;
+
+               tps23881_set_notifs_helper(priv, BIT(i), notifs,
+                                          notifs_mask,
+                                          ETHTOOL_C33_PSE_EVENT_CLASSIFICATION);
+       }
+
+       return 0;
 }
 
 static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg,
@@ -1106,7 +1281,7 @@ static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg,
                                      unsigned long *notifs_mask)
 {
        struct i2c_client *client = priv->client;
-       int ret;
+       int ret, val;
 
        /* The Supply event bit is repeated twice so we only need to read
         * the one from the first byte.
@@ -1118,13 +1293,36 @@ static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg,
                tps23881_irq_event_over_temp(priv, ret, notifs, notifs_mask);
        }
 
-       if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8)) {
+       if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8 |
+                  TPS23881_REG_IT_DISF | TPS23881_REG_IT_DISF << 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);
+               ret = tps23881_irq_event_over_current(priv, ret, notifs,
+                                                     notifs_mask);
+               if (ret)
+                       return ret;
+
+               tps23881_irq_event_disconnection(priv, ret, notifs, notifs_mask);
        }
 
+       if (reg & (TPS23881_REG_IT_DETC | TPS23881_REG_IT_DETC << 8 |
+                  TPS23881_REG_IT_CLASC | TPS23881_REG_IT_CLASC << 8)) {
+               ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_EVENT);
+               if (ret < 0)
+                       return ret;
+
+               val = ret;
+               ret = tps23881_irq_event_detection(priv, val, notifs,
+                                                  notifs_mask);
+               if (ret)
+                       return ret;
+
+               ret = tps23881_irq_event_classification(priv, val, notifs,
+                                                       notifs_mask);
+               if (ret)
+                       return ret;
+       }
        return 0;
 }
 
@@ -1178,7 +1376,14 @@ static int tps23881_setup_irq(struct tps23881_priv *priv, int irq)
        int ret;
        u16 val;
 
-       val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF;
+       if (!irq) {
+               dev_err(&client->dev, "interrupt is missing");
+               return -EINVAL;
+       }
+
+       val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF |
+             TPS23881_REG_IT_DETC | TPS23881_REG_IT_CLASC |
+             TPS23881_REG_IT_DISF;
        val |= val << 8;
        ret = i2c_smbus_write_word_data(client, TPS23881_REG_IT_MASK, val);
        if (ret)
@@ -1188,11 +1393,19 @@ static int tps23881_setup_irq(struct tps23881_priv *priv, int irq)
        if (ret < 0)
                return ret;
 
-       val = (u16)(ret | TPS23881_REG_INTEN | TPS23881_REG_INTEN << 8);
+       val = TPS23881_REG_INTEN | TPS23881_REG_CLCHE | TPS23881_REG_DECHE;
+       val |= val << 8;
+       val |= (u16)ret;
        ret = i2c_smbus_write_word_data(client, TPS23881_REG_GEN_MASK, val);
        if (ret < 0)
                return ret;
 
+       /* Reset interrupts registers */
+       ret = i2c_smbus_write_word_data(client, TPS23881_REG_RESET,
+                                       TPS23881_REG_CLRAIN);
+       if (ret < 0)
+               return ret;
+
        return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc);
 }
 
@@ -1270,17 +1483,16 @@ static int tps23881_i2c_probe(struct i2c_client *client)
        priv->pcdev.dev = dev;
        priv->pcdev.types = ETHTOOL_PSE_C33;
        priv->pcdev.nr_lines = TPS23881_MAX_CHANS;
+       priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_STATIC;
        ret = devm_pse_controller_register(dev, &priv->pcdev);
        if (ret) {
                return dev_err_probe(dev, ret,
                                     "failed to register PSE controller\n");
        }
 
-       if (client->irq) {
-               ret = tps23881_setup_irq(priv, client->irq);
-               if (ret)
-                       return ret;
-       }
+       ret = tps23881_setup_irq(priv, client->irq);
+       if (ret)
+               return ret;
 
        return ret;
 }