linux/drivers/hwmon/ltc4215.c
<<
>>
Prefs
   1/*
   2 * Driver for Linear Technology LTC4215 I2C Hot Swap Controller
   3 *
   4 * Copyright (C) 2009 Ira W. Snyder <iws@ovro.caltech.edu>
   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; version 2 of the License.
   9 *
  10 * Datasheet:
  11 * http://www.linear.com/pc/downloadDocument.do?navId=H0,C1,C1003,C1006,C1163,P17572,D12697
  12 */
  13
  14#include <linux/kernel.h>
  15#include <linux/module.h>
  16#include <linux/init.h>
  17#include <linux/err.h>
  18#include <linux/slab.h>
  19#include <linux/i2c.h>
  20#include <linux/hwmon.h>
  21#include <linux/hwmon-sysfs.h>
  22
  23/* Here are names of the chip's registers (a.k.a. commands) */
  24enum ltc4215_cmd {
  25        LTC4215_CONTROL                 = 0x00, /* rw */
  26        LTC4215_ALERT                   = 0x01, /* rw */
  27        LTC4215_STATUS                  = 0x02, /* ro */
  28        LTC4215_FAULT                   = 0x03, /* rw */
  29        LTC4215_SENSE                   = 0x04, /* rw */
  30        LTC4215_SOURCE                  = 0x05, /* rw */
  31        LTC4215_ADIN                    = 0x06, /* rw */
  32};
  33
  34struct ltc4215_data {
  35        struct device *hwmon_dev;
  36
  37        struct mutex update_lock;
  38        bool valid;
  39        unsigned long last_updated; /* in jiffies */
  40
  41        /* Registers */
  42        u8 regs[7];
  43};
  44
  45static struct ltc4215_data *ltc4215_update_device(struct device *dev)
  46{
  47        struct i2c_client *client = to_i2c_client(dev);
  48        struct ltc4215_data *data = i2c_get_clientdata(client);
  49        s32 val;
  50        int i;
  51
  52        mutex_lock(&data->update_lock);
  53
  54        /* The chip's A/D updates 10 times per second */
  55        if (time_after(jiffies, data->last_updated + HZ / 10) || !data->valid) {
  56
  57                dev_dbg(&client->dev, "Starting ltc4215 update\n");
  58
  59                /* Read all registers */
  60                for (i = 0; i < ARRAY_SIZE(data->regs); i++) {
  61                        val = i2c_smbus_read_byte_data(client, i);
  62                        if (unlikely(val < 0))
  63                                data->regs[i] = 0;
  64                        else
  65                                data->regs[i] = val;
  66                }
  67
  68                data->last_updated = jiffies;
  69                data->valid = 1;
  70        }
  71
  72        mutex_unlock(&data->update_lock);
  73
  74        return data;
  75}
  76
  77/* Return the voltage from the given register in millivolts */
  78static int ltc4215_get_voltage(struct device *dev, u8 reg)
  79{
  80        struct ltc4215_data *data = ltc4215_update_device(dev);
  81        const u8 regval = data->regs[reg];
  82        u32 voltage = 0;
  83
  84        switch (reg) {
  85        case LTC4215_SENSE:
  86                /* 151 uV per increment */
  87                voltage = regval * 151 / 1000;
  88                break;
  89        case LTC4215_SOURCE:
  90                /* 60.5 mV per increment */
  91                voltage = regval * 605 / 10;
  92                break;
  93        case LTC4215_ADIN:
  94                /*
  95                 * The ADIN input is divided by 12.5, and has 4.82 mV
  96                 * per increment, so we have the additional multiply
  97                 */
  98                voltage = regval * 482 * 125 / 1000;
  99                break;
 100        default:
 101                /* If we get here, the developer messed up */
 102                WARN_ON_ONCE(1);
 103                break;
 104        }
 105
 106        return voltage;
 107}
 108
 109/* Return the current from the sense resistor in mA */
 110static unsigned int ltc4215_get_current(struct device *dev)
 111{
 112        struct ltc4215_data *data = ltc4215_update_device(dev);
 113
 114        /*
 115         * The strange looking conversions that follow are fixed-point
 116         * math, since we cannot do floating point in the kernel.
 117         *
 118         * Step 1: convert sense register to microVolts
 119         * Step 2: convert voltage to milliAmperes
 120         *
 121         * If you play around with the V=IR equation, you come up with
 122         * the following: X uV / Y mOhm == Z mA
 123         *
 124         * With the resistors that are fractions of a milliOhm, we multiply
 125         * the voltage and resistance by 10, to shift the decimal point.
 126         * Now we can use the normal division operator again.
 127         */
 128
 129        /* Calculate voltage in microVolts (151 uV per increment) */
 130        const unsigned int voltage = data->regs[LTC4215_SENSE] * 151;
 131
 132        /* Calculate current in milliAmperes (4 milliOhm sense resistor) */
 133        const unsigned int curr = voltage / 4;
 134
 135        return curr;
 136}
 137
 138static ssize_t ltc4215_show_voltage(struct device *dev,
 139                                    struct device_attribute *da,
 140                                    char *buf)
 141{
 142        struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
 143        const int voltage = ltc4215_get_voltage(dev, attr->index);
 144
 145        return snprintf(buf, PAGE_SIZE, "%d\n", voltage);
 146}
 147
 148static ssize_t ltc4215_show_current(struct device *dev,
 149                                    struct device_attribute *da,
 150                                    char *buf)
 151{
 152        const unsigned int curr = ltc4215_get_current(dev);
 153
 154        return snprintf(buf, PAGE_SIZE, "%u\n", curr);
 155}
 156
 157static ssize_t ltc4215_show_power(struct device *dev,
 158                                  struct device_attribute *da,
 159                                  char *buf)
 160{
 161        const unsigned int curr = ltc4215_get_current(dev);
 162        const int output_voltage = ltc4215_get_voltage(dev, LTC4215_ADIN);
 163
 164        /* current in mA * voltage in mV == power in uW */
 165        const unsigned int power = abs(output_voltage * curr);
 166
 167        return snprintf(buf, PAGE_SIZE, "%u\n", power);
 168}
 169
 170static ssize_t ltc4215_show_alarm(struct device *dev,
 171                                          struct device_attribute *da,
 172                                          char *buf)
 173{
 174        struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da);
 175        struct ltc4215_data *data = ltc4215_update_device(dev);
 176        const u8 reg = data->regs[attr->index];
 177        const u32 mask = attr->nr;
 178
 179        return snprintf(buf, PAGE_SIZE, "%u\n", (reg & mask) ? 1 : 0);
 180}
 181
 182/*
 183 * These macros are used below in constructing device attribute objects
 184 * for use with sysfs_create_group() to make a sysfs device file
 185 * for each register.
 186 */
 187
 188#define LTC4215_VOLTAGE(name, ltc4215_cmd_idx) \
 189        static SENSOR_DEVICE_ATTR(name, S_IRUGO, \
 190        ltc4215_show_voltage, NULL, ltc4215_cmd_idx)
 191
 192#define LTC4215_CURRENT(name) \
 193        static SENSOR_DEVICE_ATTR(name, S_IRUGO, \
 194        ltc4215_show_current, NULL, 0);
 195
 196#define LTC4215_POWER(name) \
 197        static SENSOR_DEVICE_ATTR(name, S_IRUGO, \
 198        ltc4215_show_power, NULL, 0);
 199
 200#define LTC4215_ALARM(name, mask, reg) \
 201        static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, \
 202        ltc4215_show_alarm, NULL, (mask), reg)
 203
 204/* Construct a sensor_device_attribute structure for each register */
 205
 206/* Current */
 207LTC4215_CURRENT(curr1_input);
 208LTC4215_ALARM(curr1_max_alarm,  (1 << 2),       LTC4215_STATUS);
 209
 210/* Power (virtual) */
 211LTC4215_POWER(power1_input);
 212
 213/* Input Voltage */
 214LTC4215_VOLTAGE(in1_input,                      LTC4215_ADIN);
 215LTC4215_ALARM(in1_max_alarm,    (1 << 0),       LTC4215_STATUS);
 216LTC4215_ALARM(in1_min_alarm,    (1 << 1),       LTC4215_STATUS);
 217
 218/* Output Voltage */
 219LTC4215_VOLTAGE(in2_input,                      LTC4215_SOURCE);
 220LTC4215_ALARM(in2_min_alarm,    (1 << 3),       LTC4215_STATUS);
 221
 222/*
 223 * Finally, construct an array of pointers to members of the above objects,
 224 * as required for sysfs_create_group()
 225 */
 226static struct attribute *ltc4215_attributes[] = {
 227        &sensor_dev_attr_curr1_input.dev_attr.attr,
 228        &sensor_dev_attr_curr1_max_alarm.dev_attr.attr,
 229
 230        &sensor_dev_attr_power1_input.dev_attr.attr,
 231
 232        &sensor_dev_attr_in1_input.dev_attr.attr,
 233        &sensor_dev_attr_in1_max_alarm.dev_attr.attr,
 234        &sensor_dev_attr_in1_min_alarm.dev_attr.attr,
 235
 236        &sensor_dev_attr_in2_input.dev_attr.attr,
 237        &sensor_dev_attr_in2_min_alarm.dev_attr.attr,
 238
 239        NULL,
 240};
 241
 242static const struct attribute_group ltc4215_group = {
 243        .attrs = ltc4215_attributes,
 244};
 245
 246static int ltc4215_probe(struct i2c_client *client,
 247                         const struct i2c_device_id *id)
 248{
 249        struct i2c_adapter *adapter = client->adapter;
 250        struct ltc4215_data *data;
 251        int ret;
 252
 253        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
 254                return -ENODEV;
 255
 256        data = kzalloc(sizeof(*data), GFP_KERNEL);
 257        if (!data) {
 258                ret = -ENOMEM;
 259                goto out_kzalloc;
 260        }
 261
 262        i2c_set_clientdata(client, data);
 263        mutex_init(&data->update_lock);
 264
 265        /* Initialize the LTC4215 chip */
 266        i2c_smbus_write_byte_data(client, LTC4215_FAULT, 0x00);
 267
 268        /* Register sysfs hooks */
 269        ret = sysfs_create_group(&client->dev.kobj, &ltc4215_group);
 270        if (ret)
 271                goto out_sysfs_create_group;
 272
 273        data->hwmon_dev = hwmon_device_register(&client->dev);
 274        if (IS_ERR(data->hwmon_dev)) {
 275                ret = PTR_ERR(data->hwmon_dev);
 276                goto out_hwmon_device_register;
 277        }
 278
 279        return 0;
 280
 281out_hwmon_device_register:
 282        sysfs_remove_group(&client->dev.kobj, &ltc4215_group);
 283out_sysfs_create_group:
 284        kfree(data);
 285out_kzalloc:
 286        return ret;
 287}
 288
 289static int ltc4215_remove(struct i2c_client *client)
 290{
 291        struct ltc4215_data *data = i2c_get_clientdata(client);
 292
 293        hwmon_device_unregister(data->hwmon_dev);
 294        sysfs_remove_group(&client->dev.kobj, &ltc4215_group);
 295
 296        kfree(data);
 297
 298        return 0;
 299}
 300
 301static const struct i2c_device_id ltc4215_id[] = {
 302        { "ltc4215", 0 },
 303        { }
 304};
 305MODULE_DEVICE_TABLE(i2c, ltc4215_id);
 306
 307/* This is the driver that will be inserted */
 308static struct i2c_driver ltc4215_driver = {
 309        .driver = {
 310                .name   = "ltc4215",
 311        },
 312        .probe          = ltc4215_probe,
 313        .remove         = ltc4215_remove,
 314        .id_table       = ltc4215_id,
 315};
 316
 317module_i2c_driver(ltc4215_driver);
 318
 319MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>");
 320MODULE_DESCRIPTION("LTC4215 driver");
 321MODULE_LICENSE("GPL");
 322
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.