root/sys/dev/i2c/pijuice.c
/*      $OpenBSD: pijuice.c,v 1.3 2022/10/25 19:32:18 mglocker Exp $ */

/*
 * Copyright (c) 2022 Marcus Glocker <mglocker@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/device.h>
#include <sys/sensors.h>

#include <machine/apmvar.h>

#include <dev/i2c/i2cvar.h>

#include "apm.h"

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

/* I2C Status commands. */
#define PIJUICE_CMD_STATUS                              0x40
#define PIJUICE_CMD_FAULT_EVENT                         0x44
#define PIJUICE_CMD_CHARGE_LEVEL                        0x41
#define PIJUICE_CMD_BUTTON_EVENT                        0x45
#define PIJUICE_CMD_BATTERY_TEMP                        0x47
#define PIJUICE_CMD_BATTERY_VOLTAGE                     0x49
#define PIJUICE_CMD_BATTERY_CURRENT                     0x4b
#define PIJUICE_CMD_IO_VOLTAGE                          0x4d
#define PIJUICE_CMD_IO_CURRENT                          0x4f
#define PIJUICE_CMD_LED_STATE                           0x66
#define PIJUICE_CMD_LED_BLINK                           0x68
#define PIJUICE_CMD_IO_PIN_ACCESS                       0x75

/* I2C Config commands. */
#define PIJUICE_CMD_CHARGING_CONFIG                     0x51
#define PIJUICE_CMD_BATTERY_PROFILE_ID                  0x52
#define PIJUICE_CMD_BATTERY_PROFILE                     0x53
#define PIJUICE_CMD_BATTERY_EXT_PROFILE                 0x54
#define PIJUICE_CMD_BATTERY_TEMP_SENSE_CONFIG           0x5d
#define PIJUICE_CMD_POWER_INPUTS_CONFIG                 0x5e
#define PIJUICE_CMD_RUN_PIN_CONFIG                      0x5f
#define PIJUICE_CMD_POWER_REGULATOR_CONFIG              0x60
#define PIJUICE_CMD_LED_CONFIG                          0x6a
#define PIJUICE_CMD_BUTTON_CONFIG                       0x6e
#define PIJUICE_CMD_IO_CONFIG                           0x72
#define PIJUICE_CMD_I2C_ADDRESS                         0x7c
#define PIJUICE_CMD_ID_EEPROM_WRITE_PROTECT_CTRL        0x7e
#define PIJUICE_CMD_ID_EEPROM_ADDRESS                   0x7f
#define PIJUICE_CMD_RESET_TO_DEFAULT                    0xf0
#define PIJUICE_CMD_FIRMWARE_VERSION                    0xfd

/* Sensors. */
#define PIJUICE_NSENSORS        3
enum pijuice_sensors {
        PIJUICE_SENSOR_CHARGE,  /* 0 */
        PIJUICE_SENSOR_TEMP,    /* 1 */
        PIJUICE_SENSOR_VOLTAGE, /* 2 */
};

struct pijuice_softc {
        struct device           sc_dev;
        i2c_tag_t               sc_tag;
        int                     sc_addr;

        struct ksensor          sc_sensor[PIJUICE_NSENSORS];
        struct ksensordev       sc_sensordev;
};

struct pijuice_softc *pijuice_sc;

int     pijuice_match(struct device *, void *, void *);
void    pijuice_attach(struct device *, struct device *, void *);
int     pijuice_read(struct pijuice_softc *, uint8_t *, uint8_t,
            uint8_t *, uint8_t);
int     pijuice_write(struct pijuice_softc *, uint8_t *, uint8_t);
int     pijuice_get_fw_version(struct pijuice_softc *, const int, char *);
int     pijuice_get_bcl(struct pijuice_softc *, uint8_t *);
int     pijuice_get_status(struct pijuice_softc *, uint8_t *);
int     pijuice_get_temp(struct pijuice_softc *sc, uint8_t *);
int     pijuice_get_voltage(struct pijuice_softc *sc, uint16_t *);
void    pijuice_refresh_sensors(void *);
int     pijuice_apminfo(struct apm_power_info *);

const struct cfattach pijuice_ca = {
        sizeof(struct pijuice_softc), pijuice_match, pijuice_attach
};

struct cfdriver pijuice_cd = {
        NULL, "pijuice", DV_DULL
};

int
pijuice_match(struct device *parent, void *v, void *arg)
{
        struct i2c_attach_args *ia = arg;

        if (strcmp(ia->ia_name, "pisupply,pijuice") == 0)
                return 1;

        return 0;
}

void
pijuice_attach(struct device *parent, struct device *self, void *arg)
{
        struct pijuice_softc *sc = (struct pijuice_softc *)self;
        struct i2c_attach_args *ia = arg;
        char fw_version[8];
        int i;

        pijuice_sc = sc;

        sc->sc_tag = ia->ia_tag;
        sc->sc_addr = ia->ia_addr;

        /* Setup sensor framework. */
        strlcpy(sc->sc_sensor[PIJUICE_SENSOR_CHARGE].desc, "battery charge",
            sizeof(sc->sc_sensor[PIJUICE_SENSOR_CHARGE].desc));
        sc->sc_sensor[PIJUICE_SENSOR_CHARGE].type = SENSOR_PERCENT;

        strlcpy(sc->sc_sensor[PIJUICE_SENSOR_TEMP].desc, "battery temperature",
            sizeof(sc->sc_sensor[PIJUICE_SENSOR_TEMP].desc));
        sc->sc_sensor[PIJUICE_SENSOR_TEMP].type = SENSOR_TEMP;

        strlcpy(sc->sc_sensor[PIJUICE_SENSOR_VOLTAGE].desc, "battery voltage",
            sizeof(sc->sc_sensor[PIJUICE_SENSOR_VOLTAGE].desc));
        sc->sc_sensor[PIJUICE_SENSOR_VOLTAGE].type = SENSOR_VOLTS_DC;

        strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
            sizeof(sc->sc_sensordev.xname));
        for (i = 0; i < PIJUICE_NSENSORS; i++)
                sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
        sensordev_install(&sc->sc_sensordev);

        if (sensor_task_register(sc, pijuice_refresh_sensors, 5) == NULL) {
                printf(": unable to register update task\n");
                return;
        }

        /* Print device firmware version. */
        if (pijuice_get_fw_version(sc, sizeof(fw_version), fw_version) == -1) {
                printf(": can't get firmware version\n");
                return;
        }
        printf(": firmware version %s\n", fw_version);

#if NAPM > 0
        apm_setinfohook(pijuice_apminfo);
#endif
}

int
pijuice_read(struct pijuice_softc *sc, uint8_t *cmd, uint8_t cmd_len,
    uint8_t *data, uint8_t data_len)
{
        int error;

        iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
        error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
            cmd, cmd_len, data, data_len, I2C_F_POLL);
        iic_release_bus(sc->sc_tag, I2C_F_POLL);

        return error;
}

int
pijuice_write(struct pijuice_softc *sc, uint8_t *data, uint8_t data_len)
{
        int error;

        iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
        error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
            NULL, 0, data, data_len, I2C_F_POLL);
        iic_release_bus(sc->sc_tag, I2C_F_POLL);

        return error;
}

/*
 * Get firmware version.
 */
int
pijuice_get_fw_version(struct pijuice_softc *sc, const int fw_version_size,
    char *fw_version)
{
        uint8_t cmd;
        uint8_t data[2];
        uint8_t fw_version_minor, fw_version_major;

        cmd = PIJUICE_CMD_FIRMWARE_VERSION;
        memset(data, 0, sizeof(data));
        if (pijuice_read(sc, &cmd, sizeof(cmd), data, sizeof(data)))
                return -1;

        fw_version_major = data[0] >> 4;
        fw_version_minor = (data[0] << 4 & 0xf0) >> 4;
        snprintf(fw_version, fw_version_size, "%d.%d",
            fw_version_major, fw_version_minor);

        return 0;
}

/*
 * Get battery charge level.
 */
int
pijuice_get_bcl(struct pijuice_softc *sc, uint8_t *bcl)
{
        uint8_t cmd;
        uint8_t data;

        cmd = PIJUICE_CMD_CHARGE_LEVEL;
        data = 0;
        if (pijuice_read(sc, &cmd, sizeof(cmd), &data, sizeof(data)))
                return -1;

        *bcl = data;

        return 0;
}

/*
 * Get AC and Battery status.
 *
 */
#define PIJUICE_STATUS_FAULT_MASK(status)       ((status >> 0) & 0x01)
#define PIJUICE_STATUS_BUTTON_MASK(status)      ((status >> 0) & 0x02)
#define PIJUICE_STATUS_BATT_MASK(status)        ((status >> 2) & 0x03)
#define   PIJUICE_STATUS_BATT_NORMAL            0
#define   PIJUICE_STATUS_BATT_CHARGE_AC         1
#define   PIJUICE_STATUS_BATT_CHARGE_5V         2
#define   PIJUICE_STATUS_BATT_ABSENT            3
#define PIJUICE_STATUS_AC_MASK(status)          ((status >> 4) & 0x03)
#define   PIJUICE_STATUS_AC_ABSENT              0
#define   PIJUICE_STATUS_AC_BAD                 1
#define   PIJUICE_STATUS_AC_WEAK                2
#define   PIJUICE_STATUS_AC_PRESENT             3
#define PIJUICE_STATUS_AC_IN_MASK(status)       ((status >> 6) & 0x03)
int
pijuice_get_status(struct pijuice_softc *sc, uint8_t *status)
{
        uint8_t cmd;
        uint8_t data;

        cmd = PIJUICE_CMD_STATUS;
        data = 0;
        if (pijuice_read(sc, &cmd, sizeof(cmd), &data, sizeof(data)))
                return -1;

        *status = data;

        return 0;
}

/*
 * Get battery temperature.
 */
int
pijuice_get_temp(struct pijuice_softc *sc, uint8_t *temp)
{
        uint8_t cmd;
        uint8_t data[2];

        cmd = PIJUICE_CMD_BATTERY_TEMP;
        memset(data, 0, sizeof(data));
        if (pijuice_read(sc, &cmd, sizeof(cmd), data, sizeof(data)))
                return -1;

        *temp = (uint8_t)data[0];
        if (data[0] & (1 << 7)) {
                /* Minus degree. */
                *temp = *temp - (1 << 8);
        }

        return 0;
}

/*
 * Get battery voltage.
 */
int
pijuice_get_voltage(struct pijuice_softc *sc, uint16_t *voltage)
{
        uint8_t cmd;
        uint8_t data[2];

        cmd = PIJUICE_CMD_BATTERY_VOLTAGE;
        memset(data, 0, sizeof(data));
        if (pijuice_read(sc, &cmd, sizeof(cmd), data, sizeof(data)))
                return -1;

        *voltage = (uint16_t)(data[1] << 8) | data[0];

        return 0;
}

void
pijuice_refresh_sensors(void *arg)
{
        struct pijuice_softc *sc = arg;
        uint8_t val8;
        uint16_t val16;
        int i;

        for (i = 0; i < PIJUICE_NSENSORS; i++)
                sc->sc_sensor[i].flags |= SENSOR_FINVALID;

        if (pijuice_get_bcl(sc, &val8) == 0) {
                DPRINTF(("%s: Battery Charge Level=%d\n", __func__, val8));

                sc->sc_sensor[0].value = val8 * 1000;
                sc->sc_sensor[0].flags &= ~SENSOR_FINVALID;
        }

        if (pijuice_get_temp(sc, &val8) == 0) {
                DPRINTF(("%s: Battery Temperature=%d\n", __func__, val8));

                sc->sc_sensor[PIJUICE_SENSOR_TEMP].value =
                    273150000 + 1000000 * val8;
                sc->sc_sensor[PIJUICE_SENSOR_TEMP].flags &= ~SENSOR_FINVALID;
        }

        if (pijuice_get_voltage(sc, &val16) == 0) {
                DPRINTF(("%s: Battery Voltage=%d\n", __func__, val16));

                sc->sc_sensor[PIJUICE_SENSOR_VOLTAGE].value = val16 * 1000;
                sc->sc_sensor[PIJUICE_SENSOR_VOLTAGE].flags &= ~SENSOR_FINVALID;
        }
}

#if NAPM > 0
int
pijuice_apminfo(struct apm_power_info *info)
{
        struct pijuice_softc *sc = pijuice_sc;
        uint8_t val8;

        info->battery_state = APM_BATT_UNKNOWN;
        info->ac_state = APM_AC_UNKNOWN;
        info->battery_life = 0;
        info->minutes_left = -1;

        if (pijuice_get_bcl(sc, &val8) == 0) {
                DPRINTF(("%s: Battery Charge Level=%d\n", __func__, val8));

                info->battery_life = val8;
                /* On "normal load" we suck 1% battery in 30 seconds. */
                info->minutes_left = (val8 * 30) / 60;
        }

        if (pijuice_get_status(sc, &val8) == 0) {
                DPRINTF(("%s: Battery Status=%d\n",
                    __func__, PIJUICE_STATUS_BATT_MASK(val8)));

                switch (PIJUICE_STATUS_BATT_MASK(val8)) {
                case PIJUICE_STATUS_BATT_NORMAL:
                        if (info->battery_life > 50)
                                info->battery_state = APM_BATT_HIGH;
                        else if (info->battery_life > 25)
                                info->battery_state = APM_BATT_LOW;
                        else
                                info->battery_state = APM_BATT_CRITICAL;
                        break;
                case PIJUICE_STATUS_BATT_CHARGE_AC:
                case PIJUICE_STATUS_BATT_CHARGE_5V:
                        info->battery_state = APM_BATT_CHARGING;
                        info->minutes_left =
                            ((99 * 30) / 60) - info->minutes_left;
                        break;
                case PIJUICE_STATUS_BATT_ABSENT:
                        info->battery_state = APM_BATTERY_ABSENT;
                        break;
                }

                DPRINTF(("%s: AC Status=%d\n",
                    __func__, PIJUICE_STATUS_AC_MASK(val8)));

                switch (PIJUICE_STATUS_AC_MASK(val8)) {
                case PIJUICE_STATUS_AC_ABSENT:
                        info->ac_state = APM_AC_OFF;
                        break;
                case PIJUICE_STATUS_AC_BAD:
                case PIJUICE_STATUS_AC_WEAK:
                        info->ac_state = APM_AC_BACKUP;
                        break;
                case PIJUICE_STATUS_AC_PRESENT:
                        info->ac_state = APM_AC_ON;
                        break;
                }
        }

        return 0;
}
#endif