1 // SPDX-License-Identifier: GPL-2.0+
3 * sgp40.c - Support for Sensirion SGP40 Gas Sensor
5 * Copyright (C) 2021 Andreas Klinger <ak@it-klinger.de>
7 * I2C slave address: 0x59
9 * Datasheet can be found here:
10 * https://www.sensirion.com/file/datasheet_sgp40
12 * There are two functionalities supported:
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 * It can be read from the attribute in_resistance_raw.
19 * 2) calculate an estimated absolute voc index (in_concentration_input)
20 * with 0 - 500 index points) for measuring the air quality.
21 * For this purpose the value of the resistance for which the voc index
22 * will be 250 can be set up using in_resistance_calibbias (default 30000).
24 * The voc index is calculated as:
25 * x = (in_resistance_raw - in_resistance_calibbias) * 0.65
26 * in_concentration_input = 500 / (1 + e^x)
28 * Compensation values of relative humidity and temperature can be set up
29 * by writing to the out values of temp and humidityrelative.
32 #include <linux/delay.h>
33 #include <linux/crc8.h>
34 #include <linux/module.h>
35 #include <linux/mutex.h>
36 #include <linux/i2c.h>
37 #include <linux/iio/iio.h>
40 * floating point calculation of voc is done as integer
41 * where numbers are multiplied by 1 << SGP40_CALC_POWER
43 #define SGP40_CALC_POWER 14
45 #define SGP40_CRC8_POLYNOMIAL 0x31
46 #define SGP40_CRC8_INIT 0xff
48 DECLARE_CRC8_TABLE(sgp40_crc8_table
);
52 struct i2c_client
*client
;
56 /* Prevent concurrent access to rht, tmp, calibbias */
60 struct sgp40_tg_measure
{
68 struct sgp40_tg_result
{
73 static const struct iio_chan_spec sgp40_channels
[] = {
75 .type
= IIO_CONCENTRATION
,
76 .channel2
= IIO_MOD_VOC
,
77 .info_mask_separate
= BIT(IIO_CHAN_INFO_PROCESSED
),
80 .type
= IIO_RESISTANCE
,
81 .info_mask_separate
= BIT(IIO_CHAN_INFO_RAW
) |
82 BIT(IIO_CHAN_INFO_CALIBBIAS
),
86 .info_mask_separate
= BIT(IIO_CHAN_INFO_RAW
),
90 .type
= IIO_HUMIDITYRELATIVE
,
91 .info_mask_separate
= BIT(IIO_CHAN_INFO_RAW
),
97 * taylor approximation of e^x:
98 * y = 1 + x + x^2 / 2 + x^3 / 6 + x^4 / 24 + ... + x^n / n!
100 * Because we are calculating x real value multiplied by 2^power we get
101 * an additional 2^power^n to divide for every element. For a reasonable
102 * precision this would overflow after a few iterations. Therefore we
103 * divide the x^n part whenever its about to overflow (xmax).
106 static u32
sgp40_exp(int exp
, u32 power
, u32 rounds
)
109 u32 factorial
, divider
, xmax
;
120 xmax
= 0x7FFFFFFF / exp
;
127 for (i
= 1; i
<= rounds
; i
++) {
130 y
+= (xp
>> divider
) / factorial
;
132 /* divide when next multiplication would overflow */
140 return (1 << (power
* 2)) / y
;
145 static int sgp40_calc_voc(struct sgp40_data
*data
, u16 resistance_raw
, int *voc
)
150 /* we calculate as a multiple of 16384 (2^14) */
151 mutex_lock(&data
->lock
);
152 x
= ((int)resistance_raw
- data
->res_calibbias
) * 106;
153 mutex_unlock(&data
->lock
);
155 /* voc = 500 / (1 + e^x) */
156 exp
= sgp40_exp(x
, SGP40_CALC_POWER
, 18);
157 *voc
= 500 * ((1 << (SGP40_CALC_POWER
* 2)) / ((1<<SGP40_CALC_POWER
) + exp
));
159 dev_dbg(data
->dev
, "raw: %d res_calibbias: %d x: %d exp: %d voc: %d\n",
160 resistance_raw
, data
->res_calibbias
, x
, exp
, *voc
);
165 static int sgp40_measure_resistance_raw(struct sgp40_data
*data
, u16
*resistance_raw
)
168 struct i2c_client
*client
= data
->client
;
172 struct sgp40_tg_measure tg
= {.command
= {0x26, 0x0F}};
173 struct sgp40_tg_result tgres
;
175 mutex_lock(&data
->lock
);
177 ticks
= (data
->rht
/ 10) * 65535 / 10000;
178 ticks16
= (u16
)clamp(ticks
, 0u, 65535u); /* clamp between 0 .. 100 %rH */
179 tg
.rht_ticks
= cpu_to_be16(ticks16
);
180 tg
.rht_crc
= crc8(sgp40_crc8_table
, (u8
*)&tg
.rht_ticks
, 2, SGP40_CRC8_INIT
);
182 ticks
= ((data
->temp
+ 45000) / 10 ) * 65535 / 17500;
183 ticks16
= (u16
)clamp(ticks
, 0u, 65535u); /* clamp between -45 .. +130 °C */
184 tg
.temp_ticks
= cpu_to_be16(ticks16
);
185 tg
.temp_crc
= crc8(sgp40_crc8_table
, (u8
*)&tg
.temp_ticks
, 2, SGP40_CRC8_INIT
);
187 mutex_unlock(&data
->lock
);
189 ret
= i2c_master_send(client
, (const char *)&tg
, sizeof(tg
));
190 if (ret
!= sizeof(tg
)) {
191 dev_warn(data
->dev
, "i2c_master_send ret: %d sizeof: %zu\n", ret
, sizeof(tg
));
196 ret
= i2c_master_recv(client
, (u8
*)&tgres
, sizeof(tgres
));
199 if (ret
!= sizeof(tgres
)) {
200 dev_warn(data
->dev
, "i2c_master_recv ret: %d sizeof: %zu\n", ret
, sizeof(tgres
));
204 crc
= crc8(sgp40_crc8_table
, (u8
*)&tgres
.res_ticks
, 2, SGP40_CRC8_INIT
);
205 if (crc
!= tgres
.res_crc
) {
206 dev_err(data
->dev
, "CRC error while measure-raw\n");
210 *resistance_raw
= be16_to_cpu(tgres
.res_ticks
);
215 static int sgp40_read_raw(struct iio_dev
*indio_dev
,
216 struct iio_chan_spec
const *chan
, int *val
,
217 int *val2
, long mask
)
219 struct sgp40_data
*data
= iio_priv(indio_dev
);
224 case IIO_CHAN_INFO_RAW
:
225 switch (chan
->type
) {
227 ret
= sgp40_measure_resistance_raw(data
, &resistance_raw
);
231 *val
= resistance_raw
;
234 mutex_lock(&data
->lock
);
236 mutex_unlock(&data
->lock
);
238 case IIO_HUMIDITYRELATIVE
:
239 mutex_lock(&data
->lock
);
241 mutex_unlock(&data
->lock
);
246 case IIO_CHAN_INFO_PROCESSED
:
247 ret
= sgp40_measure_resistance_raw(data
, &resistance_raw
);
251 ret
= sgp40_calc_voc(data
, resistance_raw
, &voc
);
255 *val
= voc
/ (1 << SGP40_CALC_POWER
);
257 * calculation should fit into integer, where:
258 * voc <= (500 * 2^SGP40_CALC_POWER) = 8192000
259 * (with SGP40_CALC_POWER = 14)
261 *val2
= ((voc
% (1 << SGP40_CALC_POWER
)) * 244) / (1 << (SGP40_CALC_POWER
- 12));
262 dev_dbg(data
->dev
, "voc: %d val: %d.%06d\n", voc
, *val
, *val2
);
263 return IIO_VAL_INT_PLUS_MICRO
;
264 case IIO_CHAN_INFO_CALIBBIAS
:
265 mutex_lock(&data
->lock
);
266 *val
= data
->res_calibbias
;
267 mutex_unlock(&data
->lock
);
274 static int sgp40_write_raw(struct iio_dev
*indio_dev
,
275 struct iio_chan_spec
const *chan
, int val
,
278 struct sgp40_data
*data
= iio_priv(indio_dev
);
281 case IIO_CHAN_INFO_RAW
:
282 switch (chan
->type
) {
284 if ((val
< -45000) || (val
> 130000))
287 mutex_lock(&data
->lock
);
289 mutex_unlock(&data
->lock
);
291 case IIO_HUMIDITYRELATIVE
:
292 if ((val
< 0) || (val
> 100000))
295 mutex_lock(&data
->lock
);
297 mutex_unlock(&data
->lock
);
302 case IIO_CHAN_INFO_CALIBBIAS
:
303 if ((val
< 20000) || (val
> 52768))
306 mutex_lock(&data
->lock
);
307 data
->res_calibbias
= val
;
308 mutex_unlock(&data
->lock
);
314 static const struct iio_info sgp40_info
= {
315 .read_raw
= sgp40_read_raw
,
316 .write_raw
= sgp40_write_raw
,
319 static int sgp40_probe(struct i2c_client
*client
)
321 const struct i2c_device_id
*id
= i2c_client_get_device_id(client
);
322 struct device
*dev
= &client
->dev
;
323 struct iio_dev
*indio_dev
;
324 struct sgp40_data
*data
;
327 indio_dev
= devm_iio_device_alloc(dev
, sizeof(*data
));
331 data
= iio_priv(indio_dev
);
332 data
->client
= client
;
335 crc8_populate_msb(sgp40_crc8_table
, SGP40_CRC8_POLYNOMIAL
);
337 mutex_init(&data
->lock
);
339 /* set default values */
340 data
->rht
= 50000; /* 50 % */
341 data
->temp
= 25000; /* 25 °C */
342 data
->res_calibbias
= 30000; /* resistance raw value for voc index of 250 */
344 indio_dev
->info
= &sgp40_info
;
345 indio_dev
->name
= id
->name
;
346 indio_dev
->modes
= INDIO_DIRECT_MODE
;
347 indio_dev
->channels
= sgp40_channels
;
348 indio_dev
->num_channels
= ARRAY_SIZE(sgp40_channels
);
350 ret
= devm_iio_device_register(dev
, indio_dev
);
352 dev_err(dev
, "failed to register iio device\n");
357 static const struct i2c_device_id sgp40_id
[] = {
362 MODULE_DEVICE_TABLE(i2c
, sgp40_id
);
364 static const struct of_device_id sgp40_dt_ids
[] = {
365 { .compatible
= "sensirion,sgp40" },
369 MODULE_DEVICE_TABLE(of
, sgp40_dt_ids
);
371 static struct i2c_driver sgp40_driver
= {
374 .of_match_table
= sgp40_dt_ids
,
376 .probe
= sgp40_probe
,
377 .id_table
= sgp40_id
,
379 module_i2c_driver(sgp40_driver
);
381 MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
382 MODULE_DESCRIPTION("Sensirion SGP40 gas sensor");
383 MODULE_LICENSE("GPL v2");