root/sys/arch/arm64/dev/ampchwm.c
/*      $OpenBSD: ampchwm.c,v 1.1 2023/12/11 11:15:44 claudio Exp $ */
/*
 * Copyright (c) 2023 Claudio Jeker <claudio@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.
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/sensors.h>

#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/amltypes.h>

int     ampchwm_match(struct device *, void *, void *);
void    ampchwm_attach(struct device *, struct device *, void *);

#define HWMON_ID                        0x304d5748
#define HWMON_UNIT_CELSIUS              0x01
#define HWMON_UNIT_JOULES               0x10
#define HWMON_UNIT_MILIJOULES           0x11
#define HWMON_UNIT_MICROJOULES          0x12
#define HWMON_MAX_METRIC_COUNT          2

union metrics_hdr {
        uint64_t        data;
        struct {
                uint32_t        id;
                uint16_t        version;
                uint16_t        count;
        };
};

union metric_hdr {
        uint64_t        data[3];
        struct {
                char            label[16];
                uint8_t         unit;
                uint8_t         data_size;
                uint16_t        data_count;
                uint32_t        pad;
        };
};


struct ampchwm_softc {
        struct device                   sc_dev;
        struct acpi_softc               *sc_acpi;
        struct aml_node                 *sc_node;

        bus_space_tag_t                 sc_iot;
        bus_space_handle_t              sc_ioh;
        size_t                          sc_size;

        uint16_t                        sc_count;
        struct {
                struct ksensor                  *sc_sens;
                uint16_t                        sc_sens_offset;
                uint16_t                        sc_sens_count;
                uint16_t                        sc_sens_size;
                uint16_t                        sc_sens_unit;
        }                               sc_metrics[HWMON_MAX_METRIC_COUNT];

        struct ksensordev               sc_sensdev;
        struct sensor_task              *sc_sens_task;
};

const struct cfattach ampchwm_ca = {
        sizeof(struct ampchwm_softc), ampchwm_match, ampchwm_attach
};

struct cfdriver ampchwm_cd = {
         NULL, "ampchwm", DV_DULL
};

const char *ampchwm_hids[] = {
        "AMPC0005",
        NULL
};

int     ampchwm_attach_sensors(struct ampchwm_softc *, int,
            union metric_hdr *, uint16_t *);
void    ampchwm_refresh_sensors(void *);
void    ampchwm_update_sensor(struct ampchwm_softc *, int, int);


int
ampchwm_match(struct device *parent, void *match, void *aux)
{
        struct acpi_attach_args *aaa = aux;
        struct cfdata *cf = match;

        if (aaa->aaa_naddr < 1)
                return (0);
        return (acpi_matchhids(aaa, ampchwm_hids, cf->cf_driver->cd_name));
}

void
ampchwm_attach(struct device *parent, struct device *self, void *aux)
{
        struct ampchwm_softc    *sc = (struct ampchwm_softc *)self;
        struct acpi_attach_args *aaa = aux;
        union metrics_hdr hdr;
        union metric_hdr metric;
        uint16_t offset = 0;
        int i;

        sc->sc_acpi = (struct acpi_softc *)parent;
        sc->sc_node = aaa->aaa_node;

        printf(" %s", sc->sc_node->name);
        printf(" addr 0x%llx/0x%llx", aaa->aaa_addr[0], aaa->aaa_size[0]);

        sc->sc_iot = aaa->aaa_bst[0];
        sc->sc_size = aaa->aaa_size[0];

        if (bus_space_map(sc->sc_iot, aaa->aaa_addr[0], aaa->aaa_size[0],
            0, &sc->sc_ioh)) {
                printf(": can't map registers\n");
                return;
        }

        bus_space_read_region_8(sc->sc_iot, sc->sc_ioh, offset, &hdr.data, 1);

        if (hdr.id != HWMON_ID) {
                printf(": bad id %x\n", hdr.id);
                goto unmap;
        }

        printf(": ver %d", hdr.version);

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

        offset += sizeof(hdr);
        for (i = 0; i < hdr.count; i++) {
                bus_space_read_region_8(sc->sc_iot, sc->sc_ioh, offset,
                    metric.data, 3);
                if (ampchwm_attach_sensors(sc, i, &metric, &offset))
                        goto unmap;
        }
        sc->sc_count = MIN(hdr.count, HWMON_MAX_METRIC_COUNT);

        sensordev_install(&sc->sc_sensdev);
        sc->sc_sens_task = sensor_task_register(sc, ampchwm_refresh_sensors, 1);
        printf("\n");

        return;
unmap:
        bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_size);
        return;
}

int
ampchwm_attach_sensors(struct ampchwm_softc *sc, int num,
    union metric_hdr *metric, uint16_t *offsetp)
{
        uint16_t off = *offsetp;
        int i, count = 0;

        if (num >= HWMON_MAX_METRIC_COUNT) {
                if (num == HWMON_MAX_METRIC_COUNT)
                        printf(" ignoring extra metrics");
                return 0;
        }

        off += sizeof(*metric);
        /* skip 0 values since those are disabled cores */
        for (i = 0; i < metric->data_count; i++) {
                if (bus_space_read_8(sc->sc_iot, sc->sc_ioh,
                    off + i * 8) == 0)
                        continue;
                count++;
        }

        sc->sc_metrics[num].sc_sens = mallocarray(count,
            sizeof(struct ksensor), M_DEVBUF, M_NOWAIT);
        if (sc->sc_metrics[num].sc_sens == NULL) {
                printf(" out of memory\n");
                return -1;
        }

        sc->sc_metrics[num].sc_sens_offset = off;
        sc->sc_metrics[num].sc_sens_count = count;
        sc->sc_metrics[num].sc_sens_unit = metric->unit;
        if (metric->data_size == 0)
                sc->sc_metrics[num].sc_sens_size = 8;
        else
                sc->sc_metrics[num].sc_sens_size = 4;

        for (i = 0; i < count; i++) {
                struct ksensor *s = &sc->sc_metrics[num].sc_sens[i];

                strlcpy(s->desc, metric->label, sizeof(s->desc));
                if (metric->unit == HWMON_UNIT_CELSIUS)
                        s->type = SENSOR_TEMP;
                else
                        s->type = SENSOR_ENERGY;
                sensor_attach(&sc->sc_sensdev, s);

                ampchwm_update_sensor(sc, num, i);
        }

        off += metric->data_count * 8;

        printf(", %d \"%s\"", count, metric->label);
        *offsetp = off;
        return 0;
}

void
ampchwm_refresh_sensors(void *arg)
{
        struct ampchwm_softc *sc = arg;
        int num, i;

        for (num = 0; num < sc->sc_count; num++)
                for (i = 0; i < sc->sc_metrics[num].sc_sens_count; i++)
                        ampchwm_update_sensor(sc, num, i);
}

void
ampchwm_update_sensor(struct ampchwm_softc *sc, int num, int i)
{
        struct ksensor *s;
        uint64_t v;

        KASSERT(i < sc->sc_metrics[num].sc_sens_count);

        s = &sc->sc_metrics[num].sc_sens[i];
        if (sc->sc_metrics[num].sc_sens_size == 8) {
                v = bus_space_read_8(sc->sc_iot, sc->sc_ioh,
                    sc->sc_metrics[num].sc_sens_offset + i * sizeof(v));
        } else {
                v = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
                    sc->sc_metrics[num].sc_sens_offset + i * sizeof(v));
        }

        if (v == 0) {
                s->flags = SENSOR_FUNKNOWN;
                s->status = SENSOR_S_UNKNOWN;
        } else {
                s->flags = 0;
                s->status = SENSOR_S_OK;
        }

        switch (sc->sc_metrics[num].sc_sens_unit) {
        case HWMON_UNIT_CELSIUS:
                s->value = v * 1000 * 1000 + 273150000;
                break;
        case HWMON_UNIT_JOULES:
                v *= 1000;
        case HWMON_UNIT_MILIJOULES:
                v *= 1000;
        case HWMON_UNIT_MICROJOULES:
                s->value = v;
                break;
        }
}