linux/drivers/iio/chemical/sgp40.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * sgp40.c - Support for Sensirion SGP40 Gas Sensor
   4 *
   5 * Copyright (C) 2021 Andreas Klinger <ak@it-klinger.de>
   6 *
   7 * I2C slave address: 0x59
   8 *
   9 * Datasheet can be found here:
  10 * https://www.sensirion.com/file/datasheet_sgp40
  11 *
  12 * There are two functionalities supported:
  13 *
  14 * 1) read raw logarithmic resistance value from sensor
  15 *    --> useful to pass it to the algorithm of the sensor vendor for
  16 *    measuring deteriorations and improvements of air quality.
  17 *
  18 * 2) calculate an estimated absolute voc index (0 - 500 index points) for
  19 *    measuring the air quality.
  20 *    For this purpose the value of the resistance for which the voc index
  21 *    will be 250 can be set up using calibbias.
  22 *
  23 * Compensation values of relative humidity and temperature can be set up
  24 * by writing to the out values of temp and humidityrelative.
  25 */
  26
  27#include <linux/delay.h>
  28#include <linux/crc8.h>
  29#include <linux/module.h>
  30#include <linux/mutex.h>
  31#include <linux/i2c.h>
  32#include <linux/iio/iio.h>
  33
  34/*
  35 * floating point calculation of voc is done as integer
  36 * where numbers are multiplied by 1 << SGP40_CALC_POWER
  37 */
  38#define SGP40_CALC_POWER        14
  39
  40#define SGP40_CRC8_POLYNOMIAL   0x31
  41#define SGP40_CRC8_INIT         0xff
  42
  43DECLARE_CRC8_TABLE(sgp40_crc8_table);
  44
  45struct sgp40_data {
  46        struct device           *dev;
  47        struct i2c_client       *client;
  48        int                     rht;
  49        int                     temp;
  50        int                     res_calibbias;
  51        /* Prevent concurrent access to rht, tmp, calibbias */
  52        struct mutex            lock;
  53};
  54
  55struct sgp40_tg_measure {
  56        u8      command[2];
  57        __be16  rht_ticks;
  58        u8      rht_crc;
  59        __be16  temp_ticks;
  60        u8      temp_crc;
  61} __packed;
  62
  63struct sgp40_tg_result {
  64        __be16  res_ticks;
  65        u8      res_crc;
  66} __packed;
  67
  68static const struct iio_chan_spec sgp40_channels[] = {
  69        {
  70                .type = IIO_CONCENTRATION,
  71                .channel2 = IIO_MOD_VOC,
  72                .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
  73        },
  74        {
  75                .type = IIO_RESISTANCE,
  76                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
  77                        BIT(IIO_CHAN_INFO_CALIBBIAS),
  78        },
  79        {
  80                .type = IIO_TEMP,
  81                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
  82                .output = 1,
  83        },
  84        {
  85                .type = IIO_HUMIDITYRELATIVE,
  86                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
  87                .output = 1,
  88        },
  89};
  90
  91/*
  92 * taylor approximation of e^x:
  93 * y = 1 + x + x^2 / 2 + x^3 / 6 + x^4 / 24 + ... + x^n / n!
  94 *
  95 * Because we are calculating x real value multiplied by 2^power we get
  96 * an additional 2^power^n to divide for every element. For a reasonable
  97 * precision this would overflow after a few iterations. Therefore we
  98 * divide the x^n part whenever its about to overflow (xmax).
  99 */
 100
 101static u32 sgp40_exp(int exp, u32 power, u32 rounds)
 102{
 103        u32 x, y, xp;
 104        u32 factorial, divider, xmax;
 105        int sign = 1;
 106        int i;
 107
 108        if (exp == 0)
 109                return 1 << power;
 110        else if (exp < 0) {
 111                sign = -1;
 112                exp *= -1;
 113        }
 114
 115        xmax = 0x7FFFFFFF / exp;
 116        x = exp;
 117        xp = 1;
 118        factorial = 1;
 119        y = 1 << power;
 120        divider = 0;
 121
 122        for (i = 1; i <= rounds; i++) {
 123                xp *= x;
 124                factorial *= i;
 125                y += (xp >> divider) / factorial;
 126                divider += power;
 127                /* divide when next multiplication would overflow */
 128                if (xp >= xmax) {
 129                        xp >>= power;
 130                        divider -= power;
 131                }
 132        }
 133
 134        if (sign == -1)
 135                return (1 << (power * 2)) / y;
 136        else
 137                return y;
 138}
 139
 140static int sgp40_calc_voc(struct sgp40_data *data, u16 resistance_raw, int *voc)
 141{
 142        int x;
 143        u32 exp = 0;
 144
 145        /* we calculate as a multiple of 16384 (2^14) */
 146        mutex_lock(&data->lock);
 147        x = ((int)resistance_raw - data->res_calibbias) * 106;
 148        mutex_unlock(&data->lock);
 149
 150        /* voc = 500 / (1 + e^x) */
 151        exp = sgp40_exp(x, SGP40_CALC_POWER, 18);
 152        *voc = 500 * ((1 << (SGP40_CALC_POWER * 2)) / ((1<<SGP40_CALC_POWER) + exp));
 153
 154        dev_dbg(data->dev, "raw: %d res_calibbias: %d x: %d exp: %d voc: %d\n",
 155                                resistance_raw, data->res_calibbias, x, exp, *voc);
 156
 157        return 0;
 158}
 159
 160static int sgp40_measure_resistance_raw(struct sgp40_data *data, u16 *resistance_raw)
 161{
 162        int ret;
 163        struct i2c_client *client = data->client;
 164        u32 ticks;
 165        u16 ticks16;
 166        u8 crc;
 167        struct sgp40_tg_measure tg = {.command = {0x26, 0x0F}};
 168        struct sgp40_tg_result tgres;
 169
 170        mutex_lock(&data->lock);
 171
 172        ticks = (data->rht / 10) * 65535 / 10000;
 173        ticks16 = (u16)clamp(ticks, 0u, 65535u); /* clamp between 0 .. 100 %rH */
 174        tg.rht_ticks = cpu_to_be16(ticks16);
 175        tg.rht_crc = crc8(sgp40_crc8_table, (u8 *)&tg.rht_ticks, 2, SGP40_CRC8_INIT);
 176
 177        ticks = ((data->temp + 45000) / 10 ) * 65535 / 17500;
 178        ticks16 = (u16)clamp(ticks, 0u, 65535u); /* clamp between -45 .. +130 °C */
 179        tg.temp_ticks = cpu_to_be16(ticks16);
 180        tg.temp_crc = crc8(sgp40_crc8_table, (u8 *)&tg.temp_ticks, 2, SGP40_CRC8_INIT);
 181
 182        mutex_unlock(&data->lock);
 183
 184        ret = i2c_master_send(client, (const char *)&tg, sizeof(tg));
 185        if (ret != sizeof(tg)) {
 186                dev_warn(data->dev, "i2c_master_send ret: %d sizeof: %zu\n", ret, sizeof(tg));
 187                return -EIO;
 188        }
 189        msleep(30);
 190
 191        ret = i2c_master_recv(client, (u8 *)&tgres, sizeof(tgres));
 192        if (ret < 0)
 193                return ret;
 194        if (ret != sizeof(tgres)) {
 195                dev_warn(data->dev, "i2c_master_recv ret: %d sizeof: %zu\n", ret, sizeof(tgres));
 196                return -EIO;
 197        }
 198
 199        crc = crc8(sgp40_crc8_table, (u8 *)&tgres.res_ticks, 2, SGP40_CRC8_INIT);
 200        if (crc != tgres.res_crc) {
 201                dev_err(data->dev, "CRC error while measure-raw\n");
 202                return -EIO;
 203        }
 204
 205        *resistance_raw = be16_to_cpu(tgres.res_ticks);
 206
 207        return 0;
 208}
 209
 210static int sgp40_read_raw(struct iio_dev *indio_dev,
 211                        struct iio_chan_spec const *chan, int *val,
 212                        int *val2, long mask)
 213{
 214        struct sgp40_data *data = iio_priv(indio_dev);
 215        int ret, voc;
 216        u16 resistance_raw;
 217
 218        switch (mask) {
 219        case IIO_CHAN_INFO_RAW:
 220                switch (chan->type) {
 221                case IIO_RESISTANCE:
 222                        ret = sgp40_measure_resistance_raw(data, &resistance_raw);
 223                        if (ret)
 224                                return ret;
 225
 226                        *val = resistance_raw;
 227                        return IIO_VAL_INT;
 228                case IIO_TEMP:
 229                        mutex_lock(&data->lock);
 230                        *val = data->temp;
 231                        mutex_unlock(&data->lock);
 232                        return IIO_VAL_INT;
 233                case IIO_HUMIDITYRELATIVE:
 234                        mutex_lock(&data->lock);
 235                        *val = data->rht;
 236                        mutex_unlock(&data->lock);
 237                        return IIO_VAL_INT;
 238                default:
 239                        return -EINVAL;
 240                }
 241        case IIO_CHAN_INFO_PROCESSED:
 242                ret = sgp40_measure_resistance_raw(data, &resistance_raw);
 243                if (ret)
 244                        return ret;
 245
 246                ret = sgp40_calc_voc(data, resistance_raw, &voc);
 247                if (ret)
 248                        return ret;
 249
 250                *val = voc / (1 << SGP40_CALC_POWER);
 251                /*
 252                 * calculation should fit into integer, where:
 253                 * voc <= (500 * 2^SGP40_CALC_POWER) = 8192000
 254                 * (with SGP40_CALC_POWER = 14)
 255                 */
 256                *val2 = ((voc % (1 << SGP40_CALC_POWER)) * 244) / (1 << (SGP40_CALC_POWER - 12));
 257                dev_dbg(data->dev, "voc: %d val: %d.%06d\n", voc, *val, *val2);
 258                return IIO_VAL_INT_PLUS_MICRO;
 259        case IIO_CHAN_INFO_CALIBBIAS:
 260                mutex_lock(&data->lock);
 261                *val = data->res_calibbias;
 262                mutex_unlock(&data->lock);
 263                return IIO_VAL_INT;
 264        default:
 265                return -EINVAL;
 266        }
 267}
 268
 269static int sgp40_write_raw(struct iio_dev *indio_dev,
 270                        struct iio_chan_spec const *chan, int val,
 271                        int val2, long mask)
 272{
 273        struct sgp40_data *data = iio_priv(indio_dev);
 274
 275        switch (mask) {
 276        case IIO_CHAN_INFO_RAW:
 277                switch (chan->type) {
 278                case IIO_TEMP:
 279                        if ((val < -45000) || (val > 130000))
 280                                return -EINVAL;
 281
 282                        mutex_lock(&data->lock);
 283                        data->temp = val;
 284                        mutex_unlock(&data->lock);
 285                        return 0;
 286                case IIO_HUMIDITYRELATIVE:
 287                        if ((val < 0) || (val > 100000))
 288                                return -EINVAL;
 289
 290                        mutex_lock(&data->lock);
 291                        data->rht = val;
 292                        mutex_unlock(&data->lock);
 293                        return 0;
 294                default:
 295                        return -EINVAL;
 296                }
 297        case IIO_CHAN_INFO_CALIBBIAS:
 298                if ((val < 20000) || (val > 52768))
 299                        return -EINVAL;
 300
 301                mutex_lock(&data->lock);
 302                data->res_calibbias = val;
 303                mutex_unlock(&data->lock);
 304                return 0;
 305        }
 306        return -EINVAL;
 307}
 308
 309static const struct iio_info sgp40_info = {
 310        .read_raw       = sgp40_read_raw,
 311        .write_raw      = sgp40_write_raw,
 312};
 313
 314static int sgp40_probe(struct i2c_client *client,
 315                     const struct i2c_device_id *id)
 316{
 317        struct device *dev = &client->dev;
 318        struct iio_dev *indio_dev;
 319        struct sgp40_data *data;
 320        int ret;
 321
 322        indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
 323        if (!indio_dev)
 324                return -ENOMEM;
 325
 326        data = iio_priv(indio_dev);
 327        data->client = client;
 328        data->dev = dev;
 329
 330        crc8_populate_msb(sgp40_crc8_table, SGP40_CRC8_POLYNOMIAL);
 331
 332        mutex_init(&data->lock);
 333
 334        /* set default values */
 335        data->rht = 50000;              /* 50 % */
 336        data->temp = 25000;             /* 25 °C */
 337        data->res_calibbias = 30000;    /* resistance raw value for voc index of 250 */
 338
 339        indio_dev->info = &sgp40_info;
 340        indio_dev->name = id->name;
 341        indio_dev->modes = INDIO_DIRECT_MODE;
 342        indio_dev->channels = sgp40_channels;
 343        indio_dev->num_channels = ARRAY_SIZE(sgp40_channels);
 344
 345        ret = devm_iio_device_register(dev, indio_dev);
 346        if (ret)
 347                dev_err(dev, "failed to register iio device\n");
 348
 349        return ret;
 350}
 351
 352static const struct i2c_device_id sgp40_id[] = {
 353        { "sgp40" },
 354        { }
 355};
 356
 357MODULE_DEVICE_TABLE(i2c, sgp40_id);
 358
 359static const struct of_device_id sgp40_dt_ids[] = {
 360        { .compatible = "sensirion,sgp40" },
 361        { }
 362};
 363
 364MODULE_DEVICE_TABLE(of, sgp40_dt_ids);
 365
 366static struct i2c_driver sgp40_driver = {
 367        .driver = {
 368                .name = "sgp40",
 369                .of_match_table = sgp40_dt_ids,
 370        },
 371        .probe = sgp40_probe,
 372        .id_table = sgp40_id,
 373};
 374module_i2c_driver(sgp40_driver);
 375
 376MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
 377MODULE_DESCRIPTION("Sensirion SGP40 gas sensor");
 378MODULE_LICENSE("GPL v2");
 379