xref: /openbsd-src/sys/dev/i2c/pijuice.c (revision 5b5a7f2d2890dd8c6563b7a877e3ed4adc26187d)
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