root/sys/dev/isa/viasio.c
/*      $OpenBSD: viasio.c,v 1.15 2022/04/06 18:59:29 naddy Exp $       */
/*
 * Copyright (c) 2005 Alexander Yurchenko <grange@openbsd.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.
 */

/*
 * VIA VT1211 LPC Super I/O driver.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/sensors.h>
#include <sys/timeout.h>

#include <machine/bus.h>

#include <dev/isa/isareg.h>
#include <dev/isa/isavar.h>

#include <dev/isa/viasioreg.h>

#ifdef VIASIO_DEBUG
#define DPRINTF(x) printf x
#else
#define DPRINTF(x)
#endif

/* autoconf flags */
#define VIASIO_CFFLAGS_HM_ENABLE        0x0001  /* enable HM if disabled */
#define VIASIO_CFFLAGS_WDG_ENABLE       0x0002  /* enable WDG if disabled */

struct viasio_softc {
        struct device           sc_dev;

        bus_space_tag_t         sc_iot;
        bus_space_handle_t      sc_ioh;

        /* Hardware monitor */
        bus_space_handle_t      sc_hm_ioh;
        int                     sc_hm_clock;
        struct ksensor          sc_hm_sensors[VT1211_HM_NSENSORS];
        struct ksensordev       sc_sensordev;
        struct timeout          sc_hm_timo;

        /* Watchdog timer */
        bus_space_handle_t      sc_wdg_ioh;
};

int     viasio_probe(struct device *, void *, void *);
void    viasio_attach(struct device *, struct device *, void *);
int     viasio_activate(struct device *, int);

void    viasio_hm_init(struct viasio_softc *);
void    viasio_hm_refresh(void *);

void    viasio_wdg_init(struct viasio_softc *);
int     viasio_wdg_cb(void *, int);

const struct cfattach viasio_ca = {
        sizeof(struct viasio_softc),
        viasio_probe,
        viasio_attach,
        NULL,
        viasio_activate
};

struct cfdriver viasio_cd = {
        NULL, "viasio", DV_DULL
};

static __inline void
viasio_conf_enable(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        bus_space_write_1(iot, ioh, VT1211_INDEX, VT1211_CONF_EN_MAGIC);
        bus_space_write_1(iot, ioh, VT1211_INDEX, VT1211_CONF_EN_MAGIC);
}

static __inline void
viasio_conf_disable(bus_space_tag_t iot, bus_space_handle_t ioh)
{
        bus_space_write_1(iot, ioh, VT1211_INDEX, VT1211_CONF_DS_MAGIC);
}

static __inline u_int8_t
viasio_conf_read(bus_space_tag_t iot, bus_space_handle_t ioh, u_int8_t index)
{
        bus_space_write_1(iot, ioh, VT1211_INDEX, index);
        return (bus_space_read_1(iot, ioh, VT1211_DATA));
}

static __inline void
viasio_conf_write(bus_space_tag_t iot, bus_space_handle_t ioh, u_int8_t index,
    u_int8_t data)
{
        bus_space_write_1(iot, ioh, VT1211_INDEX, index);
        bus_space_write_1(iot, ioh, VT1211_DATA, data);
}

static __inline int64_t
viasio_raw2temp(int raw)
{
        int tblsize = sizeof(vt1211_hm_temptbl) / sizeof(vt1211_hm_temptbl[0]);
        int i;
        int raw1, raw2;
        int64_t temp = -1, temp1, temp2;

        if (raw < vt1211_hm_temptbl[0].raw ||
            raw > vt1211_hm_temptbl[tblsize - 1].raw)
                return (-1);

        for (i = 0; i < tblsize - 1; i++) {
                raw1 = vt1211_hm_temptbl[i].raw;
                temp1 = vt1211_hm_temptbl[i].temp;
                raw2 = vt1211_hm_temptbl[i + 1].raw;
                temp2 = vt1211_hm_temptbl[i + 1].temp;

                if (raw >= raw1 && raw <= raw2) {
                        /* linear interpolation */
                        temp = temp1 + ((raw - raw1) * (temp2 - temp1)) /
                            (raw2 - raw1);
                        break;
                }
        }

        return (temp);
}

int
viasio_probe(struct device *parent, void *match, void *aux)
{
        struct isa_attach_args *ia = aux;
        bus_space_tag_t iot;
        bus_space_handle_t ioh;
        u_int8_t reg;

        /* Match by device ID */
        iot = ia->ia_iot;
        if (bus_space_map(iot, ia->ipa_io[0].base, VT1211_IOSIZE, 0, &ioh))
                return (0);
        viasio_conf_enable(iot, ioh);
        reg = viasio_conf_read(iot, ioh, VT1211_ID);
        DPRINTF(("viasio_probe: id 0x%02x\n", reg));
        viasio_conf_disable(iot, ioh);
        bus_space_unmap(iot, ioh, VT1211_IOSIZE);
        if (reg == VT1211_ID_VT1211) {
                ia->ipa_nio = 1;
                ia->ipa_io[0].length = VT1211_IOSIZE;
                ia->ipa_nmem = 0;
                ia->ipa_nirq = 0;
                ia->ipa_ndrq = 0;
                return (1);
        }

        return (0);
}

void
viasio_attach(struct device *parent, struct device *self, void *aux)
{
        struct viasio_softc *sc = (void *)self;
        struct isa_attach_args *ia = aux;
        u_int8_t reg;

        /* Map ISA I/O space */
        sc->sc_iot = ia->ia_iot;
        if (bus_space_map(sc->sc_iot, ia->ipa_io[0].base,
            VT1211_IOSIZE, 0, &sc->sc_ioh)) {
                printf(": can't map i/o space\n");
                return;
        }

        /* Enter configuration mode */
        viasio_conf_enable(sc->sc_iot, sc->sc_ioh);

        /* Read device revision */
        reg = viasio_conf_read(sc->sc_iot, sc->sc_ioh, VT1211_REV);
        printf(": VT1211 rev 0x%02x", reg);

        /* Initialize logical devices */
        viasio_hm_init(sc);
        viasio_wdg_init(sc);
        printf("\n");

        /* Escape from configuration mode */
        viasio_conf_disable(sc->sc_iot, sc->sc_ioh);
}

int
viasio_activate(struct device *self, int act)
{
        switch (act) {
        case DVACT_POWERDOWN:
                wdog_shutdown(self);
                break;
        }

        return (0);
}

void
viasio_hm_init(struct viasio_softc *sc)
{
        u_int8_t reg0, reg1;
        u_int16_t iobase;
        int i;

        printf(", HM");

        /* Select HM logical device */
        viasio_conf_write(sc->sc_iot, sc->sc_ioh, VT1211_LDN, VT1211_LDN_HM);

        /*
         * Check if logical device is activated by firmware.  If not
         * try to activate it only if requested.
         */
        reg0 = viasio_conf_read(sc->sc_iot, sc->sc_ioh, VT1211_HM_ACT);
        DPRINTF((": ACT 0x%02x", reg0));
        if ((reg0 & VT1211_HM_ACT_EN) == 0) {
                if ((sc->sc_dev.dv_cfdata->cf_flags &
                    VIASIO_CFFLAGS_HM_ENABLE) != 0) {
                        reg0 |= VT1211_HM_ACT_EN;
                        viasio_conf_write(sc->sc_iot, sc->sc_ioh,
                            VT1211_HM_ACT, reg0);
                        reg0 = viasio_conf_read(sc->sc_iot, sc->sc_ioh,
                            VT1211_HM_ACT);
                        DPRINTF((", new ACT 0x%02x", reg0));
                        if ((reg0 & VT1211_HM_ACT_EN) == 0) {
                                printf(" failed to activate");
                                return;
                        }
                } else {
                        printf(" not activated");
                        return;
                }
        }

        /* Read HM I/O space address */
        reg0 = viasio_conf_read(sc->sc_iot, sc->sc_ioh, VT1211_HM_ADDR_LSB);
        reg1 = viasio_conf_read(sc->sc_iot, sc->sc_ioh, VT1211_HM_ADDR_MSB);
        iobase = (reg1 << 8) | reg0;
        DPRINTF((", addr 0x%04x", iobase));

        /* Map HM I/O space */
        if (bus_space_map(sc->sc_iot, iobase, VT1211_HM_IOSIZE, 0,
            &sc->sc_hm_ioh)) {
                printf(" can't map i/o space");
                return;
        }

        /*
         * Check if hardware monitoring is enabled by firmware.  If not
         * try to enable it only if requested.
         */
        reg0 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh, VT1211_HM_CONF);
        DPRINTF((", CONF 0x%02x", reg0));
        if ((reg0 & VT1211_HM_CONF_START) == 0) {
                if ((sc->sc_dev.dv_cfdata->cf_flags &
                    VIASIO_CFFLAGS_HM_ENABLE) != 0) {
                        reg0 |= VT1211_HM_CONF_START;
                        bus_space_write_1(sc->sc_iot, sc->sc_hm_ioh,
                            VT1211_HM_CONF, reg0);
                        reg0 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh,
                            VT1211_HM_CONF);
                        DPRINTF((", new CONF 0x%02x", reg0));
                        if ((reg0 & VT1211_HM_CONF_START) == 0) {
                                printf(" failed to enable monitoring");
                                return;
                        }
                } else {
                        printf(" monitoring not enabled");
                        return;
                }
        }

        /* Read PWM clock frequency */
        reg0 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh, VT1211_HM_PWMCS);
        sc->sc_hm_clock = vt1211_hm_clock[reg0 & 0x07];
        DPRINTF((", PWMCS 0x%02x, %dHz", reg0, sc->sc_hm_clock));

        /* Temperature reading 1 */
        sc->sc_hm_sensors[VT1211_HMS_TEMP1].type = SENSOR_TEMP;

        /* Universal channels (UCH) 1-5 */
        reg0 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh, VT1211_HM_UCHCONF);
        DPRINTF((", UCHCONF 0x%02x", reg0));
        for (i = 1; i <= 5; i++) {
                /* UCH can be configured either as thermal or voltage input */
                if (VT1211_HM_UCHCONF_ISTEMP(reg0, i)) {
                        sc->sc_hm_sensors[VT1211_HMS_UCH1 + i - 1].type =
                            SENSOR_TEMP;
                } else {
                        sc->sc_hm_sensors[VT1211_HMS_UCH1 + i - 1].type =
                            SENSOR_VOLTS_DC;
                }
                snprintf(sc->sc_hm_sensors[VT1211_HMS_UCH1 + i - 1].desc,
                    sizeof(sc->sc_hm_sensors[VT1211_HMS_UCH1 + i - 1].desc),
                    "UCH%d", i);
        }

        /* Internal +3.3V */
        sc->sc_hm_sensors[VT1211_HMS_33V].type = SENSOR_VOLTS_DC;
        strlcpy(sc->sc_hm_sensors[VT1211_HMS_33V].desc, "+3.3V",
            sizeof(sc->sc_hm_sensors[VT1211_HMS_33V].desc));

        /* FAN reading 1, 2 */
        sc->sc_hm_sensors[VT1211_HMS_FAN1].type = SENSOR_FANRPM;
        sc->sc_hm_sensors[VT1211_HMS_FAN2].type = SENSOR_FANRPM;

        /* Start sensors */
        strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
            sizeof(sc->sc_sensordev.xname));
        for (i = 0; i < VT1211_HM_NSENSORS; i++)
                sensor_attach(&sc->sc_sensordev, &sc->sc_hm_sensors[i]);
        sensordev_install(&sc->sc_sensordev);
        timeout_set(&sc->sc_hm_timo, viasio_hm_refresh, sc);
        timeout_add_sec(&sc->sc_hm_timo, 1);
}

void
viasio_hm_refresh(void *arg)
{
        struct viasio_softc *sc = arg;
        u_int8_t reg0, reg1;
        int64_t val, rfact;
        int i;

        /* TEMP1 is a 10-bit thermal input */
        reg0 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh, VT1211_HM_TEMP1);
        reg1 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh, VT1211_HM_TCONF1);
        reg1 = VT1211_HM_TCONF1_TEMP1(reg1);
        val = (reg0 << 2) | reg1;

        /* Convert to uK */
        /* XXX: conversion function is guessed */
        val = viasio_raw2temp(val);
        if (val == -1) {
                sc->sc_hm_sensors[VT1211_HMS_TEMP1].flags |= SENSOR_FINVALID;
        } else {
                sc->sc_hm_sensors[VT1211_HMS_TEMP1].flags &= ~SENSOR_FINVALID;
                sc->sc_hm_sensors[VT1211_HMS_TEMP1].value = val;
        }

        /* Universal channels 1-5 */
        for (i = 1; i <= 5; i++) {
                if (sc->sc_hm_sensors[VT1211_HMS_UCH1 + i - 1].type ==
                    SENSOR_TEMP) {
                        /* UCH is a 10-bit thermal input */
                        reg0 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh,
                            VT1211_HM_UCH1 + i - 1);
                        if (i == 1) {
                                reg1 = bus_space_read_1(sc->sc_iot,
                                    sc->sc_hm_ioh, VT1211_HM_VID4);
                                reg1 = VT1211_HM_VID4_UCH1(reg1);
                        } else {
                                reg1 = bus_space_read_1(sc->sc_iot,
                                    sc->sc_hm_ioh, VT1211_HM_ETR);
                                reg1 = VT1211_HM_ETR_UCH(reg1, i);
                        }
                        val = (reg0 << 2) | reg1;

                        /* Convert to uK */
                        /* XXX: conversion function is guessed */
                        val = viasio_raw2temp(val);
                        if (val == -1) {
                                sc->sc_hm_sensors[VT1211_HMS_UCH1 +
                                    i - 1].flags |= SENSOR_FINVALID;
                        } else {
                                sc->sc_hm_sensors[VT1211_HMS_UCH1 +
                                    i - 1].flags &= ~SENSOR_FINVALID;
                                sc->sc_hm_sensors[VT1211_HMS_UCH1 +
                                    i - 1].value = val;
                        }
                } else {
                        /* UCH is a voltage input */
                        reg0 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh,
                            VT1211_HM_UCH1 + i - 1);
                        val = reg0;

                        /* Convert to uV */
                        /* XXX: conversion function is guessed */
                        rfact = vt1211_hm_vrfact[i - 1];
                        sc->sc_hm_sensors[VT1211_HMS_UCH1 + i - 1].value =
                            ((val * 100000000000ULL) / (rfact * 958));
                }
        }

        /* Read internal +3.3V */
        reg0 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh, VT1211_HM_33V);
        val = reg0;

        /* Convert to uV */
        /* XXX: conversion function is guessed */
        rfact = vt1211_hm_vrfact[5];
        sc->sc_hm_sensors[VT1211_HMS_33V].value = ((val * 100000000000ULL) /
            (rfact * 958));

        /* Read FAN1 clock counter and divisor */
        reg0 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh, VT1211_HM_FAN1);
        reg1 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh, VT1211_HM_FSCTL);
        reg1 = VT1211_HM_FSCTL_DIV1(reg1);
        val = reg0 << reg1;

        /* Convert to RPM */
        /* XXX: conversion function is guessed */
        if (val != 0) {         
                sc->sc_hm_sensors[VT1211_HMS_FAN1].value =
                    (sc->sc_hm_clock * 60 / 2) / val;
                sc->sc_hm_sensors[VT1211_HMS_FAN1].flags &= ~SENSOR_FINVALID;
        } else {
                sc->sc_hm_sensors[VT1211_HMS_FAN1].flags |= SENSOR_FINVALID;
        }

        /* Read FAN2 clock counter and divisor */
        reg0 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh, VT1211_HM_FAN2);
        reg1 = bus_space_read_1(sc->sc_iot, sc->sc_hm_ioh, VT1211_HM_FSCTL);
        reg1 = VT1211_HM_FSCTL_DIV2(reg1);
        val = reg0 << reg1;

        /* Convert to RPM */
        /* XXX: conversion function is guessed */
        if (val != 0) {         
                sc->sc_hm_sensors[VT1211_HMS_FAN2].value =
                    (sc->sc_hm_clock * 60 / 2) / val;
                sc->sc_hm_sensors[VT1211_HMS_FAN2].flags &= ~SENSOR_FINVALID;
        } else {
                sc->sc_hm_sensors[VT1211_HMS_FAN2].flags |= SENSOR_FINVALID;
        }

        timeout_add_sec(&sc->sc_hm_timo, 1);
}

void
viasio_wdg_init(struct viasio_softc *sc)
{
        u_int8_t reg0, reg1;
        u_int16_t iobase;

        printf(", WDG");

        /* Select WDG logical device */
        viasio_conf_write(sc->sc_iot, sc->sc_ioh, VT1211_LDN, VT1211_LDN_WDG);

        /*
         * Check if logical device is activated by firmware.  If not
         * try to activate it only if requested.
         */
        reg0 = viasio_conf_read(sc->sc_iot, sc->sc_ioh, VT1211_WDG_ACT);
        DPRINTF((": ACT 0x%02x", reg0));
        if ((reg0 & VT1211_WDG_ACT_EN) == 0) {
                if ((sc->sc_dev.dv_cfdata->cf_flags &
                    VIASIO_CFFLAGS_WDG_ENABLE) != 0) {
                        reg0 |= VT1211_WDG_ACT_EN;
                        viasio_conf_write(sc->sc_iot, sc->sc_ioh,
                            VT1211_WDG_ACT, reg0);
                        reg0 = viasio_conf_read(sc->sc_iot, sc->sc_ioh,
                            VT1211_WDG_ACT);
                        DPRINTF((", new ACT 0x%02x", reg0));
                        if ((reg0 & VT1211_WDG_ACT_EN) == 0) {
                                printf(" failed to activate");
                                return;
                        }
                } else {
                        printf(" not activated");
                        return;
                }
        }

        /* Read WDG I/O space address */
        reg0 = viasio_conf_read(sc->sc_iot, sc->sc_ioh, VT1211_WDG_ADDR_LSB);
        reg1 = viasio_conf_read(sc->sc_iot, sc->sc_ioh, VT1211_WDG_ADDR_MSB);
        iobase = (reg1 << 8) | reg0;
        DPRINTF((", addr 0x%04x", iobase));

        /* Map WDG I/O space */
        if (bus_space_map(sc->sc_iot, iobase, VT1211_WDG_IOSIZE, 0,
            &sc->sc_wdg_ioh)) {
                printf(" can't map i/o space");
                return;
        }

        /* Register new watchdog */
        wdog_register(viasio_wdg_cb, sc);
}

int
viasio_wdg_cb(void *arg, int period)
{
        struct viasio_softc *sc = arg;
        int mins;

        mins = (period + 59) / 60;
        if (mins > 255)
                mins = 255;

        bus_space_write_1(sc->sc_iot, sc->sc_wdg_ioh, VT1211_WDG_TIMEOUT, mins);
        DPRINTF(("viasio_wdg_cb: %d mins\n", mins));

        return (mins * 60);
}