hwmon: (pmbus/pim4328) Add PMBus driver for PIM4006, PIM4328 and PIM4820
authorErik Rosen <erik.rosen@metormote.com>
Wed, 9 Jun 2021 09:32:08 +0000 (11:32 +0200)
committerGuenter Roeck <linux@roeck-us.net>
Thu, 17 Jun 2021 11:21:46 +0000 (04:21 -0700)
Add hardware monitoring support for Flex power interface modules PIM4006,
PIM4328 and PIM4820.

Signed-off-by: Erik Rosen <erik.rosen@metormote.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
drivers/hwmon/pmbus/Kconfig
drivers/hwmon/pmbus/Makefile
drivers/hwmon/pmbus/pim4328.c [new file with mode: 0644]

index 52d8cd63603e87bf22f97d372f435b6c519f0c6f..10ef548f74a464a98f856603d6508ecb7dff133e 100644 (file)
@@ -267,6 +267,15 @@ config SENSORS_MP2975
          This driver can also be built as a module. If so, the module will
          be called mp2975.
 
+config SENSORS_PIM4328
+       tristate "Flex PIM4328 and compatibles"
+       help
+         If you say yes here you get hardware monitoring support for Flex
+         PIM4328, PIM4820 and PIM4006 Power Interface Modules.
+
+         This driver can also be built as a module. If so, the module will
+         be called pim4328.
+
 config SENSORS_PM6764TR
        tristate "ST PM6764TR"
        help
index 35d293bb44bf9b7de99fd251516a0b16d3540e5b..b3354518f66f9ab9f63f3065a33e0157437ca238 100644 (file)
@@ -40,3 +40,4 @@ obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
 obj-$(CONFIG_SENSORS_UCD9200)  += ucd9200.o
 obj-$(CONFIG_SENSORS_XDPE122)  += xdpe12284.o
 obj-$(CONFIG_SENSORS_ZL6100)   += zl6100.o
+obj-$(CONFIG_SENSORS_PIM4328)  += pim4328.o
diff --git a/drivers/hwmon/pmbus/pim4328.c b/drivers/hwmon/pmbus/pim4328.c
new file mode 100644 (file)
index 0000000..273ff6e
--- /dev/null
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for PIM4006, PIM4328 and PIM4820
+ *
+ * Copyright (c) 2021 Flextronics International Sweden AB
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pmbus.h>
+#include <linux/slab.h>
+#include "pmbus.h"
+
+enum chips { pim4006, pim4328, pim4820 };
+
+struct pim4328_data {
+       enum chips id;
+       struct pmbus_driver_info info;
+};
+
+#define to_pim4328_data(x)  container_of(x, struct pim4328_data, info)
+
+/* PIM4006 and PIM4328 */
+#define PIM4328_MFR_READ_VINA          0xd3
+#define PIM4328_MFR_READ_VINB          0xd4
+
+/* PIM4006 */
+#define PIM4328_MFR_READ_IINA          0xd6
+#define PIM4328_MFR_READ_IINB          0xd7
+#define PIM4328_MFR_FET_CHECKSTATUS    0xd9
+
+/* PIM4328 */
+#define PIM4328_MFR_STATUS_BITS                0xd5
+
+/* PIM4820 */
+#define PIM4328_MFR_READ_STATUS                0xd0
+
+static const struct i2c_device_id pim4328_id[] = {
+       {"bmr455", pim4328},
+       {"pim4006", pim4006},
+       {"pim4106", pim4006},
+       {"pim4206", pim4006},
+       {"pim4306", pim4006},
+       {"pim4328", pim4328},
+       {"pim4406", pim4006},
+       {"pim4820", pim4820},
+       {}
+};
+MODULE_DEVICE_TABLE(i2c, pim4328_id);
+
+static int pim4328_read_word_data(struct i2c_client *client, int page,
+                                 int phase, int reg)
+{
+       int ret;
+
+       if (page > 0)
+               return -ENXIO;
+
+       if (phase == 0xff)
+               return -ENODATA;
+
+       switch (reg) {
+       case PMBUS_READ_VIN:
+               ret = pmbus_read_word_data(client, page, phase,
+                                          phase == 0 ? PIM4328_MFR_READ_VINA
+                                                     : PIM4328_MFR_READ_VINB);
+               break;
+       case PMBUS_READ_IIN:
+               ret = pmbus_read_word_data(client, page, phase,
+                                          phase == 0 ? PIM4328_MFR_READ_IINA
+                                                     : PIM4328_MFR_READ_IINB);
+               break;
+       default:
+               ret = -ENODATA;
+       }
+
+       return ret;
+}
+
+static int pim4328_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+       const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+       struct pim4328_data *data = to_pim4328_data(info);
+       int ret, status;
+
+       if (page > 0)
+               return -ENXIO;
+
+       switch (reg) {
+       case PMBUS_STATUS_BYTE:
+               ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_BYTE);
+               if (ret < 0)
+                       return ret;
+               if (data->id == pim4006) {
+                       status = pmbus_read_word_data(client, page, 0xff,
+                                                     PIM4328_MFR_FET_CHECKSTATUS);
+                       if (status < 0)
+                               return status;
+                       if (status & 0x0630) /* Input UV */
+                               ret |= PB_STATUS_VIN_UV;
+               } else if (data->id == pim4328) {
+                       status = pmbus_read_byte_data(client, page,
+                                                     PIM4328_MFR_STATUS_BITS);
+                       if (status < 0)
+                               return status;
+                       if (status & 0x04) /* Input UV */
+                               ret |= PB_STATUS_VIN_UV;
+                       if (status & 0x40) /* Output UV */
+                               ret |= PB_STATUS_NONE_ABOVE;
+               } else if (data->id == pim4820) {
+                       status = pmbus_read_byte_data(client, page,
+                                                     PIM4328_MFR_READ_STATUS);
+                       if (status < 0)
+                               return status;
+                       if (status & 0x05) /* Input OV or OC */
+                               ret |= PB_STATUS_NONE_ABOVE;
+                       if (status & 0x1a) /* Input UV */
+                               ret |= PB_STATUS_VIN_UV;
+                       if (status & 0x40) /* OT */
+                               ret |= PB_STATUS_TEMPERATURE;
+               }
+               break;
+       default:
+               ret = -ENODATA;
+       }
+
+       return ret;
+}
+
+static int pim4328_probe(struct i2c_client *client)
+{
+       int status;
+       u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
+       const struct i2c_device_id *mid;
+       struct pim4328_data *data;
+       struct pmbus_driver_info *info;
+       struct pmbus_platform_data *pdata;
+       struct device *dev = &client->dev;
+
+       if (!i2c_check_functionality(client->adapter,
+                                    I2C_FUNC_SMBUS_READ_BYTE_DATA
+                                    | I2C_FUNC_SMBUS_BLOCK_DATA))
+               return -ENODEV;
+
+       data = devm_kzalloc(&client->dev, sizeof(struct pim4328_data),
+                           GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);
+       if (status < 0) {
+               dev_err(&client->dev, "Failed to read Manufacturer Model\n");
+               return status;
+       }
+       for (mid = pim4328_id; mid->name[0]; mid++) {
+               if (!strncasecmp(mid->name, device_id, strlen(mid->name)))
+                       break;
+       }
+       if (!mid->name[0]) {
+               dev_err(&client->dev, "Unsupported device\n");
+               return -ENODEV;
+       }
+
+       if (strcmp(client->name, mid->name))
+               dev_notice(&client->dev,
+                          "Device mismatch: Configured %s, detected %s\n",
+                          client->name, mid->name);
+
+       data->id = mid->driver_data;
+       info = &data->info;
+       info->pages = 1;
+       info->read_byte_data = pim4328_read_byte_data;
+       info->read_word_data = pim4328_read_word_data;
+
+       pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data),
+                            GFP_KERNEL);
+       if (!pdata)
+               return -ENOMEM;
+       dev->platform_data = pdata;
+       pdata->flags = PMBUS_NO_CAPABILITY | PMBUS_NO_WRITE_PROTECT;
+
+       switch (data->id) {
+       case pim4006:
+               info->phases[0] = 2;
+               info->func[0] = PMBUS_PHASE_VIRTUAL | PMBUS_HAVE_VIN
+                       | PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT;
+               info->pfunc[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN;
+               info->pfunc[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN;
+               break;
+       case pim4328:
+               info->phases[0] = 2;
+               info->func[0] = PMBUS_PHASE_VIRTUAL
+                       | PMBUS_HAVE_VCAP | PMBUS_HAVE_VIN
+                       | PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT;
+               info->pfunc[0] = PMBUS_HAVE_VIN;
+               info->pfunc[1] = PMBUS_HAVE_VIN;
+               info->format[PSC_VOLTAGE_IN] = direct;
+               info->format[PSC_TEMPERATURE] = direct;
+               info->format[PSC_CURRENT_OUT] = direct;
+               pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD;
+               break;
+       case pim4820:
+               info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_TEMP
+                       | PMBUS_HAVE_IIN;
+               info->format[PSC_VOLTAGE_IN] = direct;
+               info->format[PSC_TEMPERATURE] = direct;
+               info->format[PSC_CURRENT_IN] = direct;
+               pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD;
+               break;
+       default:
+               return -ENODEV;
+       }
+
+       return pmbus_do_probe(client, info);
+}
+
+static struct i2c_driver pim4328_driver = {
+       .driver = {
+                  .name = "pim4328",
+                  },
+       .probe_new = pim4328_probe,
+       .id_table = pim4328_id,
+};
+
+module_i2c_driver(pim4328_driver);
+
+MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");
+MODULE_DESCRIPTION("PMBus driver for PIM4006, PIM4328, PIM4820 power interface modules");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PMBUS);