root/drivers/iio/chemical/sgp30.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. sgp_verify_buffer
  2. sgp_read_cmd
  3. sgp_measure_iaq
  4. sgp_iaq_thread_sleep_until
  5. sgp_iaq_threadfn
  6. sgp_read_raw
  7. sgp_check_compat
  8. sgp_init
  9. sgp_probe
  10. sgp_remove

   1 // SPDX-License-Identifier: GPL-2.0
   2 /*
   3  * sgp30.c - Support for Sensirion SGP Gas Sensors
   4  *
   5  * Copyright (C) 2018 Andreas Brauchli <andreas.brauchli@sensirion.com>
   6  *
   7  * I2C slave address: 0x58
   8  *
   9  * Datasheets:
  10  * https://www.sensirion.com/file/datasheet_sgp30
  11  * https://www.sensirion.com/file/datasheet_sgpc3
  12  *
  13  * TODO:
  14  * - baseline support
  15  * - humidity compensation
  16  * - power mode switching (SGPC3)
  17  */
  18 
  19 #include <linux/crc8.h>
  20 #include <linux/delay.h>
  21 #include <linux/kthread.h>
  22 #include <linux/module.h>
  23 #include <linux/mutex.h>
  24 #include <linux/i2c.h>
  25 #include <linux/of_device.h>
  26 #include <linux/iio/iio.h>
  27 #include <linux/iio/sysfs.h>
  28 
  29 #define SGP_WORD_LEN                            2
  30 #define SGP_CRC8_POLYNOMIAL                     0x31
  31 #define SGP_CRC8_INIT                           0xff
  32 #define SGP_CRC8_LEN                            1
  33 #define SGP_CMD(cmd_word)                       cpu_to_be16(cmd_word)
  34 #define SGP_CMD_DURATION_US                     12000
  35 #define SGP_MEASUREMENT_DURATION_US             50000
  36 #define SGP_CMD_LEN                             SGP_WORD_LEN
  37 #define SGP_CMD_MAX_BUF_SIZE                    (SGP_CMD_LEN + 2 * SGP_WORD_LEN)
  38 #define SGP_MEASUREMENT_LEN                     2
  39 #define SGP30_MEASURE_INTERVAL_HZ               1
  40 #define SGPC3_MEASURE_INTERVAL_HZ               2
  41 #define SGP_VERS_PRODUCT(data)  ((((data)->feature_set) & 0xf000) >> 12)
  42 #define SGP_VERS_RESERVED(data) ((((data)->feature_set) & 0x0800) >> 11)
  43 #define SGP_VERS_GEN(data)      ((((data)->feature_set) & 0x0600) >> 9)
  44 #define SGP_VERS_ENG_BIT(data)  ((((data)->feature_set) & 0x0100) >> 8)
  45 #define SGP_VERS_MAJOR(data)    ((((data)->feature_set) & 0x00e0) >> 5)
  46 #define SGP_VERS_MINOR(data)    (((data)->feature_set) & 0x001f)
  47 
  48 DECLARE_CRC8_TABLE(sgp_crc8_table);
  49 
  50 enum sgp_product_id {
  51         SGP30 = 0,
  52         SGPC3,
  53 };
  54 
  55 enum sgp30_channel_idx {
  56         SGP30_IAQ_TVOC_IDX = 0,
  57         SGP30_IAQ_CO2EQ_IDX,
  58         SGP30_SIG_ETOH_IDX,
  59         SGP30_SIG_H2_IDX,
  60 };
  61 
  62 enum sgpc3_channel_idx {
  63         SGPC3_IAQ_TVOC_IDX = 10,
  64         SGPC3_SIG_ETOH_IDX,
  65 };
  66 
  67 enum sgp_cmd {
  68         SGP_CMD_IAQ_INIT                        = SGP_CMD(0x2003),
  69         SGP_CMD_IAQ_MEASURE                     = SGP_CMD(0x2008),
  70         SGP_CMD_GET_FEATURE_SET                 = SGP_CMD(0x202f),
  71         SGP_CMD_GET_SERIAL_ID                   = SGP_CMD(0x3682),
  72 
  73         SGP30_CMD_MEASURE_SIGNAL                = SGP_CMD(0x2050),
  74 
  75         SGPC3_CMD_MEASURE_RAW                   = SGP_CMD(0x2046),
  76 };
  77 
  78 struct sgp_version {
  79         u8 major;
  80         u8 minor;
  81 };
  82 
  83 struct sgp_crc_word {
  84         __be16 value;
  85         u8 crc8;
  86 } __attribute__((__packed__));
  87 
  88 union sgp_reading {
  89         u8 start;
  90         struct sgp_crc_word raw_words[4];
  91 };
  92 
  93 enum _iaq_buffer_state {
  94         IAQ_BUFFER_EMPTY = 0,
  95         IAQ_BUFFER_DEFAULT_VALS,
  96         IAQ_BUFFER_VALID,
  97 };
  98 
  99 struct sgp_data {
 100         struct i2c_client *client;
 101         struct task_struct *iaq_thread;
 102         struct mutex data_lock;
 103         unsigned long iaq_init_start_jiffies;
 104         unsigned long iaq_defval_skip_jiffies;
 105         u16 product_id;
 106         u16 feature_set;
 107         unsigned long measure_interval_jiffies;
 108         enum sgp_cmd iaq_init_cmd;
 109         enum sgp_cmd measure_iaq_cmd;
 110         enum sgp_cmd measure_gas_signals_cmd;
 111         union sgp_reading buffer;
 112         union sgp_reading iaq_buffer;
 113         enum _iaq_buffer_state iaq_buffer_state;
 114 };
 115 
 116 struct sgp_device {
 117         const struct iio_chan_spec *channels;
 118         int num_channels;
 119 };
 120 
 121 static const struct sgp_version supported_versions_sgp30[] = {
 122         {
 123                 .major = 1,
 124                 .minor = 0,
 125         },
 126 };
 127 
 128 static const struct sgp_version supported_versions_sgpc3[] = {
 129         {
 130                 .major = 0,
 131                 .minor = 4,
 132         },
 133 };
 134 
 135 static const struct iio_chan_spec sgp30_channels[] = {
 136         {
 137                 .type = IIO_CONCENTRATION,
 138                 .channel2 = IIO_MOD_VOC,
 139                 .modified = 1,
 140                 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
 141                 .address = SGP30_IAQ_TVOC_IDX,
 142         },
 143         {
 144                 .type = IIO_CONCENTRATION,
 145                 .channel2 = IIO_MOD_CO2,
 146                 .modified = 1,
 147                 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
 148                 .address = SGP30_IAQ_CO2EQ_IDX,
 149         },
 150         {
 151                 .type = IIO_CONCENTRATION,
 152                 .channel2 = IIO_MOD_ETHANOL,
 153                 .modified = 1,
 154                 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
 155                 .address = SGP30_SIG_ETOH_IDX,
 156         },
 157         {
 158                 .type = IIO_CONCENTRATION,
 159                 .channel2 = IIO_MOD_H2,
 160                 .modified = 1,
 161                 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
 162                 .address = SGP30_SIG_H2_IDX,
 163         },
 164 };
 165 
 166 static const struct iio_chan_spec sgpc3_channels[] = {
 167         {
 168                 .type = IIO_CONCENTRATION,
 169                 .channel2 = IIO_MOD_VOC,
 170                 .modified = 1,
 171                 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
 172                 .address = SGPC3_IAQ_TVOC_IDX,
 173         },
 174         {
 175                 .type = IIO_CONCENTRATION,
 176                 .channel2 = IIO_MOD_ETHANOL,
 177                 .modified = 1,
 178                 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
 179                 .address = SGPC3_SIG_ETOH_IDX,
 180         },
 181 };
 182 
 183 static const struct sgp_device sgp_devices[] = {
 184         [SGP30] = {
 185                 .channels = sgp30_channels,
 186                 .num_channels = ARRAY_SIZE(sgp30_channels),
 187         },
 188         [SGPC3] = {
 189                 .channels = sgpc3_channels,
 190                 .num_channels = ARRAY_SIZE(sgpc3_channels),
 191         },
 192 };
 193 
 194 /**
 195  * sgp_verify_buffer() - verify the checksums of the data buffer words
 196  *
 197  * @data:       SGP data
 198  * @buf:        Raw data buffer
 199  * @word_count: Num data words stored in the buffer, excluding CRC bytes
 200  *
 201  * Return:      0 on success, negative error otherwise.
 202  */
 203 static int sgp_verify_buffer(const struct sgp_data *data,
 204                              union sgp_reading *buf, size_t word_count)
 205 {
 206         size_t size = word_count * (SGP_WORD_LEN + SGP_CRC8_LEN);
 207         int i;
 208         u8 crc;
 209         u8 *data_buf = &buf->start;
 210 
 211         for (i = 0; i < size; i += SGP_WORD_LEN + SGP_CRC8_LEN) {
 212                 crc = crc8(sgp_crc8_table, &data_buf[i], SGP_WORD_LEN,
 213                            SGP_CRC8_INIT);
 214                 if (crc != data_buf[i + SGP_WORD_LEN]) {
 215                         dev_err(&data->client->dev, "CRC error\n");
 216                         return -EIO;
 217                 }
 218         }
 219 
 220         return 0;
 221 }
 222 
 223 /**
 224  * sgp_read_cmd() - reads data from sensor after issuing a command
 225  * The caller must hold data->data_lock for the duration of the call.
 226  * @data:        SGP data
 227  * @cmd:         SGP Command to issue
 228  * @buf:         Raw data buffer to use
 229  * @word_count:  Num words to read, excluding CRC bytes
 230  *
 231  * Return:       0 on success, negative error otherwise.
 232  */
 233 static int sgp_read_cmd(struct sgp_data *data, enum sgp_cmd cmd,
 234                         union sgp_reading *buf, size_t word_count,
 235                         unsigned long duration_us)
 236 {
 237         int ret;
 238         struct i2c_client *client = data->client;
 239         size_t size = word_count * (SGP_WORD_LEN + SGP_CRC8_LEN);
 240         u8 *data_buf;
 241 
 242         ret = i2c_master_send(client, (const char *)&cmd, SGP_CMD_LEN);
 243         if (ret != SGP_CMD_LEN)
 244                 return -EIO;
 245         usleep_range(duration_us, duration_us + 1000);
 246 
 247         if (word_count == 0)
 248                 return 0;
 249 
 250         data_buf = &buf->start;
 251         ret = i2c_master_recv(client, data_buf, size);
 252         if (ret < 0)
 253                 return ret;
 254         if (ret != size)
 255                 return -EIO;
 256 
 257         return sgp_verify_buffer(data, buf, word_count);
 258 }
 259 
 260 /**
 261  * sgp_measure_iaq() - measure and retrieve IAQ values from sensor
 262  * The caller must hold data->data_lock for the duration of the call.
 263  * @data:       SGP data
 264  *
 265  * Return:      0 on success, -EBUSY on default values, negative error
 266  *              otherwise.
 267  */
 268 
 269 static int sgp_measure_iaq(struct sgp_data *data)
 270 {
 271         int ret;
 272         /* data contains default values */
 273         bool default_vals = !time_after(jiffies, data->iaq_init_start_jiffies +
 274                                                  data->iaq_defval_skip_jiffies);
 275 
 276         ret = sgp_read_cmd(data, data->measure_iaq_cmd, &data->iaq_buffer,
 277                            SGP_MEASUREMENT_LEN, SGP_MEASUREMENT_DURATION_US);
 278         if (ret < 0)
 279                 return ret;
 280 
 281         data->iaq_buffer_state = IAQ_BUFFER_DEFAULT_VALS;
 282 
 283         if (default_vals)
 284                 return -EBUSY;
 285 
 286         data->iaq_buffer_state = IAQ_BUFFER_VALID;
 287 
 288         return 0;
 289 }
 290 
 291 static void sgp_iaq_thread_sleep_until(const struct sgp_data *data,
 292                                        unsigned long sleep_jiffies)
 293 {
 294         const long IAQ_POLL = 50000;
 295 
 296         while (!time_after(jiffies, sleep_jiffies)) {
 297                 usleep_range(IAQ_POLL, IAQ_POLL + 10000);
 298                 if (kthread_should_stop() || data->iaq_init_start_jiffies == 0)
 299                         return;
 300         }
 301 }
 302 
 303 static int sgp_iaq_threadfn(void *p)
 304 {
 305         struct sgp_data *data = (struct sgp_data *)p;
 306         unsigned long next_update_jiffies;
 307         int ret;
 308 
 309         while (!kthread_should_stop()) {
 310                 mutex_lock(&data->data_lock);
 311                 if (data->iaq_init_start_jiffies == 0) {
 312                         ret = sgp_read_cmd(data, data->iaq_init_cmd, NULL, 0,
 313                                            SGP_CMD_DURATION_US);
 314                         if (ret < 0)
 315                                 goto unlock_sleep_continue;
 316                         data->iaq_init_start_jiffies = jiffies;
 317                 }
 318 
 319                 ret = sgp_measure_iaq(data);
 320                 if (ret && ret != -EBUSY) {
 321                         dev_warn(&data->client->dev,
 322                                  "IAQ measurement error [%d]\n", ret);
 323                 }
 324 unlock_sleep_continue:
 325                 next_update_jiffies = jiffies + data->measure_interval_jiffies;
 326                 mutex_unlock(&data->data_lock);
 327                 sgp_iaq_thread_sleep_until(data, next_update_jiffies);
 328         }
 329 
 330         return 0;
 331 }
 332 
 333 static int sgp_read_raw(struct iio_dev *indio_dev,
 334                         struct iio_chan_spec const *chan, int *val,
 335                         int *val2, long mask)
 336 {
 337         struct sgp_data *data = iio_priv(indio_dev);
 338         struct sgp_crc_word *words;
 339         int ret;
 340 
 341         switch (mask) {
 342         case IIO_CHAN_INFO_PROCESSED:
 343                 mutex_lock(&data->data_lock);
 344                 if (data->iaq_buffer_state != IAQ_BUFFER_VALID) {
 345                         mutex_unlock(&data->data_lock);
 346                         return -EBUSY;
 347                 }
 348                 words = data->iaq_buffer.raw_words;
 349                 switch (chan->address) {
 350                 case SGP30_IAQ_TVOC_IDX:
 351                 case SGPC3_IAQ_TVOC_IDX:
 352                         *val = 0;
 353                         *val2 = be16_to_cpu(words[1].value);
 354                         ret = IIO_VAL_INT_PLUS_NANO;
 355                         break;
 356                 case SGP30_IAQ_CO2EQ_IDX:
 357                         *val = 0;
 358                         *val2 = be16_to_cpu(words[0].value);
 359                         ret = IIO_VAL_INT_PLUS_MICRO;
 360                         break;
 361                 default:
 362                         ret = -EINVAL;
 363                         break;
 364                 }
 365                 mutex_unlock(&data->data_lock);
 366                 break;
 367         case IIO_CHAN_INFO_RAW:
 368                 mutex_lock(&data->data_lock);
 369                 if (chan->address == SGPC3_SIG_ETOH_IDX) {
 370                         if (data->iaq_buffer_state == IAQ_BUFFER_EMPTY)
 371                                 ret = -EBUSY;
 372                         else
 373                                 ret = 0;
 374                         words = data->iaq_buffer.raw_words;
 375                 } else {
 376                         ret = sgp_read_cmd(data, data->measure_gas_signals_cmd,
 377                                            &data->buffer, SGP_MEASUREMENT_LEN,
 378                                            SGP_MEASUREMENT_DURATION_US);
 379                         words = data->buffer.raw_words;
 380                 }
 381                 if (ret) {
 382                         mutex_unlock(&data->data_lock);
 383                         return ret;
 384                 }
 385 
 386                 switch (chan->address) {
 387                 case SGP30_SIG_ETOH_IDX:
 388                         *val = be16_to_cpu(words[1].value);
 389                         ret = IIO_VAL_INT;
 390                         break;
 391                 case SGPC3_SIG_ETOH_IDX:
 392                 case SGP30_SIG_H2_IDX:
 393                         *val = be16_to_cpu(words[0].value);
 394                         ret = IIO_VAL_INT;
 395                         break;
 396                 default:
 397                         ret = -EINVAL;
 398                         break;
 399                 }
 400                 mutex_unlock(&data->data_lock);
 401                 break;
 402         default:
 403                 return -EINVAL;
 404         }
 405 
 406         return ret;
 407 }
 408 
 409 static int sgp_check_compat(struct sgp_data *data,
 410                             unsigned int product_id)
 411 {
 412         const struct sgp_version *supported_versions;
 413         u16 ix, num_fs;
 414         u16 product, generation, major, minor;
 415 
 416         /* driver does not match product */
 417         generation = SGP_VERS_GEN(data);
 418         if (generation != 0) {
 419                 dev_err(&data->client->dev,
 420                         "incompatible product generation %d != 0", generation);
 421                 return -ENODEV;
 422         }
 423 
 424         product = SGP_VERS_PRODUCT(data);
 425         if (product != product_id) {
 426                 dev_err(&data->client->dev,
 427                         "sensor reports a different product: 0x%04hx\n",
 428                         product);
 429                 return -ENODEV;
 430         }
 431 
 432         if (SGP_VERS_RESERVED(data))
 433                 dev_warn(&data->client->dev, "reserved bit is set\n");
 434 
 435         /* engineering samples are not supported: no interface guarantees */
 436         if (SGP_VERS_ENG_BIT(data))
 437                 return -ENODEV;
 438 
 439         switch (product) {
 440         case SGP30:
 441                 supported_versions = supported_versions_sgp30;
 442                 num_fs = ARRAY_SIZE(supported_versions_sgp30);
 443                 break;
 444         case SGPC3:
 445                 supported_versions = supported_versions_sgpc3;
 446                 num_fs = ARRAY_SIZE(supported_versions_sgpc3);
 447                 break;
 448         default:
 449                 return -ENODEV;
 450         }
 451 
 452         major = SGP_VERS_MAJOR(data);
 453         minor = SGP_VERS_MINOR(data);
 454         for (ix = 0; ix < num_fs; ix++) {
 455                 if (major == supported_versions[ix].major &&
 456                     minor >= supported_versions[ix].minor)
 457                         return 0;
 458         }
 459         dev_err(&data->client->dev, "unsupported sgp version: %d.%d\n",
 460                 major, minor);
 461 
 462         return -ENODEV;
 463 }
 464 
 465 static void sgp_init(struct sgp_data *data)
 466 {
 467         data->iaq_init_cmd = SGP_CMD_IAQ_INIT;
 468         data->iaq_init_start_jiffies = 0;
 469         data->iaq_buffer_state = IAQ_BUFFER_EMPTY;
 470         switch (SGP_VERS_PRODUCT(data)) {
 471         case SGP30:
 472                 data->measure_interval_jiffies = SGP30_MEASURE_INTERVAL_HZ * HZ;
 473                 data->measure_iaq_cmd = SGP_CMD_IAQ_MEASURE;
 474                 data->measure_gas_signals_cmd = SGP30_CMD_MEASURE_SIGNAL;
 475                 data->product_id = SGP30;
 476                 data->iaq_defval_skip_jiffies = 15 * HZ;
 477                 break;
 478         case SGPC3:
 479                 data->measure_interval_jiffies = SGPC3_MEASURE_INTERVAL_HZ * HZ;
 480                 data->measure_iaq_cmd = SGPC3_CMD_MEASURE_RAW;
 481                 data->measure_gas_signals_cmd = SGPC3_CMD_MEASURE_RAW;
 482                 data->product_id = SGPC3;
 483                 data->iaq_defval_skip_jiffies =
 484                         43 * data->measure_interval_jiffies;
 485                 break;
 486         };
 487 }
 488 
 489 static const struct iio_info sgp_info = {
 490         .read_raw       = sgp_read_raw,
 491 };
 492 
 493 static const struct of_device_id sgp_dt_ids[] = {
 494         { .compatible = "sensirion,sgp30", .data = (void *)SGP30 },
 495         { .compatible = "sensirion,sgpc3", .data = (void *)SGPC3 },
 496         { }
 497 };
 498 
 499 static int sgp_probe(struct i2c_client *client,
 500                      const struct i2c_device_id *id)
 501 {
 502         struct iio_dev *indio_dev;
 503         struct sgp_data *data;
 504         const struct of_device_id *of_id;
 505         unsigned long product_id;
 506         int ret;
 507 
 508         indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
 509         if (!indio_dev)
 510                 return -ENOMEM;
 511 
 512         of_id = of_match_device(sgp_dt_ids, &client->dev);
 513         if (of_id)
 514                 product_id = (unsigned long)of_id->data;
 515         else
 516                 product_id = id->driver_data;
 517 
 518         data = iio_priv(indio_dev);
 519         i2c_set_clientdata(client, indio_dev);
 520         data->client = client;
 521         crc8_populate_msb(sgp_crc8_table, SGP_CRC8_POLYNOMIAL);
 522         mutex_init(&data->data_lock);
 523 
 524         /* get feature set version and write it to client data */
 525         ret = sgp_read_cmd(data, SGP_CMD_GET_FEATURE_SET, &data->buffer, 1,
 526                            SGP_CMD_DURATION_US);
 527         if (ret < 0)
 528                 return ret;
 529 
 530         data->feature_set = be16_to_cpu(data->buffer.raw_words[0].value);
 531 
 532         ret = sgp_check_compat(data, product_id);
 533         if (ret)
 534                 return ret;
 535 
 536         indio_dev->dev.parent = &client->dev;
 537         indio_dev->info = &sgp_info;
 538         indio_dev->name = id->name;
 539         indio_dev->modes = INDIO_DIRECT_MODE;
 540         indio_dev->channels = sgp_devices[product_id].channels;
 541         indio_dev->num_channels = sgp_devices[product_id].num_channels;
 542 
 543         sgp_init(data);
 544 
 545         ret = devm_iio_device_register(&client->dev, indio_dev);
 546         if (ret) {
 547                 dev_err(&client->dev, "failed to register iio device\n");
 548                 return ret;
 549         }
 550 
 551         data->iaq_thread = kthread_run(sgp_iaq_threadfn, data,
 552                                        "%s-iaq", data->client->name);
 553 
 554         return 0;
 555 }
 556 
 557 static int sgp_remove(struct i2c_client *client)
 558 {
 559         struct iio_dev *indio_dev = i2c_get_clientdata(client);
 560         struct sgp_data *data = iio_priv(indio_dev);
 561 
 562         if (data->iaq_thread)
 563                 kthread_stop(data->iaq_thread);
 564 
 565         return 0;
 566 }
 567 
 568 static const struct i2c_device_id sgp_id[] = {
 569         { "sgp30", SGP30 },
 570         { "sgpc3", SGPC3 },
 571         { }
 572 };
 573 
 574 MODULE_DEVICE_TABLE(i2c, sgp_id);
 575 MODULE_DEVICE_TABLE(of, sgp_dt_ids);
 576 
 577 static struct i2c_driver sgp_driver = {
 578         .driver = {
 579                 .name = "sgp30",
 580                 .of_match_table = of_match_ptr(sgp_dt_ids),
 581         },
 582         .probe = sgp_probe,
 583         .remove = sgp_remove,
 584         .id_table = sgp_id,
 585 };
 586 module_i2c_driver(sgp_driver);
 587 
 588 MODULE_AUTHOR("Andreas Brauchli <andreas.brauchli@sensirion.com>");
 589 MODULE_AUTHOR("Pascal Sachs <pascal.sachs@sensirion.com>");
 590 MODULE_DESCRIPTION("Sensirion SGP gas sensors");
 591 MODULE_LICENSE("GPL v2");

/* [<][>][^][v][top][bottom][index][help] */