root/sys/dev/i2c/hytp14.c
/*-
 * 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: hytp14.c,v 1.15 2022/03/30 00:06:50 pgoyette Exp $");

#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 <sys/condvar.h>
#include <sys/kthread.h>

#include <dev/sysmon/sysmonvar.h>
#include <dev/i2c/i2cvar.h>
#include <dev/i2c/hytp14reg.h>
#include <dev/i2c/hytp14var.h>

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;
        }
}