Home | History | Annotate | Line # | Download | only in i2c
      1 /*	$NetBSD: aht20.c,v 1.1 2022/11/17 19:20:06 brad Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2022 Brad Spencer <brad (at) 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
     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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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