/*- * Copyright (c) 2014,2016 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Frank Kardel. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * IST-AG P14 calibrated Hygro-/Temperature sensor module * Devices: HYT-271, HYT-221 and HYT-939 * * see: * http://www.ist-ag.com/eh/ist-ag/resource.nsf/imgref/Download_AHHYTM_E2.1.pdf/ * $FILE/AHHYTM_E2.1.pdf */ /* * FDT direct configuration fragment to be added to i2cX definition in dtsi file * like in bcm2835-rpi.dtsi or another specific file. * * &i2c1 { * pinctrl-names = "default"; * pinctrl-0 = <&i2c1_gpio2>; * status = "okay"; * clock-frequency = <100000>; * #address-cells = <1>; * #size-cells = <0>; * hythygtemp@28 { * compatible = "ist-ag,i2c-hytp14"; * reg = <0x28>; * status = "okay"; * }; * hythygtemp@29 { * compatible = "ist-ag,i2c-hytp14"; * reg = <0x29>; * status = "okay"; * }; * }; */ #include __KERNEL_RCSID(0, "$NetBSD: hytp14.c,v 1.15 2022/03/30 00:06:50 pgoyette Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include static int hytp14_match(device_t, cfdata_t, void *); static void hytp14_attach(device_t, device_t, void *); static int hytp14_detach(device_t, int); static void hytp14_measurement_request(void *); static int hytp14_refresh_sensor(struct hytp14_sc *sc); static void hytp14_refresh(struct sysmon_envsys *, envsys_data_t *); static void hytp14_refresh_humidity(struct hytp14_sc *, envsys_data_t *); static void hytp14_refresh_temp(struct hytp14_sc *, envsys_data_t *); static void hytp14_thread(void *); static int sysctl_hytp14_interval(SYSCTLFN_ARGS); /* #define HYT_DEBUG 3 */ #ifdef HYT_DEBUG volatile int hythygtemp_debug = HYT_DEBUG; #define DPRINTF(_L_, _X_) do { \ if ((_L_) <= hythygtemp_debug) { \ printf _X_; \ } \ } while (0) #else #define DPRINTF(_L_, _X_) #endif CFATTACH_DECL_NEW(hythygtemp, sizeof(struct hytp14_sc), hytp14_match, hytp14_attach, hytp14_detach, NULL); static struct hytp14_sensor hytp14_sensors[] = { { .desc = "humidity", .type = ENVSYS_SRELHUMIDITY, .refresh = hytp14_refresh_humidity }, { .desc = "temperature", .type = ENVSYS_STEMP, .refresh = hytp14_refresh_temp } }; static const struct device_compatible_entry compat_data[] = { { .compat = "i2c-hytp14" }, DEVICE_COMPAT_EOL }; static int hytp14_match(device_t parent, cfdata_t match, void *aux) { struct i2c_attach_args *ia = aux; int match_result; if (iic_use_direct_match(ia, match, compat_data, &match_result)) return match_result; /* * This device can be reprogrammed to use a different * I2C address, thus checking for specific addresses * is not helpful here. * reprogramming is done via setting new values in * the device EEPROM via the hytctl utility and * a special GPIO setup - see hythygtemp(4) for more * information. */ return I2C_MATCH_ADDRESS_ONLY; } static void hytp14_attach(device_t parent, device_t self, void *aux) { const struct sysctlnode *rnode, *node; struct hytp14_sc *sc; struct i2c_attach_args *ia; int i, rv; ia = aux; sc = device_private(self); sc->sc_dev = self; sc->sc_tag = ia->ia_tag; sc->sc_addr = ia->ia_addr; mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE); cv_init(&sc->sc_condvar, "hytcv"); sc->sc_state = HYTP14_THR_INIT; sc->sc_valid = ENVSYS_SINVALID; sc->sc_numsensors = __arraycount(hytp14_sensors); if ((sc->sc_sme = sysmon_envsys_create()) == NULL) { aprint_error_dev(sc->sc_dev, "unable to create sysmon structure\n"); return; } for (i = 0; i < sc->sc_numsensors; i++) { strlcpy(sc->sc_sensors[i].desc, hytp14_sensors[i].desc, sizeof sc->sc_sensors[i].desc); sc->sc_sensors[i].units = hytp14_sensors[i].type; sc->sc_sensors[i].state = ENVSYS_SINVALID; DPRINTF(2, ("hytp14_attach: registering sensor %d (%s)\n", i, sc->sc_sensors[i].desc)); if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensors[i])) { aprint_error_dev(sc->sc_dev, "unable to attach sensor\n"); sysmon_envsys_destroy(sc->sc_sme); sc->sc_sme = NULL; return; } } sc->sc_sme->sme_name = device_xname(sc->sc_dev); sc->sc_sme->sme_cookie = sc; sc->sc_sme->sme_refresh = hytp14_refresh; DPRINTF(2, ("hytp14_attach: registering with envsys\n")); if (sysmon_envsys_register(sc->sc_sme)) { aprint_error_dev(sc->sc_dev, "unable to register with sysmon\n"); sysmon_envsys_destroy(sc->sc_sme); sc->sc_sme = NULL; return; } /* create a sysctl node for setting the measurement interval */ rnode = node = NULL; sysctl_createv(NULL, 0, NULL, &rnode, CTLFLAG_READWRITE, CTLTYPE_NODE, device_xname(sc->sc_dev), NULL, NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL); if (rnode != NULL) sysctl_createv(NULL, 0, NULL, &node, CTLFLAG_READWRITE | CTLFLAG_OWNDESC, CTLTYPE_INT, "interval", SYSCTL_DESCR("Sensor sampling interval in seconds"), sysctl_hytp14_interval, 0, (void *)sc, 0, CTL_HW, rnode->sysctl_num, CTL_CREATE, CTL_EOL); /* set up the default measurement interval for worker thread */ sc->sc_mrinterval = HYTP14_MR_INTERVAL; /* create worker kthread */ rv = kthread_create(PRI_NONE, KTHREAD_MUSTJOIN, NULL, hytp14_thread, sc, &sc->sc_thread, "%s", device_xname(sc->sc_dev)); if (rv) aprint_error_dev(self, "unable to create intr thread\n"); aprint_normal(": HYT-221/271/939 humidity and temperature sensor\n"); } static int hytp14_detach(device_t self, int flags) { struct hytp14_sc *sc; sc = device_private(self); if (sc->sc_sme != NULL) sysmon_envsys_unregister(sc->sc_sme); /* stop measurement thread */ mutex_enter(&sc->sc_mutex); sc->sc_state = HYTP14_THR_STOP; cv_signal(&sc->sc_condvar); mutex_exit(&sc->sc_mutex); /* await thread completion */ kthread_join(sc->sc_thread); /* cleanup */ cv_destroy(&sc->sc_condvar); mutex_destroy(&sc->sc_mutex); return 0; } static void hytp14_thread(void *aux) { struct hytp14_sc *sc = aux; int rv; mutex_enter(&sc->sc_mutex); DPRINTF(2, ("%s(%s): thread start - state=%d\n", __func__, device_xname(sc->sc_dev), sc->sc_state)); while (sc->sc_state != HYTP14_THR_STOP) { sc->sc_state = HYTP14_THR_RUN; DPRINTF(2, ("%s(%s): waiting %d seconds\n", __func__, device_xname(sc->sc_dev), sc->sc_mrinterval)); rv = cv_timedwait(&sc->sc_condvar, &sc->sc_mutex, hz * sc->sc_mrinterval); if (rv == EWOULDBLOCK) { /* timeout - run measurement */ DPRINTF(2, ("%s(%s): timeout -> measurement\n", __func__, device_xname(sc->sc_dev))); hytp14_measurement_request(sc); } else { DPRINTF(2, ("%s(%s): condvar signalled - state=%d\n", __func__, device_xname(sc->sc_dev), sc->sc_state)); } } mutex_exit(&sc->sc_mutex); DPRINTF(2, ("%s(%s): thread exit\n", __func__, device_xname(sc->sc_dev))); kthread_exit(0); } static void hytp14_measurement_request(void *aux) { uint8_t buf[I2C_EXEC_MAX_BUFLEN]; struct hytp14_sc *sc; int error; sc = aux; DPRINTF(2, ("%s(%s)\n", __func__, device_xname(sc->sc_dev))); error = iic_acquire_bus(sc->sc_tag, 0); if (error == 0) { /* send DF command - read last data from sensor */ error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, NULL, 0, sc->sc_data, sizeof(sc->sc_data), 0); if (error != 0) { DPRINTF(2, ("%s: %s: failed read from 0x%02x - error %d\n", device_xname(sc->sc_dev), __func__, sc->sc_addr, error)); sc->sc_valid = ENVSYS_SINVALID; } else { DPRINTF(3, ("%s(%s): DF success : " "0x%02x%02x%02x%02x\n", __func__, device_xname(sc->sc_dev), sc->sc_data[0], sc->sc_data[1], sc->sc_data[2], sc->sc_data[3])); /* remember last data, when valid */ if (!(sc->sc_data[0] & (HYTP14_RESP_CMDMODE | HYTP14_RESP_STALE))) { memcpy(sc->sc_last, sc->sc_data, sizeof(sc->sc_last)); sc->sc_valid = ENVSYS_SVALID; } } /* send MR command to request a new measurement */ error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, NULL, 0, buf, sizeof(buf), 0); if (error == 0) { DPRINTF(3, ("%s(%s): MR sent\n", __func__, device_xname(sc->sc_dev))); } else { DPRINTF(2, ("%s: %s: failed read from 0x%02x - error %d\n", device_xname(sc->sc_dev), __func__, sc->sc_addr, error)); } iic_release_bus(sc->sc_tag, 0); DPRINTF(3, ("%s(%s): bus released\n", __func__, device_xname(sc->sc_dev))); } else { DPRINTF(2, ("%s: %s: failed acquire i2c bus - error %d\n", device_xname(sc->sc_dev), __func__, error)); } } static int hytp14_refresh_sensor(struct hytp14_sc *sc) { int error; DPRINTF(2, ("%s(%s)\n", __func__, device_xname(sc->sc_dev))); error = iic_acquire_bus(sc->sc_tag, 0); if (error == 0) { /* send DF command - read last data from sensor */ error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, NULL, 0, sc->sc_data, sizeof(sc->sc_data), 0); if (error != 0) { DPRINTF(2, ("%s: %s: failed read from 0x%02x - error %d\n", device_xname(sc->sc_dev), __func__, sc->sc_addr, error)); sc->sc_valid = ENVSYS_SINVALID; } else { DPRINTF(3, ("%s(%s): DF success : " "0x%02x%02x%02x%02x\n", __func__, device_xname(sc->sc_dev), sc->sc_data[0], sc->sc_data[1], sc->sc_data[2], sc->sc_data[3])); /* * Use old data from sc_last[] when new data * is not yet valid (i.e. DF command came too * quickly after the last command). */ if (!(sc->sc_data[0] & (HYTP14_RESP_CMDMODE | HYTP14_RESP_STALE))) { memcpy(sc->sc_last, sc->sc_data, sizeof(sc->sc_last)); sc->sc_valid = ENVSYS_SVALID; } else memcpy(sc->sc_data, sc->sc_last, sizeof(sc->sc_data)); } iic_release_bus(sc->sc_tag, 0); DPRINTF(3, ("%s(%s): bus released\n", __func__, device_xname(sc->sc_dev))); } else { DPRINTF(2, ("%s: %s: failed acquire i2c bus - error %d\n", device_xname(sc->sc_dev), __func__, error)); } return sc->sc_valid; } static void hytp14_refresh_humidity(struct hytp14_sc *sc, envsys_data_t *edata) { uint16_t hyg; int status; status = hytp14_refresh_sensor(sc); if (status == ENVSYS_SVALID) { hyg = (sc->sc_data[0] << 8) | sc->sc_data[1]; edata->value_cur = (1000000000 / HYTP14_HYG_SCALE) * (int32_t)HYTP14_HYG_RAWVAL(hyg); edata->value_cur /= 10; } edata->state = status; } static void hytp14_refresh_temp(struct hytp14_sc *sc, envsys_data_t *edata) { uint16_t temp; int status; status = hytp14_refresh_sensor(sc); if (status == ENVSYS_SVALID) { temp = HYTP14_TEMP_RAWVAL((sc->sc_data[2] << 8) | sc->sc_data[3]); edata->value_cur = (HYTP14_TEMP_FACTOR * 1000000) / HYTP14_TEMP_SCALE; edata->value_cur *= (int32_t)temp; edata->value_cur += HYTP14_TEMP_OFFSET * 1000000 + 273150000; } edata->state = status; } static void hytp14_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) { struct hytp14_sc *sc; sc = sme->sme_cookie; hytp14_sensors[edata->sensor].refresh(sc, edata); } static int sysctl_hytp14_interval(SYSCTLFN_ARGS) { struct sysctlnode node; struct hytp14_sc *sc; int32_t t; int error; node = *rnode; sc = node.sysctl_data; t = sc->sc_mrinterval; node.sysctl_data = &t; error = sysctl_lookup(SYSCTLFN_CALL(&node)); if (error || newp == NULL) return error; if (t <= 0) return EINVAL; sc->sc_mrinterval = t; return 0; } MODULE(MODULE_CLASS_DRIVER, hythygtemp, "iic,sysmon_envsys"); #ifdef _MODULE #include "ioconf.c" #endif static int hythygtemp_modcmd(modcmd_t cmd, void *opaque) { int error; error = 0; switch (cmd) { case MODULE_CMD_INIT: #ifdef _MODULE error = config_init_component(cfdriver_ioconf_hythygtemp, cfattach_ioconf_hythygtemp, cfdata_ioconf_hythygtemp); #endif return error; case MODULE_CMD_FINI: #ifdef _MODULE error = config_fini_component(cfdriver_ioconf_hythygtemp, cfattach_ioconf_hythygtemp, cfdata_ioconf_hythygtemp); #endif return error; default: return ENOTTY; } }