1/* $NetBSD: cwfg.c,v 1.5 2021/11/07 17:14:38 jmcneill Exp $ */
2
3/*-
4 * Copyright (c) 2020 Jared McNeill <jmcneill@invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: cwfg.c,v 1.5 2021/11/07 17:14:38 jmcneill Exp $");
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/kernel.h>
35#include <sys/device.h>
36#include <sys/conf.h>
37#include <sys/bus.h>
38#include <sys/kmem.h>
39
40#include <dev/i2c/i2cvar.h>
41
42#include <dev/sysmon/sysmonvar.h>
43#include <dev/sysmon/sysmon_taskq.h>
44
45#include <dev/fdt/fdtvar.h>
46
47#define	VERSION_REG	0x00
48#define	VCELL_HI_REG	0x02
49#define	 VCELL_HI	__BITS(5,0)
50#define	VCELL_LO_REG	0x03
51#define	 VCELL_LO	__BITS(7,0)
52#define	SOC_HI_REG	0x04
53#define	SOC_LO_REG	0x05
54#define	RTT_ALRT_HI_REG	0x06
55#define	 RTT_ALRT	__BIT(7)
56#define	 RTT_HI		__BITS(4,0)
57#define	RTT_ALRT_LO_REG	0x07
58#define	 RTT_LO		__BITS(7,0)
59#define	CONFIG_REG	0x08
60#define	 CONFIG_ATHD	__BITS(7,3)
61#define	 CONFIG_UFG	__BIT(1)
62#define	MODE_REG	0x0a
63#define	 MODE_SLEEP	__BITS(7,6)
64#define	  MODE_SLEEP_WAKE	0x0
65#define	  MODE_SLEEP_SLEEP	0x3
66#define	 MODE_QSTRT	__BITS(5,4)
67#define	 MODE_POR	__BITS(3,0)
68#define	BATINFO_REG(n)	(0x10 + (n))
69
70#define	VCELL_STEP	312
71#define	VCELL_DIV	1024
72#define	BATINFO_SIZE	64
73#define	RESET_COUNT	30
74#define	RESET_DELAY	100000
75
76enum cwfg_sensor {
77	CWFG_SENSOR_VCELL,
78	CWFG_SENSOR_SOC,
79	CWFG_SENSOR_RTT,
80	CWFG_NSENSORS
81};
82
83struct cwfg_softc {
84	device_t	sc_dev;
85	i2c_tag_t	sc_i2c;
86	i2c_addr_t	sc_addr;
87	int		sc_phandle;
88
89	uint8_t		sc_batinfo[BATINFO_SIZE];
90
91	u_int		sc_alert_level;
92	u_int		sc_monitor_interval;
93	u_int		sc_design_capacity;
94
95	struct sysmon_envsys *sc_sme;
96
97	envsys_data_t	sc_sensor[CWFG_NSENSORS];
98};
99
100#define	CWFG_MONITOR_INTERVAL_DEFAULT	8
101#define	CWFG_DESIGN_CAPACITY_DEFAULT	2000
102#define	CWFG_ALERT_LEVEL_DEFAULT	0
103
104static const struct device_compatible_entry compat_data[] = {
105	{ .compat = "cellwise,cw2015" },
106	{ .compat = "cellwise,cw201x" },	/* DTCOMPAT */
107	DEVICE_COMPAT_EOL
108};
109
110static int
111cwfg_lock(struct cwfg_softc *sc)
112{
113	return iic_acquire_bus(sc->sc_i2c, 0);
114}
115
116static void
117cwfg_unlock(struct cwfg_softc *sc)
118{
119	iic_release_bus(sc->sc_i2c, 0);
120}
121
122static int
123cwfg_read(struct cwfg_softc *sc, uint8_t reg, uint8_t *val)
124{
125	return iic_smbus_read_byte(sc->sc_i2c, sc->sc_addr, reg, val, 0);
126}
127
128static int
129cwfg_write(struct cwfg_softc *sc, uint8_t reg, uint8_t val)
130{
131	return iic_smbus_write_byte(sc->sc_i2c, sc->sc_addr, reg, val, 0);
132}
133
134static void
135cwfg_sensor_refresh(struct sysmon_envsys *sme, envsys_data_t *e)
136{
137	struct cwfg_softc *sc = sme->sme_cookie;
138	u_int vcell, rtt, tmp;
139	uint8_t val;
140	int error, n;
141
142	e->state = ENVSYS_SINVALID;
143
144	if ((error = cwfg_lock(sc)) != 0)
145		return;
146
147	switch (e->private) {
148	case CWFG_SENSOR_VCELL:
149		/* Take the average of three readings */
150		vcell = 0;
151		for (n = 0; n < 3; n++) {
152			if ((error = cwfg_read(sc, VCELL_HI_REG, &val)) != 0)
153				goto done;
154			tmp = __SHIFTOUT(val, VCELL_HI) << 8;
155			if ((error = cwfg_read(sc, VCELL_LO_REG, &val)) != 0)
156				goto done;
157			tmp |= __SHIFTOUT(val, VCELL_LO);
158			vcell += tmp;
159		}
160		vcell /= 3;
161
162		e->state = ENVSYS_SVALID;
163		e->value_cur = ((vcell * VCELL_STEP) / VCELL_DIV) * 1000;
164		break;
165
166	case CWFG_SENSOR_SOC:
167		if ((error = cwfg_read(sc, SOC_HI_REG, &val)) != 0)
168			goto done;
169
170		if (val != 0xff) {
171			e->state = ENVSYS_SVALID;
172			e->value_cur = val;			/* batt % */
173		}
174		break;
175
176	case CWFG_SENSOR_RTT:
177		if ((error = cwfg_read(sc, RTT_ALRT_HI_REG, &val)) != 0)
178			goto done;
179		rtt = __SHIFTOUT(val, RTT_HI) << 8;
180		if ((error = cwfg_read(sc, RTT_ALRT_LO_REG, &val)) != 0)
181			goto done;
182		rtt |= __SHIFTOUT(val, RTT_LO);
183
184		if (rtt != 0x1fff) {
185			e->state = ENVSYS_SVALID;
186			e->value_cur = rtt;			/* minutes */
187		}
188		break;
189	}
190
191done:
192	cwfg_unlock(sc);
193}
194
195static void
196cwfg_attach_battery(struct cwfg_softc *sc)
197{
198	envsys_data_t *e;
199
200	/* Cell voltage */
201	e = &sc->sc_sensor[CWFG_SENSOR_VCELL];
202	e->private = CWFG_SENSOR_VCELL;
203	e->units = ENVSYS_SVOLTS_DC;
204	e->state = ENVSYS_SINVALID;
205	strlcpy(e->desc, "battery voltage", sizeof(e->desc));
206	sysmon_envsys_sensor_attach(sc->sc_sme, e);
207
208	/* State of charge */
209	e = &sc->sc_sensor[CWFG_SENSOR_SOC];
210	e->private = CWFG_SENSOR_SOC;
211	e->units = ENVSYS_INTEGER;
212	e->state = ENVSYS_SINVALID;
213	e->flags = ENVSYS_FPERCENT;
214	strlcpy(e->desc, "battery percent", sizeof(e->desc));
215	sysmon_envsys_sensor_attach(sc->sc_sme, e);
216
217	/* Remaining run time */
218	e = &sc->sc_sensor[CWFG_SENSOR_RTT];
219	e->private = CWFG_SENSOR_RTT;
220	e->units = ENVSYS_INTEGER;
221	e->state = ENVSYS_SINVALID;
222	strlcpy(e->desc, "battery remaining minutes", sizeof(e->desc));
223	sysmon_envsys_sensor_attach(sc->sc_sme, e);
224}
225
226static void
227cwfg_attach_sensors(struct cwfg_softc *sc)
228{
229	sc->sc_sme = sysmon_envsys_create();
230	sc->sc_sme->sme_name = device_xname(sc->sc_dev);
231	sc->sc_sme->sme_cookie = sc;
232	sc->sc_sme->sme_refresh = cwfg_sensor_refresh;
233	sc->sc_sme->sme_events_timeout = sc->sc_monitor_interval;
234	sc->sc_sme->sme_class = SME_CLASS_BATTERY;
235	sc->sc_sme->sme_flags = SME_INIT_REFRESH;
236
237	cwfg_attach_battery(sc);
238
239	sysmon_envsys_register(sc->sc_sme);
240}
241
242static int
243cwfg_set_config(struct cwfg_softc *sc)
244{
245	u_int alert_level;
246	bool need_update;
247	uint8_t config, mode, val;
248	int error, n;
249
250	/* Read current config */
251	if ((error = cwfg_read(sc, CONFIG_REG, &config)) != 0)
252		return error;
253
254	/* Update alert level, if necessary */
255	alert_level = __SHIFTOUT(config, CONFIG_ATHD);
256	if (alert_level != sc->sc_alert_level) {
257		config &= ~CONFIG_ATHD;
258		config |= __SHIFTIN(sc->sc_alert_level, CONFIG_ATHD);
259		if ((error = cwfg_write(sc, CONFIG_REG, config)) != 0)
260			return error;
261	}
262
263	/* Re-read current config */
264	if ((error = cwfg_read(sc, CONFIG_REG, &config)) != 0)
265		return error;
266
267	/*
268	 * We need to upload a battery profile if either the UFG flag
269	 * is unset, or the current battery profile differs from the
270	 * one in the DT.
271	 */
272	need_update = (config & CONFIG_UFG) == 0;
273	if (need_update == false) {
274		for (n = 0; n < BATINFO_SIZE; n++) {
275			if ((error = cwfg_read(sc, BATINFO_REG(n), &val)) != 0)
276				return error;
277			if (sc->sc_batinfo[n] != val) {
278				need_update = true;
279				break;
280			}
281		}
282	}
283	if (need_update == false)
284		return 0;
285
286	aprint_verbose_dev(sc->sc_dev, "updating battery profile\n");
287
288	/* Update battery profile */
289	for (n = 0; n < BATINFO_SIZE; n++) {
290		val = sc->sc_batinfo[n];
291		if ((error = cwfg_write(sc, BATINFO_REG(n), val)) != 0)
292			return error;
293	}
294
295	/* Set UFG flag to switch to new profile */
296	if ((error = cwfg_read(sc, CONFIG_REG, &config)) != 0)
297		return error;
298	config |= CONFIG_UFG;
299	if ((error = cwfg_write(sc, CONFIG_REG, config)) != 0)
300		return error;
301
302	/* Restart the IC with new profile */
303	if ((error = cwfg_read(sc, MODE_REG, &mode)) != 0)
304		return error;
305	mode |= MODE_POR;
306	if ((error = cwfg_write(sc, MODE_REG, mode)) != 0)
307		return error;
308	delay(20000);
309	mode &= ~MODE_POR;
310	if ((error = cwfg_write(sc, MODE_REG, mode)) != 0)
311		return error;
312
313	return error;
314}
315
316static int
317cwfg_init(struct cwfg_softc *sc)
318{
319	uint8_t mode, soc;
320	int error, retry;
321
322	cwfg_lock(sc);
323
324	/* If the device is in sleep mode, wake it up */
325	if ((error = cwfg_read(sc, MODE_REG, &mode)) != 0)
326		goto done;
327	if (__SHIFTOUT(mode, MODE_SLEEP) == MODE_SLEEP_SLEEP) {
328		mode &= ~MODE_SLEEP;
329		mode |= __SHIFTIN(MODE_SLEEP_WAKE, MODE_SLEEP);
330		if ((error = cwfg_write(sc, MODE_REG, mode)) != 0)
331			goto done;
332	}
333
334	/* Load battery profile */
335	if ((error = cwfg_set_config(sc)) != 0)
336		goto done;
337
338	/* Wait for chip to become ready */
339	for (retry = RESET_COUNT; retry > 0; retry--) {
340		if ((error = cwfg_read(sc, SOC_HI_REG, &soc)) != 0)
341			goto done;
342		if (soc != 0xff)
343			break;
344		delay(RESET_DELAY);
345	}
346	if (retry == 0) {
347		aprint_error_dev(sc->sc_dev,
348		    "WARNING: timeout waiting for chip ready\n");
349	}
350
351done:
352	cwfg_unlock(sc);
353
354	return error;
355}
356
357static int
358cwfg_parse_resources(struct cwfg_softc *sc)
359{
360	const u_int *batinfo;
361	u_int val;
362	int len = 0, n;
363
364	batinfo = fdtbus_get_prop(sc->sc_phandle,
365	    "cellwise,battery-profile", &len);
366	if (batinfo == NULL) {
367		/* DTCOMPAT */
368		batinfo = fdtbus_get_prop(sc->sc_phandle,
369		    "cellwise,bat-config-info", &len);
370	}
371	switch (len) {
372	case BATINFO_SIZE:
373		memcpy(sc->sc_batinfo, batinfo, BATINFO_SIZE);
374		break;
375	case BATINFO_SIZE * 4:
376		for (n = 0; n < BATINFO_SIZE; n++)
377			sc->sc_batinfo[n] = be32toh(batinfo[n]);
378		break;
379	default:
380		aprint_error_dev(sc->sc_dev,
381		    "missing or invalid battery info\n");
382		return EINVAL;
383	}
384
385	if (of_getprop_uint32(sc->sc_phandle,
386	    "cellwise,monitor-interval-ms", &val) == 0) {
387		sc->sc_monitor_interval = howmany(val, 1000);
388	} else if (of_getprop_uint32(sc->sc_phandle,
389	    "cellwise,monitor-interval", &val) == 0) {
390		/* DTCOMPAT */
391		sc->sc_monitor_interval = val;
392	} else {
393		sc->sc_monitor_interval = CWFG_MONITOR_INTERVAL_DEFAULT;
394	}
395
396	const int bphandle = fdtbus_get_phandle(sc->sc_phandle, "monitored-battery");
397	if (bphandle != -1 && of_getprop_uint32(bphandle,
398	    "charge-full-design-microamp-hours", &val) == 0) {
399		sc->sc_design_capacity = howmany(val, 1000);
400	} else if (of_getprop_uint32(sc->sc_phandle,
401	    "cellwise,design-capacity", &val) == 0) {
402		/* DTCOMPAT */
403		sc->sc_design_capacity = val;
404	} else {
405		sc->sc_design_capacity = CWFG_DESIGN_CAPACITY_DEFAULT;
406	}
407
408	if (of_getprop_uint32(sc->sc_phandle,
409	    "cellwise,alert-level", &sc->sc_alert_level) != 0) {
410		sc->sc_alert_level = CWFG_ALERT_LEVEL_DEFAULT;
411	}
412
413	return 0;
414}
415
416static int
417cwfg_match(device_t parent, cfdata_t match, void *aux)
418{
419	struct i2c_attach_args *ia = aux;
420	int match_result;
421
422	if (iic_use_direct_match(ia, match, compat_data, &match_result))
423		return match_result;
424
425	/* This device is direct-config only. */
426
427	return 0;
428}
429
430static void
431cwfg_attach(device_t parent, device_t self, void *aux)
432{
433	struct cwfg_softc *sc = device_private(self);
434	struct i2c_attach_args *ia = aux;
435	uint8_t ver;
436	int error;
437
438	sc->sc_dev = self;
439	sc->sc_i2c = ia->ia_tag;
440	sc->sc_addr = ia->ia_addr;
441	sc->sc_phandle = ia->ia_cookie;
442
443	cwfg_lock(sc);
444	error = cwfg_read(sc, VERSION_REG, &ver);
445	cwfg_unlock(sc);
446
447	if (error != 0) {
448		aprint_error(": device not responding, error = %d\n", error);
449		return;
450	}
451
452	aprint_naive("\n");
453	aprint_normal(": CellWise CW2015 Fuel Gauge IC (ver. 0x%02x)\n", ver);
454
455	if (cwfg_parse_resources(sc) != 0) {
456		aprint_error_dev(self, "failed to parse resources\n");
457		return;
458	}
459
460	if (cwfg_init(sc) != 0) {
461		aprint_error_dev(self, "failed to initialize device\n");
462		return;
463	}
464
465	cwfg_attach_sensors(sc);
466}
467
468CFATTACH_DECL_NEW(cwfg, sizeof(struct cwfg_softc),
469    cwfg_match, cwfg_attach, NULL, NULL);
470