summaryrefslogtreecommitdiff
path: root/sys/dev/i2c/pijuice.c
diff options
context:
space:
mode:
authorMarcus Glocker <mglocker@cvs.openbsd.org>2022-10-23 18:43:01 +0000
committerMarcus Glocker <mglocker@cvs.openbsd.org>2022-10-23 18:43:01 +0000
commit935ceb8db01984dfeab3d412a66d9039ed084e4c (patch)
tree6470a40dc62053ff73d1676e648336d9c205c7af /sys/dev/i2c/pijuice.c
parent62dd8b8849b1bcf681f61c025353af18aa55ff8b (diff)
Initial apm/sensor driver for the PiJuice HAT UPS, to feedback battery
status information. ok deraadt@
Diffstat (limited to 'sys/dev/i2c/pijuice.c')
-rw-r--r--sys/dev/i2c/pijuice.c408
1 files changed, 408 insertions, 0 deletions
diff --git a/sys/dev/i2c/pijuice.c b/sys/dev/i2c/pijuice.c
new file mode 100644
index 00000000000..393e7f5bca4
--- /dev/null
+++ b/sys/dev/i2c/pijuice.c
@@ -0,0 +1,408 @@
+/* $OpenBSD: pijuice.c,v 1.1 2022/10/23 18:43:00 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 sconds. */
+ 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;
+ 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