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 | |
43 | DECLARE_CRC8_TABLE(sgp40_crc8_table); |
44 | |
45 | struct 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 | |
55 | struct 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 | |
63 | struct sgp40_tg_result { |
64 | __be16 res_ticks; |
65 | u8 res_crc; |
66 | } __packed; |
67 | |
68 | static 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 | |
101 | static 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 | |
140 | static 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(lock: &data->lock); |
149 | |
150 | /* voc = 500 / (1 + e^x) */ |
151 | exp = sgp40_exp(exp: x, SGP40_CALC_POWER, rounds: 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 | |
160 | static 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(table: sgp40_crc8_table, pdata: (u8 *)&tg.rht_ticks, nbytes: 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(table: sgp40_crc8_table, pdata: (u8 *)&tg.temp_ticks, nbytes: 2, SGP40_CRC8_INIT); |
181 | |
182 | mutex_unlock(lock: &data->lock); |
183 | |
184 | ret = i2c_master_send(client, buf: (const char *)&tg, count: 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(msecs: 30); |
190 | |
191 | ret = i2c_master_recv(client, buf: (u8 *)&tgres, count: 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(table: sgp40_crc8_table, pdata: (u8 *)&tgres.res_ticks, nbytes: 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 | |
210 | static 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: &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(lock: &data->lock); |
232 | return IIO_VAL_INT; |
233 | case IIO_HUMIDITYRELATIVE: |
234 | mutex_lock(&data->lock); |
235 | *val = data->rht; |
236 | mutex_unlock(lock: &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: &resistance_raw); |
243 | if (ret) |
244 | return ret; |
245 | |
246 | ret = sgp40_calc_voc(data, resistance_raw, voc: &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(lock: &data->lock); |
263 | return IIO_VAL_INT; |
264 | default: |
265 | return -EINVAL; |
266 | } |
267 | } |
268 | |
269 | static 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(lock: &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(lock: &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(lock: &data->lock); |
304 | return 0; |
305 | } |
306 | return -EINVAL; |
307 | } |
308 | |
309 | static const struct iio_info sgp40_info = { |
310 | .read_raw = sgp40_read_raw, |
311 | .write_raw = sgp40_write_raw, |
312 | }; |
313 | |
314 | static int sgp40_probe(struct i2c_client *client) |
315 | { |
316 | const struct i2c_device_id *id = i2c_client_get_device_id(client); |
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(parent: dev, sizeof_priv: 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(table: 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 | |
352 | static const struct i2c_device_id sgp40_id[] = { |
353 | { "sgp40" }, |
354 | { } |
355 | }; |
356 | |
357 | MODULE_DEVICE_TABLE(i2c, sgp40_id); |
358 | |
359 | static const struct of_device_id sgp40_dt_ids[] = { |
360 | { .compatible = "sensirion,sgp40" }, |
361 | { } |
362 | }; |
363 | |
364 | MODULE_DEVICE_TABLE(of, sgp40_dt_ids); |
365 | |
366 | static 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 | }; |
374 | module_i2c_driver(sgp40_driver); |
375 | |
376 | MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>" ); |
377 | MODULE_DESCRIPTION("Sensirion SGP40 gas sensor" ); |
378 | MODULE_LICENSE("GPL v2" ); |
379 | |