1 /* $OpenBSD: pijuice.c,v 1.3 2022/10/25 19:32:18 mglocker Exp $ */ 2 3 /* 4 * Copyright (c) 2022 Marcus Glocker <mglocker@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/param.h> 20 #include <sys/systm.h> 21 #include <sys/device.h> 22 #include <sys/sensors.h> 23 24 #include <machine/apmvar.h> 25 26 #include <dev/i2c/i2cvar.h> 27 28 #include "apm.h" 29 30 #ifdef PIJUICE_DEBUG 31 #define DPRINTF(x) printf x 32 #else 33 #define DPRINTF(x) 34 #endif 35 36 /* I2C Status commands. */ 37 #define PIJUICE_CMD_STATUS 0x40 38 #define PIJUICE_CMD_FAULT_EVENT 0x44 39 #define PIJUICE_CMD_CHARGE_LEVEL 0x41 40 #define PIJUICE_CMD_BUTTON_EVENT 0x45 41 #define PIJUICE_CMD_BATTERY_TEMP 0x47 42 #define PIJUICE_CMD_BATTERY_VOLTAGE 0x49 43 #define PIJUICE_CMD_BATTERY_CURRENT 0x4b 44 #define PIJUICE_CMD_IO_VOLTAGE 0x4d 45 #define PIJUICE_CMD_IO_CURRENT 0x4f 46 #define PIJUICE_CMD_LED_STATE 0x66 47 #define PIJUICE_CMD_LED_BLINK 0x68 48 #define PIJUICE_CMD_IO_PIN_ACCESS 0x75 49 50 /* I2C Config commands. */ 51 #define PIJUICE_CMD_CHARGING_CONFIG 0x51 52 #define PIJUICE_CMD_BATTERY_PROFILE_ID 0x52 53 #define PIJUICE_CMD_BATTERY_PROFILE 0x53 54 #define PIJUICE_CMD_BATTERY_EXT_PROFILE 0x54 55 #define PIJUICE_CMD_BATTERY_TEMP_SENSE_CONFIG 0x5d 56 #define PIJUICE_CMD_POWER_INPUTS_CONFIG 0x5e 57 #define PIJUICE_CMD_RUN_PIN_CONFIG 0x5f 58 #define PIJUICE_CMD_POWER_REGULATOR_CONFIG 0x60 59 #define PIJUICE_CMD_LED_CONFIG 0x6a 60 #define PIJUICE_CMD_BUTTON_CONFIG 0x6e 61 #define PIJUICE_CMD_IO_CONFIG 0x72 62 #define PIJUICE_CMD_I2C_ADDRESS 0x7c 63 #define PIJUICE_CMD_ID_EEPROM_WRITE_PROTECT_CTRL 0x7e 64 #define PIJUICE_CMD_ID_EEPROM_ADDRESS 0x7f 65 #define PIJUICE_CMD_RESET_TO_DEFAULT 0xf0 66 #define PIJUICE_CMD_FIRMWARE_VERSION 0xfd 67 68 /* Sensors. */ 69 #define PIJUICE_NSENSORS 3 70 enum pijuice_sensors { 71 PIJUICE_SENSOR_CHARGE, /* 0 */ 72 PIJUICE_SENSOR_TEMP, /* 1 */ 73 PIJUICE_SENSOR_VOLTAGE, /* 2 */ 74 }; 75 76 struct pijuice_softc { 77 struct device sc_dev; 78 i2c_tag_t sc_tag; 79 int sc_addr; 80 81 struct ksensor sc_sensor[PIJUICE_NSENSORS]; 82 struct ksensordev sc_sensordev; 83 }; 84 85 struct pijuice_softc *pijuice_sc; 86 87 int pijuice_match(struct device *, void *, void *); 88 void pijuice_attach(struct device *, struct device *, void *); 89 int pijuice_read(struct pijuice_softc *, uint8_t *, uint8_t, 90 uint8_t *, uint8_t); 91 int pijuice_write(struct pijuice_softc *, uint8_t *, uint8_t); 92 int pijuice_get_fw_version(struct pijuice_softc *, const int, char *); 93 int pijuice_get_bcl(struct pijuice_softc *, uint8_t *); 94 int pijuice_get_status(struct pijuice_softc *, uint8_t *); 95 int pijuice_get_temp(struct pijuice_softc *sc, uint8_t *); 96 int pijuice_get_voltage(struct pijuice_softc *sc, uint16_t *); 97 void pijuice_refresh_sensors(void *); 98 int pijuice_apminfo(struct apm_power_info *); 99 100 const struct cfattach pijuice_ca = { 101 sizeof(struct pijuice_softc), pijuice_match, pijuice_attach 102 }; 103 104 struct cfdriver pijuice_cd = { 105 NULL, "pijuice", DV_DULL 106 }; 107 108 int 109 pijuice_match(struct device *parent, void *v, void *arg) 110 { 111 struct i2c_attach_args *ia = arg; 112 113 if (strcmp(ia->ia_name, "pisupply,pijuice") == 0) 114 return 1; 115 116 return 0; 117 } 118 119 void 120 pijuice_attach(struct device *parent, struct device *self, void *arg) 121 { 122 struct pijuice_softc *sc = (struct pijuice_softc *)self; 123 struct i2c_attach_args *ia = arg; 124 char fw_version[8]; 125 int i; 126 127 pijuice_sc = sc; 128 129 sc->sc_tag = ia->ia_tag; 130 sc->sc_addr = ia->ia_addr; 131 132 /* Setup sensor framework. */ 133 strlcpy(sc->sc_sensor[PIJUICE_SENSOR_CHARGE].desc, "battery charge", 134 sizeof(sc->sc_sensor[PIJUICE_SENSOR_CHARGE].desc)); 135 sc->sc_sensor[PIJUICE_SENSOR_CHARGE].type = SENSOR_PERCENT; 136 137 strlcpy(sc->sc_sensor[PIJUICE_SENSOR_TEMP].desc, "battery temperature", 138 sizeof(sc->sc_sensor[PIJUICE_SENSOR_TEMP].desc)); 139 sc->sc_sensor[PIJUICE_SENSOR_TEMP].type = SENSOR_TEMP; 140 141 strlcpy(sc->sc_sensor[PIJUICE_SENSOR_VOLTAGE].desc, "battery voltage", 142 sizeof(sc->sc_sensor[PIJUICE_SENSOR_VOLTAGE].desc)); 143 sc->sc_sensor[PIJUICE_SENSOR_VOLTAGE].type = SENSOR_VOLTS_DC; 144 145 strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname, 146 sizeof(sc->sc_sensordev.xname)); 147 for (i = 0; i < PIJUICE_NSENSORS; i++) 148 sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]); 149 sensordev_install(&sc->sc_sensordev); 150 151 if (sensor_task_register(sc, pijuice_refresh_sensors, 5) == NULL) { 152 printf(": unable to register update task\n"); 153 return; 154 } 155 156 /* Print device firmware version. */ 157 if (pijuice_get_fw_version(sc, sizeof(fw_version), fw_version) == -1) { 158 printf(": can't get firmware version\n"); 159 return; 160 } 161 printf(": firmware version %s\n", fw_version); 162 163 #if NAPM > 0 164 apm_setinfohook(pijuice_apminfo); 165 #endif 166 } 167 168 int 169 pijuice_read(struct pijuice_softc *sc, uint8_t *cmd, uint8_t cmd_len, 170 uint8_t *data, uint8_t data_len) 171 { 172 int error; 173 174 iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 175 error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, 176 cmd, cmd_len, data, data_len, I2C_F_POLL); 177 iic_release_bus(sc->sc_tag, I2C_F_POLL); 178 179 return error; 180 } 181 182 int 183 pijuice_write(struct pijuice_softc *sc, uint8_t *data, uint8_t data_len) 184 { 185 int error; 186 187 iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 188 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, 189 NULL, 0, data, data_len, I2C_F_POLL); 190 iic_release_bus(sc->sc_tag, I2C_F_POLL); 191 192 return error; 193 } 194 195 /* 196 * Get firmware version. 197 */ 198 int 199 pijuice_get_fw_version(struct pijuice_softc *sc, const int fw_version_size, 200 char *fw_version) 201 { 202 uint8_t cmd; 203 uint8_t data[2]; 204 uint8_t fw_version_minor, fw_version_major; 205 206 cmd = PIJUICE_CMD_FIRMWARE_VERSION; 207 memset(data, 0, sizeof(data)); 208 if (pijuice_read(sc, &cmd, sizeof(cmd), data, sizeof(data))) 209 return -1; 210 211 fw_version_major = data[0] >> 4; 212 fw_version_minor = (data[0] << 4 & 0xf0) >> 4; 213 snprintf(fw_version, fw_version_size, "%d.%d", 214 fw_version_major, fw_version_minor); 215 216 return 0; 217 } 218 219 /* 220 * Get battery charge level. 221 */ 222 int 223 pijuice_get_bcl(struct pijuice_softc *sc, uint8_t *bcl) 224 { 225 uint8_t cmd; 226 uint8_t data; 227 228 cmd = PIJUICE_CMD_CHARGE_LEVEL; 229 data = 0; 230 if (pijuice_read(sc, &cmd, sizeof(cmd), &data, sizeof(data))) 231 return -1; 232 233 *bcl = data; 234 235 return 0; 236 } 237 238 /* 239 * Get AC and Battery status. 240 * 241 */ 242 #define PIJUICE_STATUS_FAULT_MASK(status) ((status >> 0) & 0x01) 243 #define PIJUICE_STATUS_BUTTON_MASK(status) ((status >> 0) & 0x02) 244 #define PIJUICE_STATUS_BATT_MASK(status) ((status >> 2) & 0x03) 245 #define PIJUICE_STATUS_BATT_NORMAL 0 246 #define PIJUICE_STATUS_BATT_CHARGE_AC 1 247 #define PIJUICE_STATUS_BATT_CHARGE_5V 2 248 #define PIJUICE_STATUS_BATT_ABSENT 3 249 #define PIJUICE_STATUS_AC_MASK(status) ((status >> 4) & 0x03) 250 #define PIJUICE_STATUS_AC_ABSENT 0 251 #define PIJUICE_STATUS_AC_BAD 1 252 #define PIJUICE_STATUS_AC_WEAK 2 253 #define PIJUICE_STATUS_AC_PRESENT 3 254 #define PIJUICE_STATUS_AC_IN_MASK(status) ((status >> 6) & 0x03) 255 int 256 pijuice_get_status(struct pijuice_softc *sc, uint8_t *status) 257 { 258 uint8_t cmd; 259 uint8_t data; 260 261 cmd = PIJUICE_CMD_STATUS; 262 data = 0; 263 if (pijuice_read(sc, &cmd, sizeof(cmd), &data, sizeof(data))) 264 return -1; 265 266 *status = data; 267 268 return 0; 269 } 270 271 /* 272 * Get battery temperature. 273 */ 274 int 275 pijuice_get_temp(struct pijuice_softc *sc, uint8_t *temp) 276 { 277 uint8_t cmd; 278 uint8_t data[2]; 279 280 cmd = PIJUICE_CMD_BATTERY_TEMP; 281 memset(data, 0, sizeof(data)); 282 if (pijuice_read(sc, &cmd, sizeof(cmd), data, sizeof(data))) 283 return -1; 284 285 *temp = (uint8_t)data[0]; 286 if (data[0] & (1 << 7)) { 287 /* Minus degree. */ 288 *temp = *temp - (1 << 8); 289 } 290 291 return 0; 292 } 293 294 /* 295 * Get battery voltage. 296 */ 297 int 298 pijuice_get_voltage(struct pijuice_softc *sc, uint16_t *voltage) 299 { 300 uint8_t cmd; 301 uint8_t data[2]; 302 303 cmd = PIJUICE_CMD_BATTERY_VOLTAGE; 304 memset(data, 0, sizeof(data)); 305 if (pijuice_read(sc, &cmd, sizeof(cmd), data, sizeof(data))) 306 return -1; 307 308 *voltage = (uint16_t)(data[1] << 8) | data[0]; 309 310 return 0; 311 } 312 313 void 314 pijuice_refresh_sensors(void *arg) 315 { 316 struct pijuice_softc *sc = arg; 317 uint8_t val8; 318 uint16_t val16; 319 int i; 320 321 for (i = 0; i < PIJUICE_NSENSORS; i++) 322 sc->sc_sensor[i].flags |= SENSOR_FINVALID; 323 324 if (pijuice_get_bcl(sc, &val8) == 0) { 325 DPRINTF(("%s: Battery Charge Level=%d\n", __func__, val8)); 326 327 sc->sc_sensor[0].value = val8 * 1000; 328 sc->sc_sensor[0].flags &= ~SENSOR_FINVALID; 329 } 330 331 if (pijuice_get_temp(sc, &val8) == 0) { 332 DPRINTF(("%s: Battery Temperature=%d\n", __func__, val8)); 333 334 sc->sc_sensor[PIJUICE_SENSOR_TEMP].value = 335 273150000 + 1000000 * val8; 336 sc->sc_sensor[PIJUICE_SENSOR_TEMP].flags &= ~SENSOR_FINVALID; 337 } 338 339 if (pijuice_get_voltage(sc, &val16) == 0) { 340 DPRINTF(("%s: Battery Voltage=%d\n", __func__, val16)); 341 342 sc->sc_sensor[PIJUICE_SENSOR_VOLTAGE].value = val16 * 1000; 343 sc->sc_sensor[PIJUICE_SENSOR_VOLTAGE].flags &= ~SENSOR_FINVALID; 344 } 345 } 346 347 #if NAPM > 0 348 int 349 pijuice_apminfo(struct apm_power_info *info) 350 { 351 struct pijuice_softc *sc = pijuice_sc; 352 uint8_t val8; 353 354 info->battery_state = APM_BATT_UNKNOWN; 355 info->ac_state = APM_AC_UNKNOWN; 356 info->battery_life = 0; 357 info->minutes_left = -1; 358 359 if (pijuice_get_bcl(sc, &val8) == 0) { 360 DPRINTF(("%s: Battery Charge Level=%d\n", __func__, val8)); 361 362 info->battery_life = val8; 363 /* On "normal load" we suck 1% battery in 30 seconds. */ 364 info->minutes_left = (val8 * 30) / 60; 365 } 366 367 if (pijuice_get_status(sc, &val8) == 0) { 368 DPRINTF(("%s: Battery Status=%d\n", 369 __func__, PIJUICE_STATUS_BATT_MASK(val8))); 370 371 switch (PIJUICE_STATUS_BATT_MASK(val8)) { 372 case PIJUICE_STATUS_BATT_NORMAL: 373 if (info->battery_life > 50) 374 info->battery_state = APM_BATT_HIGH; 375 else if (info->battery_life > 25) 376 info->battery_state = APM_BATT_LOW; 377 else 378 info->battery_state = APM_BATT_CRITICAL; 379 break; 380 case PIJUICE_STATUS_BATT_CHARGE_AC: 381 case PIJUICE_STATUS_BATT_CHARGE_5V: 382 info->battery_state = APM_BATT_CHARGING; 383 info->minutes_left = 384 ((99 * 30) / 60) - info->minutes_left; 385 break; 386 case PIJUICE_STATUS_BATT_ABSENT: 387 info->battery_state = APM_BATTERY_ABSENT; 388 break; 389 } 390 391 DPRINTF(("%s: AC Status=%d\n", 392 __func__, PIJUICE_STATUS_AC_MASK(val8))); 393 394 switch (PIJUICE_STATUS_AC_MASK(val8)) { 395 case PIJUICE_STATUS_AC_ABSENT: 396 info->ac_state = APM_AC_OFF; 397 break; 398 case PIJUICE_STATUS_AC_BAD: 399 case PIJUICE_STATUS_AC_WEAK: 400 info->ac_state = APM_AC_BACKUP; 401 break; 402 case PIJUICE_STATUS_AC_PRESENT: 403 info->ac_state = APM_AC_ON; 404 break; 405 } 406 } 407 408 return 0; 409 } 410 #endif 411