diff options
| author | Guenter Roeck <guenter.roeck@ericsson.com> | 2011-09-02 12:58:37 -0400 |
|---|---|---|
| committer | Guenter Roeck <guenter.roeck@ericsson.com> | 2011-10-24 14:09:42 -0400 |
| commit | c3ff9a674c2313d4f28e38d384b18b561b313eb7 (patch) | |
| tree | 6c8dea7a9c7f43e471abac7ad56e882b74cc3b8d /drivers/hwmon/pmbus/ltc2978.c | |
| parent | 3d790287c4e6caa8790421737b1cf8f0a6531559 (diff) | |
hwmon: (pmbus/ltc2978) Explicit driver for LTC2978
Provide explicit driver for LTC2978 to enable support for minimum and peak
attributes. Remove ltc2978 chip id from generic pmbus driver.
Signed-off-by: Guenter Roeck <guenter.roeck@ericsson.com>
Reviewed-by: Robert Coulson <robert.coulson@ericsson.com>
Diffstat (limited to 'drivers/hwmon/pmbus/ltc2978.c')
| -rw-r--r-- | drivers/hwmon/pmbus/ltc2978.c | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c new file mode 100644 index 000000000000..02b2e49adb31 --- /dev/null +++ b/drivers/hwmon/pmbus/ltc2978.c | |||
| @@ -0,0 +1,295 @@ | |||
| 1 | /* | ||
| 2 | * Hardware monitoring driver for LTC2978 | ||
| 3 | * | ||
| 4 | * Copyright (c) 2011 Ericsson AB. | ||
| 5 | * | ||
| 6 | * This program is free software; you can redistribute it and/or modify | ||
| 7 | * it under the terms of the GNU General Public License as published by | ||
| 8 | * the Free Software Foundation; either version 2 of the License, or | ||
| 9 | * (at your option) any later version. | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, | ||
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | * GNU General Public License for more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU General Public License | ||
| 17 | * along with this program; if not, write to the Free Software | ||
| 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
| 19 | */ | ||
| 20 | |||
| 21 | #include <linux/kernel.h> | ||
| 22 | #include <linux/module.h> | ||
| 23 | #include <linux/init.h> | ||
| 24 | #include <linux/err.h> | ||
| 25 | #include <linux/slab.h> | ||
| 26 | #include <linux/i2c.h> | ||
| 27 | #include "pmbus.h" | ||
| 28 | |||
| 29 | enum chips { ltc2978 }; | ||
| 30 | |||
| 31 | #define LTC2978_MFR_VOUT_PEAK 0xdd | ||
| 32 | #define LTC2978_MFR_VIN_PEAK 0xde | ||
| 33 | #define LTC2978_MFR_TEMPERATURE_PEAK 0xdf | ||
| 34 | #define LTC2978_MFR_SPECIAL_ID 0xe7 | ||
| 35 | |||
| 36 | #define LTC2978_MFR_VOUT_MIN 0xfb | ||
| 37 | #define LTC2978_MFR_VIN_MIN 0xfc | ||
| 38 | #define LTC2978_MFR_TEMPERATURE_MIN 0xfd | ||
| 39 | |||
| 40 | #define LTC2978_ID_REV1 0x0121 | ||
| 41 | #define LTC2978_ID_REV2 0x0122 | ||
| 42 | |||
| 43 | /* | ||
| 44 | * LTC2978 clears peak data whenever the CLEAR_FAULTS command is executed, which | ||
| 45 | * happens pretty much each time chip data is updated. Raw peak data therefore | ||
| 46 | * does not provide much value. To be able to provide useful peak data, keep an | ||
| 47 | * internal cache of measured peak data, which is only cleared if an explicit | ||
| 48 | * "clear peak" command is executed for the sensor in question. | ||
| 49 | */ | ||
| 50 | struct ltc2978_data { | ||
| 51 | enum chips id; | ||
| 52 | int vin_min, vin_max; | ||
| 53 | int temp_min, temp_max; | ||
| 54 | int vout_min[8], vout_max[8]; | ||
| 55 | struct pmbus_driver_info info; | ||
| 56 | }; | ||
| 57 | |||
| 58 | #define to_ltc2978_data(x) container_of(x, struct ltc2978_data, info) | ||
| 59 | |||
| 60 | static inline int lin11_to_val(int data) | ||
| 61 | { | ||
| 62 | s16 e = ((s16)data) >> 11; | ||
| 63 | s32 m = (((s16)(data << 5)) >> 5); | ||
| 64 | |||
| 65 | /* | ||
| 66 | * mantissa is 10 bit + sign, exponent adds up to 15 bit. | ||
| 67 | * Add 6 bit to exponent for maximum accuracy (10 + 15 + 6 = 31). | ||
| 68 | */ | ||
| 69 | e += 6; | ||
| 70 | return (e < 0 ? m >> -e : m << e); | ||
| 71 | } | ||
| 72 | |||
| 73 | static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg) | ||
| 74 | { | ||
| 75 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | ||
| 76 | struct ltc2978_data *data = to_ltc2978_data(info); | ||
| 77 | int ret; | ||
| 78 | |||
| 79 | switch (reg) { | ||
| 80 | case PMBUS_VIRT_READ_VIN_MAX: | ||
| 81 | ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_PEAK); | ||
| 82 | if (ret >= 0) { | ||
| 83 | if (lin11_to_val(ret) > lin11_to_val(data->vin_max)) | ||
| 84 | data->vin_max = ret; | ||
| 85 | ret = data->vin_max; | ||
| 86 | } | ||
| 87 | break; | ||
| 88 | case PMBUS_VIRT_READ_VOUT_MAX: | ||
| 89 | ret = pmbus_read_word_data(client, page, LTC2978_MFR_VOUT_PEAK); | ||
| 90 | if (ret >= 0) { | ||
| 91 | /* | ||
| 92 | * VOUT is 16 bit unsigned with fixed exponent, | ||
| 93 | * so we can compare it directly | ||
| 94 | */ | ||
| 95 | if (ret > data->vout_max[page]) | ||
| 96 | data->vout_max[page] = ret; | ||
| 97 | ret = data->vout_max[page]; | ||
| 98 | } | ||
| 99 | break; | ||
| 100 | case PMBUS_VIRT_READ_TEMP_MAX: | ||
| 101 | ret = pmbus_read_word_data(client, page, | ||
| 102 | LTC2978_MFR_TEMPERATURE_PEAK); | ||
| 103 | if (ret >= 0) { | ||
| 104 | if (lin11_to_val(ret) > lin11_to_val(data->temp_max)) | ||
| 105 | data->temp_max = ret; | ||
| 106 | ret = data->temp_max; | ||
| 107 | } | ||
| 108 | break; | ||
| 109 | case PMBUS_VIRT_READ_VIN_MIN: | ||
| 110 | ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_MIN); | ||
| 111 | if (ret >= 0) { | ||
| 112 | if (lin11_to_val(ret) < lin11_to_val(data->vin_min)) | ||
| 113 | data->vin_min = ret; | ||
| 114 | ret = data->vin_min; | ||
| 115 | } | ||
| 116 | break; | ||
| 117 | case PMBUS_VIRT_READ_VOUT_MIN: | ||
| 118 | ret = pmbus_read_word_data(client, page, LTC2978_MFR_VOUT_MIN); | ||
| 119 | if (ret >= 0) { | ||
| 120 | /* | ||
| 121 | * VOUT_MIN is known to not be supported on some lots | ||
| 122 | * of LTC2978 revision 1, and will return the maximum | ||
| 123 | * possible voltage if read. If VOUT_MAX is valid and | ||
| 124 | * lower than the reading of VOUT_MIN, use it instead. | ||
| 125 | */ | ||
| 126 | if (data->vout_max[page] && ret > data->vout_max[page]) | ||
| 127 | ret = data->vout_max[page]; | ||
| 128 | if (ret < data->vout_min[page]) | ||
| 129 | data->vout_min[page] = ret; | ||
| 130 | ret = data->vout_min[page]; | ||
| 131 | } | ||
| 132 | break; | ||
| 133 | case PMBUS_VIRT_READ_TEMP_MIN: | ||
| 134 | ret = pmbus_read_word_data(client, page, | ||
| 135 | LTC2978_MFR_TEMPERATURE_MIN); | ||
| 136 | if (ret >= 0) { | ||
| 137 | if (lin11_to_val(ret) | ||
| 138 | < lin11_to_val(data->temp_min)) | ||
| 139 | data->temp_min = ret; | ||
| 140 | ret = data->temp_min; | ||
| 141 | } | ||
| 142 | break; | ||
| 143 | case PMBUS_VIRT_RESET_VOUT_HISTORY: | ||
| 144 | case PMBUS_VIRT_RESET_VIN_HISTORY: | ||
| 145 | case PMBUS_VIRT_RESET_TEMP_HISTORY: | ||
| 146 | ret = 0; | ||
| 147 | break; | ||
| 148 | default: | ||
| 149 | ret = -ENODATA; | ||
| 150 | break; | ||
| 151 | } | ||
| 152 | return ret; | ||
| 153 | } | ||
| 154 | |||
| 155 | static int ltc2978_write_word_data(struct i2c_client *client, int page, | ||
| 156 | int reg, u16 word) | ||
| 157 | { | ||
| 158 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | ||
| 159 | struct ltc2978_data *data = to_ltc2978_data(info); | ||
| 160 | int ret; | ||
| 161 | |||
| 162 | switch (reg) { | ||
| 163 | case PMBUS_VIRT_RESET_VOUT_HISTORY: | ||
| 164 | data->vout_min[page] = 0xffff; | ||
| 165 | data->vout_max[page] = 0; | ||
| 166 | ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); | ||
| 167 | break; | ||
| 168 | case PMBUS_VIRT_RESET_VIN_HISTORY: | ||
| 169 | data->vin_min = 0x7bff; | ||
| 170 | data->vin_max = 0; | ||
| 171 | ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); | ||
| 172 | break; | ||
| 173 | case PMBUS_VIRT_RESET_TEMP_HISTORY: | ||
| 174 | data->temp_min = 0x7bff; | ||
| 175 | data->temp_max = 0x7fff; | ||
| 176 | ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); | ||
| 177 | break; | ||
| 178 | default: | ||
| 179 | ret = -ENODATA; | ||
| 180 | break; | ||
| 181 | } | ||
| 182 | return ret; | ||
| 183 | } | ||
| 184 | |||
| 185 | static const struct i2c_device_id ltc2978_id[] = { | ||
| 186 | {"ltc2978", ltc2978}, | ||
| 187 | {} | ||
| 188 | }; | ||
| 189 | MODULE_DEVICE_TABLE(i2c, ltc2978_id); | ||
| 190 | |||
| 191 | static int ltc2978_probe(struct i2c_client *client, | ||
| 192 | const struct i2c_device_id *id) | ||
| 193 | { | ||
| 194 | int chip_id, ret, i; | ||
| 195 | struct ltc2978_data *data; | ||
| 196 | struct pmbus_driver_info *info; | ||
| 197 | |||
| 198 | if (!i2c_check_functionality(client->adapter, | ||
| 199 | I2C_FUNC_SMBUS_READ_WORD_DATA)) | ||
| 200 | return -ENODEV; | ||
| 201 | |||
| 202 | data = kzalloc(sizeof(struct ltc2978_data), GFP_KERNEL); | ||
| 203 | if (!data) | ||
| 204 | return -ENOMEM; | ||
| 205 | |||
| 206 | chip_id = i2c_smbus_read_word_data(client, LTC2978_MFR_SPECIAL_ID); | ||
| 207 | if (chip_id < 0) { | ||
| 208 | ret = chip_id; | ||
| 209 | goto err_mem; | ||
| 210 | } | ||
| 211 | |||
| 212 | if (chip_id == LTC2978_ID_REV1 || chip_id == LTC2978_ID_REV2) { | ||
| 213 | data->id = ltc2978; | ||
| 214 | } else { | ||
| 215 | dev_err(&client->dev, "Unsupported chip ID 0x%x\n", chip_id); | ||
| 216 | ret = -ENODEV; | ||
| 217 | goto err_mem; | ||
| 218 | } | ||
| 219 | if (data->id != id->driver_data) | ||
| 220 | dev_warn(&client->dev, | ||
| 221 | "Device mismatch: Configured %s, detected %s\n", | ||
| 222 | id->name, | ||
| 223 | ltc2978_id[data->id].name); | ||
| 224 | |||
| 225 | info = &data->info; | ||
| 226 | info->read_word_data = ltc2978_read_word_data; | ||
| 227 | info->write_word_data = ltc2978_write_word_data; | ||
| 228 | |||
| 229 | data->vout_min[0] = 0xffff; | ||
| 230 | data->vin_min = 0x7bff; | ||
| 231 | data->temp_min = 0x7bff; | ||
| 232 | data->temp_max = 0x7fff; | ||
| 233 | |||
| 234 | switch (id->driver_data) { | ||
| 235 | case ltc2978: | ||
| 236 | info->pages = 8; | ||
| 237 | info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | ||
| 238 | | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ||
| 239 | | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; | ||
| 240 | for (i = 1; i < 8; i++) { | ||
| 241 | info->func[i] = PMBUS_HAVE_VOUT | ||
| 242 | | PMBUS_HAVE_STATUS_VOUT; | ||
| 243 | data->vout_min[i] = 0xffff; | ||
| 244 | } | ||
| 245 | break; | ||
| 246 | default: | ||
| 247 | ret = -ENODEV; | ||
| 248 | goto err_mem; | ||
| 249 | } | ||
| 250 | |||
| 251 | ret = pmbus_do_probe(client, id, info); | ||
| 252 | if (ret) | ||
| 253 | goto err_mem; | ||
| 254 | return 0; | ||
| 255 | |||
| 256 | err_mem: | ||
| 257 | kfree(data); | ||
| 258 | return ret; | ||
| 259 | } | ||
| 260 | |||
| 261 | static int ltc2978_remove(struct i2c_client *client) | ||
| 262 | { | ||
| 263 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | ||
| 264 | const struct ltc2978_data *data = to_ltc2978_data(info); | ||
| 265 | |||
| 266 | pmbus_do_remove(client); | ||
| 267 | kfree(data); | ||
| 268 | return 0; | ||
| 269 | } | ||
| 270 | |||
| 271 | /* This is the driver that will be inserted */ | ||
| 272 | static struct i2c_driver ltc2978_driver = { | ||
| 273 | .driver = { | ||
| 274 | .name = "ltc2978", | ||
| 275 | }, | ||
| 276 | .probe = ltc2978_probe, | ||
| 277 | .remove = ltc2978_remove, | ||
| 278 | .id_table = ltc2978_id, | ||
| 279 | }; | ||
| 280 | |||
| 281 | static int __init ltc2978_init(void) | ||
| 282 | { | ||
| 283 | return i2c_add_driver(<c2978_driver); | ||
| 284 | } | ||
| 285 | |||
| 286 | static void __exit ltc2978_exit(void) | ||
| 287 | { | ||
| 288 | i2c_del_driver(<c2978_driver); | ||
| 289 | } | ||
| 290 | |||
| 291 | MODULE_AUTHOR("Guenter Roeck"); | ||
| 292 | MODULE_DESCRIPTION("PMBus driver for LTC2978"); | ||
| 293 | MODULE_LICENSE("GPL"); | ||
| 294 | module_init(ltc2978_init); | ||
| 295 | module_exit(ltc2978_exit); | ||
