sgp40.c revision 1.2 1 1.2 christos /* $NetBSD: sgp40.c,v 1.2 2021/10/20 17:52:44 christos Exp $ */
2 1.1 brad
3 1.1 brad /*
4 1.1 brad * Copyright (c) 2021 Brad Spencer <brad (at) anduin.eldar.org>
5 1.1 brad *
6 1.1 brad * Permission to use, copy, modify, and distribute this software for any
7 1.1 brad * purpose with or without fee is hereby granted, provided that the above
8 1.1 brad * copyright notice and this permission notice appear in all copies.
9 1.1 brad *
10 1.1 brad * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 1.1 brad * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 1.1 brad * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 1.1 brad * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 1.1 brad * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 1.1 brad * ACTION OF CONTRACT, NEGL`IGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 1.1 brad * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 1.1 brad */
18 1.1 brad
19 1.1 brad #include <sys/cdefs.h>
20 1.2 christos __KERNEL_RCSID(0, "$NetBSD: sgp40.c,v 1.2 2021/10/20 17:52:44 christos Exp $");
21 1.1 brad
22 1.1 brad /*
23 1.1 brad Driver for the Sensirion SGP40 MOx gas sensor for air quality
24 1.1 brad */
25 1.1 brad
26 1.1 brad #include <sys/param.h>
27 1.1 brad #include <sys/systm.h>
28 1.1 brad #include <sys/kernel.h>
29 1.1 brad #include <sys/device.h>
30 1.1 brad #include <sys/module.h>
31 1.1 brad #include <sys/sysctl.h>
32 1.1 brad #include <sys/mutex.h>
33 1.1 brad #include <sys/condvar.h>
34 1.1 brad #include <sys/kthread.h>
35 1.1 brad
36 1.1 brad #include <dev/sysmon/sysmonvar.h>
37 1.1 brad #include <dev/i2c/i2cvar.h>
38 1.1 brad #include <dev/i2c/sgp40reg.h>
39 1.1 brad #include <dev/i2c/sgp40var.h>
40 1.1 brad
41 1.1 brad #include <dev/i2c/sensirion_arch_config.h>
42 1.1 brad #include <dev/i2c/sensirion_voc_algorithm.h>
43 1.1 brad
44 1.1 brad static uint8_t sgp40_crc(uint8_t *, size_t);
45 1.2 christos static int sgp40_cmdr(struct sgp40_sc *, uint16_t, uint8_t *, uint8_t,
46 1.2 christos uint8_t *, size_t);
47 1.1 brad static int sgp40_poke(i2c_tag_t, i2c_addr_t, bool);
48 1.1 brad static int sgp40_match(device_t, cfdata_t, void *);
49 1.1 brad static void sgp40_attach(device_t, device_t, void *);
50 1.1 brad static int sgp40_detach(device_t, int);
51 1.1 brad static void sgp40_refresh(struct sysmon_envsys *, envsys_data_t *);
52 1.1 brad static int sgp40_verify_sysctl(SYSCTLFN_ARGS);
53 1.1 brad static int sgp40_verify_temp_sysctl(SYSCTLFN_ARGS);
54 1.1 brad static int sgp40_verify_rh_sysctl(SYSCTLFN_ARGS);
55 1.1 brad static void sgp40_thread(void *);
56 1.1 brad static void sgp40_stop_thread(void *);
57 1.1 brad static void sgp40_take_measurement(void *, VocAlgorithmParams *);
58 1.1 brad
59 1.1 brad #define SGP40_DEBUG
60 1.1 brad #ifdef SGP40_DEBUG
61 1.1 brad #define DPRINTF(s, l, x) \
62 1.1 brad do { \
63 1.1 brad if (l <= s->sc_sgp40debug) \
64 1.1 brad printf x; \
65 1.1 brad } while (/*CONSTCOND*/0)
66 1.1 brad #else
67 1.1 brad #define DPRINTF(s, l, x)
68 1.1 brad #endif
69 1.1 brad
70 1.1 brad CFATTACH_DECL_NEW(sgp40mox, sizeof(struct sgp40_sc),
71 1.1 brad sgp40_match, sgp40_attach, sgp40_detach, NULL);
72 1.1 brad
73 1.1 brad static struct sgp40_sensor sgp40_sensors[] = {
74 1.1 brad {
75 1.1 brad .desc = "VOC index",
76 1.1 brad .type = ENVSYS_INTEGER,
77 1.1 brad }
78 1.1 brad };
79 1.1 brad
80 1.1 brad static struct sgp40_timing sgp40_timings[] = {
81 1.1 brad {
82 1.1 brad .cmd = SGP40_MEASURE_RAW,
83 1.1 brad .typicaldelay = 25000,
84 1.1 brad },
85 1.1 brad {
86 1.1 brad .cmd = SGP40_MEASURE_TEST,
87 1.1 brad .typicaldelay = 240000,
88 1.1 brad },
89 1.1 brad {
90 1.1 brad .cmd = SGP40_HEATER_OFF,
91 1.1 brad .typicaldelay = 100,
92 1.1 brad },
93 1.1 brad {
94 1.1 brad .cmd = SGP40_GET_SERIAL_NUMBER,
95 1.1 brad .typicaldelay = 100,
96 1.1 brad },
97 1.1 brad {
98 1.1 brad .cmd = SGP40_GET_FEATURESET,
99 1.1 brad .typicaldelay = 1000,
100 1.1 brad }
101 1.1 brad };
102 1.1 brad
103 1.1 brad void
104 1.1 brad sgp40_thread(void *aux)
105 1.1 brad {
106 1.1 brad struct sgp40_sc *sc = aux;
107 1.1 brad int rv;
108 1.1 brad VocAlgorithmParams voc_algorithm_params;
109 1.1 brad
110 1.1 brad mutex_enter(&sc->sc_threadmutex);
111 1.1 brad
112 1.1 brad VocAlgorithm_init(&voc_algorithm_params);
113 1.1 brad
114 1.2 christos while (!sc->sc_stopping) {
115 1.2 christos rv = cv_timedwait(&sc->sc_condvar, &sc->sc_threadmutex,
116 1.2 christos mstohz(1000));
117 1.2 christos if (rv == EWOULDBLOCK && !sc->sc_stopping) {
118 1.1 brad sgp40_take_measurement(sc,&voc_algorithm_params);
119 1.1 brad }
120 1.1 brad }
121 1.1 brad mutex_exit(&sc->sc_threadmutex);
122 1.1 brad kthread_exit(0);
123 1.1 brad }
124 1.1 brad
125 1.1 brad static void
126 1.1 brad sgp40_stop_thread(void *aux)
127 1.1 brad {
128 1.1 brad struct sgp40_sc *sc;
129 1.1 brad sc = aux;
130 1.1 brad int error;
131 1.1 brad
132 1.1 brad mutex_enter(&sc->sc_threadmutex);
133 1.1 brad sc->sc_stopping = true;
134 1.1 brad cv_signal(&sc->sc_condvar);
135 1.1 brad mutex_exit(&sc->sc_threadmutex);
136 1.1 brad
137 1.1 brad /* wait for the thread to exit */
138 1.1 brad kthread_join(sc->sc_thread);
139 1.1 brad
140 1.1 brad mutex_enter(&sc->sc_mutex);
141 1.1 brad error = iic_acquire_bus(sc->sc_tag, 0);
142 1.1 brad if (error) {
143 1.2 christos DPRINTF(sc, 2, ("%s: Could not acquire iic bus for heater off "
144 1.2 christos "in stop thread: %d\n", device_xname(sc->sc_dev), error));
145 1.2 christos goto out;
146 1.2 christos }
147 1.2 christos error = sgp40_cmdr(sc, SGP40_HEATER_OFF, NULL, 0, NULL, 0);
148 1.2 christos if (error) {
149 1.2 christos DPRINTF(sc, 2, ("%s: Error turning heater off: %d\n",
150 1.1 brad device_xname(sc->sc_dev), error));
151 1.1 brad }
152 1.2 christos out:
153 1.2 christos iic_release_bus(sc->sc_tag, 0);
154 1.1 brad mutex_exit(&sc->sc_mutex);
155 1.1 brad }
156 1.1 brad
157 1.1 brad static int
158 1.1 brad sgp40_compute_temp_comp(int unconverted)
159 1.1 brad {
160 1.2 christos /*
161 1.2 christos * The published algorithm for this conversion is:
162 1.2 christos * (temp_in_celcius + 45) * 65535 / 175
163 1.2 christos *
164 1.2 christos * However, this did not exactly yield the results that
165 1.2 christos * the example in the data sheet, so something a little
166 1.2 christos * different was done.
167 1.2 christos *
168 1.2 christos * (temp_in_celcius + 45) * 65536 / 175
169 1.2 christos *
170 1.2 christos * This was also scaled up by 10^2 and then scaled back to
171 1.2 christos * preserve some percision. 37449 is simply (65536 * 100) / 175
172 1.2 christos * and rounded.
173 1.2 christos */
174 1.1 brad
175 1.1 brad return (((unconverted + 45) * 100) * 37449) / 10000;
176 1.1 brad }
177 1.1 brad
178 1.1 brad static int
179 1.1 brad sgp40_compute_rh_comp(int unconverted)
180 1.1 brad {
181 1.1 brad int q;
182 1.1 brad
183 1.2 christos /*
184 1.2 christos * The published algorithm for this conversion is:
185 1.2 christos * %rh * 65535 / 100
186 1.2 christos *
187 1.2 christos * However, this did not exactly yield the results that
188 1.2 christos * the example in the data sheet, so something a little
189 1.2 christos * different was done.
190 1.2 christos *
191 1.2 christos * %rh * 65536 / 100
192 1.2 christos *
193 1.2 christos * This was also scaled up by 10^2 and then scaled back to
194 1.2 christos * preserve some percision. The value is also latched to 65535
195 1.2 christos * as an upper limit.
196 1.2 christos */
197 1.1 brad
198 1.1 brad q = ((unconverted * 100) * 65536) / 10000;
199 1.1 brad if (q > 65535)
200 1.1 brad q = 65535;
201 1.1 brad return q;
202 1.1 brad }
203 1.1 brad
204 1.1 brad static void
205 1.1 brad sgp40_take_measurement(void *aux, VocAlgorithmParams* params)
206 1.1 brad {
207 1.1 brad struct sgp40_sc *sc;
208 1.1 brad sc = aux;
209 1.1 brad uint8_t args[6];
210 1.1 brad uint8_t buf[3];
211 1.1 brad uint16_t rawmeasurement;
212 1.1 brad int error;
213 1.1 brad uint8_t crc;
214 1.1 brad uint16_t convertedrh, convertedtemp;
215 1.1 brad int32_t voc_index;
216 1.1 brad
217 1.1 brad mutex_enter(&sc->sc_mutex);
218 1.1 brad convertedrh = (uint16_t)sgp40_compute_rh_comp(sc->sc_rhcomp);
219 1.1 brad convertedtemp = (uint16_t)sgp40_compute_temp_comp(sc->sc_tempcomp);
220 1.1 brad
221 1.1 brad DPRINTF(sc, 2, ("%s: Converted RH and Temp: %04x %04x\n",
222 1.1 brad device_xname(sc->sc_dev), convertedrh, convertedtemp));
223 1.1 brad
224 1.1 brad args[0] = convertedrh >> 8;
225 1.1 brad args[1] = convertedrh & 0x00ff;
226 1.2 christos args[2] = sgp40_crc(&args[0], 2);
227 1.1 brad args[3] = convertedtemp >> 8;
228 1.1 brad args[4] = convertedtemp & 0x00ff;
229 1.2 christos args[5] = sgp40_crc(&args[3], 2);
230 1.1 brad
231 1.2 christos /*
232 1.2 christos * The VOC algoritm has a black out time when it first starts to run
233 1.2 christos * and does not return any indicator that is going on, so voc_index
234 1.2 christos * in that case would be 0.. however, that is also a valid response
235 1.2 christos * otherwise, although an unlikely one.
236 1.2 christos */
237 1.1 brad error = iic_acquire_bus(sc->sc_tag, 0);
238 1.1 brad if (error) {
239 1.2 christos DPRINTF(sc, 2, ("%s: Could not acquire iic bus for take "
240 1.2 christos "measurement: %d\n", device_xname(sc->sc_dev), error));
241 1.1 brad sc->sc_voc = 0;
242 1.1 brad sc->sc_vocvalid = false;
243 1.2 christos goto out;
244 1.2 christos }
245 1.2 christos
246 1.2 christos error = sgp40_cmdr(sc, SGP40_MEASURE_RAW, args, 6, buf, 3);
247 1.2 christos iic_release_bus(sc->sc_tag, 0);
248 1.2 christos if (error) {
249 1.2 christos DPRINTF(sc, 2, ("%s: Failed to get measurement %d\n",
250 1.2 christos device_xname(sc->sc_dev), error));
251 1.2 christos goto out;
252 1.1 brad }
253 1.1 brad
254 1.2 christos crc = sgp40_crc(&buf[0], 2);
255 1.2 christos DPRINTF(sc, 2, ("%s: Raw ticks and crc: %02x%02x %02x "
256 1.2 christos "%02x\n", device_xname(sc->sc_dev), buf[0], buf[1],
257 1.2 christos buf[2], crc));
258 1.2 christos if (buf[2] != crc)
259 1.2 christos goto out;
260 1.2 christos
261 1.2 christos rawmeasurement = buf[0] << 8;
262 1.2 christos rawmeasurement |= buf[1];
263 1.2 christos VocAlgorithm_process(params, rawmeasurement,
264 1.2 christos &voc_index);
265 1.2 christos DPRINTF(sc, 2, ("%s: VOC index: %d\n",
266 1.2 christos device_xname(sc->sc_dev), voc_index));
267 1.2 christos sc->sc_voc = voc_index;
268 1.2 christos sc->sc_vocvalid = true;
269 1.2 christos
270 1.2 christos mutex_exit(&sc->sc_mutex);
271 1.2 christos return;
272 1.2 christos out:
273 1.2 christos sc->sc_voc = 0;
274 1.2 christos sc->sc_vocvalid = false;
275 1.1 brad mutex_exit(&sc->sc_mutex);
276 1.1 brad }
277 1.1 brad
278 1.1 brad int
279 1.1 brad sgp40_verify_sysctl(SYSCTLFN_ARGS)
280 1.1 brad {
281 1.1 brad int error, t;
282 1.1 brad struct sysctlnode node;
283 1.1 brad
284 1.1 brad node = *rnode;
285 1.1 brad t = *(int *)rnode->sysctl_data;
286 1.1 brad node.sysctl_data = &t;
287 1.1 brad error = sysctl_lookup(SYSCTLFN_CALL(&node));
288 1.1 brad if (error || newp == NULL)
289 1.1 brad return error;
290 1.1 brad
291 1.1 brad if (t < 0)
292 1.1 brad return EINVAL;
293 1.1 brad
294 1.1 brad *(int *)rnode->sysctl_data = t;
295 1.1 brad
296 1.1 brad return 0;
297 1.1 brad }
298 1.1 brad
299 1.1 brad int
300 1.1 brad sgp40_verify_temp_sysctl(SYSCTLFN_ARGS)
301 1.1 brad {
302 1.1 brad int error, t;
303 1.1 brad struct sysctlnode node;
304 1.1 brad
305 1.1 brad node = *rnode;
306 1.1 brad t = *(int *)rnode->sysctl_data;
307 1.1 brad node.sysctl_data = &t;
308 1.1 brad error = sysctl_lookup(SYSCTLFN_CALL(&node));
309 1.1 brad if (error || newp == NULL)
310 1.1 brad return error;
311 1.1 brad
312 1.1 brad if (t < -45 || t > 130)
313 1.1 brad return EINVAL;
314 1.1 brad
315 1.1 brad *(int *)rnode->sysctl_data = t;
316 1.1 brad
317 1.1 brad return 0;
318 1.1 brad }
319 1.1 brad
320 1.1 brad int
321 1.1 brad sgp40_verify_rh_sysctl(SYSCTLFN_ARGS)
322 1.1 brad {
323 1.1 brad int error, t;
324 1.1 brad struct sysctlnode node;
325 1.1 brad
326 1.1 brad node = *rnode;
327 1.1 brad t = *(int *)rnode->sysctl_data;
328 1.1 brad node.sysctl_data = &t;
329 1.1 brad error = sysctl_lookup(SYSCTLFN_CALL(&node));
330 1.1 brad if (error || newp == NULL)
331 1.1 brad return error;
332 1.1 brad
333 1.1 brad if (t < 0 || t > 100)
334 1.1 brad return EINVAL;
335 1.1 brad
336 1.1 brad *(int *)rnode->sysctl_data = t;
337 1.1 brad
338 1.1 brad return 0;
339 1.1 brad }
340 1.1 brad
341 1.1 brad static int
342 1.1 brad sgp40_cmddelay(uint16_t cmd)
343 1.1 brad {
344 1.1 brad int r = -1;
345 1.1 brad
346 1.1 brad for(int i = 0;i < __arraycount(sgp40_timings);i++) {
347 1.1 brad if (cmd == sgp40_timings[i].cmd) {
348 1.1 brad r = sgp40_timings[i].typicaldelay;
349 1.1 brad break;
350 1.1 brad }
351 1.1 brad }
352 1.1 brad
353 1.1 brad if (r == -1) {
354 1.2 christos panic("sgp40: Bad command look up in cmd delay: cmd: %d\n",
355 1.2 christos cmd);
356 1.1 brad }
357 1.1 brad
358 1.1 brad return r;
359 1.1 brad }
360 1.1 brad
361 1.1 brad static int
362 1.1 brad sgp40_cmd(i2c_tag_t tag, i2c_addr_t addr, uint8_t *cmd,
363 1.1 brad uint8_t clen, uint8_t *buf, size_t blen, int readattempts)
364 1.1 brad {
365 1.1 brad int error;
366 1.1 brad int cmddelay;
367 1.1 brad uint16_t cmd16;
368 1.1 brad
369 1.1 brad cmd16 = cmd[0] << 8;
370 1.1 brad cmd16 = cmd16 | cmd[1];
371 1.1 brad
372 1.2 christos error = iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, cmd, clen, NULL, 0,
373 1.2 christos 0);
374 1.2 christos if (error)
375 1.2 christos return error;
376 1.1 brad
377 1.2 christos /*
378 1.2 christos * Every command returns something except for turning the heater off
379 1.2 christos * and the general soft reset which returns nothing.
380 1.2 christos */
381 1.2 christos if (cmd16 == SGP40_HEATER_OFF)
382 1.2 christos return 0;
383 1.2 christos /*
384 1.2 christos * Every command has a particular delay for how long
385 1.2 christos * it typically takes and the max time it will take.
386 1.2 christos */
387 1.2 christos cmddelay = sgp40_cmddelay(cmd16);
388 1.2 christos delay(cmddelay);
389 1.2 christos
390 1.2 christos for (int aint = 0; aint < readattempts; aint++) {
391 1.2 christos error = iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, NULL, 0,
392 1.2 christos buf, blen, 0);
393 1.2 christos if (error == 0)
394 1.2 christos break;
395 1.2 christos delay(1000);
396 1.1 brad }
397 1.1 brad
398 1.1 brad return error;
399 1.1 brad }
400 1.1 brad
401 1.1 brad static int
402 1.2 christos sgp40_cmdr(struct sgp40_sc *sc, uint16_t cmd, uint8_t *extraargs,
403 1.2 christos uint8_t argslen, uint8_t *buf, size_t blen)
404 1.1 brad {
405 1.1 brad uint8_t fullcmd[8];
406 1.1 brad uint8_t cmdlen;
407 1.1 brad int n;
408 1.1 brad
409 1.2 christos /*
410 1.2 christos * The biggest documented command + arguments is 8 uint8_t bytes long.
411 1.2 christos * Catch anything that ties to have an arglen more than 6
412 1.2 christos */
413 1.1 brad KASSERT(argslen <= 6);
414 1.1 brad
415 1.1 brad memset(fullcmd, 0, 8);
416 1.1 brad
417 1.1 brad fullcmd[0] = cmd >> 8;
418 1.1 brad fullcmd[1] = cmd & 0x00ff;
419 1.1 brad cmdlen = 2;
420 1.1 brad
421 1.1 brad n = 0;
422 1.1 brad while (extraargs != NULL && n < argslen && cmdlen <= 7) {
423 1.1 brad fullcmd[cmdlen] = extraargs[n];
424 1.1 brad cmdlen++;
425 1.1 brad n++;
426 1.1 brad }
427 1.2 christos DPRINTF(sc, 2, ("%s: Full command and arguments: %02x %02x %02x %02x "
428 1.2 christos "%02x %02x %02x %02x\n",
429 1.1 brad device_xname(sc->sc_dev), fullcmd[0], fullcmd[1],
430 1.1 brad fullcmd[2], fullcmd[3], fullcmd[4], fullcmd[5],
431 1.1 brad fullcmd[6], fullcmd[7]));
432 1.2 christos return sgp40_cmd(sc->sc_tag, sc->sc_addr, fullcmd, cmdlen, buf, blen,
433 1.2 christos sc->sc_readattempts);
434 1.1 brad }
435 1.1 brad
436 1.1 brad static uint8_t
437 1.1 brad sgp40_crc(uint8_t * data, size_t size)
438 1.1 brad {
439 1.1 brad uint8_t crc = 0xFF;
440 1.1 brad
441 1.1 brad for (size_t i = 0; i < size; i++) {
442 1.1 brad crc ^= data[i];
443 1.1 brad for (size_t j = 8; j > 0; j--) {
444 1.1 brad if (crc & 0x80)
445 1.1 brad crc = (crc << 1) ^ 0x31;
446 1.1 brad else
447 1.1 brad crc <<= 1;
448 1.1 brad }
449 1.1 brad }
450 1.1 brad return crc;
451 1.1 brad }
452 1.1 brad
453 1.1 brad static int
454 1.1 brad sgp40_poke(i2c_tag_t tag, i2c_addr_t addr, bool matchdebug)
455 1.1 brad {
456 1.1 brad uint8_t reg[2];
457 1.1 brad uint8_t buf[9];
458 1.1 brad int error;
459 1.1 brad
460 1.2 christos /*
461 1.2 christos * Possible bug... this command may not work if the chip is not idle,
462 1.2 christos * however, it appears to be used by a lot of other code as a probe.
463 1.2 christos */
464 1.1 brad reg[0] = SGP40_GET_SERIAL_NUMBER >> 8;
465 1.1 brad reg[1] = SGP40_GET_SERIAL_NUMBER & 0x00ff;
466 1.1 brad
467 1.1 brad error = sgp40_cmd(tag, addr, reg, 2, buf, 9, 10);
468 1.1 brad if (matchdebug) {
469 1.1 brad printf("poke X 1: %d\n", error);
470 1.1 brad }
471 1.1 brad return error;
472 1.1 brad }
473 1.1 brad
474 1.1 brad static int
475 1.1 brad sgp40_sysctl_init(struct sgp40_sc *sc)
476 1.1 brad {
477 1.1 brad int error;
478 1.1 brad const struct sysctlnode *cnode;
479 1.1 brad int sysctlroot_num;
480 1.1 brad
481 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
482 1.1 brad 0, CTLTYPE_NODE, device_xname(sc->sc_dev),
483 1.1 brad SYSCTL_DESCR("SGP40 controls"), NULL, 0, NULL, 0, CTL_HW,
484 1.1 brad CTL_CREATE, CTL_EOL)) != 0)
485 1.1 brad return error;
486 1.1 brad
487 1.1 brad sysctlroot_num = cnode->sysctl_num;
488 1.1 brad
489 1.1 brad #ifdef SGP40_DEBUG
490 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
491 1.1 brad CTLFLAG_READWRITE, CTLTYPE_INT, "debug",
492 1.1 brad SYSCTL_DESCR("Debug level"), sgp40_verify_sysctl, 0,
493 1.1 brad &sc->sc_sgp40debug, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
494 1.1 brad CTL_EOL)) != 0)
495 1.1 brad return error;
496 1.1 brad
497 1.1 brad #endif
498 1.1 brad
499 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
500 1.1 brad CTLFLAG_READWRITE, CTLTYPE_INT, "readattempts",
501 1.1 brad SYSCTL_DESCR("The number of times to attempt to read the values"),
502 1.1 brad sgp40_verify_sysctl, 0, &sc->sc_readattempts, 0, CTL_HW,
503 1.1 brad sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
504 1.1 brad return error;
505 1.1 brad
506 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
507 1.1 brad CTLFLAG_READWRITE, CTLTYPE_BOOL, "ignorecrc",
508 1.1 brad SYSCTL_DESCR("Ignore the CRC byte"), NULL, 0, &sc->sc_ignorecrc,
509 1.1 brad 0, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
510 1.1 brad return error;
511 1.1 brad
512 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
513 1.1 brad 0, CTLTYPE_NODE, "compensation",
514 1.2 christos SYSCTL_DESCR("SGP40 measurement compensations"), NULL, 0, NULL, 0,
515 1.2 christos CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
516 1.1 brad return error;
517 1.1 brad int compensation_num = cnode->sysctl_num;
518 1.1 brad
519 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
520 1.1 brad CTLFLAG_READWRITE, CTLTYPE_INT, "temperature",
521 1.1 brad SYSCTL_DESCR("Temperature compensation in celsius"),
522 1.1 brad sgp40_verify_temp_sysctl, 0, &sc->sc_tempcomp, 0, CTL_HW,
523 1.1 brad sysctlroot_num, compensation_num, CTL_CREATE, CTL_EOL)) != 0)
524 1.1 brad return error;
525 1.1 brad
526 1.1 brad if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
527 1.1 brad CTLFLAG_READWRITE, CTLTYPE_INT, "humidity",
528 1.1 brad SYSCTL_DESCR("Humidity compensation in %RH"),
529 1.1 brad sgp40_verify_rh_sysctl, 0, &sc->sc_rhcomp, 0, CTL_HW,
530 1.1 brad sysctlroot_num, compensation_num, CTL_CREATE, CTL_EOL)) != 0)
531 1.1 brad return error;
532 1.1 brad
533 1.1 brad return 0;
534 1.1 brad }
535 1.1 brad
536 1.1 brad static int
537 1.1 brad sgp40_match(device_t parent, cfdata_t match, void *aux)
538 1.1 brad {
539 1.1 brad struct i2c_attach_args *ia = aux;
540 1.1 brad int error, match_result;
541 1.1 brad const bool matchdebug = false;
542 1.1 brad
543 1.1 brad if (matchdebug)
544 1.1 brad printf("in match\n");
545 1.1 brad
546 1.1 brad if (iic_use_direct_match(ia, match, NULL, &match_result))
547 1.1 brad return match_result;
548 1.1 brad
549 1.1 brad /* indirect config - check for configured address */
550 1.1 brad if (ia->ia_addr != SGP40_TYPICAL_ADDR)
551 1.1 brad return 0;
552 1.1 brad
553 1.1 brad /*
554 1.1 brad * Check to see if something is really at this i2c address. This will
555 1.1 brad * keep phantom devices from appearing
556 1.1 brad */
557 1.1 brad if (iic_acquire_bus(ia->ia_tag, 0) != 0) {
558 1.1 brad if (matchdebug)
559 1.1 brad printf("in match acquire bus failed\n");
560 1.1 brad return 0;
561 1.1 brad }
562 1.1 brad
563 1.1 brad error = sgp40_poke(ia->ia_tag, ia->ia_addr, matchdebug);
564 1.1 brad iic_release_bus(ia->ia_tag, 0);
565 1.1 brad
566 1.1 brad return error == 0 ? I2C_MATCH_ADDRESS_AND_PROBE : 0;
567 1.1 brad }
568 1.1 brad
569 1.1 brad static void
570 1.1 brad sgp40_attach(device_t parent, device_t self, void *aux)
571 1.1 brad {
572 1.1 brad struct sgp40_sc *sc;
573 1.1 brad struct i2c_attach_args *ia;
574 1.1 brad int error, i;
575 1.1 brad int ecount = 0;
576 1.1 brad uint8_t buf[9];
577 1.1 brad uint8_t tstcrc;
578 1.1 brad uint16_t chiptestvalue;
579 1.1 brad uint64_t serial_number = 0;
580 1.1 brad uint8_t sn_crc1, sn_crc2, sn_crc3, sn_crcv1, sn_crcv2, sn_crcv3;
581 1.1 brad uint8_t fs_crc, fs_crcv;
582 1.1 brad uint16_t featureset;
583 1.1 brad
584 1.1 brad ia = aux;
585 1.1 brad sc = device_private(self);
586 1.1 brad
587 1.1 brad sc->sc_dev = self;
588 1.1 brad sc->sc_tag = ia->ia_tag;
589 1.1 brad sc->sc_addr = ia->ia_addr;
590 1.1 brad sc->sc_sgp40debug = 0;
591 1.1 brad sc->sc_readattempts = 10;
592 1.1 brad sc->sc_ignorecrc = false;
593 1.1 brad sc->sc_stopping = false;
594 1.1 brad sc->sc_voc = 0;
595 1.1 brad sc->sc_vocvalid = false;
596 1.1 brad sc->sc_tempcomp = SGP40_DEFAULT_TEMP_COMP;
597 1.1 brad sc->sc_rhcomp = SGP40_DEFAULT_RH_COMP;
598 1.1 brad sc->sc_sme = NULL;
599 1.1 brad
600 1.1 brad aprint_normal("\n");
601 1.1 brad
602 1.1 brad mutex_init(&sc->sc_threadmutex, MUTEX_DEFAULT, IPL_NONE);
603 1.1 brad mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE);
604 1.1 brad cv_init(&sc->sc_condvar, "sgp40cv");
605 1.1 brad sc->sc_numsensors = __arraycount(sgp40_sensors);
606 1.1 brad
607 1.1 brad if ((sc->sc_sme = sysmon_envsys_create()) == NULL) {
608 1.1 brad aprint_error_dev(self,
609 1.1 brad "Unable to create sysmon structure\n");
610 1.1 brad sc->sc_sme = NULL;
611 1.1 brad return;
612 1.1 brad }
613 1.1 brad if ((error = sgp40_sysctl_init(sc)) != 0) {
614 1.1 brad aprint_error_dev(self, "Can't setup sysctl tree (%d)\n", error);
615 1.1 brad goto out;
616 1.1 brad }
617 1.1 brad
618 1.1 brad error = iic_acquire_bus(sc->sc_tag, 0);
619 1.1 brad if (error) {
620 1.1 brad aprint_error_dev(self, "Could not acquire iic bus: %d\n",
621 1.1 brad error);
622 1.1 brad goto out;
623 1.1 brad }
624 1.1 brad
625 1.2 christos /*
626 1.2 christos * Usually one would reset the chip here, but that is not possible
627 1.2 christos * without resetting the entire bus, so we won't do that.
628 1.2 christos *
629 1.2 christos * What we will do is make sure that the chip is idle by running the
630 1.2 christos * turn-the-heater command.
631 1.1 brad */
632 1.1 brad
633 1.1 brad error = sgp40_cmdr(sc, SGP40_HEATER_OFF, NULL, 0, NULL, 0);
634 1.1 brad if (error) {
635 1.1 brad aprint_error_dev(self, "Failed to turn off the heater: %d\n",
636 1.1 brad error);
637 1.1 brad ecount++;
638 1.1 brad }
639 1.1 brad
640 1.1 brad error = sgp40_cmdr(sc, SGP40_GET_SERIAL_NUMBER, NULL, 0, buf, 9);
641 1.1 brad if (error) {
642 1.1 brad aprint_error_dev(self, "Failed to get serial number: %d\n",
643 1.1 brad error);
644 1.1 brad ecount++;
645 1.1 brad }
646 1.1 brad
647 1.2 christos sn_crc1 = sgp40_crc(&buf[0], 2);
648 1.2 christos sn_crc2 = sgp40_crc(&buf[3], 2);
649 1.2 christos sn_crc3 = sgp40_crc(&buf[6], 2);
650 1.1 brad sn_crcv1 = buf[2];
651 1.1 brad sn_crcv2 = buf[5];
652 1.1 brad sn_crcv3 = buf[8];
653 1.1 brad serial_number = buf[0];
654 1.1 brad serial_number = (serial_number << 8) | buf[1];
655 1.1 brad serial_number = (serial_number << 8) | buf[3];
656 1.1 brad serial_number = (serial_number << 8) | buf[4];
657 1.1 brad serial_number = (serial_number << 8) | buf[6];
658 1.1 brad serial_number = (serial_number << 8) | buf[7];
659 1.1 brad
660 1.2 christos DPRINTF(sc, 2, ("%s: raw serial number: %02x %02x %02x %02x %02x %02x "
661 1.2 christos "%02x %02x %02x\n",
662 1.1 brad device_xname(sc->sc_dev), buf[0], buf[1], buf[2], buf[3], buf[4],
663 1.1 brad buf[5], buf[6], buf[7], buf[8]));
664 1.1 brad
665 1.1 brad error = sgp40_cmdr(sc, SGP40_GET_FEATURESET, NULL, 0, buf, 3);
666 1.1 brad if (error) {
667 1.1 brad aprint_error_dev(self, "Failed to get featureset: %d\n",
668 1.1 brad error);
669 1.1 brad ecount++;
670 1.1 brad }
671 1.1 brad
672 1.2 christos fs_crc = sgp40_crc(&buf[0], 2);
673 1.1 brad fs_crcv = buf[2];
674 1.1 brad featureset = buf[0];
675 1.1 brad featureset = (featureset << 8) | buf[1];
676 1.1 brad
677 1.1 brad DPRINTF(sc, 2, ("%s: raw feature set: %02x %02x %02x\n",
678 1.1 brad device_xname(sc->sc_dev), buf[0], buf[1], buf[2]));
679 1.1 brad
680 1.1 brad error = sgp40_cmdr(sc, SGP40_MEASURE_TEST, NULL, 0, buf, 3);
681 1.1 brad if (error) {
682 1.1 brad aprint_error_dev(self, "Failed to perform a chip test: %d\n",
683 1.1 brad error);
684 1.1 brad ecount++;
685 1.1 brad }
686 1.1 brad
687 1.2 christos tstcrc = sgp40_crc(&buf[0], 2);
688 1.1 brad
689 1.1 brad DPRINTF(sc, 2, ("%s: chip test values: %02x%02x - %02x ; %02x\n",
690 1.1 brad device_xname(sc->sc_dev), buf[0], buf[1], buf[2], tstcrc));
691 1.1 brad
692 1.1 brad iic_release_bus(sc->sc_tag, 0);
693 1.1 brad if (error != 0) {
694 1.1 brad aprint_error_dev(self, "Unable to setup device\n");
695 1.1 brad goto out;
696 1.1 brad }
697 1.1 brad
698 1.1 brad chiptestvalue = buf[0] << 8;
699 1.1 brad chiptestvalue |= buf[1];
700 1.1 brad
701 1.1 brad for (i = 0; i < sc->sc_numsensors; i++) {
702 1.1 brad strlcpy(sc->sc_sensors[i].desc, sgp40_sensors[i].desc,
703 1.1 brad sizeof(sc->sc_sensors[i].desc));
704 1.1 brad
705 1.1 brad sc->sc_sensors[i].units = sgp40_sensors[i].type;
706 1.1 brad sc->sc_sensors[i].state = ENVSYS_SINVALID;
707 1.1 brad
708 1.1 brad DPRINTF(sc, 2, ("%s: registering sensor %d (%s)\n", __func__, i,
709 1.1 brad sc->sc_sensors[i].desc));
710 1.1 brad
711 1.1 brad error = sysmon_envsys_sensor_attach(sc->sc_sme,
712 1.1 brad &sc->sc_sensors[i]);
713 1.1 brad if (error) {
714 1.1 brad aprint_error_dev(self,
715 1.1 brad "Unable to attach sensor %d: %d\n", i, error);
716 1.1 brad goto out;
717 1.1 brad }
718 1.1 brad }
719 1.1 brad
720 1.1 brad sc->sc_sme->sme_name = device_xname(sc->sc_dev);
721 1.1 brad sc->sc_sme->sme_cookie = sc;
722 1.1 brad sc->sc_sme->sme_refresh = sgp40_refresh;
723 1.1 brad
724 1.1 brad DPRINTF(sc, 2, ("sgp40_attach: registering with envsys\n"));
725 1.1 brad
726 1.1 brad if (sysmon_envsys_register(sc->sc_sme)) {
727 1.1 brad aprint_error_dev(self,
728 1.1 brad "unable to register with sysmon\n");
729 1.1 brad sysmon_envsys_destroy(sc->sc_sme);
730 1.1 brad sc->sc_sme = NULL;
731 1.1 brad return;
732 1.1 brad }
733 1.1 brad
734 1.1 brad error = kthread_create(PRI_NONE, KTHREAD_MUSTJOIN, NULL,
735 1.2 christos sgp40_thread, sc, &sc->sc_thread, "%s", device_xname(sc->sc_dev));
736 1.1 brad if (error) {
737 1.1 brad aprint_error_dev(self,"Unable to create measurement thread\n");
738 1.1 brad goto out;
739 1.1 brad }
740 1.1 brad
741 1.2 christos aprint_normal_dev(self, "Sensirion SGP40, Serial number: %jx%s"
742 1.2 christos "Feature set word: 0x%jx%s%s%s", serial_number,
743 1.2 christos (sn_crc1 == sn_crcv1 && sn_crc2 == sn_crcv2 && sn_crc3 == sn_crcv3)
744 1.2 christos ? ", " : " (bad crc), ",
745 1.1 brad (uintmax_t)featureset,
746 1.1 brad (fs_crc == fs_crcv) ? ", " : " (bad crc), ",
747 1.2 christos (chiptestvalue == SGP40_TEST_RESULTS_ALL_PASSED) ?
748 1.2 christos "All chip tests passed" :
749 1.2 christos (chiptestvalue == SGP40_TEST_RESULTS_SOME_FAILED) ?
750 1.2 christos "Some chip tests failed" :
751 1.1 brad "Unknown test results",
752 1.1 brad (tstcrc == buf[2]) ? "\n" : " (bad crc)\n");
753 1.1 brad return;
754 1.1 brad out:
755 1.1 brad sysmon_envsys_destroy(sc->sc_sme);
756 1.1 brad sc->sc_sme = NULL;
757 1.1 brad }
758 1.1 brad
759 1.1 brad static void
760 1.1 brad sgp40_refresh(struct sysmon_envsys * sme, envsys_data_t * edata)
761 1.1 brad {
762 1.1 brad struct sgp40_sc *sc;
763 1.1 brad sc = sme->sme_cookie;
764 1.1 brad
765 1.1 brad mutex_enter(&sc->sc_mutex);
766 1.1 brad if (sc->sc_vocvalid == true) {
767 1.1 brad edata->value_cur = (uint32_t)sc->sc_voc;
768 1.1 brad edata->state = ENVSYS_SVALID;
769 1.1 brad } else {
770 1.1 brad edata->state = ENVSYS_SINVALID;
771 1.1 brad }
772 1.1 brad mutex_exit(&sc->sc_mutex);
773 1.1 brad }
774 1.1 brad
775 1.1 brad static int
776 1.1 brad sgp40_detach(device_t self, int flags)
777 1.1 brad {
778 1.1 brad struct sgp40_sc *sc;
779 1.1 brad
780 1.1 brad sc = device_private(self);
781 1.1 brad
782 1.1 brad /* stop the measurement thread */
783 1.1 brad sgp40_stop_thread(sc);
784 1.1 brad
785 1.1 brad /* Remove the sensors */
786 1.1 brad mutex_enter(&sc->sc_mutex);
787 1.1 brad if (sc->sc_sme != NULL) {
788 1.1 brad sysmon_envsys_unregister(sc->sc_sme);
789 1.1 brad sc->sc_sme = NULL;
790 1.1 brad }
791 1.1 brad mutex_exit(&sc->sc_mutex);
792 1.1 brad
793 1.1 brad /* Remove the sysctl tree */
794 1.1 brad sysctl_teardown(&sc->sc_sgp40log);
795 1.1 brad
796 1.1 brad /* Remove the mutex */
797 1.1 brad mutex_destroy(&sc->sc_mutex);
798 1.1 brad mutex_destroy(&sc->sc_threadmutex);
799 1.1 brad
800 1.1 brad return 0;
801 1.1 brad }
802 1.1 brad
803 1.1 brad MODULE(MODULE_CLASS_DRIVER, sgp40mox, "i2cexec,sysmon_envsys");
804 1.1 brad
805 1.1 brad #ifdef _MODULE
806 1.1 brad #include "ioconf.c"
807 1.1 brad #endif
808 1.1 brad
809 1.1 brad static int
810 1.1 brad sgp40mox_modcmd(modcmd_t cmd, void *opaque)
811 1.1 brad {
812 1.1 brad
813 1.1 brad switch (cmd) {
814 1.1 brad case MODULE_CMD_INIT:
815 1.1 brad #ifdef _MODULE
816 1.1 brad return config_init_component(cfdriver_ioconf_sgp40mox,
817 1.1 brad cfattach_ioconf_sgp40mox, cfdata_ioconf_sgp40mox);
818 1.1 brad #else
819 1.1 brad return 0;
820 1.1 brad #endif
821 1.1 brad case MODULE_CMD_FINI:
822 1.1 brad #ifdef _MODULE
823 1.1 brad return config_fini_component(cfdriver_ioconf_sgp40mox,
824 1.1 brad cfattach_ioconf_sgp40mox, cfdata_ioconf_sgp40mox);
825 1.1 brad #else
826 1.1 brad return 0;
827 1.1 brad #endif
828 1.1 brad default:
829 1.1 brad return ENOTTY;
830 1.1 brad }
831 1.1 brad }
832