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
pijuice_match(struct device * parent,void * v,void * arg)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
pijuice_attach(struct device * parent,struct device * self,void * arg)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
pijuice_read(struct pijuice_softc * sc,uint8_t * cmd,uint8_t cmd_len,uint8_t * data,uint8_t data_len)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
pijuice_write(struct pijuice_softc * sc,uint8_t * data,uint8_t data_len)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
pijuice_get_fw_version(struct pijuice_softc * sc,const int fw_version_size,char * fw_version)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
pijuice_get_bcl(struct pijuice_softc * sc,uint8_t * bcl)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
pijuice_get_status(struct pijuice_softc * sc,uint8_t * status)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
pijuice_get_temp(struct pijuice_softc * sc,uint8_t * temp)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
pijuice_get_voltage(struct pijuice_softc * sc,uint16_t * voltage)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
pijuice_refresh_sensors(void * arg)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
pijuice_apminfo(struct apm_power_info * info)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