hwmon: (pmbus) Add client driver for IR35221
authorSamuel Mendoza-Jonas <sam@mendozajonas.com>
Mon, 1 May 2017 00:39:01 +0000 (10:39 +1000)
committerGuenter Roeck <linux@roeck-us.net>
Mon, 12 Jun 2017 00:08:19 +0000 (17:08 -0700)
IR35221 is a Digital DC-DC Multiphase Converter

Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
[groeck: Preserve alphabetic order in Kconfig;
add missing break statements (from Dan Carpenter);
add missing error checks]
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Documentation/hwmon/ir35221 [new file with mode: 0644]
drivers/hwmon/pmbus/Kconfig
drivers/hwmon/pmbus/Makefile
drivers/hwmon/pmbus/ir35221.c [new file with mode: 0644]

diff --git a/Documentation/hwmon/ir35221 b/Documentation/hwmon/ir35221
new file mode 100644 (file)
index 0000000..f7e1127
--- /dev/null
@@ -0,0 +1,87 @@
+Kernel driver ir35221
+=====================
+
+Supported chips:
+  * Infinion IR35221
+    Prefix: 'ir35221'
+    Addresses scanned: -
+    Datasheet: Datasheet is not publicly available.
+
+Author: Samuel Mendoza-Jonas <sam@mendozajonas.com>
+
+
+Description
+-----------
+
+IR35221 is a Digital DC-DC Multiphase Converter
+
+
+Usage Notes
+-----------
+
+This driver does not probe for PMBus devices. You will have to instantiate
+devices explicitly.
+
+Example: the following commands will load the driver for an IR35221
+at address 0x70 on I2C bus #4:
+
+# modprobe ir35221
+# echo ir35221 0x70 > /sys/bus/i2c/devices/i2c-4/new_device
+
+
+Sysfs attributes
+----------------
+
+curr1_label            "iin"
+curr1_input            Measured input current
+curr1_max              Maximum current
+curr1_max_alarm                Current high alarm
+
+curr[2-3]_label                "iout[1-2]"
+curr[2-3]_input                Measured output current
+curr[2-3]_crit         Critical maximum current
+curr[2-3]_crit_alarm   Current critical high alarm
+curr[2-3]_highest      Highest output current
+curr[2-3]_lowest       Lowest output current
+curr[2-3]_max          Maximum current
+curr[2-3]_max_alarm    Current high alarm
+
+in1_label              "vin"
+in1_input              Measured input voltage
+in1_crit               Critical maximum input voltage
+in1_crit_alarm         Input voltage critical high alarm
+in1_highest            Highest input voltage
+in1_lowest             Lowest input voltage
+in1_min                        Minimum input voltage
+in1_min_alarm          Input voltage low alarm
+
+in[2-3]_label          "vout[1-2]"
+in[2-3]_input          Measured output voltage
+in[2-3]_lcrit          Critical minimum output voltage
+in[2-3]_lcrit_alarm    Output voltage critical low alarm
+in[2-3]_crit           Critical maximum output voltage
+in[2-3]_crit_alarm     Output voltage critical high alarm
+in[2-3]_highest                Highest output voltage
+in[2-3]_lowest         Lowest output voltage
+in[2-3]_max            Maximum output voltage
+in[2-3]_max_alarm      Output voltage high alarm
+in[2-3]_min            Minimum output voltage
+in[2-3]_min_alarm      Output voltage low alarm
+
+power1_label           "pin"
+power1_input           Measured input power
+power1_alarm           Input power high alarm
+power1_max             Input power limit
+
+power[2-3]_label       "pout[1-2]"
+power[2-3]_input       Measured output power
+power[2-3]_max         Output power limit
+power[2-3]_max_alarm   Output power high alarm
+
+temp[1-2]_input                Measured temperature
+temp[1-2]_crit         Critical high temperature
+temp[1-2]_crit_alarm   Chip temperature critical high alarm
+temp[1-2]_highest      Highest temperature
+temp[1-2]_lowest       Lowest temperature
+temp[1-2]_max          Maximum temperature
+temp[1-2]_max_alarm    Chip temperature high alarm
index cad1229b7e17220dbc51aa0282338cca34aa537f..68d717a3fd59ca675463e9942fe5077cc2e93ae7 100644 (file)
@@ -37,6 +37,16 @@ config SENSORS_ADM1275
          This driver can also be built as a module. If so, the module will
          be called adm1275.
 
+config SENSORS_IR35221
+       tristate "Infineon IR35221"
+       default n
+       help
+         If you say yes here you get hardware monitoring support for the
+         Infineon IR35221 controller.
+
+         This driver can also be built as a module. If so, the module will
+         be called ir35521.
+
 config SENSORS_LM25066
        tristate "National Semiconductor LM25066 and compatibles"
        default n
index 562132054aafe9e7d5154ab582ec6080f84a19a2..75bb7ca619d99923ed35e5cd046297369477da1a 100644 (file)
@@ -5,6 +5,7 @@
 obj-$(CONFIG_PMBUS)            += pmbus_core.o
 obj-$(CONFIG_SENSORS_PMBUS)    += pmbus.o
 obj-$(CONFIG_SENSORS_ADM1275)  += adm1275.o
+obj-$(CONFIG_SENSORS_IR35221)  += ir35221.o
 obj-$(CONFIG_SENSORS_LM25066)  += lm25066.o
 obj-$(CONFIG_SENSORS_LTC2978)  += ltc2978.o
 obj-$(CONFIG_SENSORS_LTC3815)  += ltc3815.o
diff --git a/drivers/hwmon/pmbus/ir35221.c b/drivers/hwmon/pmbus/ir35221.c
new file mode 100644 (file)
index 0000000..8b906b4
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ * Hardware monitoring driver for IR35221
+ *
+ * Copyright (C) IBM Corporation 2017.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include "pmbus.h"
+
+#define IR35221_MFR_VIN_PEAK           0xc5
+#define IR35221_MFR_VOUT_PEAK          0xc6
+#define IR35221_MFR_IOUT_PEAK          0xc7
+#define IR35221_MFR_TEMP_PEAK          0xc8
+#define IR35221_MFR_VIN_VALLEY         0xc9
+#define IR35221_MFR_VOUT_VALLEY                0xca
+#define IR35221_MFR_IOUT_VALLEY                0xcb
+#define IR35221_MFR_TEMP_VALLEY                0xcc
+
+static long ir35221_reg2data(int data, enum pmbus_sensor_classes class)
+{
+       s16 exponent;
+       s32 mantissa;
+       long val;
+
+       /* We only modify LINEAR11 formats */
+       exponent = ((s16)data) >> 11;
+       mantissa = ((s16)((data & 0x7ff) << 5)) >> 5;
+
+       val = mantissa * 1000L;
+
+       /* scale result to micro-units for power sensors */
+       if (class == PSC_POWER)
+               val = val * 1000L;
+
+       if (exponent >= 0)
+               val <<= exponent;
+       else
+               val >>= -exponent;
+
+       return val;
+}
+
+#define MAX_MANTISSA   (1023 * 1000)
+#define MIN_MANTISSA   (511 * 1000)
+
+static u16 ir35221_data2reg(long val, enum pmbus_sensor_classes class)
+{
+       s16 exponent = 0, mantissa;
+       bool negative = false;
+
+       if (val == 0)
+               return 0;
+
+       if (val < 0) {
+               negative = true;
+               val = -val;
+       }
+
+       /* Power is in uW. Convert to mW before converting. */
+       if (class == PSC_POWER)
+               val = DIV_ROUND_CLOSEST(val, 1000L);
+
+       /* Reduce large mantissa until it fits into 10 bit */
+       while (val >= MAX_MANTISSA && exponent < 15) {
+               exponent++;
+               val >>= 1;
+       }
+       /* Increase small mantissa to improve precision */
+       while (val < MIN_MANTISSA && exponent > -15) {
+               exponent--;
+               val <<= 1;
+       }
+
+       /* Convert mantissa from milli-units to units */
+       mantissa = DIV_ROUND_CLOSEST(val, 1000);
+
+       /* Ensure that resulting number is within range */
+       if (mantissa > 0x3ff)
+               mantissa = 0x3ff;
+
+       /* restore sign */
+       if (negative)
+               mantissa = -mantissa;
+
+       /* Convert to 5 bit exponent, 11 bit mantissa */
+       return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800);
+}
+
+static u16 ir35221_scale_result(s16 data, int shift,
+                               enum pmbus_sensor_classes class)
+{
+       long val;
+
+       val = ir35221_reg2data(data, class);
+
+       if (shift < 0)
+               val >>= -shift;
+       else
+               val <<= shift;
+
+       return ir35221_data2reg(val, class);
+}
+
+static int ir35221_read_word_data(struct i2c_client *client, int page, int reg)
+{
+       int ret;
+
+       switch (reg) {
+       case PMBUS_IOUT_OC_FAULT_LIMIT:
+       case PMBUS_IOUT_OC_WARN_LIMIT:
+               ret = pmbus_read_word_data(client, page, reg);
+               if (ret < 0)
+                       break;
+               ret = ir35221_scale_result(ret, 1, PSC_CURRENT_OUT);
+               break;
+       case PMBUS_VIN_OV_FAULT_LIMIT:
+       case PMBUS_VIN_OV_WARN_LIMIT:
+       case PMBUS_VIN_UV_WARN_LIMIT:
+               ret = pmbus_read_word_data(client, page, reg);
+               ret = ir35221_scale_result(ret, -4, PSC_VOLTAGE_IN);
+               break;
+       case PMBUS_IIN_OC_WARN_LIMIT:
+               ret = pmbus_read_word_data(client, page, reg);
+               if (ret < 0)
+                       break;
+               ret = ir35221_scale_result(ret, -1, PSC_CURRENT_IN);
+               break;
+       case PMBUS_READ_VIN:
+               ret = pmbus_read_word_data(client, page, PMBUS_READ_VIN);
+               if (ret < 0)
+                       break;
+               ret = ir35221_scale_result(ret, -5, PSC_VOLTAGE_IN);
+               break;
+       case PMBUS_READ_IIN:
+               ret = pmbus_read_word_data(client, page, PMBUS_READ_IIN);
+               if (ret < 0)
+                       break;
+               if (page == 0)
+                       ret = ir35221_scale_result(ret, -4, PSC_CURRENT_IN);
+               else
+                       ret = ir35221_scale_result(ret, -5, PSC_CURRENT_IN);
+               break;
+       case PMBUS_READ_POUT:
+               ret = pmbus_read_word_data(client, page, PMBUS_READ_POUT);
+               if (ret < 0)
+                       break;
+               ret = ir35221_scale_result(ret, -1, PSC_POWER);
+               break;
+       case PMBUS_READ_PIN:
+               ret = pmbus_read_word_data(client, page, PMBUS_READ_PIN);
+               if (ret < 0)
+                       break;
+               ret = ir35221_scale_result(ret, -1, PSC_POWER);
+               break;
+       case PMBUS_READ_IOUT:
+               ret = pmbus_read_word_data(client, page, PMBUS_READ_IOUT);
+               if (ret < 0)
+                       break;
+               if (page == 0)
+                       ret = ir35221_scale_result(ret, -1, PSC_CURRENT_OUT);
+               else
+                       ret = ir35221_scale_result(ret, -2, PSC_CURRENT_OUT);
+               break;
+       case PMBUS_VIRT_READ_VIN_MAX:
+               ret = pmbus_read_word_data(client, page, IR35221_MFR_VIN_PEAK);
+               if (ret < 0)
+                       break;
+               ret = ir35221_scale_result(ret, -5, PSC_VOLTAGE_IN);
+               break;
+       case PMBUS_VIRT_READ_VOUT_MAX:
+               ret = pmbus_read_word_data(client, page, IR35221_MFR_VOUT_PEAK);
+               break;
+       case PMBUS_VIRT_READ_IOUT_MAX:
+               ret = pmbus_read_word_data(client, page, IR35221_MFR_IOUT_PEAK);
+               if (ret < 0)
+                       break;
+               if (page == 0)
+                       ret = ir35221_scale_result(ret, -1, PSC_CURRENT_IN);
+               else
+                       ret = ir35221_scale_result(ret, -2, PSC_CURRENT_IN);
+               break;
+       case PMBUS_VIRT_READ_TEMP_MAX:
+               ret = pmbus_read_word_data(client, page, IR35221_MFR_TEMP_PEAK);
+               break;
+       case PMBUS_VIRT_READ_VIN_MIN:
+               ret = pmbus_read_word_data(client, page,
+                                          IR35221_MFR_VIN_VALLEY);
+               if (ret < 0)
+                       break;
+               ret = ir35221_scale_result(ret, -5, PSC_VOLTAGE_IN);
+               break;
+       case PMBUS_VIRT_READ_VOUT_MIN:
+               ret = pmbus_read_word_data(client, page,
+                                          IR35221_MFR_VOUT_VALLEY);
+               break;
+       case PMBUS_VIRT_READ_IOUT_MIN:
+               ret = pmbus_read_word_data(client, page,
+                                          IR35221_MFR_IOUT_VALLEY);
+               if (ret < 0)
+                       break;
+               if (page == 0)
+                       ret = ir35221_scale_result(ret, -1, PSC_CURRENT_IN);
+               else
+                       ret = ir35221_scale_result(ret, -2, PSC_CURRENT_IN);
+               break;
+       case PMBUS_VIRT_READ_TEMP_MIN:
+               ret = pmbus_read_word_data(client, page,
+                                          IR35221_MFR_TEMP_VALLEY);
+               break;
+       default:
+               ret = -ENODATA;
+               break;
+       }
+
+       return ret;
+}
+
+static int ir35221_write_word_data(struct i2c_client *client, int page, int reg,
+                                  u16 word)
+{
+       int ret;
+       u16 val;
+
+       switch (reg) {
+       case PMBUS_IOUT_OC_FAULT_LIMIT:
+       case PMBUS_IOUT_OC_WARN_LIMIT:
+               val = ir35221_scale_result(word, -1, PSC_CURRENT_OUT);
+               ret = pmbus_write_word_data(client, page, reg, val);
+               break;
+       case PMBUS_VIN_OV_FAULT_LIMIT:
+       case PMBUS_VIN_OV_WARN_LIMIT:
+       case PMBUS_VIN_UV_WARN_LIMIT:
+               val = ir35221_scale_result(word, 4, PSC_VOLTAGE_IN);
+               ret = pmbus_write_word_data(client, page, reg, val);
+               break;
+       case PMBUS_IIN_OC_WARN_LIMIT:
+               val = ir35221_scale_result(word, 1, PSC_CURRENT_IN);
+               ret = pmbus_write_word_data(client, page, reg, val);
+               break;
+       default:
+               ret = -ENODATA;
+               break;
+       }
+
+       return ret;
+}
+
+static int ir35221_probe(struct i2c_client *client,
+                        const struct i2c_device_id *id)
+{
+       struct pmbus_driver_info *info;
+       u8 buf[I2C_SMBUS_BLOCK_MAX];
+       int ret;
+
+       if (!i2c_check_functionality(client->adapter,
+                                    I2C_FUNC_SMBUS_READ_BYTE_DATA
+                               | I2C_FUNC_SMBUS_READ_WORD_DATA
+                               | I2C_FUNC_SMBUS_READ_BLOCK_DATA))
+               return -ENODEV;
+
+       ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
+       if (ret < 0) {
+               dev_err(&client->dev, "Failed to read PMBUS_MFR_ID\n");
+               return ret;
+       }
+       if (ret != 2 || strncmp(buf, "RI", strlen("RI"))) {
+               dev_err(&client->dev, "MFR_ID unrecognised\n");
+               return -ENODEV;
+       }
+
+       ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
+       if (ret < 0) {
+               dev_err(&client->dev, "Failed to read PMBUS_MFR_MODEL\n");
+               return ret;
+       }
+       if (ret != 2 || !(buf[0] == 0x6c && buf[1] == 0x00)) {
+               dev_err(&client->dev, "MFR_MODEL unrecognised\n");
+               return -ENODEV;
+       }
+
+       info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info),
+                           GFP_KERNEL);
+       if (!info)
+               return -ENOMEM;
+
+       info->write_word_data = ir35221_write_word_data;
+       info->read_word_data = ir35221_read_word_data;
+
+       info->pages = 2;
+       info->format[PSC_VOLTAGE_IN] = linear;
+       info->format[PSC_VOLTAGE_OUT] = linear;
+       info->format[PSC_CURRENT_IN] = linear;
+       info->format[PSC_CURRENT_OUT] = linear;
+       info->format[PSC_POWER] = linear;
+       info->format[PSC_TEMPERATURE] = linear;
+
+       info->func[0] = PMBUS_HAVE_VIN
+               | PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN
+               | PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN
+               | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP
+               | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
+               | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP;
+       info->func[1] = info->func[0];
+
+       return pmbus_do_probe(client, id, info);
+}
+
+static const struct i2c_device_id ir35221_id[] = {
+       {"ir35221", 0},
+       {}
+};
+
+MODULE_DEVICE_TABLE(i2c, ir35221_id);
+
+static struct i2c_driver ir35221_driver = {
+       .driver = {
+               .name   = "ir35221",
+       },
+       .probe          = ir35221_probe,
+       .remove         = pmbus_do_remove,
+       .id_table       = ir35221_id,
+};
+
+module_i2c_driver(ir35221_driver);
+
+MODULE_AUTHOR("Samuel Mendoza-Jonas <sam@mendozajonas.com");
+MODULE_DESCRIPTION("PMBus driver for IR35221");
+MODULE_LICENSE("GPL");