1 /* $NetBSD: cwfg.c,v 1.1 2020/01/03 18:00:05 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.1 2020/01/03 18:00:05 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 76 enum cwfg_sensor { 77 CWFG_SENSOR_VCELL, 78 CWFG_SENSOR_SOC, 79 CWFG_SENSOR_RTT, 80 CWFG_NSENSORS 81 }; 82 83 struct 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 104 static const struct device_compatible_entry compat_data[] = { 105 { "cellwise,cw201x", 1 }, 106 { NULL, 0 } 107 }; 108 109 static int 110 cwfg_lock(struct cwfg_softc *sc) 111 { 112 return iic_acquire_bus(sc->sc_i2c, 0); 113 } 114 115 static void 116 cwfg_unlock(struct cwfg_softc *sc) 117 { 118 iic_release_bus(sc->sc_i2c, 0); 119 } 120 121 static int 122 cwfg_read(struct cwfg_softc *sc, uint8_t reg, uint8_t *val) 123 { 124 return iic_smbus_read_byte(sc->sc_i2c, sc->sc_addr, reg, val, 0); 125 } 126 127 static int 128 cwfg_write(struct cwfg_softc *sc, uint8_t reg, uint8_t val) 129 { 130 return iic_smbus_write_byte(sc->sc_i2c, sc->sc_addr, reg, val, 0); 131 } 132 133 static void 134 cwfg_sensor_refresh(struct sysmon_envsys *sme, envsys_data_t *e) 135 { 136 struct cwfg_softc *sc = sme->sme_cookie; 137 u_int vcell, rtt, tmp; 138 uint8_t val; 139 int error, n; 140 141 e->state = ENVSYS_SINVALID; 142 143 if ((error = cwfg_lock(sc)) != 0) 144 return; 145 146 switch (e->private) { 147 case CWFG_SENSOR_VCELL: 148 /* Take the average of three readings */ 149 vcell = 0; 150 for (n = 0; n < 3; n++) { 151 if ((error = cwfg_read(sc, VCELL_HI_REG, &val)) != 0) 152 goto done; 153 tmp = __SHIFTOUT(val, VCELL_HI) << 8; 154 if ((error = cwfg_read(sc, VCELL_LO_REG, &val)) != 0) 155 goto done; 156 tmp |= __SHIFTOUT(val, VCELL_LO); 157 vcell += tmp; 158 } 159 vcell /= 3; 160 161 e->state = ENVSYS_SVALID; 162 e->value_cur = ((vcell * VCELL_STEP) / VCELL_DIV) * 1000; 163 break; 164 165 case CWFG_SENSOR_SOC: 166 if ((error = cwfg_read(sc, SOC_HI_REG, &val)) != 0) 167 goto done; 168 169 if (val != 0xff) { 170 e->state = ENVSYS_SVALID; 171 e->value_cur = val; /* batt % */ 172 } 173 break; 174 175 case CWFG_SENSOR_RTT: 176 if ((error = cwfg_read(sc, RTT_ALRT_HI_REG, &val)) != 0) 177 goto done; 178 rtt = __SHIFTOUT(val, RTT_HI) << 8; 179 if ((error = cwfg_read(sc, RTT_ALRT_LO_REG, &val)) != 0) 180 goto done; 181 rtt |= __SHIFTOUT(val, RTT_LO); 182 183 if (rtt != 0x1fff) { 184 e->state = ENVSYS_SVALID; 185 e->value_cur = rtt; /* minutes */ 186 } 187 break; 188 } 189 190 done: 191 cwfg_unlock(sc); 192 } 193 194 static void 195 cwfg_attach_battery(struct cwfg_softc *sc) 196 { 197 envsys_data_t *e; 198 199 /* Cell voltage */ 200 e = &sc->sc_sensor[CWFG_SENSOR_VCELL]; 201 e->private = CWFG_SENSOR_VCELL; 202 e->units = ENVSYS_SVOLTS_DC; 203 e->state = ENVSYS_SINVALID; 204 strlcpy(e->desc, "battery voltage", sizeof(e->desc)); 205 sysmon_envsys_sensor_attach(sc->sc_sme, e); 206 207 /* State of charge */ 208 e = &sc->sc_sensor[CWFG_SENSOR_SOC]; 209 e->private = CWFG_SENSOR_SOC; 210 e->units = ENVSYS_INTEGER; 211 e->state = ENVSYS_SINVALID; 212 e->flags = ENVSYS_FPERCENT; 213 strlcpy(e->desc, "battery percent", sizeof(e->desc)); 214 sysmon_envsys_sensor_attach(sc->sc_sme, e); 215 216 /* Remaining run time */ 217 e = &sc->sc_sensor[CWFG_SENSOR_RTT]; 218 e->private = CWFG_SENSOR_RTT; 219 e->units = ENVSYS_INTEGER; 220 e->state = ENVSYS_SINVALID; 221 strlcpy(e->desc, "battery remaining minutes", sizeof(e->desc)); 222 sysmon_envsys_sensor_attach(sc->sc_sme, e); 223 } 224 225 static void 226 cwfg_attach_sensors(struct cwfg_softc *sc) 227 { 228 sc->sc_sme = sysmon_envsys_create(); 229 sc->sc_sme->sme_name = device_xname(sc->sc_dev); 230 sc->sc_sme->sme_cookie = sc; 231 sc->sc_sme->sme_refresh = cwfg_sensor_refresh; 232 sc->sc_sme->sme_events_timeout = sc->sc_monitor_interval; 233 sc->sc_sme->sme_class = SME_CLASS_BATTERY; 234 sc->sc_sme->sme_flags = SME_INIT_REFRESH; 235 236 cwfg_attach_battery(sc); 237 238 sysmon_envsys_register(sc->sc_sme); 239 } 240 241 static int 242 cwfg_set_config(struct cwfg_softc *sc) 243 { 244 u_int alert_level; 245 bool need_update; 246 uint8_t config, mode, val; 247 int error, n; 248 249 /* Read current config */ 250 if ((error = cwfg_read(sc, CONFIG_REG, &config)) != 0) 251 return error; 252 253 /* Update alert level, if necessary */ 254 alert_level = __SHIFTOUT(config, CONFIG_ATHD); 255 if (alert_level != sc->sc_alert_level) { 256 config &= ~CONFIG_ATHD; 257 config |= __SHIFTIN(sc->sc_alert_level, CONFIG_ATHD); 258 if ((error = cwfg_write(sc, CONFIG_REG, config)) != 0) 259 return error; 260 } 261 262 /* Re-read current config */ 263 if ((error = cwfg_read(sc, CONFIG_REG, &config)) != 0) 264 return error; 265 266 /* 267 * We need to upload a battery profile if either the UFG flag 268 * is unset, or the current battery profile differs from the 269 * one in the DT. 270 */ 271 need_update = (config & CONFIG_UFG) == 0; 272 if (need_update == false) { 273 for (n = 0; n < BATINFO_SIZE; n++) { 274 if ((error = cwfg_read(sc, BATINFO_REG(n), &val)) != 0) 275 return error; 276 if (sc->sc_batinfo[n] != val) { 277 need_update = true; 278 break; 279 } 280 } 281 } 282 if (need_update == false) 283 return 0; 284 285 aprint_verbose_dev(sc->sc_dev, "updating battery profile\n"); 286 287 /* Update battery profile */ 288 for (n = 0; n < BATINFO_SIZE; n++) { 289 val = sc->sc_batinfo[n]; 290 if ((error = cwfg_write(sc, BATINFO_REG(n), val)) != 0) 291 return error; 292 } 293 294 /* Set UFG flag to switch to new profile */ 295 if ((error = cwfg_read(sc, CONFIG_REG, &config)) != 0) 296 return error; 297 config |= CONFIG_UFG; 298 if ((error = cwfg_write(sc, CONFIG_REG, config)) != 0) 299 return error; 300 301 /* Restart the IC with new profile */ 302 if ((error = cwfg_read(sc, MODE_REG, &mode)) != 0) 303 return error; 304 mode |= MODE_POR; 305 if ((error = cwfg_write(sc, MODE_REG, mode)) != 0) 306 return error; 307 delay(20000); 308 mode &= ~MODE_POR; 309 if ((error = cwfg_write(sc, MODE_REG, mode)) != 0) 310 return error; 311 312 return error; 313 } 314 315 static int 316 cwfg_init(struct cwfg_softc *sc) 317 { 318 uint8_t mode, soc; 319 int error, retry; 320 321 cwfg_lock(sc); 322 323 /* If the device is in sleep mode, wake it up */ 324 if ((error = cwfg_read(sc, MODE_REG, &mode)) != 0) 325 goto done; 326 if (__SHIFTOUT(mode, MODE_SLEEP) == MODE_SLEEP_SLEEP) { 327 mode &= ~MODE_SLEEP; 328 mode |= __SHIFTIN(MODE_SLEEP_WAKE, MODE_SLEEP); 329 if ((error = cwfg_write(sc, MODE_REG, mode)) != 0) 330 goto done; 331 } 332 333 /* Load battery profile */ 334 if ((error = cwfg_set_config(sc)) != 0) 335 goto done; 336 337 /* Wait for chip to become ready */ 338 for (retry = RESET_COUNT; retry > 0; retry--) { 339 if ((error = cwfg_read(sc, SOC_HI_REG, &soc)) != 0) 340 goto done; 341 if (soc != 0xff) 342 break; 343 delay(RESET_DELAY); 344 } 345 if (retry == 0) { 346 aprint_error_dev(sc->sc_dev, 347 "WARNING: timeout waiting for chip ready\n"); 348 } 349 350 done: 351 cwfg_unlock(sc); 352 353 return error; 354 } 355 356 static int 357 cwfg_parse_resources(struct cwfg_softc *sc) 358 { 359 const u_int *batinfo; 360 int len = 0, n; 361 362 batinfo = fdtbus_get_prop(sc->sc_phandle, 363 "cellwise,bat-config-info", &len); 364 switch (len) { 365 case BATINFO_SIZE: 366 memcpy(sc->sc_batinfo, batinfo, BATINFO_SIZE); 367 break; 368 case BATINFO_SIZE * 4: 369 for (n = 0; n < BATINFO_SIZE; n++) 370 sc->sc_batinfo[n] = be32toh(batinfo[n]); 371 break; 372 default: 373 aprint_error_dev(sc->sc_dev, 374 "missing or invalid battery info\n"); 375 return EINVAL; 376 } 377 378 if (of_getprop_uint32(sc->sc_phandle, 379 "cellwise,monitor-interval", &sc->sc_monitor_interval) != 0) { 380 sc->sc_monitor_interval = CWFG_MONITOR_INTERVAL_DEFAULT; 381 } 382 383 if (of_getprop_uint32(sc->sc_phandle, 384 "cellwise,design-capacity", &sc->sc_design_capacity) != 0) { 385 sc->sc_design_capacity = CWFG_DESIGN_CAPACITY_DEFAULT; 386 } 387 388 if (of_getprop_uint32(sc->sc_phandle, 389 "cellwise,alert-level", &sc->sc_alert_level) != 0) { 390 sc->sc_alert_level = CWFG_ALERT_LEVEL_DEFAULT; 391 } 392 393 return 0; 394 } 395 396 static int 397 cwfg_match(device_t parent, cfdata_t match, void *aux) 398 { 399 struct i2c_attach_args *ia = aux; 400 int match_result; 401 402 if (iic_use_direct_match(ia, match, compat_data, &match_result)) 403 return match_result; 404 405 /* This device is direct-config only. */ 406 407 return 0; 408 } 409 410 static void 411 cwfg_attach(device_t parent, device_t self, void *aux) 412 { 413 struct cwfg_softc *sc = device_private(self); 414 struct i2c_attach_args *ia = aux; 415 uint8_t ver; 416 int error; 417 418 sc->sc_dev = self; 419 sc->sc_i2c = ia->ia_tag; 420 sc->sc_addr = ia->ia_addr; 421 sc->sc_phandle = ia->ia_cookie; 422 423 cwfg_lock(sc); 424 error = cwfg_read(sc, VERSION_REG, &ver); 425 cwfg_unlock(sc); 426 427 if (error != 0) { 428 aprint_error(": device not responding, error = %d\n", error); 429 return; 430 } 431 432 aprint_naive("\n"); 433 aprint_normal(": CellWise CW2015 Fuel Gauge IC (ver. 0x%02x)\n", ver); 434 435 if (cwfg_parse_resources(sc) != 0) { 436 aprint_error_dev(self, "failed to parse resources\n"); 437 return; 438 } 439 440 if (cwfg_init(sc) != 0) { 441 aprint_error_dev(self, "failed to initialize device\n"); 442 return; 443 } 444 445 cwfg_attach_sensors(sc); 446 } 447 448 CFATTACH_DECL_NEW(cwfg, sizeof(struct cwfg_softc), 449 cwfg_match, cwfg_attach, NULL, NULL); 450