sgp40.c (9434B)
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");