linux/drivers/iio/chemical/ams-iaq-core.c
<<
>>
Prefs
   1/*
   2 * ams-iaq-core.c - Support for AMS iAQ-Core 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#include <linux/iio/iio.h>
  23
  24#define AMS_IAQCORE_DATA_SIZE           9
  25
  26#define AMS_IAQCORE_VOC_CO2_IDX         0
  27#define AMS_IAQCORE_VOC_RESISTANCE_IDX  1
  28#define AMS_IAQCORE_VOC_TVOC_IDX        2
  29
  30struct ams_iaqcore_reading {
  31        __be16 co2_ppm;
  32        u8 status;
  33        __be32 resistance;
  34        __be16 voc_ppb;
  35} __attribute__((__packed__));
  36
  37struct ams_iaqcore_data {
  38        struct i2c_client *client;
  39        struct mutex lock;
  40        unsigned long last_update;
  41
  42        struct ams_iaqcore_reading buffer;
  43};
  44
  45static const struct iio_chan_spec ams_iaqcore_channels[] = {
  46        {
  47                .type = IIO_CONCENTRATION,
  48                .channel2 = IIO_MOD_CO2,
  49                .modified = 1,
  50                .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
  51                .address = AMS_IAQCORE_VOC_CO2_IDX,
  52        },
  53        {
  54                .type = IIO_RESISTANCE,
  55                .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
  56                .address = AMS_IAQCORE_VOC_RESISTANCE_IDX,
  57        },
  58        {
  59                .type = IIO_CONCENTRATION,
  60                .channel2 = IIO_MOD_VOC,
  61                .modified = 1,
  62                .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
  63                .address = AMS_IAQCORE_VOC_TVOC_IDX,
  64        },
  65};
  66
  67static int ams_iaqcore_read_measurement(struct ams_iaqcore_data *data)
  68{
  69        struct i2c_client *client = data->client;
  70        int ret;
  71
  72        struct i2c_msg msg = {
  73                .addr = client->addr,
  74                .flags = client->flags | I2C_M_RD,
  75                .len = AMS_IAQCORE_DATA_SIZE,
  76                .buf = (char *) &data->buffer,
  77        };
  78
  79        ret = i2c_transfer(client->adapter, &msg, 1);
  80
  81        return (ret == AMS_IAQCORE_DATA_SIZE) ? 0 : ret;
  82}
  83
  84static int ams_iaqcore_get_measurement(struct ams_iaqcore_data *data)
  85{
  86        int ret;
  87
  88        /* sensor can only be polled once a second max per datasheet */
  89        if (!time_after(jiffies, data->last_update + HZ))
  90                return 0;
  91
  92        ret = ams_iaqcore_read_measurement(data);
  93        if (ret < 0)
  94                return ret;
  95
  96        data->last_update = jiffies;
  97
  98        return 0;
  99}
 100
 101static int ams_iaqcore_read_raw(struct iio_dev *indio_dev,
 102                                struct iio_chan_spec const *chan, int *val,
 103                                int *val2, long mask)
 104{
 105        struct ams_iaqcore_data *data = iio_priv(indio_dev);
 106        int ret;
 107
 108        if (mask != IIO_CHAN_INFO_PROCESSED)
 109                return -EINVAL;
 110
 111        mutex_lock(&data->lock);
 112        ret = ams_iaqcore_get_measurement(data);
 113
 114        if (ret)
 115                goto err_out;
 116
 117        switch (chan->address) {
 118        case AMS_IAQCORE_VOC_CO2_IDX:
 119                *val = 0;
 120                *val2 = be16_to_cpu(data->buffer.co2_ppm);
 121                ret = IIO_VAL_INT_PLUS_MICRO;
 122                break;
 123        case AMS_IAQCORE_VOC_RESISTANCE_IDX:
 124                *val = be32_to_cpu(data->buffer.resistance);
 125                ret = IIO_VAL_INT;
 126                break;
 127        case AMS_IAQCORE_VOC_TVOC_IDX:
 128                *val = 0;
 129                *val2 = be16_to_cpu(data->buffer.voc_ppb);
 130                ret = IIO_VAL_INT_PLUS_NANO;
 131                break;
 132        default:
 133                ret = -EINVAL;
 134        }
 135
 136err_out:
 137        mutex_unlock(&data->lock);
 138
 139        return ret;
 140}
 141
 142static const struct iio_info ams_iaqcore_info = {
 143        .read_raw       = ams_iaqcore_read_raw,
 144        .driver_module  = THIS_MODULE,
 145};
 146
 147static int ams_iaqcore_probe(struct i2c_client *client,
 148                             const struct i2c_device_id *id)
 149{
 150        struct iio_dev *indio_dev;
 151        struct ams_iaqcore_data *data;
 152
 153        indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
 154        if (!indio_dev)
 155                return -ENOMEM;
 156
 157        data = iio_priv(indio_dev);
 158        i2c_set_clientdata(client, indio_dev);
 159        data->client = client;
 160
 161        /* so initial reading will complete */
 162        data->last_update = jiffies - HZ;
 163        mutex_init(&data->lock);
 164
 165        indio_dev->dev.parent = &client->dev;
 166        indio_dev->info = &ams_iaqcore_info;
 167        indio_dev->name = dev_name(&client->dev);
 168        indio_dev->modes = INDIO_DIRECT_MODE;
 169
 170        indio_dev->channels = ams_iaqcore_channels;
 171        indio_dev->num_channels = ARRAY_SIZE(ams_iaqcore_channels);
 172
 173        return devm_iio_device_register(&client->dev, indio_dev);
 174}
 175
 176static const struct i2c_device_id ams_iaqcore_id[] = {
 177        { "ams-iaq-core", 0 },
 178        { }
 179};
 180MODULE_DEVICE_TABLE(i2c, ams_iaqcore_id);
 181
 182static const struct of_device_id ams_iaqcore_dt_ids[] = {
 183        { .compatible = "ams,iaq-core" },
 184        { }
 185};
 186MODULE_DEVICE_TABLE(of, ams_iaqcore_dt_ids);
 187
 188static struct i2c_driver ams_iaqcore_driver = {
 189        .driver = {
 190                .name   = "ams-iaq-core",
 191                .of_match_table = of_match_ptr(ams_iaqcore_dt_ids),
 192        },
 193        .probe = ams_iaqcore_probe,
 194        .id_table = ams_iaqcore_id,
 195};
 196module_i2c_driver(ams_iaqcore_driver);
 197
 198MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
 199MODULE_DESCRIPTION("AMS iAQ-Core VOC sensors");
 200MODULE_LICENSE("GPL v2");
 201