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