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/platform_data/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 i2c_client *client;
  71        struct mutex update_lock;
  72        char valid;             /* !=0 if following fields are valid */
  73        unsigned long last_updated;     /* In jiffies */
  74
  75        s16 temp[3];            /* Register values, word */
  76};
  77
  78static void ds620_init_client(struct i2c_client *client)
  79{
  80        struct ds620_platform_data *ds620_info = dev_get_platdata(&client->dev);
  81        u16 conf, new_conf;
  82
  83        new_conf = conf =
  84            i2c_smbus_read_word_swapped(client, DS620_REG_CONF);
  85
  86        /* switch to continuous conversion mode */
  87        new_conf &= ~DS620_REG_CONFIG_1SHOT;
  88        /* already high at power-on, but don't trust the BIOS! */
  89        new_conf |= DS620_REG_CONFIG_PO2;
  90        /* thermostat mode according to platform data */
  91        if (ds620_info && ds620_info->pomode == 1)
  92                new_conf &= ~DS620_REG_CONFIG_PO1; /* PO_LOW */
  93        else if (ds620_info && ds620_info->pomode == 2)
  94                new_conf |= DS620_REG_CONFIG_PO1; /* PO_HIGH */
  95        else
  96                new_conf &= ~DS620_REG_CONFIG_PO2; /* always low */
  97        /* with highest precision */
  98        new_conf |= DS620_REG_CONFIG_R1 | DS620_REG_CONFIG_R0;
  99
 100        if (conf != new_conf)
 101                i2c_smbus_write_word_swapped(client, DS620_REG_CONF, new_conf);
 102
 103        /* start conversion */
 104        i2c_smbus_write_byte(client, DS620_COM_START);
 105}
 106
 107static struct ds620_data *ds620_update_client(struct device *dev)
 108{
 109        struct ds620_data *data = dev_get_drvdata(dev);
 110        struct i2c_client *client = data->client;
 111        struct ds620_data *ret = data;
 112
 113        mutex_lock(&data->update_lock);
 114
 115        if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
 116            || !data->valid) {
 117                int i;
 118                int res;
 119
 120                dev_dbg(&client->dev, "Starting ds620 update\n");
 121
 122                for (i = 0; i < ARRAY_SIZE(data->temp); i++) {
 123                        res = i2c_smbus_read_word_swapped(client,
 124                                                          DS620_REG_TEMP[i]);
 125                        if (res < 0) {
 126                                ret = ERR_PTR(res);
 127                                goto abort;
 128                        }
 129
 130                        data->temp[i] = res;
 131                }
 132
 133                data->last_updated = jiffies;
 134                data->valid = 1;
 135        }
 136abort:
 137        mutex_unlock(&data->update_lock);
 138
 139        return ret;
 140}
 141
 142static ssize_t show_temp(struct device *dev, struct device_attribute *da,
 143                         char *buf)
 144{
 145        struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
 146        struct ds620_data *data = ds620_update_client(dev);
 147
 148        if (IS_ERR(data))
 149                return PTR_ERR(data);
 150
 151        return sprintf(buf, "%d\n", ((data->temp[attr->index] / 8) * 625) / 10);
 152}
 153
 154static ssize_t set_temp(struct device *dev, struct device_attribute *da,
 155                        const char *buf, size_t count)
 156{
 157        int res;
 158        long val;
 159
 160        struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
 161        struct ds620_data *data = dev_get_drvdata(dev);
 162        struct i2c_client *client = data->client;
 163
 164        res = kstrtol(buf, 10, &val);
 165
 166        if (res)
 167                return res;
 168
 169        val = (clamp_val(val, -128000, 128000) * 10 / 625) * 8;
 170
 171        mutex_lock(&data->update_lock);
 172        data->temp[attr->index] = val;
 173        i2c_smbus_write_word_swapped(client, DS620_REG_TEMP[attr->index],
 174                                     data->temp[attr->index]);
 175        mutex_unlock(&data->update_lock);
 176        return count;
 177}
 178
 179static ssize_t show_alarm(struct device *dev, struct device_attribute *da,
 180                          char *buf)
 181{
 182        struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
 183        struct ds620_data *data = ds620_update_client(dev);
 184        struct i2c_client *client;
 185        u16 conf, new_conf;
 186        int res;
 187
 188        if (IS_ERR(data))
 189                return PTR_ERR(data);
 190
 191        client = data->client;
 192
 193        /* reset alarms if necessary */
 194        res = i2c_smbus_read_word_swapped(client, DS620_REG_CONF);
 195        if (res < 0)
 196                return res;
 197
 198        new_conf = conf = res;
 199        new_conf &= ~attr->index;
 200        if (conf != new_conf) {
 201                res = i2c_smbus_write_word_swapped(client, DS620_REG_CONF,
 202                                                   new_conf);
 203                if (res < 0)
 204                        return res;
 205        }
 206
 207        return sprintf(buf, "%d\n", !!(conf & attr->index));
 208}
 209
 210static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
 211static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp, set_temp, 1);
 212static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp, set_temp, 2);
 213static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL,
 214                          DS620_REG_CONFIG_TLF);
 215static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL,
 216                          DS620_REG_CONFIG_THF);
 217
 218static struct attribute *ds620_attrs[] = {
 219        &sensor_dev_attr_temp1_input.dev_attr.attr,
 220        &sensor_dev_attr_temp1_min.dev_attr.attr,
 221        &sensor_dev_attr_temp1_max.dev_attr.attr,
 222        &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
 223        &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
 224        NULL
 225};
 226
 227ATTRIBUTE_GROUPS(ds620);
 228
 229static int ds620_probe(struct i2c_client *client,
 230                       const struct i2c_device_id *id)
 231{
 232        struct device *dev = &client->dev;
 233        struct device *hwmon_dev;
 234        struct ds620_data *data;
 235
 236        data = devm_kzalloc(dev, sizeof(struct ds620_data), GFP_KERNEL);
 237        if (!data)
 238                return -ENOMEM;
 239
 240        data->client = client;
 241        mutex_init(&data->update_lock);
 242
 243        /* Initialize the DS620 chip */
 244        ds620_init_client(client);
 245
 246        hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
 247                                                           data, ds620_groups);
 248        return PTR_ERR_OR_ZERO(hwmon_dev);
 249}
 250
 251static const struct i2c_device_id ds620_id[] = {
 252        {"ds620", 0},
 253        {}
 254};
 255
 256MODULE_DEVICE_TABLE(i2c, ds620_id);
 257
 258/* This is the driver that will be inserted */
 259static struct i2c_driver ds620_driver = {
 260        .class = I2C_CLASS_HWMON,
 261        .driver = {
 262                   .name = "ds620",
 263        },
 264        .probe = ds620_probe,
 265        .id_table = ds620_id,
 266};
 267
 268module_i2c_driver(ds620_driver);
 269
 270MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>");
 271MODULE_DESCRIPTION("DS620 driver");
 272MODULE_LICENSE("GPL");
 273