root/sys/dev/pci/itherm.c
/*
 * Copyright (c) 2010 Mike Larkin <mlarkin@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.
 */

/*
 * Intel 3400 thermal sensor controller driver
 */

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

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>

/*
 * Intel 5 series (3400) Thermal Sensor Data
 * See Intel document 322169-004 (January 2012)
 */
#define ITHERM_NUM_SENSORS              12
#define ITHERM_SENSOR_THERMOMETER       0
#define ITHERM_SENSOR_CORETEMP1         1
#define ITHERM_SENSOR_CORETEMP2         2
#define ITHERM_SENSOR_COREENERGY        3
#define ITHERM_SENSOR_GPUTEMP           4
#define ITHERM_SENSOR_MAXPROCTEMP       5
#define ITHERM_SENSOR_DIMMTEMP1         6
#define ITHERM_SENSOR_DIMMTEMP2         7
#define ITHERM_SENSOR_DIMMTEMP3         8
#define ITHERM_SENSOR_DIMMTEMP4         9
#define ITHERM_SENSOR_GPUTEMP_ABSOLUTE  10
#define ITHERM_SENSOR_PCHTEMP_ABSOLUTE  11

/* Section 22.2 of datasheet */
#define ITHERM_TSE      0x1     /* TS enable */
#define ITHERM_TSTR     0x3     /* TS thermometer read */
#define ITHERM_TRC      0x1A    /* TS reporting control */
#define ITHERM_CTV1     0x30    /* TS core temp value 1 */
#define ITHERM_CTV2     0x32    /* TS core temp value 2 */
#define ITHERM_CEV1     0x34    /* TS core energy value 1 */
#define ITHERM_MGTV     0x58    /* mem/GPU temp value */
#define ITHERM_PTV      0x60    /* TS CPU temp value */
#define ITHERM_DTV      0xAC    /* DIMM temp values */
#define ITHERM_ITV      0xD8    /* Internal temp values */

#define ITHERM_TEMP_READ_ENABLE         0xFF
#define ITHERM_TDR_ENABLE               0x1000
#define ITHERM_SECOND_CORE_ENABLE       0x8000

#define ITHERM_TSE_ENABLE       0xB8    /* magic number in datasheet */

#define ITHERM_CTV_INVALID      0x8000
#define ITHERM_CTV_INT_MASK     0x3FC0  /* higher 8 bits */
#define ITHERM_CTV_FRAC_MASK    0x003F  /* lower 6 bits */

#define ITHERM_REFRESH_INTERVAL 5

struct itherm_softc {
        struct device           sc_dev;

        bus_addr_t              sc_addr;
        bus_space_tag_t         iot;
        bus_space_handle_t      ioh;
        bus_size_t              size;

        int64_t                 energy_prev;

        struct ksensor sensors[ITHERM_NUM_SENSORS];
        struct ksensordev sensordev;
        void (*refresh_sensor_data)(struct itherm_softc *);
};

#define IREAD1(sc, a) bus_space_read_1((sc)->iot, (sc)->ioh, (a))
#define IREAD2(sc, a) bus_space_read_2((sc)->iot, (sc)->ioh, (a))
#define IREAD4(sc, a) bus_space_read_4((sc)->iot, (sc)->ioh, (a))
#define IWRITE1(sc, a, x) bus_space_write_1((sc)->iot, (sc)->ioh, (a), (x))
#define IWRITE2(sc, a, x) bus_space_write_2((sc)->iot, (sc)->ioh, (a), (x))

int  itherm_probe(struct device *, void *, void *);
void itherm_attach(struct device *, struct device *, void *);
void itherm_refresh(void *);
void itherm_enable(struct itherm_softc *);
void itherm_refresh_sensor_data(struct itherm_softc *);
int  itherm_activate(struct device *, int);
void itherm_bias_temperature_sensor(struct ksensor *);

const struct pci_matchid itherm_devices[] = {
        { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_3400_THERMAL }
};

struct cfdriver itherm_cd = {
        NULL, "itherm", DV_DULL
};

const struct cfattach itherm_ca = {
        sizeof(struct itherm_softc), itherm_probe, itherm_attach, NULL,
        itherm_activate
};

int
itherm_probe(struct device *parent, void *match, void *aux)
{
        return (pci_matchbyid((struct pci_attach_args *)aux, itherm_devices,
            sizeof(itherm_devices)/sizeof(itherm_devices[0])));
}

void
itherm_attach(struct device *parent, struct device *self, void *aux)
{
        struct itherm_softc *sc = (struct itherm_softc *)self;
        struct pci_attach_args *pa = aux;
        int i;
        pcireg_t v;

        v = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_MAPREG_START);
        v &= PCI_MAPREG_TYPE_MASK | PCI_MAPREG_MEM_TYPE_MASK;
        if (pci_mapreg_map(pa, PCI_MAPREG_START,
            v, 0, &sc->iot, &sc->ioh, NULL, &sc->size, 0)) {
                printf(": can't map mem space\n");
                return;
        }

        sc->sensors[ITHERM_SENSOR_THERMOMETER].type = SENSOR_TEMP;
        sc->sensors[ITHERM_SENSOR_CORETEMP1].type = SENSOR_TEMP;
        sc->sensors[ITHERM_SENSOR_CORETEMP2].type = SENSOR_TEMP;
        sc->sensors[ITHERM_SENSOR_COREENERGY].type = SENSOR_WATTS;
        sc->sensors[ITHERM_SENSOR_GPUTEMP].type = SENSOR_TEMP;
        sc->sensors[ITHERM_SENSOR_MAXPROCTEMP].type = SENSOR_TEMP;
        sc->sensors[ITHERM_SENSOR_DIMMTEMP1].type = SENSOR_TEMP;
        sc->sensors[ITHERM_SENSOR_DIMMTEMP2].type = SENSOR_TEMP;
        sc->sensors[ITHERM_SENSOR_DIMMTEMP3].type = SENSOR_TEMP;
        sc->sensors[ITHERM_SENSOR_DIMMTEMP4].type = SENSOR_TEMP;
        sc->sensors[ITHERM_SENSOR_GPUTEMP_ABSOLUTE].type = SENSOR_TEMP;
        sc->sensors[ITHERM_SENSOR_PCHTEMP_ABSOLUTE].type = SENSOR_TEMP;

        strlcpy(sc->sensors[ITHERM_SENSOR_THERMOMETER].desc,
            "Thermometer",
            sizeof(sc->sensors[ITHERM_SENSOR_THERMOMETER].desc));

        strlcpy(sc->sensors[ITHERM_SENSOR_CORETEMP1].desc,
            "Core 1",
            sizeof(sc->sensors[ITHERM_SENSOR_CORETEMP1].desc));

        strlcpy(sc->sensors[ITHERM_SENSOR_CORETEMP2].desc,
            "Core 2",
            sizeof(sc->sensors[ITHERM_SENSOR_CORETEMP2].desc));

        strlcpy(sc->sensors[ITHERM_SENSOR_COREENERGY].desc,
            "CPU power consumption",
            sizeof(sc->sensors[ITHERM_SENSOR_COREENERGY].desc));

        strlcpy(sc->sensors[ITHERM_SENSOR_GPUTEMP].desc,
            "GPU/Memory Controller Temp",
            sizeof(sc->sensors[ITHERM_SENSOR_GPUTEMP].desc));

        strlcpy(sc->sensors[ITHERM_SENSOR_MAXPROCTEMP].desc,
            "CPU/GPU Max temp",
            sizeof(sc->sensors[ITHERM_SENSOR_MAXPROCTEMP].desc));

        strlcpy(sc->sensors[ITHERM_SENSOR_DIMMTEMP1].desc,
            "DIMM 1",
            sizeof(sc->sensors[ITHERM_SENSOR_DIMMTEMP1].desc));

        strlcpy(sc->sensors[ITHERM_SENSOR_DIMMTEMP2].desc,
            "DIMM 2",
            sizeof(sc->sensors[ITHERM_SENSOR_DIMMTEMP2].desc));

        strlcpy(sc->sensors[ITHERM_SENSOR_DIMMTEMP3].desc,
            "DIMM 3",
            sizeof(sc->sensors[ITHERM_SENSOR_DIMMTEMP3].desc));

        strlcpy(sc->sensors[ITHERM_SENSOR_DIMMTEMP4].desc,
            "DIMM 4",
            sizeof(sc->sensors[ITHERM_SENSOR_DIMMTEMP4].desc));

        strlcpy(sc->sensors[ITHERM_SENSOR_GPUTEMP_ABSOLUTE].desc,
            "GPU/Memory controller abs.",
            sizeof(sc->sensors[ITHERM_SENSOR_GPUTEMP_ABSOLUTE].desc));

        strlcpy(sc->sensors[ITHERM_SENSOR_PCHTEMP_ABSOLUTE].desc,
            "PCH abs.",
            sizeof(sc->sensors[ITHERM_SENSOR_PCHTEMP_ABSOLUTE].desc));

        strlcpy(sc->sensordev.xname, sc->sc_dev.dv_xname,
            sizeof(sc->sensordev.xname));

        itherm_enable(sc);

        for (i = 0; i < ITHERM_NUM_SENSORS; i++)
                sensor_attach(&sc->sensordev, &sc->sensors[i]);

        sensordev_install(&sc->sensordev);
        sensor_task_register(sc, itherm_refresh, ITHERM_REFRESH_INTERVAL);

        printf("\n");

        return;
}

void
itherm_enable(struct itherm_softc *sc)
{
        sc->energy_prev = 0;

        /* Enable thermal sensor */
        IWRITE1(sc, ITHERM_TSE, ITHERM_TSE_ENABLE);

        /* Enable thermal reporting */
        IWRITE2(sc, ITHERM_TRC, (ITHERM_TEMP_READ_ENABLE |
            ITHERM_TDR_ENABLE | ITHERM_SECOND_CORE_ENABLE));
}

int
itherm_activate(struct device *self, int act)
{
        struct itherm_softc *sc = (struct itherm_softc *)self;

        switch (act) {
        case DVACT_RESUME:
                itherm_enable(sc);
                break;
        }

        return (0);
}

void
itherm_refresh_sensor_data(struct itherm_softc *sc)
{
        u_int16_t data;
        int64_t energy;
        u_int32_t i;

        /* Thermometer sensor */
        sc->sensors[ITHERM_SENSOR_THERMOMETER].value =
            IREAD1(sc, ITHERM_TSTR);

        itherm_bias_temperature_sensor(
            &sc->sensors[ITHERM_SENSOR_THERMOMETER]);

        /*
         * The Intel 3400 Thermal Sensor has separate sensors for each
         * core, reported as a 16 bit value. Bits 13:6 are the integer
         * part of the temperature in C and bits 5:0 are the fractional
         * part of the temperature, in 1/64 degree C intervals.
         * Bit 15 is used to indicate an invalid temperature
         */

        /* Core 1 temperature */
        data = IREAD2(sc, ITHERM_CTV1);
        if (data & ITHERM_CTV_INVALID)
                sc->sensors[ITHERM_SENSOR_CORETEMP1].flags |=
                    SENSOR_FINVALID;
        else {
                sc->sensors[ITHERM_SENSOR_CORETEMP1].flags &=
                    ~SENSOR_FINVALID;
                sc->sensors[ITHERM_SENSOR_CORETEMP1].value =
                    (data & ITHERM_CTV_INT_MASK) >> 6;
                sc->sensors[ITHERM_SENSOR_CORETEMP1].value *=
                    1000000;
                data &= ITHERM_CTV_FRAC_MASK;
                data *= 1000000 / 64;
                sc->sensors[ITHERM_SENSOR_CORETEMP1].value +=
                    data;
                itherm_bias_temperature_sensor(
                    &sc->sensors[ITHERM_SENSOR_CORETEMP1]);
        }

        /* Core 2 temperature */
        data = IREAD2(sc, ITHERM_CTV2);
        if (data & ITHERM_CTV_INVALID)
                sc->sensors[ITHERM_SENSOR_CORETEMP2].flags |=
                    SENSOR_FINVALID;
        else {
                sc->sensors[ITHERM_SENSOR_CORETEMP2].flags &=
                    ~SENSOR_FINVALID;
                sc->sensors[ITHERM_SENSOR_CORETEMP2].value =
                    (data & ITHERM_CTV_INT_MASK) >> 6;
                sc->sensors[ITHERM_SENSOR_CORETEMP2].value *=
                    1000000;
                data &= ITHERM_CTV_FRAC_MASK;
                data *= 1000000 / 64;
                sc->sensors[ITHERM_SENSOR_CORETEMP2].value +=
                    data;
                itherm_bias_temperature_sensor(
                    &sc->sensors[ITHERM_SENSOR_CORETEMP2]);
        }

        /*
         * The core energy sensor reports the number of Joules
         * of energy consumed by the processor since powerup.
         * This number is scaled by 65535 and is continually
         * increasing, so we save the old value and compute
         * the difference for the Watt sensor value.
         */

        i = IREAD4(sc, ITHERM_CEV1);
        /* Convert to Joules per interval */
        energy = (i / 65535);
        energy = energy - sc->energy_prev;
        sc->energy_prev = (i / 65535);
        /* Convert to Joules per second */
        energy = energy / ITHERM_REFRESH_INTERVAL;
        /* Convert to micro Joules per second (micro Watts) */
        energy = energy * 1000 * 1000;

        sc->sensors[ITHERM_SENSOR_COREENERGY].value = energy;

        /*
         * XXX - the GPU temp is reported as a 64 bit value with no
         * documented structure. Disabled for now
         */
        sc->sensors[ITHERM_SENSOR_GPUTEMP].flags |= SENSOR_FINVALID;
#if 0
        bus_space_read_multi_4(sc->iot, sc->ioh, ITHERM_MGTV,
            (u_int32_t *)&sc->sensors[ITHERM_SENSOR_GPUTEMP].value, 2);
        sc->sensors[ITHERM_SENSOR_GPUTEMP].value *= 1000000;
        sc->sensors[ITHERM_SENSOR_GPUTEMP].value += 273150000;
#endif

        /* Max processor temperature */
        sc->sensors[ITHERM_SENSOR_MAXPROCTEMP].value =
            IREAD1(sc, ITHERM_PTV) * 1000000;
        itherm_bias_temperature_sensor(
            &sc->sensors[ITHERM_SENSOR_MAXPROCTEMP]);

        /* DIMM 1 */
        sc->sensors[ITHERM_SENSOR_DIMMTEMP1].value =
            IREAD1(sc, ITHERM_DTV) * 1000000;
        itherm_bias_temperature_sensor(
            &sc->sensors[ITHERM_SENSOR_DIMMTEMP1]);

        /* DIMM 2 */
        sc->sensors[ITHERM_SENSOR_DIMMTEMP2].value =
            IREAD1(sc, ITHERM_DTV+1) * 1000000;
        itherm_bias_temperature_sensor(
            &sc->sensors[ITHERM_SENSOR_DIMMTEMP2]);

        /* DIMM 3 */
        sc->sensors[ITHERM_SENSOR_DIMMTEMP3].value =
            IREAD1(sc, ITHERM_DTV+2) * 1000000;
        itherm_bias_temperature_sensor(
            &sc->sensors[ITHERM_SENSOR_DIMMTEMP3]);

        /* DIMM 4 */
        sc->sensors[ITHERM_SENSOR_DIMMTEMP4].value =
            IREAD1(sc, ITHERM_DTV+3) * 1000000;
        itherm_bias_temperature_sensor(
            &sc->sensors[ITHERM_SENSOR_DIMMTEMP4]);

        /* GPU Temperature */
        sc->sensors[ITHERM_SENSOR_GPUTEMP_ABSOLUTE].value =
            IREAD1(sc, ITHERM_ITV+1) * 1000000;
        itherm_bias_temperature_sensor(
            &sc->sensors[ITHERM_SENSOR_GPUTEMP_ABSOLUTE]);

        /* PCH Temperature */
        sc->sensors[ITHERM_SENSOR_PCHTEMP_ABSOLUTE].value =
            IREAD1(sc, ITHERM_ITV) * 1000000;
        itherm_bias_temperature_sensor(
            &sc->sensors[ITHERM_SENSOR_PCHTEMP_ABSOLUTE]);
}

void
itherm_bias_temperature_sensor(struct ksensor *sensor)
{
        if (sensor->value == 0 || sensor->value == 0xff)
                sensor->flags |= SENSOR_FINVALID;
        else
                sensor->flags &= ~SENSOR_FINVALID;

        /* Bias anyway from degC to degK, even if invalid */
        sensor->value += 273150000;
}

void
itherm_refresh(void *arg)
{
        struct itherm_softc *sc = (struct itherm_softc *)arg;

        itherm_refresh_sensor_data(sc);
}