/* $OpenBSD: pijuice.c,v 1.3 2022/10/25 19:32:18 mglocker Exp $ */ /* * Copyright (c) 2022 Marcus Glocker * * 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 #include #include #include #include #include #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