linux/drivers/iio/chemical/vz89x.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * vz89x.c - Support for SGX Sensortech MiCS VZ89X VOC sensors
   4 *
   5 * Copyright (C) 2015-2018
   6 * Author: Matt Ranostay <matt.ranostay@konsulko.com>
   7 */
   8
   9#include <linux/module.h>
  10#include <linux/mutex.h>
  11#include <linux/init.h>
  12#include <linux/i2c.h>
  13#include <linux/mod_devicetable.h>
  14
  15#include <linux/iio/iio.h>
  16#include <linux/iio/sysfs.h>
  17
  18#define VZ89X_REG_MEASUREMENT           0x09
  19#define VZ89X_REG_MEASUREMENT_RD_SIZE   6
  20#define VZ89X_REG_MEASUREMENT_WR_SIZE   3
  21
  22#define VZ89X_VOC_CO2_IDX               0
  23#define VZ89X_VOC_SHORT_IDX             1
  24#define VZ89X_VOC_TVOC_IDX              2
  25#define VZ89X_VOC_RESISTANCE_IDX        3
  26
  27#define VZ89TE_REG_MEASUREMENT          0x0c
  28#define VZ89TE_REG_MEASUREMENT_RD_SIZE  7
  29#define VZ89TE_REG_MEASUREMENT_WR_SIZE  6
  30
  31#define VZ89TE_VOC_TVOC_IDX             0
  32#define VZ89TE_VOC_CO2_IDX              1
  33#define VZ89TE_VOC_RESISTANCE_IDX       2
  34
  35enum {
  36        VZ89X,
  37        VZ89TE,
  38};
  39
  40struct vz89x_chip_data;
  41
  42struct vz89x_data {
  43        struct i2c_client *client;
  44        const struct vz89x_chip_data *chip;
  45        struct mutex lock;
  46        int (*xfer)(struct vz89x_data *data, u8 cmd);
  47
  48        bool is_valid;
  49        unsigned long last_update;
  50        u8 buffer[VZ89TE_REG_MEASUREMENT_RD_SIZE];
  51};
  52
  53struct vz89x_chip_data {
  54        bool (*valid)(struct vz89x_data *data);
  55        const struct iio_chan_spec *channels;
  56        u8 num_channels;
  57
  58        u8 cmd;
  59        u8 read_size;
  60        u8 write_size;
  61};
  62
  63static const struct iio_chan_spec vz89x_channels[] = {
  64        {
  65                .type = IIO_CONCENTRATION,
  66                .channel2 = IIO_MOD_CO2,
  67                .modified = 1,
  68                .info_mask_separate =
  69                        BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
  70                .address = VZ89X_VOC_CO2_IDX,
  71        },
  72        {
  73                .type = IIO_CONCENTRATION,
  74                .channel2 = IIO_MOD_VOC,
  75                .modified = 1,
  76                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
  77                .address = VZ89X_VOC_SHORT_IDX,
  78                .extend_name = "short",
  79        },
  80        {
  81                .type = IIO_CONCENTRATION,
  82                .channel2 = IIO_MOD_VOC,
  83                .modified = 1,
  84                .info_mask_separate =
  85                        BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
  86                .address = VZ89X_VOC_TVOC_IDX,
  87        },
  88        {
  89                .type = IIO_RESISTANCE,
  90                .info_mask_separate =
  91                        BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
  92                .address = VZ89X_VOC_RESISTANCE_IDX,
  93                .scan_index = -1,
  94                .scan_type = {
  95                        .endianness = IIO_LE,
  96                },
  97        },
  98};
  99
 100static const struct iio_chan_spec vz89te_channels[] = {
 101        {
 102                .type = IIO_CONCENTRATION,
 103                .channel2 = IIO_MOD_VOC,
 104                .modified = 1,
 105                .info_mask_separate =
 106                        BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
 107                .address = VZ89TE_VOC_TVOC_IDX,
 108        },
 109
 110        {
 111                .type = IIO_CONCENTRATION,
 112                .channel2 = IIO_MOD_CO2,
 113                .modified = 1,
 114                .info_mask_separate =
 115                        BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
 116                .address = VZ89TE_VOC_CO2_IDX,
 117        },
 118        {
 119                .type = IIO_RESISTANCE,
 120                .info_mask_separate =
 121                        BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
 122                .address = VZ89TE_VOC_RESISTANCE_IDX,
 123                .scan_index = -1,
 124                .scan_type = {
 125                        .endianness = IIO_BE,
 126                },
 127        },
 128};
 129
 130static IIO_CONST_ATTR(in_concentration_co2_scale, "0.00000698689");
 131static IIO_CONST_ATTR(in_concentration_voc_scale, "0.00000000436681223");
 132
 133static struct attribute *vz89x_attributes[] = {
 134        &iio_const_attr_in_concentration_co2_scale.dev_attr.attr,
 135        &iio_const_attr_in_concentration_voc_scale.dev_attr.attr,
 136        NULL,
 137};
 138
 139static const struct attribute_group vz89x_attrs_group = {
 140        .attrs = vz89x_attributes,
 141};
 142
 143/*
 144 * Chipset sometime updates in the middle of a reading causing it to reset the
 145 * data pointer, and causing invalid reading of previous data.
 146 * We can check for this by reading MSB of the resistance reading that is
 147 * always zero, and by also confirming the VOC_short isn't zero.
 148 */
 149
 150static bool vz89x_measurement_is_valid(struct vz89x_data *data)
 151{
 152        if (data->buffer[VZ89X_VOC_SHORT_IDX] == 0)
 153                return true;
 154
 155        return !!(data->buffer[data->chip->read_size - 1] > 0);
 156}
 157
 158/* VZ89TE device has a modified CRC-8 two complement check */
 159static bool vz89te_measurement_is_valid(struct vz89x_data *data)
 160{
 161        u8 crc = 0;
 162        int i, sum = 0;
 163
 164        for (i = 0; i < (data->chip->read_size - 1); i++) {
 165                sum = crc + data->buffer[i];
 166                crc = sum;
 167                crc += sum / 256;
 168        }
 169
 170        return !((0xff - crc) == data->buffer[data->chip->read_size - 1]);
 171}
 172
 173static int vz89x_i2c_xfer(struct vz89x_data *data, u8 cmd)
 174{
 175        const struct vz89x_chip_data *chip = data->chip;
 176        struct i2c_client *client = data->client;
 177        struct i2c_msg msg[2];
 178        int ret;
 179        u8 buf[6] = { cmd, 0, 0, 0, 0, 0xf3 };
 180
 181        msg[0].addr = client->addr;
 182        msg[0].flags = client->flags;
 183        msg[0].len = chip->write_size;
 184        msg[0].buf  = (char *) &buf;
 185
 186        msg[1].addr = client->addr;
 187        msg[1].flags = client->flags | I2C_M_RD;
 188        msg[1].len = chip->read_size;
 189        msg[1].buf = (char *) &data->buffer;
 190
 191        ret = i2c_transfer(client->adapter, msg, 2);
 192
 193        return (ret == 2) ? 0 : ret;
 194}
 195
 196static int vz89x_smbus_xfer(struct vz89x_data *data, u8 cmd)
 197{
 198        struct i2c_client *client = data->client;
 199        int ret;
 200        int i;
 201
 202        ret = i2c_smbus_write_word_data(client, cmd, 0);
 203        if (ret < 0)
 204                return ret;
 205
 206        for (i = 0; i < data->chip->read_size; i++) {
 207                ret = i2c_smbus_read_byte(client);
 208                if (ret < 0)
 209                        return ret;
 210                data->buffer[i] = ret;
 211        }
 212
 213        return 0;
 214}
 215
 216static int vz89x_get_measurement(struct vz89x_data *data)
 217{
 218        const struct vz89x_chip_data *chip = data->chip;
 219        int ret;
 220
 221        /* sensor can only be polled once a second max per datasheet */
 222        if (!time_after(jiffies, data->last_update + HZ))
 223                return data->is_valid ? 0 : -EAGAIN;
 224
 225        data->is_valid = false;
 226        data->last_update = jiffies;
 227
 228        ret = data->xfer(data, chip->cmd);
 229        if (ret < 0)
 230                return ret;
 231
 232        ret = chip->valid(data);
 233        if (ret)
 234                return -EAGAIN;
 235
 236        data->is_valid = true;
 237
 238        return 0;
 239}
 240
 241static int vz89x_get_resistance_reading(struct vz89x_data *data,
 242                                        struct iio_chan_spec const *chan,
 243                                        int *val)
 244{
 245        u8 *tmp = (u8 *) &data->buffer[chan->address];
 246
 247        switch (chan->scan_type.endianness) {
 248        case IIO_LE:
 249                *val = le32_to_cpup((__le32 *) tmp) & GENMASK(23, 0);
 250                break;
 251        case IIO_BE:
 252                *val = be32_to_cpup((__be32 *) tmp) >> 8;
 253                break;
 254        default:
 255                return -EINVAL;
 256        }
 257
 258        return 0;
 259}
 260
 261static int vz89x_read_raw(struct iio_dev *indio_dev,
 262                          struct iio_chan_spec const *chan, int *val,
 263                          int *val2, long mask)
 264{
 265        struct vz89x_data *data = iio_priv(indio_dev);
 266        int ret = -EINVAL;
 267
 268        switch (mask) {
 269        case IIO_CHAN_INFO_RAW:
 270                mutex_lock(&data->lock);
 271                ret = vz89x_get_measurement(data);
 272                mutex_unlock(&data->lock);
 273
 274                if (ret)
 275                        return ret;
 276
 277                switch (chan->type) {
 278                case IIO_CONCENTRATION:
 279                        *val = data->buffer[chan->address];
 280                        return IIO_VAL_INT;
 281                case IIO_RESISTANCE:
 282                        ret = vz89x_get_resistance_reading(data, chan, val);
 283                        if (!ret)
 284                                return IIO_VAL_INT;
 285                        break;
 286                default:
 287                        return -EINVAL;
 288                }
 289                break;
 290        case IIO_CHAN_INFO_SCALE:
 291                switch (chan->type) {
 292                case IIO_RESISTANCE:
 293                        *val = 10;
 294                        return IIO_VAL_INT;
 295                default:
 296                        return -EINVAL;
 297                }
 298                break;
 299        case IIO_CHAN_INFO_OFFSET:
 300                switch (chan->channel2) {
 301                case IIO_MOD_CO2:
 302                        *val = 44;
 303                        *val2 = 250000;
 304                        return IIO_VAL_INT_PLUS_MICRO;
 305                case IIO_MOD_VOC:
 306                        *val = -13;
 307                        return IIO_VAL_INT;
 308                default:
 309                        return -EINVAL;
 310                }
 311        }
 312
 313        return ret;
 314}
 315
 316static const struct iio_info vz89x_info = {
 317        .attrs          = &vz89x_attrs_group,
 318        .read_raw       = vz89x_read_raw,
 319};
 320
 321static const struct vz89x_chip_data vz89x_chips[] = {
 322        {
 323                .valid = vz89x_measurement_is_valid,
 324
 325                .cmd = VZ89X_REG_MEASUREMENT,
 326                .read_size = VZ89X_REG_MEASUREMENT_RD_SIZE,
 327                .write_size = VZ89X_REG_MEASUREMENT_WR_SIZE,
 328
 329                .channels = vz89x_channels,
 330                .num_channels = ARRAY_SIZE(vz89x_channels),
 331        },
 332        {
 333                .valid = vz89te_measurement_is_valid,
 334
 335                .cmd = VZ89TE_REG_MEASUREMENT,
 336                .read_size = VZ89TE_REG_MEASUREMENT_RD_SIZE,
 337                .write_size = VZ89TE_REG_MEASUREMENT_WR_SIZE,
 338
 339                .channels = vz89te_channels,
 340                .num_channels = ARRAY_SIZE(vz89te_channels),
 341        },
 342};
 343
 344static const struct of_device_id vz89x_dt_ids[] = {
 345        { .compatible = "sgx,vz89x", .data = (void *) VZ89X },
 346        { .compatible = "sgx,vz89te", .data = (void *) VZ89TE },
 347        { }
 348};
 349MODULE_DEVICE_TABLE(of, vz89x_dt_ids);
 350
 351static int vz89x_probe(struct i2c_client *client,
 352                       const struct i2c_device_id *id)
 353{
 354        struct device *dev = &client->dev;
 355        struct iio_dev *indio_dev;
 356        struct vz89x_data *data;
 357        int chip_id;
 358
 359        indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
 360        if (!indio_dev)
 361                return -ENOMEM;
 362        data = iio_priv(indio_dev);
 363
 364        if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
 365                data->xfer = vz89x_i2c_xfer;
 366        else if (i2c_check_functionality(client->adapter,
 367                                I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE))
 368                data->xfer = vz89x_smbus_xfer;
 369        else
 370                return -EOPNOTSUPP;
 371
 372        if (!dev_fwnode(dev))
 373                chip_id = id->driver_data;
 374        else
 375                chip_id = (unsigned long)device_get_match_data(dev);
 376
 377        i2c_set_clientdata(client, indio_dev);
 378        data->client = client;
 379        data->chip = &vz89x_chips[chip_id];
 380        data->last_update = jiffies - HZ;
 381        mutex_init(&data->lock);
 382
 383        indio_dev->info = &vz89x_info;
 384        indio_dev->name = dev_name(dev);
 385        indio_dev->modes = INDIO_DIRECT_MODE;
 386
 387        indio_dev->channels = data->chip->channels;
 388        indio_dev->num_channels = data->chip->num_channels;
 389
 390        return devm_iio_device_register(dev, indio_dev);
 391}
 392
 393static const struct i2c_device_id vz89x_id[] = {
 394        { "vz89x", VZ89X },
 395        { "vz89te", VZ89TE },
 396        { }
 397};
 398MODULE_DEVICE_TABLE(i2c, vz89x_id);
 399
 400static struct i2c_driver vz89x_driver = {
 401        .driver = {
 402                .name   = "vz89x",
 403                .of_match_table = vz89x_dt_ids,
 404        },
 405        .probe = vz89x_probe,
 406        .id_table = vz89x_id,
 407};
 408module_i2c_driver(vz89x_driver);
 409
 410MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
 411MODULE_DESCRIPTION("SGX Sensortech MiCS VZ89X VOC sensors");
 412MODULE_LICENSE("GPL v2");
 413