xref: /netbsd-src/sys/dev/i2c/aht20.c (revision 50bf6d2dfdf0cd11a30fb00e366be19f1fdc740c)
1 /*	$NetBSD: aht20.c,v 1.1 2022/11/17 19:20:06 brad Exp $	*/
2 
3 /*
4  * Copyright (c) 2022 Brad Spencer <brad@anduin.eldar.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/cdefs.h>
20 __KERNEL_RCSID(0, "$NetBSD: aht20.c,v 1.1 2022/11/17 19:20:06 brad Exp $");
21 
22 /*
23   Driver for the Guangzhou Aosong AHT20 temperature and humidity sensor
24 */
25 
26 #include <sys/param.h>
27 #include <sys/systm.h>
28 #include <sys/kernel.h>
29 #include <sys/device.h>
30 #include <sys/module.h>
31 #include <sys/sysctl.h>
32 #include <sys/mutex.h>
33 
34 #include <dev/sysmon/sysmonvar.h>
35 #include <dev/i2c/i2cvar.h>
36 #include <dev/i2c/aht20reg.h>
37 #include <dev/i2c/aht20var.h>
38 
39 
40 static uint8_t 	aht20_crc(uint8_t *, size_t);
41 static int 	aht20_poke(i2c_tag_t, i2c_addr_t, bool);
42 static int 	aht20_match(device_t, cfdata_t, void *);
43 static void 	aht20_attach(device_t, device_t, void *);
44 static int 	aht20_detach(device_t, int);
45 static void 	aht20_refresh(struct sysmon_envsys *, envsys_data_t *);
46 static int 	aht20_verify_sysctl(SYSCTLFN_ARGS);
47 
48 #define AHT20_DEBUG
49 #ifdef AHT20_DEBUG
50 #define DPRINTF(s, l, x) \
51     do { \
52 	if (l <= s->sc_aht20debug) \
53 	    printf x; \
54     } while (/*CONSTCOND*/0)
55 #else
56 #define DPRINTF(s, l, x)
57 #endif
58 
59 CFATTACH_DECL_NEW(aht20temp, sizeof(struct aht20_sc),
60     aht20_match, aht20_attach, aht20_detach, NULL);
61 
62 static struct aht20_sensor aht20_sensors[] = {
63 	{
64 		.desc = "humidity",
65 		.type = ENVSYS_SRELHUMIDITY,
66 	},
67 	{
68 		.desc = "temperature",
69 		.type = ENVSYS_STEMP,
70 	}
71 };
72 
73 /*
74  * The delays are mentioned in the datasheet for the chip, except for
75  * the get status command.
76  */
77 
78 static struct aht20_timing aht20_timings[] = {
79 	{
80 		.cmd = AHT20_INITIALIZE,
81 		.typicaldelay = 10000,
82 	},
83 	{
84 		.cmd = AHT20_TRIGGER_MEASUREMENT,
85 		.typicaldelay = 80000,
86 	},
87 	{
88 		.cmd = AHT20_GET_STATUS,
89 		.typicaldelay = 5000,
90 	},
91 	{
92 		.cmd = AHT20_SOFT_RESET,
93 		.typicaldelay = 20000,
94 	}
95 };
96 
97 int
aht20_verify_sysctl(SYSCTLFN_ARGS)98 aht20_verify_sysctl(SYSCTLFN_ARGS)
99 {
100 	int error, t;
101 	struct sysctlnode node;
102 
103 	node = *rnode;
104 	t = *(int *)rnode->sysctl_data;
105 	node.sysctl_data = &t;
106 	error = sysctl_lookup(SYSCTLFN_CALL(&node));
107 	if (error || newp == NULL)
108 		return error;
109 
110 	if (t < 0)
111 		return EINVAL;
112 
113 	*(int *)rnode->sysctl_data = t;
114 
115 	return 0;
116 }
117 
118 static int
aht20_cmddelay(uint8_t cmd)119 aht20_cmddelay(uint8_t cmd)
120 {
121 	int r = -1;
122 
123 	for(int i = 0;i < __arraycount(aht20_timings);i++) {
124 		if (cmd == aht20_timings[i].cmd) {
125 			r = aht20_timings[i].typicaldelay;
126 			break;
127 		}
128 	}
129 
130 	if (r == -1) {
131 		panic("Bad command look up in cmd delay: cmd: %d\n",cmd);
132 	}
133 
134 	return r;
135 }
136 
137 static int
aht20_cmd(i2c_tag_t tag,i2c_addr_t addr,uint8_t * cmd,uint8_t clen,uint8_t * buf,size_t blen,int readattempts)138 aht20_cmd(i2c_tag_t tag, i2c_addr_t addr, uint8_t *cmd,
139     uint8_t clen, uint8_t *buf, size_t blen, int readattempts)
140 {
141 	int error;
142 	int cmddelay;
143 
144 	error = iic_exec(tag,I2C_OP_WRITE_WITH_STOP,addr,cmd,clen,NULL,0,0);
145 
146 	/* Every command returns something except for the soft reset and
147 	   initialize which returns nothing.
148 	*/
149 
150 	if (error == 0) {
151 		cmddelay = aht20_cmddelay(cmd[0]);
152 		delay(cmddelay);
153 
154 		if (cmd[0] != AHT20_SOFT_RESET &&
155 		    cmd[0] != AHT20_INITIALIZE) {
156 			for (int aint = 0; aint < readattempts; aint++) {
157 				error = iic_exec(tag,I2C_OP_READ_WITH_STOP,addr,NULL,0,buf,blen,0);
158 				if (error == 0)
159 					break;
160 				delay(1000);
161 			}
162 		}
163 	}
164 
165 	return error;
166 }
167 
168 static int
aht20_cmdr(struct aht20_sc * sc,uint8_t * cmd,uint8_t clen,uint8_t * buf,size_t blen)169 aht20_cmdr(struct aht20_sc *sc, uint8_t *cmd, uint8_t clen, uint8_t *buf, size_t blen)
170 {
171 	KASSERT(clen > 0);
172 
173 	return aht20_cmd(sc->sc_tag, sc->sc_addr, cmd, clen, buf, blen, sc->sc_readattempts);
174 }
175 
176 static	uint8_t
aht20_crc(uint8_t * data,size_t size)177 aht20_crc(uint8_t * data, size_t size)
178 {
179 	uint8_t crc = 0xFF;
180 
181 	for (size_t i = 0; i < size; i++) {
182 		crc ^= data[i];
183 		for (size_t j = 8; j > 0; j--) {
184 			if (crc & 0x80)
185 				crc = (crc << 1) ^ 0x31;
186 			else
187 				crc <<= 1;
188 		}
189 	}
190 	return crc;
191 }
192 
193 static int
aht20_poke(i2c_tag_t tag,i2c_addr_t addr,bool matchdebug)194 aht20_poke(i2c_tag_t tag, i2c_addr_t addr, bool matchdebug)
195 {
196 	uint8_t reg = AHT20_GET_STATUS;
197 	uint8_t buf[6];
198 	int error;
199 
200 	error = aht20_cmd(tag, addr, &reg, 1, buf, 1, 10);
201 	if (matchdebug) {
202 		printf("poke X 1: %d\n", error);
203 	}
204 	return error;
205 }
206 
207 static int
aht20_sysctl_init(struct aht20_sc * sc)208 aht20_sysctl_init(struct aht20_sc *sc)
209 {
210 	int error;
211 	const struct sysctlnode *cnode;
212 	int sysctlroot_num;
213 
214 	if ((error = sysctl_createv(&sc->sc_aht20log, 0, NULL, &cnode,
215 	    0, CTLTYPE_NODE, device_xname(sc->sc_dev),
216 	    SYSCTL_DESCR("aht20 controls"), NULL, 0, NULL, 0, CTL_HW,
217 	    CTL_CREATE, CTL_EOL)) != 0)
218 		return error;
219 
220 	sysctlroot_num = cnode->sysctl_num;
221 
222 #ifdef AHT20_DEBUG
223 	if ((error = sysctl_createv(&sc->sc_aht20log, 0, NULL, &cnode,
224 	    CTLFLAG_READWRITE, CTLTYPE_INT, "debug",
225 	    SYSCTL_DESCR("Debug level"), aht20_verify_sysctl, 0,
226 	    &sc->sc_aht20debug, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
227 	    CTL_EOL)) != 0)
228 		return error;
229 
230 #endif
231 
232 	if ((error = sysctl_createv(&sc->sc_aht20log, 0, NULL, &cnode,
233 	    CTLFLAG_READWRITE, CTLTYPE_INT, "readattempts",
234 	    SYSCTL_DESCR("The number of times to attempt to read the values"),
235 	    aht20_verify_sysctl, 0, &sc->sc_readattempts, 0, CTL_HW,
236 	    sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
237 		return error;
238 
239 	if ((error = sysctl_createv(&sc->sc_aht20log, 0, NULL, &cnode,
240 	    CTLFLAG_READWRITE, CTLTYPE_BOOL, "ignorecrc",
241 	    SYSCTL_DESCR("Ignore the CRC byte"), NULL, 0, &sc->sc_ignorecrc,
242 	    0, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
243 		return error;
244 
245 	return 0;
246 }
247 
248 static int
aht20_match(device_t parent,cfdata_t match,void * aux)249 aht20_match(device_t parent, cfdata_t match, void *aux)
250 {
251 	struct i2c_attach_args *ia = aux;
252 	int error, match_result;
253 	const bool matchdebug = false;
254 
255 	if (iic_use_direct_match(ia, match, NULL, &match_result))
256 		return match_result;
257 
258 	/* indirect config - check for configured address */
259 	if (ia->ia_addr != AHT20_TYPICAL_ADDR)
260 		return 0;
261 
262 	/*
263 	 * Check to see if something is really at this i2c address. This will
264 	 * keep phantom devices from appearing
265 	 */
266 	if (iic_acquire_bus(ia->ia_tag, 0) != 0) {
267 		if (matchdebug)
268 			printf("in match acquire bus failed\n");
269 		return 0;
270 	}
271 
272 	error = aht20_poke(ia->ia_tag, ia->ia_addr, matchdebug);
273 	iic_release_bus(ia->ia_tag, 0);
274 
275 	return error == 0 ? I2C_MATCH_ADDRESS_AND_PROBE : 0;
276 }
277 
278 static void
aht20_attach(device_t parent,device_t self,void * aux)279 aht20_attach(device_t parent, device_t self, void *aux)
280 {
281 	struct aht20_sc *sc;
282 	struct i2c_attach_args *ia;
283 	uint8_t cmd[1];
284 	int error, i;
285 
286 	ia = aux;
287 	sc = device_private(self);
288 
289 	sc->sc_dev = self;
290 	sc->sc_tag = ia->ia_tag;
291 	sc->sc_addr = ia->ia_addr;
292 	sc->sc_aht20debug = 0;
293 	sc->sc_readattempts = 10;
294 	sc->sc_ignorecrc = false;
295 	sc->sc_sme = NULL;
296 
297 	aprint_normal("\n");
298 
299 	mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE);
300 	sc->sc_numsensors = __arraycount(aht20_sensors);
301 
302 	if ((sc->sc_sme = sysmon_envsys_create()) == NULL) {
303 		aprint_error_dev(self,
304 		    "Unable to create sysmon structure\n");
305 		sc->sc_sme = NULL;
306 		return;
307 	}
308 	if ((error = aht20_sysctl_init(sc)) != 0) {
309 		aprint_error_dev(self, "Can't setup sysctl tree (%d)\n", error);
310 		goto out;
311 	}
312 
313 	error = iic_acquire_bus(sc->sc_tag, 0);
314 	if (error) {
315 		aprint_error_dev(self, "Could not acquire iic bus: %d\n",
316 		    error);
317 		goto out;
318 	}
319 
320 	cmd[0] = AHT20_SOFT_RESET;
321 	error = aht20_cmdr(sc, cmd, 1, NULL, 0);
322 	if (error != 0)
323 		aprint_error_dev(self, "Reset failed: %d\n", error);
324 
325 	iic_release_bus(sc->sc_tag, 0);
326 
327 	if (error != 0) {
328 		aprint_error_dev(self, "Unable to setup device\n");
329 		goto out;
330 	}
331 
332 	for (i = 0; i < sc->sc_numsensors; i++) {
333 		strlcpy(sc->sc_sensors[i].desc, aht20_sensors[i].desc,
334 		    sizeof(sc->sc_sensors[i].desc));
335 
336 		sc->sc_sensors[i].units = aht20_sensors[i].type;
337 		sc->sc_sensors[i].state = ENVSYS_SINVALID;
338 
339 		DPRINTF(sc, 2, ("%s: registering sensor %d (%s)\n", __func__, i,
340 		    sc->sc_sensors[i].desc));
341 
342 		error = sysmon_envsys_sensor_attach(sc->sc_sme,
343 		    &sc->sc_sensors[i]);
344 		if (error) {
345 			aprint_error_dev(self,
346 			    "Unable to attach sensor %d: %d\n", i, error);
347 			goto out;
348 		}
349 	}
350 
351 	sc->sc_sme->sme_name = device_xname(sc->sc_dev);
352 	sc->sc_sme->sme_cookie = sc;
353 	sc->sc_sme->sme_refresh = aht20_refresh;
354 
355 	DPRINTF(sc, 2, ("aht20_attach: registering with envsys\n"));
356 
357 	if (sysmon_envsys_register(sc->sc_sme)) {
358 		aprint_error_dev(self,
359 			"unable to register with sysmon\n");
360 		sysmon_envsys_destroy(sc->sc_sme);
361 		sc->sc_sme = NULL;
362 		return;
363 	}
364 
365 	aprint_normal_dev(self, "Guangzhou Aosong AHT20\n");
366 
367 	return;
368 out:
369 	sysmon_envsys_destroy(sc->sc_sme);
370 	sc->sc_sme = NULL;
371 }
372 
373 static void
aht20_refresh(struct sysmon_envsys * sme,envsys_data_t * edata)374 aht20_refresh(struct sysmon_envsys * sme, envsys_data_t * edata)
375 {
376 	struct aht20_sc *sc;
377 	sc = sme->sme_cookie;
378 	int error;
379 	uint8_t cmd[3];
380 	uint8_t rawdata[7];
381 	edata->state = ENVSYS_SINVALID;
382 
383 	mutex_enter(&sc->sc_mutex);
384 	error = iic_acquire_bus(sc->sc_tag, 0);
385 	if (error) {
386 		DPRINTF(sc, 2, ("%s: Could not acquire i2c bus: %x\n",
387 		    device_xname(sc->sc_dev), error));
388 		goto out;
389 	}
390 
391 	/*
392 	  The documented conversion calculations for the raw values are as follows:
393 
394 	  %RH = (Srh / 2^20) * 100%
395 
396 	  T in Celsius = ((St / 2^20) * 200) - 50
397 
398 	  It follows then:
399 
400 	  T in Kelvin = ((St / 2^20) * 200) + 223.15
401 
402 	  given the relationship between Celsius and Kelvin.
403 
404 	  What follows reorders the calculation a bit and scales it up to avoid
405 	  the use of any floating point.  All that would really have to happen
406 	  is a scale up to 10^6 for the sysenv framework, which wants
407 	  temperature in micro-kelvin and percent relative humidity scaled up
408 	  10^6, but since this conversion uses 64 bits due to intermediate
409 	  values that are bigger than 32 bits the conversion first scales up to
410 	  10^9 and the scales back down by 10^3 at the end.  This preserves some
411 	  precision in the conversion that would otherwise be lost.
412 	 */
413 
414 	cmd[0] = AHT20_TRIGGER_MEASUREMENT;
415 	cmd[1] = AHT20_TRIGGER_PARAM1;
416 	cmd[2] = AHT20_TRIGGER_PARAM2;
417 	error = aht20_cmdr(sc, cmd, 3, rawdata, 7);
418 
419 	if (error == 0) {
420 		if (rawdata[0] & AHT20_STATUS_BUSY_MASK) {
421 			aprint_error_dev(sc->sc_dev,
422 			    "Chip is busy.  Status register: %02x\n",
423 			    rawdata[0]);
424 			error = EINVAL;
425 		}
426 
427 		if (error == 0 &&
428 		    rawdata[0] & AHT20_STATUS_CAL_MASK) {
429 
430 			uint8_t testcrc;
431 
432 			testcrc = aht20_crc(&rawdata[0],6);
433 
434 			DPRINTF(sc, 2, ("%s: Raw data: STATUS: %02x - RH: %02x %02x - %02x - TEMP: %02x %02x - CRC: %02x -- %02x\n",
435 			    device_xname(sc->sc_dev), rawdata[0], rawdata[1], rawdata[2],
436 			    rawdata[3], rawdata[4], rawdata[5], rawdata[6], testcrc));
437 
438 			/* This chip splits the %rh and temp raw files ove the 3 byte returned.  Since
439 			   there is no choice but to get both, split them both apart every time */
440 
441 			uint64_t rawhum;
442 			uint64_t rawtemp;
443 
444 			rawhum = (rawdata[1] << 12) | (rawdata[2] << 4) | ((rawdata[3] & 0xf0) >> 4);
445 			rawtemp = ((rawdata[3] & 0x0f) << 16) | (rawdata[4] << 8) | rawdata[5];
446 
447 			DPRINTF(sc, 2, ("%s: Raw broken data: RH: %04jx (%jd) - TEMP: %04jx (%jd)\n",
448 			    device_xname(sc->sc_dev), rawhum, rawhum, rawtemp, rawtemp));
449 
450 			/* Fake out the CRC check if being asked to ignore CRC */
451 			if (sc->sc_ignorecrc) {
452 				testcrc = rawdata[6];
453 			}
454 
455 			if (rawdata[6] == testcrc) {
456 				uint64_t q = 0;
457 
458 				switch (edata->sensor) {
459 				case AHT20_TEMP_SENSOR:
460 					q = (((rawtemp * 1000000000) / 10485760) * 2) + 223150000;
461 
462 					break;
463 				case AHT20_HUMIDITY_SENSOR:
464 					q = (rawhum * 1000000000) / 10485760;
465 
466 					break;
467 				default:
468 					error = EINVAL;
469 					break;
470 				}
471 
472 				DPRINTF(sc, 2, ("%s: Computed sensor: %#jx (%jd)\n",
473 				    device_xname(sc->sc_dev), (uintmax_t)q, (uintmax_t)q));
474 
475 				/* The results will fit in 32 bits, so nothing will be lost */
476 				edata->value_cur = (uint32_t) q;
477 				edata->state = ENVSYS_SVALID;
478 			} else {
479 				error = EINVAL;
480 			}
481 		} else {
482 			if (error == 0) {
483 				aprint_error_dev(sc->sc_dev,"Calibration needs to be run on the chip.\n");
484 
485 				cmd[0] = AHT20_INITIALIZE;
486 				cmd[1] = AHT20_INITIALIZE_PARAM1;
487 				cmd[2] = AHT20_INITIALIZE_PARAM2;
488 				error = aht20_cmdr(sc, cmd, 3, NULL, 0);
489 
490 				if (error) {
491 					DPRINTF(sc, 2, ("%s: Calibration failed to run: %d\n",
492 					    device_xname(sc->sc_dev), error));
493 				}
494 			}
495 		}
496 	}
497 
498 	if (error) {
499 		DPRINTF(sc, 2, ("%s: Failed to get new status in refresh %d\n",
500 		    device_xname(sc->sc_dev), error));
501 	}
502 
503 	iic_release_bus(sc->sc_tag, 0);
504 out:
505 	mutex_exit(&sc->sc_mutex);
506 }
507 
508 static int
aht20_detach(device_t self,int flags)509 aht20_detach(device_t self, int flags)
510 {
511 	struct aht20_sc *sc;
512 
513 	sc = device_private(self);
514 
515 	mutex_enter(&sc->sc_mutex);
516 
517 	/* Remove the sensors */
518 	if (sc->sc_sme != NULL) {
519 		sysmon_envsys_unregister(sc->sc_sme);
520 		sc->sc_sme = NULL;
521 	}
522 	mutex_exit(&sc->sc_mutex);
523 
524 	/* Remove the sysctl tree */
525 	sysctl_teardown(&sc->sc_aht20log);
526 
527 	/* Remove the mutex */
528 	mutex_destroy(&sc->sc_mutex);
529 
530 	return 0;
531 }
532 
533 MODULE(MODULE_CLASS_DRIVER, aht20temp, "iic,sysmon_envsys");
534 
535 #ifdef _MODULE
536 #include "ioconf.c"
537 #endif
538 
539 static int
aht20temp_modcmd(modcmd_t cmd,void * opaque)540 aht20temp_modcmd(modcmd_t cmd, void *opaque)
541 {
542 
543 	switch (cmd) {
544 	case MODULE_CMD_INIT:
545 #ifdef _MODULE
546 		return config_init_component(cfdriver_ioconf_aht20temp,
547 		    cfattach_ioconf_aht20temp, cfdata_ioconf_aht20temp);
548 #else
549 		return 0;
550 #endif
551 	case MODULE_CMD_FINI:
552 #ifdef _MODULE
553 		return config_fini_component(cfdriver_ioconf_aht20temp,
554 		      cfattach_ioconf_aht20temp, cfdata_ioconf_aht20temp);
555 #else
556 		return 0;
557 #endif
558 	default:
559 		return ENOTTY;
560 	}
561 }
562