* Author: Mike Looijmans <mike.looijmans@topic.nl>
  *
  * License: GPLv2
- *
- * This driver assumes the chip is wired as a dual current monitor, and
- * reports the voltage drop across two series resistors. It also reports
- * the chip's internal temperature and Vcc power supply voltage.
  */
 
 #include <linux/bitops.h>
 #include <linux/i2c.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/of.h>
 
 #define LTC2990_STATUS 0x00
 #define LTC2990_CONTROL        0x01
 #define LTC2990_V4_MSB 0x0C
 #define LTC2990_VCC_MSB        0x0E
 
-#define LTC2990_CONTROL_KELVIN         BIT(7)
-#define LTC2990_CONTROL_SINGLE         BIT(6)
-#define LTC2990_CONTROL_MEASURE_ALL    (0x3 << 3)
-#define LTC2990_CONTROL_MODE_CURRENT   0x06
-#define LTC2990_CONTROL_MODE_VOLTAGE   0x07
+#define LTC2990_IN0    BIT(0)
+#define LTC2990_IN1    BIT(1)
+#define LTC2990_IN2    BIT(2)
+#define LTC2990_IN3    BIT(3)
+#define LTC2990_IN4    BIT(4)
+#define LTC2990_CURR1  BIT(5)
+#define LTC2990_CURR2  BIT(6)
+#define LTC2990_TEMP1  BIT(7)
+#define LTC2990_TEMP2  BIT(8)
+#define LTC2990_TEMP3  BIT(9)
+#define LTC2990_NONE   0
+#define LTC2990_ALL    GENMASK(9, 0)
+
+#define LTC2990_MODE0_SHIFT    0
+#define LTC2990_MODE0_MASK     GENMASK(2, 0)
+#define LTC2990_MODE1_SHIFT    3
+#define LTC2990_MODE1_MASK     GENMASK(1, 0)
+
+/* Enabled measurements for mode bits 2..0 */
+static const int ltc2990_attrs_ena_0[] = {
+       LTC2990_IN1 | LTC2990_IN2 | LTC2990_TEMP3,
+       LTC2990_CURR1 | LTC2990_TEMP3,
+       LTC2990_CURR1 | LTC2990_IN3 | LTC2990_IN4,
+       LTC2990_TEMP2 | LTC2990_IN3 | LTC2990_IN4,
+       LTC2990_TEMP2 | LTC2990_CURR2,
+       LTC2990_TEMP2 | LTC2990_TEMP3,
+       LTC2990_CURR1 | LTC2990_CURR2,
+       LTC2990_IN1 | LTC2990_IN2 | LTC2990_IN3 | LTC2990_IN4
+};
+
+/* Enabled measurements for mode bits 4..3 */
+static const int ltc2990_attrs_ena_1[] = {
+       LTC2990_NONE,
+       LTC2990_TEMP2 | LTC2990_IN1 | LTC2990_CURR1,
+       LTC2990_TEMP3 | LTC2990_IN3 | LTC2990_CURR2,
+       LTC2990_ALL
+};
+
+struct ltc2990_data {
+       struct i2c_client *i2c;
+       u32 mode[2];
+};
 
 /* Return the converted value from the given register in uV or mC */
-static int ltc2990_get_value(struct i2c_client *i2c, u8 reg, int *result)
+static int ltc2990_get_value(struct i2c_client *i2c, int index, int *result)
 {
        int val;
+       u8 reg;
+
+       switch (index) {
+       case LTC2990_IN0:
+               reg = LTC2990_VCC_MSB;
+               break;
+       case LTC2990_IN1:
+       case LTC2990_CURR1:
+       case LTC2990_TEMP2:
+               reg = LTC2990_V1_MSB;
+               break;
+       case LTC2990_IN2:
+               reg = LTC2990_V2_MSB;
+               break;
+       case LTC2990_IN3:
+       case LTC2990_CURR2:
+       case LTC2990_TEMP3:
+               reg = LTC2990_V3_MSB;
+               break;
+       case LTC2990_IN4:
+               reg = LTC2990_V4_MSB;
+               break;
+       case LTC2990_TEMP1:
+               reg = LTC2990_TINT_MSB;
+               break;
+       default:
+               return -EINVAL;
+       }
 
        val = i2c_smbus_read_word_swapped(i2c, reg);
        if (unlikely(val < 0))
                return val;
 
-       switch (reg) {
-       case LTC2990_TINT_MSB:
-               /* internal temp, 0.0625 degrees/LSB, 13-bit  */
+       switch (index) {
+       case LTC2990_TEMP1:
+       case LTC2990_TEMP2:
+       case LTC2990_TEMP3:
+               /* temp, 0.0625 degrees/LSB */
                *result = sign_extend32(val, 12) * 1000 / 16;
                break;
-       case LTC2990_V1_MSB:
-       case LTC2990_V3_MSB:
-                /* Vx-Vy, 19.42uV/LSB. Depends on mode. */
+       case LTC2990_CURR1:
+       case LTC2990_CURR2:
+                /* Vx-Vy, 19.42uV/LSB */
                *result = sign_extend32(val, 14) * 1942 / 100;
                break;
-       case LTC2990_VCC_MSB:
-               /* Vcc, 305.18μV/LSB, 2.5V offset */
+       case LTC2990_IN0:
+               /* Vcc, 305.18uV/LSB, 2.5V offset */
                *result = sign_extend32(val, 14) * 30518 / (100 * 1000) + 2500;
                break;
+       case LTC2990_IN1:
+       case LTC2990_IN2:
+       case LTC2990_IN3:
+       case LTC2990_IN4:
+               /* Vx, 305.18uV/LSB */
+               *result = sign_extend32(val, 14) * 30518 / (100 * 1000);
+               break;
        default:
                return -EINVAL; /* won't happen, keep compiler happy */
        }
                                  struct device_attribute *da, char *buf)
 {
        struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+       struct ltc2990_data *data = dev_get_drvdata(dev);
        int value;
        int ret;
 
-       ret = ltc2990_get_value(dev_get_drvdata(dev), attr->index, &value);
+       ret = ltc2990_get_value(data->i2c, attr->index, &value);
        if (unlikely(ret < 0))
                return ret;
 
        return snprintf(buf, PAGE_SIZE, "%d\n", value);
 }
 
+static umode_t ltc2990_attrs_visible(struct kobject *kobj,
+                                    struct attribute *a, int n)
+{
+       struct device *dev = container_of(kobj, struct device, kobj);
+       struct ltc2990_data *data = dev_get_drvdata(dev);
+       struct device_attribute *da =
+                       container_of(a, struct device_attribute, attr);
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+
+       int attrs_mask = LTC2990_IN0 | LTC2990_TEMP1 |
+                        (ltc2990_attrs_ena_0[data->mode[0]] &
+                         ltc2990_attrs_ena_1[data->mode[1]]);
+
+       if (attr->index & attrs_mask)
+               return a->mode;
+
+       return 0;
+}
+
 static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, ltc2990_show_value, NULL,
-                         LTC2990_TINT_MSB);
+                         LTC2990_TEMP1);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, ltc2990_show_value, NULL,
+                         LTC2990_TEMP2);
+static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, ltc2990_show_value, NULL,
+                         LTC2990_TEMP3);
 static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ltc2990_show_value, NULL,
-                         LTC2990_V1_MSB);
+                         LTC2990_CURR1);
 static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO, ltc2990_show_value, NULL,
-                         LTC2990_V3_MSB);
+                         LTC2990_CURR2);
 static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ltc2990_show_value, NULL,
-                         LTC2990_VCC_MSB);
+                         LTC2990_IN0);
+static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ltc2990_show_value, NULL,
+                         LTC2990_IN1);
+static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ltc2990_show_value, NULL,
+                         LTC2990_IN2);
+static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, ltc2990_show_value, NULL,
+                         LTC2990_IN3);
+static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, ltc2990_show_value, NULL,
+                         LTC2990_IN4);
 
 static struct attribute *ltc2990_attrs[] = {
        &sensor_dev_attr_temp1_input.dev_attr.attr,
+       &sensor_dev_attr_temp2_input.dev_attr.attr,
+       &sensor_dev_attr_temp3_input.dev_attr.attr,
        &sensor_dev_attr_curr1_input.dev_attr.attr,
        &sensor_dev_attr_curr2_input.dev_attr.attr,
        &sensor_dev_attr_in0_input.dev_attr.attr,
+       &sensor_dev_attr_in1_input.dev_attr.attr,
+       &sensor_dev_attr_in2_input.dev_attr.attr,
+       &sensor_dev_attr_in3_input.dev_attr.attr,
+       &sensor_dev_attr_in4_input.dev_attr.attr,
        NULL,
 };
-ATTRIBUTE_GROUPS(ltc2990);
+
+static const struct attribute_group ltc2990_group = {
+       .attrs = ltc2990_attrs,
+       .is_visible = ltc2990_attrs_visible,
+};
+__ATTRIBUTE_GROUPS(ltc2990);
 
 static int ltc2990_i2c_probe(struct i2c_client *i2c,
                             const struct i2c_device_id *id)
 {
        int ret;
        struct device *hwmon_dev;
+       struct ltc2990_data *data;
+       struct device_node *of_node = i2c->dev.of_node;
 
        if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
                                     I2C_FUNC_SMBUS_WORD_DATA))
                return -ENODEV;
 
-       /* Setup continuous mode, current monitor */
+       data = devm_kzalloc(&i2c->dev, sizeof(struct ltc2990_data), GFP_KERNEL);
+       if (unlikely(!data))
+               return -ENOMEM;
+
+       data->i2c = i2c;
+
+       if (of_node) {
+               ret = of_property_read_u32_array(of_node, "lltc,meas-mode",
+                                                data->mode, 2);
+               if (ret < 0)
+                       return ret;
+
+               if (data->mode[0] & ~LTC2990_MODE0_MASK ||
+                   data->mode[1] & ~LTC2990_MODE1_MASK)
+                       return -EINVAL;
+       } else {
+               ret = i2c_smbus_read_byte_data(i2c, LTC2990_CONTROL);
+               if (ret < 0)
+                       return ret;
+
+               data->mode[0] = ret >> LTC2990_MODE0_SHIFT & LTC2990_MODE0_MASK;
+               data->mode[1] = ret >> LTC2990_MODE1_SHIFT & LTC2990_MODE1_MASK;
+       }
+
+       /* Setup continuous mode */
        ret = i2c_smbus_write_byte_data(i2c, LTC2990_CONTROL,
-                                       LTC2990_CONTROL_MEASURE_ALL |
-                                       LTC2990_CONTROL_MODE_CURRENT);
+                                       data->mode[0] << LTC2990_MODE0_SHIFT |
+                                       data->mode[1] << LTC2990_MODE1_SHIFT);
        if (ret < 0) {
                dev_err(&i2c->dev, "Error: Failed to set control mode.\n");
                return ret;
 
        hwmon_dev = devm_hwmon_device_register_with_groups(&i2c->dev,
                                                           i2c->name,
-                                                          i2c,
+                                                          data,
                                                           ltc2990_groups);
 
        return PTR_ERR_OR_ZERO(hwmon_dev);