hwmon: (pmbus/tps53679) Add support for TPS53681
authorGuenter Roeck <linux@roeck-us.net>
Mon, 20 Jan 2020 16:11:07 +0000 (08:11 -0800)
committerGuenter Roeck <linux@roeck-us.net>
Mon, 9 Mar 2020 03:35:47 +0000 (20:35 -0700)
TPS53681 is a dual-channel multiphase step-down controller
supporting per-phase and per-channel output telemetry.

Cc: Vadim Pasternak <vadimp@mellanox.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
drivers/hwmon/pmbus/Kconfig
drivers/hwmon/pmbus/tps53679.c

index 21b916093c3754155d6774f1d93b6e28672febdb..1909ce8fca5338fe0a641734f7a93face42ed5b2 100644 (file)
@@ -210,10 +210,10 @@ config SENSORS_TPS40422
          be called tps40422.
 
 config SENSORS_TPS53679
-       tristate "TI TPS53679, TPS53688"
+       tristate "TI TPS53679, TPS53681, TPS53688"
        help
          If you say yes here you get hardware monitoring support for TI
-         TPS53679, TPS53688
+         TPS53679, TPS53681, and TPS53688.
 
          This driver can also be built as a module. If so, the module will
          be called tps53679.
index 2a6495424a87af9d43e10f2cb6077bbeccdad409..eb6e2ea0e66c80a4d9c36aad97e31e35647cc77a 100644 (file)
@@ -6,6 +6,7 @@
  * Copyright (c) 2017 Vadim Pasternak <vadimp@mellanox.com>
  */
 
+#include <linux/bits.h>
 #include <linux/err.h>
 #include <linux/i2c.h>
 #include <linux/init.h>
@@ -15,7 +16,7 @@
 #include "pmbus.h"
 
 enum chips {
-       tps53679, tps53688,
+       tps53679, tps53681, tps53688
 };
 
 #define TPS53679_PROT_VR12_5MV         0x01 /* VR12.0 mode, 5-mV DAC */
@@ -25,8 +26,14 @@ enum chips {
 #define TPS53679_PROT_VR13_5MV         0x07 /* VR13.0 mode, 5-mV DAC */
 #define TPS53679_PAGE_NUM              2
 
-static int tps53679_identify(struct i2c_client *client,
-                            struct pmbus_driver_info *info)
+#define TPS53681_DEVICE_ID             0x81
+
+#define TPS53681_PMBUS_REVISION                0x33
+
+#define TPS53681_MFR_SPECIFIC_20       0xe4    /* Number of phases, per page */
+
+static int tps53679_identify_mode(struct i2c_client *client,
+                                 struct pmbus_driver_info *info)
 {
        u8 vout_params;
        int i, ret;
@@ -57,6 +64,99 @@ static int tps53679_identify(struct i2c_client *client,
        return 0;
 }
 
+static int tps53679_identify_phases(struct i2c_client *client,
+                                   struct pmbus_driver_info *info)
+{
+       int ret;
+
+       /* On TPS53681, only channel A provides per-phase output current */
+       ret = pmbus_read_byte_data(client, 0, TPS53681_MFR_SPECIFIC_20);
+       if (ret < 0)
+               return ret;
+       info->phases[0] = (ret & 0x07) + 1;
+
+       return 0;
+}
+
+static int tps53679_identify_chip(struct i2c_client *client,
+                                 u8 revision, u16 id)
+{
+       u8 buf[I2C_SMBUS_BLOCK_MAX];
+       int ret;
+
+       ret = pmbus_read_byte_data(client, 0, PMBUS_REVISION);
+       if (ret < 0)
+               return ret;
+       if (ret != revision) {
+               dev_err(&client->dev, "Unexpected PMBus revision 0x%x\n", ret);
+               return -ENODEV;
+       }
+
+       ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
+       if (ret < 0)
+               return ret;
+       if (ret != 1 || buf[0] != id) {
+               dev_err(&client->dev, "Unexpected device ID 0x%x\n", buf[0]);
+               return -ENODEV;
+       }
+       return 0;
+}
+
+/*
+ * Common identification function for chips with multi-phase support.
+ * Since those chips have special configuration registers, we want to have
+ * some level of reassurance that we are really talking with the chip
+ * being probed. Check PMBus revision and chip ID.
+ */
+static int tps53679_identify_multiphase(struct i2c_client *client,
+                                       struct pmbus_driver_info *info,
+                                       int pmbus_rev, int device_id)
+{
+       int ret;
+
+       ret = tps53679_identify_chip(client, pmbus_rev, device_id);
+       if (ret < 0)
+               return ret;
+
+       ret = tps53679_identify_mode(client, info);
+       if (ret < 0)
+               return ret;
+
+       return tps53679_identify_phases(client, info);
+}
+
+static int tps53679_identify(struct i2c_client *client,
+                            struct pmbus_driver_info *info)
+{
+       return tps53679_identify_mode(client, info);
+}
+
+static int tps53681_identify(struct i2c_client *client,
+                            struct pmbus_driver_info *info)
+{
+       return tps53679_identify_multiphase(client, info,
+                                           TPS53681_PMBUS_REVISION,
+                                           TPS53681_DEVICE_ID);
+}
+
+static int tps53681_read_word_data(struct i2c_client *client, int page,
+                                  int phase, int reg)
+{
+       /*
+        * For reading the total output current (READ_IOUT) for all phases,
+        * the chip datasheet is a bit vague. It says "PHASE must be set to
+        * FFh to access all phases simultaneously. PHASE may also be set to
+        * 80h readack (!) the total phase current".
+        * Experiments show that the command does _not_ report the total
+        * current for all phases if the phase is set to 0xff. Instead, it
+        * appears to report the current of one of the phases. Override phase
+        * parameter with 0x80 when reading the total output current on page 0.
+        */
+       if (reg == PMBUS_READ_IOUT && page == 0 && phase == 0xff)
+               return pmbus_read_word_data(client, page, 0x80, reg);
+       return -ENODATA;
+}
+
 static struct pmbus_driver_info tps53679_info = {
        .format[PSC_VOLTAGE_IN] = linear,
        .format[PSC_VOLTAGE_OUT] = vid,
@@ -73,6 +173,12 @@ static struct pmbus_driver_info tps53679_info = {
                PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
                PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
                PMBUS_HAVE_POUT,
+       .pfunc[0] = PMBUS_HAVE_IOUT,
+       .pfunc[1] = PMBUS_HAVE_IOUT,
+       .pfunc[2] = PMBUS_HAVE_IOUT,
+       .pfunc[3] = PMBUS_HAVE_IOUT,
+       .pfunc[4] = PMBUS_HAVE_IOUT,
+       .pfunc[5] = PMBUS_HAVE_IOUT,
 };
 
 static int tps53679_probe(struct i2c_client *client,
@@ -97,6 +203,12 @@ static int tps53679_probe(struct i2c_client *client,
                info->pages = TPS53679_PAGE_NUM;
                info->identify = tps53679_identify;
                break;
+       case tps53681:
+               info->pages = TPS53679_PAGE_NUM;
+               info->phases[0] = 6;
+               info->identify = tps53681_identify;
+               info->read_word_data = tps53681_read_word_data;
+               break;
        default:
                return -ENODEV;
        }
@@ -106,6 +218,7 @@ static int tps53679_probe(struct i2c_client *client,
 
 static const struct i2c_device_id tps53679_id[] = {
        {"tps53679", tps53679},
+       {"tps53681", tps53681},
        {"tps53688", tps53688},
        {}
 };
@@ -114,6 +227,7 @@ MODULE_DEVICE_TABLE(i2c, tps53679_id);
 
 static const struct of_device_id __maybe_unused tps53679_of_match[] = {
        {.compatible = "ti,tps53679", .data = (void *)tps53679},
+       {.compatible = "ti,tps53681", .data = (void *)tps53681},
        {.compatible = "ti,tps53688", .data = (void *)tps53688},
        {}
 };