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