linux/drivers/hwmon/ds620.c
<<
>>
Prefs
   1/*
   2 *  ds620.c - Support for temperature sensor and thermostat DS620
   3 *
   4 *  Copyright (C) 2010, 2011 Roland Stigge <stigge@antcom.de>
   5 *
   6 *  based on ds1621.c by Christian W. Zuckschwerdt  <zany@triq.net>
   7 *
   8 *  This program is free software; you can redistribute it and/or modify
   9 *  it under the terms of the GNU General Public License as published by
  10 *  the Free Software Foundation; either version 2 of the License, or
  11 *  (at your option) any later version.
  12 *
  13 *  This program is distributed in the hope that it will be useful,
  14 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 *  GNU General Public License for more details.
  17 *
  18 *  You should have received a copy of the GNU General Public License
  19 *  along with this program; if not, write to the Free Software
  20 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  21 */
  22
  23#include <linux/module.h>
  24#include <linux/init.h>
  25#include <linux/slab.h>
  26#include <linux/jiffies.h>
  27#include <linux/i2c.h>
  28#include <linux/hwmon.h>
  29#include <linux/hwmon-sysfs.h>
  30#include <linux/err.h>
  31#include <linux/mutex.h>
  32#include <linux/sysfs.h>
  33#include <linux/i2c/ds620.h>
  34
  35/*
  36 * Many DS620 constants specified below
  37 *  15   14   13   12   11   10   09    08
  38 * |Done|NVB |THF |TLF |R1  |R0  |AUTOC|1SHOT|
  39 *
  40 *  07   06   05   04   03   02   01    00
  41 * |PO2 |PO1 |A2  |A1  |A0  |    |     |     |
  42 */
  43#define DS620_REG_CONFIG_DONE           0x8000
  44#define DS620_REG_CONFIG_NVB            0x4000
  45#define DS620_REG_CONFIG_THF            0x2000
  46#define DS620_REG_CONFIG_TLF            0x1000
  47#define DS620_REG_CONFIG_R1             0x0800
  48#define DS620_REG_CONFIG_R0             0x0400
  49#define DS620_REG_CONFIG_AUTOC          0x0200
  50#define DS620_REG_CONFIG_1SHOT          0x0100
  51#define DS620_REG_CONFIG_PO2            0x0080
  52#define DS620_REG_CONFIG_PO1            0x0040
  53#define DS620_REG_CONFIG_A2             0x0020
  54#define DS620_REG_CONFIG_A1             0x0010
  55#define DS620_REG_CONFIG_A0             0x0008
  56
  57/* The DS620 registers */
  58static const u8 DS620_REG_TEMP[3] = {
  59        0xAA,                   /* input, word, RO */
  60        0xA2,                   /* min, word, RW */
  61        0xA0,                   /* max, word, RW */
  62};
  63
  64#define DS620_REG_CONF          0xAC    /* word, RW */
  65#define DS620_COM_START         0x51    /* no data */
  66#define DS620_COM_STOP          0x22    /* no data */
  67
  68/* Each client has this additional data */
  69struct ds620_data {
  70        struct device *hwmon_dev;
  71        struct mutex update_lock;
  72        char valid;             /* !=0 if following fields are valid */
  73        unsigned long last_updated;     /* In jiffies */
  74
  75        u16 temp[3];            /* Register values, word */
  76};
  77
  78/*
  79 *  Temperature registers are word-sized.
  80 *  DS620 uses a high-byte first convention, which is exactly opposite to
  81 *  the SMBus standard.
  82 */
  83static int ds620_read_temp(struct i2c_client *client, u8 reg)
  84{
  85        int ret;
  86
  87        ret = i2c_smbus_read_word_data(client, reg);
  88        if (ret < 0)
  89                return ret;
  90        return swab16(ret);
  91}
  92
  93static int ds620_write_temp(struct i2c_client *client, u8 reg, u16 value)
  94{
  95        return i2c_smbus_write_word_data(client, reg, swab16(value));
  96}
  97
  98static void ds620_init_client(struct i2c_client *client)
  99{
 100        struct ds620_platform_data *ds620_info = client->dev.platform_data;
 101        u16 conf, new_conf;
 102
 103        new_conf = conf =
 104            swab16(i2c_smbus_read_word_data(client, DS620_REG_CONF));
 105
 106        /* switch to continuous conversion mode */
 107        new_conf &= ~DS620_REG_CONFIG_1SHOT;
 108        /* already high at power-on, but don't trust the BIOS! */
 109        new_conf |= DS620_REG_CONFIG_PO2;
 110        /* thermostat mode according to platform data */
 111        if (ds620_info && ds620_info->pomode == 1)
 112                new_conf &= ~DS620_REG_CONFIG_PO1; /* PO_LOW */
 113        else if (ds620_info && ds620_info->pomode == 2)
 114                new_conf |= DS620_REG_CONFIG_PO1; /* PO_HIGH */
 115        else
 116                new_conf &= ~DS620_REG_CONFIG_PO2; /* always low */
 117        /* with highest precision */
 118        new_conf |= DS620_REG_CONFIG_R1 | DS620_REG_CONFIG_R0;
 119
 120        if (conf != new_conf)
 121                i2c_smbus_write_word_data(client, DS620_REG_CONF,
 122                                          swab16(new_conf));
 123
 124        /* start conversion */
 125        i2c_smbus_write_byte(client, DS620_COM_START);
 126}
 127
 128static struct ds620_data *ds620_update_client(struct device *dev)
 129{
 130        struct i2c_client *client = to_i2c_client(dev);
 131        struct ds620_data *data = i2c_get_clientdata(client);
 132        struct ds620_data *ret = data;
 133
 134        mutex_lock(&data->update_lock);
 135
 136        if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
 137            || !data->valid) {
 138                int i;
 139                int res;
 140
 141                dev_dbg(&client->dev, "Starting ds620 update\n");
 142
 143                for (i = 0; i < ARRAY_SIZE(data->temp); i++) {
 144                        res = ds620_read_temp(client,
 145                                              DS620_REG_TEMP[i]);
 146                        if (res < 0) {
 147                                ret = ERR_PTR(res);
 148                                goto abort;
 149                        }
 150
 151                        data->temp[i] = res;
 152                }
 153
 154                data->last_updated = jiffies;
 155                data->valid = 1;
 156        }
 157abort:
 158        mutex_unlock(&data->update_lock);
 159
 160        return ret;
 161}
 162
 163static ssize_t show_temp(struct device *dev, struct device_attribute *da,
 164                         char *buf)
 165{
 166        struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
 167        struct ds620_data *data = ds620_update_client(dev);
 168
 169        if (IS_ERR(data))
 170                return PTR_ERR(data);
 171
 172        return sprintf(buf, "%d\n", ((data->temp[attr->index] / 8) * 625) / 10);
 173}
 174
 175static ssize_t set_temp(struct device *dev, struct device_attribute *da,
 176                        const char *buf, size_t count)
 177{
 178        int res;
 179        long val;
 180
 181        struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
 182        struct i2c_client *client = to_i2c_client(dev);
 183        struct ds620_data *data = i2c_get_clientdata(client);
 184
 185        res = strict_strtol(buf, 10, &val);
 186
 187        if (res)
 188                return res;
 189
 190        val = (val * 10 / 625) * 8;
 191
 192        mutex_lock(&data->update_lock);
 193        data->temp[attr->index] = val;
 194        ds620_write_temp(client, DS620_REG_TEMP[attr->index],
 195                         data->temp[attr->index]);
 196        mutex_unlock(&data->update_lock);
 197        return count;
 198}
 199
 200static ssize_t show_alarm(struct device *dev, struct device_attribute *da,
 201                          char *buf)
 202{
 203        struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
 204        struct ds620_data *data = ds620_update_client(dev);
 205        struct i2c_client *client = to_i2c_client(dev);
 206        u16 conf, new_conf;
 207        int res;
 208
 209        if (IS_ERR(data))
 210                return PTR_ERR(data);
 211
 212        /* reset alarms if necessary */
 213        res = i2c_smbus_read_word_data(client, DS620_REG_CONF);
 214        if (res < 0)
 215                return res;
 216
 217        conf = swab16(res);
 218        new_conf = conf;
 219        new_conf &= ~attr->index;
 220        if (conf != new_conf) {
 221                res = i2c_smbus_write_word_data(client, DS620_REG_CONF,
 222                                                swab16(new_conf));
 223                if (res < 0)
 224                        return res;
 225        }
 226
 227        return sprintf(buf, "%d\n", !!(conf & attr->index));
 228}
 229
 230static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
 231static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp, set_temp, 1);
 232static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp, set_temp, 2);
 233static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL,
 234                          DS620_REG_CONFIG_TLF);
 235static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL,
 236                          DS620_REG_CONFIG_THF);
 237
 238static struct attribute *ds620_attributes[] = {
 239        &sensor_dev_attr_temp1_input.dev_attr.attr,
 240        &sensor_dev_attr_temp1_min.dev_attr.attr,
 241        &sensor_dev_attr_temp1_max.dev_attr.attr,
 242        &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
 243        &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
 244        NULL
 245};
 246
 247static const struct attribute_group ds620_group = {
 248        .attrs = ds620_attributes,
 249};
 250
 251static int ds620_probe(struct i2c_client *client,
 252                       const struct i2c_device_id *id)
 253{
 254        struct ds620_data *data;
 255        int err;
 256
 257        data = kzalloc(sizeof(struct ds620_data), GFP_KERNEL);
 258        if (!data) {
 259                err = -ENOMEM;
 260                goto exit;
 261        }
 262
 263        i2c_set_clientdata(client, data);
 264        mutex_init(&data->update_lock);
 265
 266        /* Initialize the DS620 chip */
 267        ds620_init_client(client);
 268
 269        /* Register sysfs hooks */
 270        err = sysfs_create_group(&client->dev.kobj, &ds620_group);
 271        if (err)
 272                goto exit_free;
 273
 274        data->hwmon_dev = hwmon_device_register(&client->dev);
 275        if (IS_ERR(data->hwmon_dev)) {
 276                err = PTR_ERR(data->hwmon_dev);
 277                goto exit_remove_files;
 278        }
 279
 280        dev_info(&client->dev, "temperature sensor found\n");
 281
 282        return 0;
 283
 284exit_remove_files:
 285        sysfs_remove_group(&client->dev.kobj, &ds620_group);
 286exit_free:
 287        kfree(data);
 288exit:
 289        return err;
 290}
 291
 292static int ds620_remove(struct i2c_client *client)
 293{
 294        struct ds620_data *data = i2c_get_clientdata(client);
 295
 296        hwmon_device_unregister(data->hwmon_dev);
 297        sysfs_remove_group(&client->dev.kobj, &ds620_group);
 298
 299        kfree(data);
 300
 301        return 0;
 302}
 303
 304static const struct i2c_device_id ds620_id[] = {
 305        {"ds620", 0},
 306        {}
 307};
 308
 309MODULE_DEVICE_TABLE(i2c, ds620_id);
 310
 311/* This is the driver that will be inserted */
 312static struct i2c_driver ds620_driver = {
 313        .class = I2C_CLASS_HWMON,
 314        .driver = {
 315                   .name = "ds620",
 316        },
 317        .probe = ds620_probe,
 318        .remove = ds620_remove,
 319        .id_table = ds620_id,
 320};
 321
 322static int __init ds620_init(void)
 323{
 324        return i2c_add_driver(&ds620_driver);
 325}
 326
 327static void __exit ds620_exit(void)
 328{
 329        i2c_del_driver(&ds620_driver);
 330}
 331
 332MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>");
 333MODULE_DESCRIPTION("DS620 driver");
 334MODULE_LICENSE("GPL");
 335
 336module_init(ds620_init);
 337module_exit(ds620_exit);
 338