Module Name:    src
Committed By:   brad
Date:           Thu Nov 17 19:20:06 UTC 2022

Modified Files:
        src/distrib/sets/lists/debug: module.mi
        src/distrib/sets/lists/man: mi
        src/distrib/sets/lists/modules: mi
        src/share/man/man4: Makefile
        src/sys/dev/i2c: files.i2c
        src/sys/modules: Makefile
Added Files:
        src/share/man/man4: aht20temp.4
        src/sys/dev/i2c: aht20.c aht20reg.h aht20var.h
        src/sys/modules/aht20temp: Makefile aht20temp.ioconf

Log Message:
A driver for the Aosong AHT20 temperature and humidity sensor.  While
slow for an I2C sensor it is inexpensive and should work well enough
in most indoor conditions.  All features of the chip are supported.


To generate a diff of this commit:
cvs rdiff -u -r1.20 -r1.21 src/distrib/sets/lists/debug/module.mi
cvs rdiff -u -r1.1754 -r1.1755 src/distrib/sets/lists/man/mi
cvs rdiff -u -r1.154 -r1.155 src/distrib/sets/lists/modules/mi
cvs rdiff -u -r1.728 -r1.729 src/share/man/man4/Makefile
cvs rdiff -u -r0 -r1.1 src/share/man/man4/aht20temp.4
cvs rdiff -u -r0 -r1.1 src/sys/dev/i2c/aht20.c src/sys/dev/i2c/aht20reg.h \
    src/sys/dev/i2c/aht20var.h
cvs rdiff -u -r1.123 -r1.124 src/sys/dev/i2c/files.i2c
cvs rdiff -u -r1.271 -r1.272 src/sys/modules/Makefile
cvs rdiff -u -r0 -r1.1 src/sys/modules/aht20temp/Makefile \
    src/sys/modules/aht20temp/aht20temp.ioconf

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/distrib/sets/lists/debug/module.mi
diff -u src/distrib/sets/lists/debug/module.mi:1.20 src/distrib/sets/lists/debug/module.mi:1.21
--- src/distrib/sets/lists/debug/module.mi:1.20	Sat Jun  4 03:32:04 2022
+++ src/distrib/sets/lists/debug/module.mi	Thu Nov 17 19:20:06 2022
@@ -1,4 +1,4 @@
-# $NetBSD: module.mi,v 1.20 2022/06/04 03:32:04 pgoyette Exp $
+# $NetBSD: module.mi,v 1.21 2022/11/17 19:20:06 brad Exp $
 ./usr/libdata/debug/@MODULEDIR@					modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/accf_dataready			modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/accf_dataready/accf_dataready.kmod.debug	modules-base-kernel	kmod,debug
@@ -8,6 +8,8 @@
 ./usr/libdata/debug/@MODULEDIR@/adiantum/adiantum.kmod.debug	modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/adosfs				modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/adosfs/adosfs.kmod.debug		modules-base-kernel	kmod,debug
+./usr/libdata/debug/@MODULEDIR@/aht20temp				modules-base-kernel	kmod,debug
+./usr/libdata/debug/@MODULEDIR@/aht20temp/aht20temp.kmod.debug		modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/aio				modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/aio/aio.kmod.debug			modules-base-kernel	kmod,debug
 ./usr/libdata/debug/@MODULEDIR@/am2315temp			modules-base-kernel	kmod,debug

Index: src/distrib/sets/lists/man/mi
diff -u src/distrib/sets/lists/man/mi:1.1754 src/distrib/sets/lists/man/mi:1.1755
--- src/distrib/sets/lists/man/mi:1.1754	Sat Aug 27 21:53:38 2022
+++ src/distrib/sets/lists/man/mi	Thu Nov 17 19:20:06 2022
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.1754 2022/08/27 21:53:38 dholland Exp $
+# $NetBSD: mi,v 1.1755 2022/11/17 19:20:06 brad Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 #
@@ -781,6 +781,7 @@
 ./usr/share/man/cat4/ahci.0			man-sys-catman		.cat
 ./usr/share/man/cat4/ahcisata.0			man-sys-catman		.cat
 ./usr/share/man/cat4/ahd.0			man-sys-catman		.cat
+./usr/share/man/cat4/aht20temp.0		man-sys-catman		.cat
 ./usr/share/man/cat4/ai.0			man-sys-catman		.cat
 ./usr/share/man/cat4/aiboost.0			man-sys-catman		.cat
 ./usr/share/man/cat4/aibs.0			man-sys-catman		.cat
@@ -4088,6 +4089,7 @@
 ./usr/share/man/html4/ahci.html			man-sys-htmlman		html
 ./usr/share/man/html4/ahcisata.html		man-sys-htmlman		html
 ./usr/share/man/html4/ahd.html			man-sys-htmlman		html
+./usr/share/man/html4/aht20temp.html		man-sys-htmlman		html
 ./usr/share/man/html4/ai.html			man-sys-htmlman		html
 ./usr/share/man/html4/aiboost.html		man-sys-htmlman		html
 ./usr/share/man/html4/aibs.html			man-sys-htmlman		html
@@ -7089,6 +7091,7 @@
 ./usr/share/man/man4/ahci.4			man-sys-man		.man
 ./usr/share/man/man4/ahcisata.4			man-sys-man		.man
 ./usr/share/man/man4/ahd.4			man-sys-man		.man
+./usr/share/man/man4/aht20temp.4		man-sys-man		.man
 ./usr/share/man/man4/ai.4			man-sys-man		.man
 ./usr/share/man/man4/aiboost.4			man-sys-man		.man
 ./usr/share/man/man4/aibs.4			man-sys-man		.man

Index: src/distrib/sets/lists/modules/mi
diff -u src/distrib/sets/lists/modules/mi:1.154 src/distrib/sets/lists/modules/mi:1.155
--- src/distrib/sets/lists/modules/mi:1.154	Sat Jun  4 03:32:04 2022
+++ src/distrib/sets/lists/modules/mi	Thu Nov 17 19:20:06 2022
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.154 2022/06/04 03:32:04 pgoyette Exp $
+# $NetBSD: mi,v 1.155 2022/11/17 19:20:06 brad Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 #
@@ -13,6 +13,8 @@
 ./@MODULEDIR@/adosfs/adosfs.kmod		modules-base-kernel	kmod
 ./@MODULEDIR@/adiantum				modules-base-kernel	kmod
 ./@MODULEDIR@/adiantum/adiantum.kmod		modules-base-kernel	kmod
+./@MODULEDIR@/aht20temp				modules-base-kernel     kmod
+./@MODULEDIR@/aht20temp/aht20temp.kmod		modules-base-kernel     kmod
 ./@MODULEDIR@/aio				modules-base-kernel	kmod
 ./@MODULEDIR@/aio/aio.kmod			modules-base-kernel	kmod
 ./@MODULEDIR@/am2315temp			modules-base-kernel	kmod

Index: src/share/man/man4/Makefile
diff -u src/share/man/man4/Makefile:1.728 src/share/man/man4/Makefile:1.729
--- src/share/man/man4/Makefile:1.728	Fri Aug 12 11:15:41 2022
+++ src/share/man/man4/Makefile	Thu Nov 17 19:20:05 2022
@@ -1,10 +1,10 @@
-#	$NetBSD: Makefile,v 1.728 2022/08/12 11:15:41 riastradh Exp $
+#	$NetBSD: Makefile,v 1.729 2022/11/17 19:20:05 brad Exp $
 #	@(#)Makefile	8.1 (Berkeley) 6/18/93
 
 MAN=	aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \
 	adbbt.4 adbkbd.4 adbms.4 \
 	adc.4 adm1026hm.4 admtemp.4 adv.4 adw.4 age.4 agp.4 agr.4 ahb.4 ahc.4 \
-	ahcisata.4 ahd.4 aibs.4 alc.4 ale.4 alipm.4 altmem.4 altq.4 \
+	ahcisata.4 ahd.4 aht20temp.4 aibs.4 alc.4 ale.4 alipm.4 altmem.4 altq.4 \
 	am2315temp.4 amdpm.4 amdtemp.4 amhphy.4 amr.4 aps.4 asus.4 \
 	an.4 aq.4 arcmsr.4 arcofi.4 aria.4 artsata.4 ata.4 atalk.4 ataraid.4 \
 	ath.4 athn.4 atphy.4 atppc.4 attimer.4 atw.4 \

Index: src/sys/dev/i2c/files.i2c
diff -u src/sys/dev/i2c/files.i2c:1.123 src/sys/dev/i2c/files.i2c:1.124
--- src/sys/dev/i2c/files.i2c:1.123	Wed Jul 20 10:01:11 2022
+++ src/sys/dev/i2c/files.i2c	Thu Nov 17 19:20:06 2022
@@ -1,4 +1,4 @@
-#	$NetBSD: files.i2c,v 1.123 2022/07/20 10:01:11 riastradh Exp $
+#	$NetBSD: files.i2c,v 1.124 2022/11/17 19:20:06 brad Exp $
 
 obsolete defflag	opt_i2cbus.h		I2C_SCAN
 define	i2cbus { }
@@ -430,3 +430,8 @@ file	dev/i2c/pcf8574.c			pcf8574io
 # Sparkfun Serial motor controller
 attach  scmd at iic with scmdi2c
 file	dev/i2c/scmdi2c.c			scmdi2c
+
+# Aosong AHT20 Temperature and Humidity sensor
+device aht20temp
+attach aht20temp at iic
+file dev/i2c/aht20.c				aht20temp

Index: src/sys/modules/Makefile
diff -u src/sys/modules/Makefile:1.271 src/sys/modules/Makefile:1.272
--- src/sys/modules/Makefile:1.271	Wed Sep  7 11:26:23 2022
+++ src/sys/modules/Makefile	Thu Nov 17 19:20:05 2022
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.271 2022/09/07 11:26:23 martin Exp $
+#	$NetBSD: Makefile,v 1.272 2022/11/17 19:20:05 brad Exp $
 
 .include <bsd.own.mk>
 
@@ -25,6 +25,7 @@ SUBDIR+=	accf_dataready
 SUBDIR+=	accf_httpready
 SUBDIR+=	adosfs
 SUBDIR+=	adiantum
+SUBDIR+=	aht20temp
 SUBDIR+=	aio
 SUBDIR+=	audio
 SUBDIR+=	autofs

Added files:

Index: src/share/man/man4/aht20temp.4
diff -u /dev/null src/share/man/man4/aht20temp.4:1.1
--- /dev/null	Thu Nov 17 19:20:06 2022
+++ src/share/man/man4/aht20temp.4	Thu Nov 17 19:20:05 2022
@@ -0,0 +1,76 @@
+.\" $NetBSD: aht20temp.4,v 1.1 2022/11/17 19:20:05 brad Exp $
+.\"
+.\" Copyright (c) 2022 Brad Spencer <b...@anduin.eldar.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd November 15, 2022
+.Dt AHT20TEMP 4
+.Os
+.Sh NAME
+.Nm aht20temp
+.Nd Driver for Guangzhou Aosong AHT20 sensor chip via I2C bus
+.Sh SYNOPSIS
+.Cd "aht20temp* at iic? addr 0x38"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides measurements from the AHT20 humidity/temperature
+sensors via the
+.Xr envsys 4
+framework.
+The
+.Nm
+.Ar addr
+argument selects the address at the
+.Xr iic 4
+bus.
+The crc validity can be changed through
+.Xr sysctl 8
+nodes.
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 3
+variables are provided:
+.Bl -tag -width indent
+.It Li hw.aht20temp0.ignorecrc
+If set, the crc calculation for %RH and temperature will be ignored.
+.It Li hw.aht20temp0.debug
+If the driver is compiled with
+.Dv AHT20_DEBUG ,
+this node will appear and can be used to set the debugging level.
+.It Li hw.aht20temp0.readattempts
+To read %RH or temperature the chip requires that the command be sent,
+then a delay must be observed before a read can be done to get the values
+back.
+The delays are documented in the datasheet for the chip.
+The driver will attempt to read back the values readattempts number of
+times.
+The default is 10 which should be more than enough for most purposes.
+.El
+.Sh SEE ALSO
+.Xr envsys 4 ,
+.Xr iic 4 ,
+.Xr envstat 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Nx 10.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Brad Spencer Aq Mt b...@anduin.eldar.org .

Index: src/sys/dev/i2c/aht20.c
diff -u /dev/null src/sys/dev/i2c/aht20.c:1.1
--- /dev/null	Thu Nov 17 19:20:06 2022
+++ src/sys/dev/i2c/aht20.c	Thu Nov 17 19:20:06 2022
@@ -0,0 +1,561 @@
+/*	$NetBSD: aht20.c,v 1.1 2022/11/17 19:20:06 brad Exp $	*/
+
+/*
+ * Copyright (c) 2022 Brad Spencer <b...@anduin.eldar.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: aht20.c,v 1.1 2022/11/17 19:20:06 brad Exp $");
+
+/*
+  Driver for the Guangzhou Aosong AHT20 temperature and humidity sensor
+*/
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/device.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+#include <sys/mutex.h>
+
+#include <dev/sysmon/sysmonvar.h>
+#include <dev/i2c/i2cvar.h>
+#include <dev/i2c/aht20reg.h>
+#include <dev/i2c/aht20var.h>
+
+
+static uint8_t 	aht20_crc(uint8_t *, size_t);
+static int 	aht20_poke(i2c_tag_t, i2c_addr_t, bool);
+static int 	aht20_match(device_t, cfdata_t, void *);
+static void 	aht20_attach(device_t, device_t, void *);
+static int 	aht20_detach(device_t, int);
+static void 	aht20_refresh(struct sysmon_envsys *, envsys_data_t *);
+static int 	aht20_verify_sysctl(SYSCTLFN_ARGS);
+
+#define AHT20_DEBUG
+#ifdef AHT20_DEBUG
+#define DPRINTF(s, l, x) \
+    do { \
+	if (l <= s->sc_aht20debug) \
+	    printf x; \
+    } while (/*CONSTCOND*/0)
+#else
+#define DPRINTF(s, l, x)
+#endif
+
+CFATTACH_DECL_NEW(aht20temp, sizeof(struct aht20_sc),
+    aht20_match, aht20_attach, aht20_detach, NULL);
+
+static struct aht20_sensor aht20_sensors[] = {
+	{
+		.desc = "humidity",
+		.type = ENVSYS_SRELHUMIDITY,
+	},
+	{
+		.desc = "temperature",
+		.type = ENVSYS_STEMP,
+	}
+};
+
+/*
+ * The delays are mentioned in the datasheet for the chip, except for
+ * the get status command.
+ */
+
+static struct aht20_timing aht20_timings[] = {
+	{
+		.cmd = AHT20_INITIALIZE,
+		.typicaldelay = 10000,
+	},
+	{
+		.cmd = AHT20_TRIGGER_MEASUREMENT,
+		.typicaldelay = 80000,
+	},
+	{
+		.cmd = AHT20_GET_STATUS,
+		.typicaldelay = 5000,
+	},
+	{
+		.cmd = AHT20_SOFT_RESET,
+		.typicaldelay = 20000,
+	}
+};
+
+int
+aht20_verify_sysctl(SYSCTLFN_ARGS)
+{
+	int error, t;
+	struct sysctlnode node;
+
+	node = *rnode;
+	t = *(int *)rnode->sysctl_data;
+	node.sysctl_data = &t;
+	error = sysctl_lookup(SYSCTLFN_CALL(&node));
+	if (error || newp == NULL)
+		return error;
+
+	if (t < 0)
+		return EINVAL;
+
+	*(int *)rnode->sysctl_data = t;
+
+	return 0;
+}
+
+static int
+aht20_cmddelay(uint8_t cmd)
+{
+	int r = -1;
+
+	for(int i = 0;i < __arraycount(aht20_timings);i++) {
+		if (cmd == aht20_timings[i].cmd) {
+			r = aht20_timings[i].typicaldelay;
+			break;
+		}
+	}
+
+	if (r == -1) {
+		panic("Bad command look up in cmd delay: cmd: %d\n",cmd);
+	}
+
+	return r;
+}
+
+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)
+{
+	int error;
+	int cmddelay;
+
+	error = iic_exec(tag,I2C_OP_WRITE_WITH_STOP,addr,cmd,clen,NULL,0,0);
+
+	/* Every command returns something except for the soft reset and
+	   initialize which returns nothing.
+	*/
+
+	if (error == 0) {
+		cmddelay = aht20_cmddelay(cmd[0]);
+		delay(cmddelay);
+
+		if (cmd[0] != AHT20_SOFT_RESET &&
+		    cmd[0] != AHT20_INITIALIZE) {
+			for (int aint = 0; aint < readattempts; aint++) {
+				error = iic_exec(tag,I2C_OP_READ_WITH_STOP,addr,NULL,0,buf,blen,0);
+				if (error == 0)
+					break;
+				delay(1000);
+			}
+		}
+	}
+
+	return error;
+}
+
+static int
+aht20_cmdr(struct aht20_sc *sc, uint8_t *cmd, uint8_t clen, uint8_t *buf, size_t blen)
+{
+	KASSERT(clen > 0);
+
+	return aht20_cmd(sc->sc_tag, sc->sc_addr, cmd, clen, buf, blen, sc->sc_readattempts);
+}
+
+static	uint8_t
+aht20_crc(uint8_t * data, size_t size)
+{
+	uint8_t crc = 0xFF;
+
+	for (size_t i = 0; i < size; i++) {
+		crc ^= data[i];
+		for (size_t j = 8; j > 0; j--) {
+			if (crc & 0x80)
+				crc = (crc << 1) ^ 0x31;
+			else
+				crc <<= 1;
+		}
+	}
+	return crc;
+}
+
+static int
+aht20_poke(i2c_tag_t tag, i2c_addr_t addr, bool matchdebug)
+{
+	uint8_t reg = AHT20_GET_STATUS;
+	uint8_t buf[6];
+	int error;
+
+	error = aht20_cmd(tag, addr, &reg, 1, buf, 1, 10);
+	if (matchdebug) {
+		printf("poke X 1: %d\n", error);
+	}
+	return error;
+}
+
+static int
+aht20_sysctl_init(struct aht20_sc *sc)
+{
+	int error;
+	const struct sysctlnode *cnode;
+	int sysctlroot_num;
+
+	if ((error = sysctl_createv(&sc->sc_aht20log, 0, NULL, &cnode,
+	    0, CTLTYPE_NODE, device_xname(sc->sc_dev),
+	    SYSCTL_DESCR("aht20 controls"), NULL, 0, NULL, 0, CTL_HW,
+	    CTL_CREATE, CTL_EOL)) != 0)
+		return error;
+
+	sysctlroot_num = cnode->sysctl_num;
+
+#ifdef AHT20_DEBUG
+	if ((error = sysctl_createv(&sc->sc_aht20log, 0, NULL, &cnode,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "debug",
+	    SYSCTL_DESCR("Debug level"), aht20_verify_sysctl, 0,
+	    &sc->sc_aht20debug, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
+	    CTL_EOL)) != 0)
+		return error;
+
+#endif
+
+	if ((error = sysctl_createv(&sc->sc_aht20log, 0, NULL, &cnode,
+	    CTLFLAG_READWRITE, CTLTYPE_INT, "readattempts",
+	    SYSCTL_DESCR("The number of times to attempt to read the values"),
+	    aht20_verify_sysctl, 0, &sc->sc_readattempts, 0, CTL_HW,
+	    sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
+		return error;
+
+	if ((error = sysctl_createv(&sc->sc_aht20log, 0, NULL, &cnode,
+	    CTLFLAG_READWRITE, CTLTYPE_BOOL, "ignorecrc",
+	    SYSCTL_DESCR("Ignore the CRC byte"), NULL, 0, &sc->sc_ignorecrc,
+	    0, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
+		return error;
+
+	return 0;
+}
+
+static int
+aht20_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct i2c_attach_args *ia = aux;
+	int error, match_result;
+	const bool matchdebug = false;
+
+	if (iic_use_direct_match(ia, match, NULL, &match_result))
+		return match_result;
+
+	/* indirect config - check for configured address */
+	if (ia->ia_addr != AHT20_TYPICAL_ADDR)
+		return 0;
+
+	/*
+	 * Check to see if something is really at this i2c address. This will
+	 * keep phantom devices from appearing
+	 */
+	if (iic_acquire_bus(ia->ia_tag, 0) != 0) {
+		if (matchdebug)
+			printf("in match acquire bus failed\n");
+		return 0;
+	}
+
+	error = aht20_poke(ia->ia_tag, ia->ia_addr, matchdebug);
+	iic_release_bus(ia->ia_tag, 0);
+
+	return error == 0 ? I2C_MATCH_ADDRESS_AND_PROBE : 0;
+}
+
+static void
+aht20_attach(device_t parent, device_t self, void *aux)
+{
+	struct aht20_sc *sc;
+	struct i2c_attach_args *ia;
+	uint8_t cmd[1];
+	int error, i;
+
+	ia = aux;
+	sc = device_private(self);
+
+	sc->sc_dev = self;
+	sc->sc_tag = ia->ia_tag;
+	sc->sc_addr = ia->ia_addr;
+	sc->sc_aht20debug = 0;
+	sc->sc_readattempts = 10;
+	sc->sc_ignorecrc = false;
+	sc->sc_sme = NULL;
+
+	aprint_normal("\n");
+
+	mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE);
+	sc->sc_numsensors = __arraycount(aht20_sensors);
+
+	if ((sc->sc_sme = sysmon_envsys_create()) == NULL) {
+		aprint_error_dev(self,
+		    "Unable to create sysmon structure\n");
+		sc->sc_sme = NULL;
+		return;
+	}
+	if ((error = aht20_sysctl_init(sc)) != 0) {
+		aprint_error_dev(self, "Can't setup sysctl tree (%d)\n", error);
+		goto out;
+	}
+
+	error = iic_acquire_bus(sc->sc_tag, 0);
+	if (error) {
+		aprint_error_dev(self, "Could not acquire iic bus: %d\n",
+		    error);
+		goto out;
+	}
+
+	cmd[0] = AHT20_SOFT_RESET;
+	error = aht20_cmdr(sc, cmd, 1, NULL, 0);
+	if (error != 0)
+		aprint_error_dev(self, "Reset failed: %d\n", error);
+
+	iic_release_bus(sc->sc_tag, 0);
+
+	if (error != 0) {
+		aprint_error_dev(self, "Unable to setup device\n");
+		goto out;
+	}
+
+	for (i = 0; i < sc->sc_numsensors; i++) {
+		strlcpy(sc->sc_sensors[i].desc, aht20_sensors[i].desc,
+		    sizeof(sc->sc_sensors[i].desc));
+
+		sc->sc_sensors[i].units = aht20_sensors[i].type;
+		sc->sc_sensors[i].state = ENVSYS_SINVALID;
+
+		DPRINTF(sc, 2, ("%s: registering sensor %d (%s)\n", __func__, i,
+		    sc->sc_sensors[i].desc));
+
+		error = sysmon_envsys_sensor_attach(sc->sc_sme,
+		    &sc->sc_sensors[i]);
+		if (error) {
+			aprint_error_dev(self,
+			    "Unable to attach sensor %d: %d\n", i, error);
+			goto out;
+		}
+	}
+
+	sc->sc_sme->sme_name = device_xname(sc->sc_dev);
+	sc->sc_sme->sme_cookie = sc;
+	sc->sc_sme->sme_refresh = aht20_refresh;
+
+	DPRINTF(sc, 2, ("aht20_attach: registering with envsys\n"));
+
+	if (sysmon_envsys_register(sc->sc_sme)) {
+		aprint_error_dev(self,
+			"unable to register with sysmon\n");
+		sysmon_envsys_destroy(sc->sc_sme);
+		sc->sc_sme = NULL;
+		return;
+	}
+
+	aprint_normal_dev(self, "Guangzhou Aosong AHT20\n");
+
+	return;
+out:
+	sysmon_envsys_destroy(sc->sc_sme);
+	sc->sc_sme = NULL;
+}
+
+static void
+aht20_refresh(struct sysmon_envsys * sme, envsys_data_t * edata)
+{
+	struct aht20_sc *sc;
+	sc = sme->sme_cookie;
+	int error;
+	uint8_t cmd[3];
+	uint8_t rawdata[7];
+	edata->state = ENVSYS_SINVALID;
+
+	mutex_enter(&sc->sc_mutex);
+	error = iic_acquire_bus(sc->sc_tag, 0);
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Could not acquire i2c bus: %x\n",
+		    device_xname(sc->sc_dev), error));
+		goto out;
+	}
+
+	/*
+	  The documented conversion calculations for the raw values are as follows:
+
+	  %RH = (Srh / 2^20) * 100%
+
+	  T in Celsius = ((St / 2^20) * 200) - 50
+
+	  It follows then:
+
+	  T in Kelvin = ((St / 2^20) * 200) + 223.15
+
+	  given the relationship between Celsius and Kelvin.
+
+	  What follows reorders the calculation a bit and scales it up to avoid
+	  the use of any floating point.  All that would really have to happen
+	  is a scale up to 10^6 for the sysenv framework, which wants
+	  temperature in micro-kelvin and percent relative humidity scaled up
+	  10^6, but since this conversion uses 64 bits due to intermediate
+	  values that are bigger than 32 bits the conversion first scales up to
+	  10^9 and the scales back down by 10^3 at the end.  This preserves some
+	  precision in the conversion that would otherwise be lost.
+	 */
+
+	cmd[0] = AHT20_TRIGGER_MEASUREMENT;
+	cmd[1] = AHT20_TRIGGER_PARAM1;
+	cmd[2] = AHT20_TRIGGER_PARAM2;
+	error = aht20_cmdr(sc, cmd, 3, rawdata, 7);
+
+	if (error == 0) {
+		if (rawdata[0] & AHT20_STATUS_BUSY_MASK) {
+			aprint_error_dev(sc->sc_dev,
+			    "Chip is busy.  Status register: %02x\n",
+			    rawdata[0]);
+			error = EINVAL;
+		}
+
+		if (error == 0 &&
+		    rawdata[0] & AHT20_STATUS_CAL_MASK) {
+
+			uint8_t testcrc;
+
+			testcrc = aht20_crc(&rawdata[0],6);
+
+			DPRINTF(sc, 2, ("%s: Raw data: STATUS: %02x - RH: %02x %02x - %02x - TEMP: %02x %02x - CRC: %02x -- %02x\n",
+			    device_xname(sc->sc_dev), rawdata[0], rawdata[1], rawdata[2],
+			    rawdata[3], rawdata[4], rawdata[5], rawdata[6], testcrc));
+
+			/* This chip splits the %rh and temp raw files ove the 3 byte returned.  Since
+			   there is no choice but to get both, split them both apart every time */
+
+			uint64_t rawhum;
+			uint64_t rawtemp;
+
+			rawhum = (rawdata[1] << 12) | (rawdata[2] << 4) | ((rawdata[3] & 0xf0) >> 4);
+			rawtemp = ((rawdata[3] & 0x0f) << 16) | (rawdata[4] << 8) | rawdata[5];
+
+			DPRINTF(sc, 2, ("%s: Raw broken data: RH: %04jx (%jd) - TEMP: %04jx (%jd)\n",
+			    device_xname(sc->sc_dev), rawhum, rawhum, rawtemp, rawtemp));
+
+			/* Fake out the CRC check if being asked to ignore CRC */
+			if (sc->sc_ignorecrc) {
+				testcrc = rawdata[6];
+			}
+
+			if (rawdata[6] == testcrc) {
+				uint64_t q = 0;
+
+				switch (edata->sensor) {
+				case AHT20_TEMP_SENSOR:
+					q = (((rawtemp * 1000000000) / 10485760) * 2) + 223150000;
+
+					break;
+				case AHT20_HUMIDITY_SENSOR:
+					q = (rawhum * 1000000000) / 10485760;
+
+					break;
+				default:
+					error = EINVAL;
+					break;
+				}
+
+				DPRINTF(sc, 2, ("%s: Computed sensor: %#jx (%jd)\n",
+				    device_xname(sc->sc_dev), (uintmax_t)q, (uintmax_t)q));
+
+				/* The results will fit in 32 bits, so nothing will be lost */
+				edata->value_cur = (uint32_t) q;
+				edata->state = ENVSYS_SVALID;
+			} else {
+				error = EINVAL;
+			}
+		} else {
+			if (error == 0) {
+				aprint_error_dev(sc->sc_dev,"Calibration needs to be run on the chip.\n");
+
+				cmd[0] = AHT20_INITIALIZE;
+				cmd[1] = AHT20_INITIALIZE_PARAM1;
+				cmd[2] = AHT20_INITIALIZE_PARAM2;
+				error = aht20_cmdr(sc, cmd, 3, NULL, 0);
+
+				if (error) {
+					DPRINTF(sc, 2, ("%s: Calibration failed to run: %d\n",
+					    device_xname(sc->sc_dev), error));
+				}
+			}
+		}
+	}
+
+	if (error) {
+		DPRINTF(sc, 2, ("%s: Failed to get new status in refresh %d\n",
+		    device_xname(sc->sc_dev), error));
+	}
+
+	iic_release_bus(sc->sc_tag, 0);
+out:
+	mutex_exit(&sc->sc_mutex);
+}
+
+static int
+aht20_detach(device_t self, int flags)
+{
+	struct aht20_sc *sc;
+
+	sc = device_private(self);
+
+	mutex_enter(&sc->sc_mutex);
+
+	/* Remove the sensors */
+	if (sc->sc_sme != NULL) {
+		sysmon_envsys_unregister(sc->sc_sme);
+		sc->sc_sme = NULL;
+	}
+	mutex_exit(&sc->sc_mutex);
+
+	/* Remove the sysctl tree */
+	sysctl_teardown(&sc->sc_aht20log);
+
+	/* Remove the mutex */
+	mutex_destroy(&sc->sc_mutex);
+
+	return 0;
+}
+
+MODULE(MODULE_CLASS_DRIVER, aht20temp, "iic,sysmon_envsys");
+
+#ifdef _MODULE
+#include "ioconf.c"
+#endif
+
+static int
+aht20temp_modcmd(modcmd_t cmd, void *opaque)
+{
+
+	switch (cmd) {
+	case MODULE_CMD_INIT:
+#ifdef _MODULE
+		return config_init_component(cfdriver_ioconf_aht20temp,
+		    cfattach_ioconf_aht20temp, cfdata_ioconf_aht20temp);
+#else
+		return 0;
+#endif
+	case MODULE_CMD_FINI:
+#ifdef _MODULE
+		return config_fini_component(cfdriver_ioconf_aht20temp,
+		      cfattach_ioconf_aht20temp, cfdata_ioconf_aht20temp);
+#else
+		return 0;
+#endif
+	default:
+		return ENOTTY;
+	}
+}
Index: src/sys/dev/i2c/aht20reg.h
diff -u /dev/null src/sys/dev/i2c/aht20reg.h:1.1
--- /dev/null	Thu Nov 17 19:20:06 2022
+++ src/sys/dev/i2c/aht20reg.h	Thu Nov 17 19:20:06 2022
@@ -0,0 +1,37 @@
+/*	$NetBSD: aht20reg.h,v 1.1 2022/11/17 19:20:06 brad Exp $	*/
+
+/*
+ * Copyright (c) 2022 Brad Spencer <b...@anduin.eldar.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _DEV_I2C_AHT20REG_H_
+#define _DEV_I2C_AHT20REG_H_
+
+#define AHT20_TYPICAL_ADDR		0x38
+
+#define AHT20_INITIALIZE		0xBE
+#define AHT20_INITIALIZE_PARAM1		0x08
+#define AHT20_INITIALIZE_PARAM2		0x00
+
+#define AHT20_TRIGGER_MEASUREMENT	0xAC
+#define AHT20_TRIGGER_PARAM1		0x33
+#define AHT20_TRIGGER_PARAM2		0x00
+
+#define AHT20_GET_STATUS		0x71
+#define AHT20_STATUS_BUSY_MASK		0x80
+#define AHT20_STATUS_CAL_MASK		0x08
+#define AHT20_SOFT_RESET		0xBA
+
+#endif
Index: src/sys/dev/i2c/aht20var.h
diff -u /dev/null src/sys/dev/i2c/aht20var.h:1.1
--- /dev/null	Thu Nov 17 19:20:06 2022
+++ src/sys/dev/i2c/aht20var.h	Thu Nov 17 19:20:06 2022
@@ -0,0 +1,50 @@
+/*	$NetBSD: aht20var.h,v 1.1 2022/11/17 19:20:06 brad Exp $	*/
+
+/*
+ * Copyright (c) 2022 Brad Spencer <b...@anduin.eldar.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _DEV_I2C_AHT20VAR_H_
+#define _DEV_I2C_AHT20VAR_H_
+
+#define AHT20_NUM_SENSORS	2
+#define AHT20_HUMIDITY_SENSOR 0
+#define AHT20_TEMP_SENSOR 1
+
+struct aht20_sc {
+	int 		sc_aht20debug;
+	device_t 	sc_dev;
+	i2c_tag_t 	sc_tag;
+	i2c_addr_t 	sc_addr;
+	kmutex_t 	sc_mutex;
+	int 		sc_numsensors;
+	struct sysmon_envsys *sc_sme;
+	struct sysctllog *sc_aht20log;
+	envsys_data_t 	sc_sensors[AHT20_NUM_SENSORS];
+	bool 		sc_ignorecrc;
+	int 		sc_readattempts;
+};
+
+struct aht20_sensor {
+	const char     *desc;
+	enum envsys_units type;
+};
+
+struct aht20_timing {
+	uint8_t cmd;
+	int     typicaldelay;
+};
+
+#endif

Index: src/sys/modules/aht20temp/Makefile
diff -u /dev/null src/sys/modules/aht20temp/Makefile:1.1
--- /dev/null	Thu Nov 17 19:20:06 2022
+++ src/sys/modules/aht20temp/Makefile	Thu Nov 17 19:20:05 2022
@@ -0,0 +1,11 @@
+.include "../Makefile.inc"
+
+.PATH:	${S}/dev/i2c
+
+KMOD=	aht20temp
+IOCONF=	aht20temp.ioconf
+SRCS=	aht20.c
+
+WARNS=	3
+
+.include <bsd.kmodule.mk>
Index: src/sys/modules/aht20temp/aht20temp.ioconf
diff -u /dev/null src/sys/modules/aht20temp/aht20temp.ioconf:1.1
--- /dev/null	Thu Nov 17 19:20:06 2022
+++ src/sys/modules/aht20temp/aht20temp.ioconf	Thu Nov 17 19:20:05 2022
@@ -0,0 +1,7 @@
+ioconf aht20temp
+
+include "conf/files"
+
+pseudo-root iic*
+
+aht20temp* at iic? addr 0x38

Reply via email to