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