hwmon: (pmbus/ltc2978) Add support for LTC3880 to LTC2978 driver
[cascardo/linux.git] / drivers / hwmon / pmbus / ltc2978.c
index 02b2e49..820fff4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Hardware monitoring driver for LTC2978
+ * Hardware monitoring driver for LTC2978 and LTC3880
  *
  * Copyright (c) 2011 Ericsson AB.
  *
 #include <linux/i2c.h>
 #include "pmbus.h"
 
-enum chips { ltc2978 };
+enum chips { ltc2978, ltc3880 };
 
+/* LTC2978 and LTC3880 */
 #define LTC2978_MFR_VOUT_PEAK          0xdd
 #define LTC2978_MFR_VIN_PEAK           0xde
 #define LTC2978_MFR_TEMPERATURE_PEAK   0xdf
 #define LTC2978_MFR_SPECIAL_ID         0xe7
 
+/* LTC2978 only */
 #define LTC2978_MFR_VOUT_MIN           0xfb
 #define LTC2978_MFR_VIN_MIN            0xfc
 #define LTC2978_MFR_TEMPERATURE_MIN    0xfd
 
+/* LTC3880 only */
+#define LTC3880_MFR_IOUT_PEAK          0xd7
+#define LTC3880_MFR_CLEAR_PEAKS                0xe3
+#define LTC3880_MFR_TEMPERATURE2_PEAK  0xf4
+
 #define LTC2978_ID_REV1                        0x0121
 #define LTC2978_ID_REV2                        0x0122
+#define LTC3880_ID                     0x4000
+#define LTC3880_ID_MASK                        0xff00
 
 /*
  * LTC2978 clears peak data whenever the CLEAR_FAULTS command is executed, which
@@ -52,6 +61,8 @@ struct ltc2978_data {
        int vin_min, vin_max;
        int temp_min, temp_max;
        int vout_min[8], vout_max[8];
+       int iout_max[2];
+       int temp2_max[2];
        struct pmbus_driver_info info;
 };
 
@@ -70,7 +81,8 @@ static inline int lin11_to_val(int data)
        return (e < 0 ? m >> -e : m << e);
 }
 
-static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg)
+static int ltc2978_read_word_data_common(struct i2c_client *client, int page,
+                                        int reg)
 {
        const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
        struct ltc2978_data *data = to_ltc2978_data(info);
@@ -106,6 +118,25 @@ static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg)
                        ret = data->temp_max;
                }
                break;
+       case PMBUS_VIRT_RESET_VOUT_HISTORY:
+       case PMBUS_VIRT_RESET_VIN_HISTORY:
+       case PMBUS_VIRT_RESET_TEMP_HISTORY:
+               ret = 0;
+               break;
+       default:
+               ret = -ENODATA;
+               break;
+       }
+       return ret;
+}
+
+static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg)
+{
+       const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+       struct ltc2978_data *data = to_ltc2978_data(info);
+       int ret;
+
+       switch (reg) {
        case PMBUS_VIRT_READ_VIN_MIN:
                ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_MIN);
                if (ret >= 0) {
@@ -140,18 +171,74 @@ static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg)
                        ret = data->temp_min;
                }
                break;
-       case PMBUS_VIRT_RESET_VOUT_HISTORY:
-       case PMBUS_VIRT_RESET_VIN_HISTORY:
-       case PMBUS_VIRT_RESET_TEMP_HISTORY:
+       case PMBUS_VIRT_READ_IOUT_MAX:
+       case PMBUS_VIRT_RESET_IOUT_HISTORY:
+       case PMBUS_VIRT_READ_TEMP2_MAX:
+       case PMBUS_VIRT_RESET_TEMP2_HISTORY:
+               ret = -ENXIO;
+               break;
+       default:
+               ret = ltc2978_read_word_data_common(client, page, reg);
+               break;
+       }
+       return ret;
+}
+
+static int ltc3880_read_word_data(struct i2c_client *client, int page, int reg)
+{
+       const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+       struct ltc2978_data *data = to_ltc2978_data(info);
+       int ret;
+
+       switch (reg) {
+       case PMBUS_VIRT_READ_IOUT_MAX:
+               ret = pmbus_read_word_data(client, page, LTC3880_MFR_IOUT_PEAK);
+               if (ret >= 0) {
+                       if (lin11_to_val(ret)
+                           > lin11_to_val(data->iout_max[page]))
+                               data->iout_max[page] = ret;
+                       ret = data->iout_max[page];
+               }
+               break;
+       case PMBUS_VIRT_READ_TEMP2_MAX:
+               ret = pmbus_read_word_data(client, page,
+                                          LTC3880_MFR_TEMPERATURE2_PEAK);
+               if (ret >= 0) {
+                       if (lin11_to_val(ret)
+                           > lin11_to_val(data->temp2_max[page]))
+                               data->temp2_max[page] = ret;
+                       ret = data->temp2_max[page];
+               }
+               break;
+       case PMBUS_VIRT_READ_VIN_MIN:
+       case PMBUS_VIRT_READ_VOUT_MIN:
+       case PMBUS_VIRT_READ_TEMP_MIN:
+               ret = -ENXIO;
+               break;
+       case PMBUS_VIRT_RESET_IOUT_HISTORY:
+       case PMBUS_VIRT_RESET_TEMP2_HISTORY:
                ret = 0;
                break;
        default:
-               ret = -ENODATA;
+               ret = ltc2978_read_word_data_common(client, page, reg);
                break;
        }
        return ret;
 }
 
+static int ltc2978_clear_peaks(struct i2c_client *client, int page,
+                              enum chips id)
+{
+       int ret;
+
+       if (id == ltc2978)
+               ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS);
+       else
+               ret = pmbus_write_byte(client, 0, LTC3880_MFR_CLEAR_PEAKS);
+
+       return ret;
+}
+
 static int ltc2978_write_word_data(struct i2c_client *client, int page,
                                    int reg, u16 word)
 {
@@ -160,20 +247,28 @@ static int ltc2978_write_word_data(struct i2c_client *client, int page,
        int ret;
 
        switch (reg) {
+       case PMBUS_VIRT_RESET_IOUT_HISTORY:
+               data->iout_max[page] = 0x7fff;
+               ret = ltc2978_clear_peaks(client, page, data->id);
+               break;
+       case PMBUS_VIRT_RESET_TEMP2_HISTORY:
+               data->temp2_max[page] = 0x7fff;
+               ret = ltc2978_clear_peaks(client, page, data->id);
+               break;
        case PMBUS_VIRT_RESET_VOUT_HISTORY:
                data->vout_min[page] = 0xffff;
                data->vout_max[page] = 0;
-               ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS);
+               ret = ltc2978_clear_peaks(client, page, data->id);
                break;
        case PMBUS_VIRT_RESET_VIN_HISTORY:
                data->vin_min = 0x7bff;
                data->vin_max = 0;
-               ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS);
+               ret = ltc2978_clear_peaks(client, page, data->id);
                break;
        case PMBUS_VIRT_RESET_TEMP_HISTORY:
                data->temp_min = 0x7bff;
                data->temp_max = 0x7fff;
-               ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS);
+               ret = ltc2978_clear_peaks(client, page, data->id);
                break;
        default:
                ret = -ENODATA;
@@ -184,6 +279,7 @@ static int ltc2978_write_word_data(struct i2c_client *client, int page,
 
 static const struct i2c_device_id ltc2978_id[] = {
        {"ltc2978", ltc2978},
+       {"ltc3880", ltc3880},
        {}
 };
 MODULE_DEVICE_TABLE(i2c, ltc2978_id);
@@ -211,6 +307,8 @@ static int ltc2978_probe(struct i2c_client *client,
 
        if (chip_id == LTC2978_ID_REV1 || chip_id == LTC2978_ID_REV2) {
                data->id = ltc2978;
+       } else if ((chip_id & LTC3880_ID_MASK) == LTC3880_ID) {
+               data->id = ltc3880;
        } else {
                dev_err(&client->dev, "Unsupported chip ID 0x%x\n", chip_id);
                ret = -ENODEV;
@@ -223,7 +321,6 @@ static int ltc2978_probe(struct i2c_client *client,
                         ltc2978_id[data->id].name);
 
        info = &data->info;
-       info->read_word_data = ltc2978_read_word_data;
        info->write_word_data = ltc2978_write_word_data;
 
        data->vout_min[0] = 0xffff;
@@ -233,6 +330,7 @@ static int ltc2978_probe(struct i2c_client *client,
 
        switch (id->driver_data) {
        case ltc2978:
+               info->read_word_data = ltc2978_read_word_data;
                info->pages = 8;
                info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
                  | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
@@ -243,6 +341,21 @@ static int ltc2978_probe(struct i2c_client *client,
                        data->vout_min[i] = 0xffff;
                }
                break;
+       case ltc3880:
+               info->read_word_data = ltc3880_read_word_data;
+               info->pages = 2;
+               info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN
+                 | PMBUS_HAVE_STATUS_INPUT
+                 | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
+                 | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
+                 | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP
+                 | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP;
+               info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
+                 | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
+                 | PMBUS_HAVE_POUT
+                 | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
+               data->vout_min[1] = 0xffff;
+               break;
        default:
                ret = -ENODEV;
                goto err_mem;
@@ -289,7 +402,7 @@ static void __exit ltc2978_exit(void)
 }
 
 MODULE_AUTHOR("Guenter Roeck");
-MODULE_DESCRIPTION("PMBus driver for LTC2978");
+MODULE_DESCRIPTION("PMBus driver for LTC2978 and LTC3880");
 MODULE_LICENSE("GPL");
 module_init(ltc2978_init);
 module_exit(ltc2978_exit);