linux/drivers/iio/chemical/vz89x.c
<<
>>
Prefs
   1/*
   2 * vz89x.c - Support for SGX Sensortech MiCS VZ89X VOC sensors
   3 *
   4 * Copyright (C) 2015 Matt Ranostay <mranostay@gmail.com>
   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 */
  17
  18#include <linux/module.h>
  19#include <linux/mutex.h>
  20#include <linux/init.h>
  21#include <linux/i2c.h>
  22
  23#include <linux/iio/iio.h>
  24#include <linux/iio/sysfs.h>
  25
  26#define VZ89X_REG_MEASUREMENT           0x09
  27#define VZ89X_REG_MEASUREMENT_SIZE      6
  28
  29#define VZ89X_VOC_CO2_IDX               0
  30#define VZ89X_VOC_SHORT_IDX             1
  31#define VZ89X_VOC_TVOC_IDX              2
  32#define VZ89X_VOC_RESISTANCE_IDX        3
  33
  34struct vz89x_data {
  35        struct i2c_client *client;
  36        struct mutex lock;
  37        int (*xfer)(struct vz89x_data *data, u8 cmd);
  38
  39        unsigned long last_update;
  40        u8 buffer[VZ89X_REG_MEASUREMENT_SIZE];
  41};
  42
  43static const struct iio_chan_spec vz89x_channels[] = {
  44        {
  45                .type = IIO_CONCENTRATION,
  46                .channel2 = IIO_MOD_CO2,
  47                .modified = 1,
  48                .info_mask_separate =
  49                        BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
  50                .address = VZ89X_VOC_CO2_IDX,
  51        },
  52        {
  53                .type = IIO_CONCENTRATION,
  54                .channel2 = IIO_MOD_VOC,
  55                .modified = 1,
  56                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
  57                .address = VZ89X_VOC_SHORT_IDX,
  58                .extend_name = "short",
  59        },
  60        {
  61                .type = IIO_CONCENTRATION,
  62                .channel2 = IIO_MOD_VOC,
  63                .modified = 1,
  64                .info_mask_separate =
  65                        BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
  66                .address = VZ89X_VOC_TVOC_IDX,
  67        },
  68        {
  69                .type = IIO_RESISTANCE,
  70                .info_mask_separate =
  71                        BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
  72                .address = VZ89X_VOC_RESISTANCE_IDX,
  73        },
  74};
  75
  76static IIO_CONST_ATTR(in_concentration_co2_scale, "0.00000698689");
  77static IIO_CONST_ATTR(in_concentration_voc_scale, "0.00000000436681223");
  78
  79static struct attribute *vz89x_attributes[] = {
  80        &iio_const_attr_in_concentration_co2_scale.dev_attr.attr,
  81        &iio_const_attr_in_concentration_voc_scale.dev_attr.attr,
  82        NULL,
  83};
  84
  85static const struct attribute_group vz89x_attrs_group = {
  86        .attrs = vz89x_attributes,
  87};
  88
  89/*
  90 * Chipset sometime updates in the middle of a reading causing it to reset the
  91 * data pointer, and causing invalid reading of previous data.
  92 * We can check for this by reading MSB of the resistance reading that is
  93 * always zero, and by also confirming the VOC_short isn't zero.
  94 */
  95
  96static int vz89x_measurement_is_valid(struct vz89x_data *data)
  97{
  98        if (data->buffer[VZ89X_VOC_SHORT_IDX] == 0)
  99                return 1;
 100
 101        return !!(data->buffer[VZ89X_REG_MEASUREMENT_SIZE - 1] > 0);
 102}
 103
 104static int vz89x_i2c_xfer(struct vz89x_data *data, u8 cmd)
 105{
 106        struct i2c_client *client = data->client;
 107        struct i2c_msg msg[2];
 108        int ret;
 109        u8 buf[3] = { cmd, 0, 0};
 110
 111        msg[0].addr = client->addr;
 112        msg[0].flags = client->flags;
 113        msg[0].len = 3;
 114        msg[0].buf  = (char *) &buf;
 115
 116        msg[1].addr = client->addr;
 117        msg[1].flags = client->flags | I2C_M_RD;
 118        msg[1].len = VZ89X_REG_MEASUREMENT_SIZE;
 119        msg[1].buf = (char *) &data->buffer;
 120
 121        ret = i2c_transfer(client->adapter, msg, 2);
 122
 123        return (ret == 2) ? 0 : ret;
 124}
 125
 126static int vz89x_smbus_xfer(struct vz89x_data *data, u8 cmd)
 127{
 128        struct i2c_client *client = data->client;
 129        int ret;
 130        int i;
 131
 132        ret = i2c_smbus_write_word_data(client, cmd, 0);
 133        if (ret < 0)
 134                return ret;
 135
 136        for (i = 0; i < VZ89X_REG_MEASUREMENT_SIZE; i++) {
 137                ret = i2c_smbus_read_byte(client);
 138                if (ret < 0)
 139                        return ret;
 140                data->buffer[i] = ret;
 141        }
 142
 143        return 0;
 144}
 145
 146static int vz89x_get_measurement(struct vz89x_data *data)
 147{
 148        int ret;
 149
 150        /* sensor can only be polled once a second max per datasheet */
 151        if (!time_after(jiffies, data->last_update + HZ))
 152                return 0;
 153
 154        ret = data->xfer(data, VZ89X_REG_MEASUREMENT);
 155        if (ret < 0)
 156                return ret;
 157
 158        ret = vz89x_measurement_is_valid(data);
 159        if (ret)
 160                return -EAGAIN;
 161
 162        data->last_update = jiffies;
 163
 164        return 0;
 165}
 166
 167static int vz89x_get_resistance_reading(struct vz89x_data *data)
 168{
 169        u8 *buf = &data->buffer[VZ89X_VOC_RESISTANCE_IDX];
 170
 171        return buf[0] | (buf[1] << 8);
 172}
 173
 174static int vz89x_read_raw(struct iio_dev *indio_dev,
 175                          struct iio_chan_spec const *chan, int *val,
 176                          int *val2, long mask)
 177{
 178        struct vz89x_data *data = iio_priv(indio_dev);
 179        int ret = -EINVAL;
 180
 181        switch (mask) {
 182        case IIO_CHAN_INFO_RAW:
 183                mutex_lock(&data->lock);
 184                ret = vz89x_get_measurement(data);
 185                mutex_unlock(&data->lock);
 186
 187                if (ret)
 188                        return ret;
 189
 190                switch (chan->address) {
 191                case VZ89X_VOC_CO2_IDX:
 192                case VZ89X_VOC_SHORT_IDX:
 193                case VZ89X_VOC_TVOC_IDX:
 194                        *val = data->buffer[chan->address];
 195                        return IIO_VAL_INT;
 196                case VZ89X_VOC_RESISTANCE_IDX:
 197                        *val = vz89x_get_resistance_reading(data);
 198                        return IIO_VAL_INT;
 199                default:
 200                        return -EINVAL;
 201                }
 202                break;
 203        case IIO_CHAN_INFO_SCALE:
 204                switch (chan->type) {
 205                case IIO_RESISTANCE:
 206                        *val = 10;
 207                        return IIO_VAL_INT;
 208                default:
 209                        return -EINVAL;
 210                }
 211                break;
 212        case IIO_CHAN_INFO_OFFSET:
 213                switch (chan->address) {
 214                case VZ89X_VOC_CO2_IDX:
 215                        *val = 44;
 216                        *val2 = 250000;
 217                        return IIO_VAL_INT_PLUS_MICRO;
 218                case VZ89X_VOC_TVOC_IDX:
 219                        *val = -13;
 220                        return IIO_VAL_INT;
 221                default:
 222                        return -EINVAL;
 223                }
 224        }
 225
 226        return ret;
 227}
 228
 229static const struct iio_info vz89x_info = {
 230        .attrs          = &vz89x_attrs_group,
 231        .read_raw       = vz89x_read_raw,
 232        .driver_module  = THIS_MODULE,
 233};
 234
 235static int vz89x_probe(struct i2c_client *client,
 236                       const struct i2c_device_id *id)
 237{
 238        struct iio_dev *indio_dev;
 239        struct vz89x_data *data;
 240
 241        indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
 242        if (!indio_dev)
 243                return -ENOMEM;
 244        data = iio_priv(indio_dev);
 245
 246        if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
 247                data->xfer = vz89x_i2c_xfer;
 248        else if (i2c_check_functionality(client->adapter,
 249                                I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE))
 250                data->xfer = vz89x_smbus_xfer;
 251        else
 252                return -EOPNOTSUPP;
 253
 254        i2c_set_clientdata(client, indio_dev);
 255        data->client = client;
 256        data->last_update = jiffies - HZ;
 257        mutex_init(&data->lock);
 258
 259        indio_dev->dev.parent = &client->dev;
 260        indio_dev->info = &vz89x_info,
 261        indio_dev->name = dev_name(&client->dev);
 262        indio_dev->modes = INDIO_DIRECT_MODE;
 263
 264        indio_dev->channels = vz89x_channels;
 265        indio_dev->num_channels = ARRAY_SIZE(vz89x_channels);
 266
 267        return devm_iio_device_register(&client->dev, indio_dev);
 268}
 269
 270static const struct i2c_device_id vz89x_id[] = {
 271        { "vz89x", 0 },
 272        { }
 273};
 274MODULE_DEVICE_TABLE(i2c, vz89x_id);
 275
 276static const struct of_device_id vz89x_dt_ids[] = {
 277        { .compatible = "sgx,vz89x" },
 278        { }
 279};
 280MODULE_DEVICE_TABLE(of, vz89x_dt_ids);
 281
 282static struct i2c_driver vz89x_driver = {
 283        .driver = {
 284                .name   = "vz89x",
 285                .of_match_table = of_match_ptr(vz89x_dt_ids),
 286        },
 287        .probe = vz89x_probe,
 288        .id_table = vz89x_id,
 289};
 290module_i2c_driver(vz89x_driver);
 291
 292MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
 293MODULE_DESCRIPTION("SGX Sensortech MiCS VZ89X VOC sensors");
 294MODULE_LICENSE("GPL v2");
 295