1/* $NetBSD: stvii.c,v 1.7 2023/12/20 14:12:25 thorpej Exp $ */ 2 3/*- 4 * Copyright (C) 2011 Michael Lorenz. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27/* 28 * a driver for the ST7 microcontroller found in Gdium Liberty 1000 notebooks 29 */ 30 31 32#include <sys/cdefs.h> 33__KERNEL_RCSID(0, "$NetBSD: stvii.c,v 1.7 2023/12/20 14:12:25 thorpej Exp $"); 34 35#include <sys/param.h> 36#include <sys/systm.h> 37#include <sys/kernel.h> 38#include <sys/device.h> 39#include <sys/sysctl.h> 40#include <sys/kthread.h> 41#include <sys/proc.h> 42 43#include <dev/sysmon/sysmonvar.h> 44#include <dev/sysmon/sysmon_taskq.h> 45 46#include <dev/i2c/i2cvar.h> 47 48#include "opt_stvii.h" 49 50#ifdef STVII_DEBUG 51#define DPRINTF aprint_error 52#else 53#define DPRINTF while (0) printf 54#endif 55 56/* register definitions from OpenBSD */ 57#define ST7_VERSION 0x00 /* only on later mobos */ 58 59#define ST7_STATUS 0x01 60#define STS_LID_CLOSED 0x01 61#define STS_POWER_BTN_DOWN 0x02 62#define STS_BATTERY_PRESENT 0x04 /* not available on old mobo */ 63#define STS_POWER_AVAILABLE 0x08 64#define STS_WAVELAN_BTN_DOWN 0x10 /* ``enable'' on old mobo */ 65#define STS_AC_AVAILABLE 0x20 66#define ST7_CONTROL 0x02 67#define STC_DDR_CLOCK 0x01 68#define STC_CHARGE_LED_LIT 0x02 69#define STC_BEEP 0x04 70#define STC_DDR_POWER 0x08 71#define STC_TRICKLE 0x10 /* trickle charge rate */ 72#define STC_RADIO_ENABLE 0x20 /* enable wavelan rf, later mobos */ 73#define STC_MAIN_POWER 0x40 74#define STC_CHARGE_ENABLE 0x80 75#define ST7_BATTERY_L 0x03 76#define ST7_BATTERY_H 0x04 77#define ST7_SIGNATURE 0x05 78#define STSIG_EC_CONTROL 0x00 79#define STSIG_OS_CONTROL 0xae 80/* rough battery operating state limits */ 81#define STSEC_BAT_MIN_VOLT 7000000 /* 7V */ 82#define STSEC_BAT_MAX_VOLT 8000000 /* 8V */ 83 84#define BAT_AC_PRESENT 0 85#define BAT_BATTERY_PRESENT 1 86#define BAT_CHARGING 2 87#define BAT_CHARGE 3 88#define BAT_MAX_CHARGE 4 89#define BAT_NSENSORS 5 90 91struct stvii_softc { 92 device_t sc_dev; 93 i2c_tag_t sc_i2c; 94 int sc_address, sc_version; 95 int sc_sleep; 96 int sc_flags, sc_charge, sc_bat_level; 97 uint8_t sc_control; 98 struct sysmon_envsys *sc_sme; 99 envsys_data_t sc_sensor[BAT_NSENSORS]; 100 struct sysmon_pswitch sc_sm_acpower; 101 struct sysmon_pswitch sc_sm_lid; 102 struct sysmon_pswitch sc_sm_powerbutton; 103}; 104 105static void stvii_attach(device_t, device_t, void *); 106static int stvii_match(device_t, cfdata_t, void *); 107static void stvii_writereg(struct stvii_softc *, int, uint8_t); 108static int stvii_readreg(struct stvii_softc *, int); 109static void stvii_worker(void *); 110static void stvii_setup_envsys(struct stvii_softc *); 111static void stvii_refresh(struct sysmon_envsys *, envsys_data_t *); 112static int stvii_battery_level(struct stvii_softc *); 113 114CFATTACH_DECL_NEW(stvii, sizeof(struct stvii_softc), 115 stvii_match, stvii_attach, NULL, NULL); 116 117void stvii_poweroff(void); 118static device_t stvii_dev = NULL; 119 120#define BAT_FULL 8000000 121#define BAT_LOW 7100000 122 123static int 124stvii_match(device_t parent, cfdata_t cf, void *aux) 125{ 126 struct i2c_attach_args *args = aux; 127 int ret = -1; 128 uint8_t out = ST7_VERSION, in = 0; 129 130 /* see if we can talk to something at address 0x40 */ 131 if (args->ia_addr == 0x40) { 132 iic_acquire_bus(args->ia_tag, 0); 133 ret = iic_exec(args->ia_tag, I2C_OP_READ_WITH_STOP, args->ia_addr, 134 &out, 1, &in, 1, 0); 135 DPRINTF("%02x\n", in); 136 iic_release_bus(args->ia_tag, 0); 137 } 138 return (ret >= 0) ? I2C_MATCH_ADDRESS_AND_PROBE : 0; 139} 140 141static void 142stvii_attach(device_t parent, device_t self, void *aux) 143{ 144 struct stvii_softc *sc = device_private(self); 145 struct i2c_attach_args *args = aux; 146 uint8_t ver, reg; 147 148 sc->sc_dev = self; 149 stvii_dev = self; 150 sc->sc_address = args->ia_addr; 151 aprint_normal(": ST7 Microcontroller\n"); 152 sc->sc_i2c = args->ia_tag; 153 ver = stvii_readreg(sc, ST7_VERSION); 154 sc->sc_version = ver; 155 aprint_normal_dev(sc->sc_dev, "firmware version %d.%d\n", (ver >> 4) & 0xf, ver & 0xf); 156#ifdef STVII_DEBUG 157 { 158 int i; 159 160 for (i = 0; i < 6; i++) { 161 printf("%02x ", stvii_readreg(sc, i)); 162 } 163 printf("\n"); 164 } 165#endif 166 stvii_writereg(sc, ST7_SIGNATURE, STSIG_OS_CONTROL); 167 reg = stvii_readreg(sc, ST7_CONTROL); 168 reg |= STC_RADIO_ENABLE; 169 stvii_writereg(sc, ST7_CONTROL, reg); 170 sc->sc_control = reg; 171 reg = stvii_readreg(sc, ST7_CONTROL); 172 173 sc->sc_bat_level = stvii_battery_level(sc); 174 175 if (kthread_create(PRI_NONE, KTHREAD_MPSAFE, NULL, stvii_worker, sc, 176 NULL, "stvii") != 0) { 177 aprint_error_dev(sc->sc_dev, "Failed to start kernel thread\n"); 178 } 179 180 memset(&sc->sc_sm_acpower, 0, sizeof(struct sysmon_pswitch)); 181 sc->sc_sm_acpower.smpsw_name = "AC Power"; 182 sc->sc_sm_acpower.smpsw_type = PSWITCH_TYPE_ACADAPTER; 183 if (sysmon_pswitch_register(&sc->sc_sm_acpower) != 0) 184 printf("%s: unable to register AC power status with sysmon\n", 185 device_xname(sc->sc_dev)); 186 memset(&sc->sc_sm_lid, 0, sizeof(struct sysmon_pswitch)); 187 sc->sc_sm_lid.smpsw_name = "Lid Switch"; 188 sc->sc_sm_lid.smpsw_type = PSWITCH_TYPE_LID; 189 if (sysmon_pswitch_register(&sc->sc_sm_lid) != 0) 190 printf("%s: unable to register lid switch with sysmon\n", 191 device_xname(sc->sc_dev)); 192 memset(&sc->sc_sm_powerbutton, 0, sizeof(struct sysmon_pswitch)); 193 sc->sc_sm_powerbutton.smpsw_name = "Power Button"; 194 sc->sc_sm_powerbutton.smpsw_type = PSWITCH_TYPE_POWER; 195 if (sysmon_pswitch_register(&sc->sc_sm_powerbutton) != 0) 196 printf("%s: unable to register power button with sysmon\n", 197 device_xname(sc->sc_dev)); 198 stvii_setup_envsys(sc); 199} 200 201static void 202stvii_writereg(struct stvii_softc *sc, int reg, uint8_t val) 203{ 204 uint8_t out[2] = {reg, val}; 205 206 if ((reg < 0) || (reg > 5)) 207 return; 208 209 iic_acquire_bus(sc->sc_i2c, 0); 210 iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, sc->sc_address, out, 2, NULL, 0, 0); 211 iic_release_bus(sc->sc_i2c, 0); 212} 213 214static int 215stvii_readreg(struct stvii_softc *sc, int reg) 216{ 217 uint8_t inreg[1], outreg[1]; 218 int ret = 1, bail = 0; 219 220 if ((reg < 0) || (reg > 5)) 221 return 0xff; 222 inreg[0] = 0x77; 223 outreg[0] = reg; 224 iic_acquire_bus(sc->sc_i2c, 0); 225 while ((ret != 0) && (bail < 10)) { 226 ret = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 227 sc->sc_address, outreg, 1, inreg, 1, 0); 228 bail++; 229 delay(10); 230 } 231 iic_release_bus(sc->sc_i2c, 0); 232 if (ret != 0) 233 return -1; 234 return inreg[0]; 235} 236 237static int 238stvii_battery_level(struct stvii_softc *sc) 239{ 240 int bl, bh, ret; 241 242 bl = stvii_readreg(sc, ST7_BATTERY_L); 243 bh = stvii_readreg(sc, ST7_BATTERY_H); 244 ret = (bl & 3) | (bh << 2); 245 ret = ((ret * 10000) / 1024) * 1000; 246 return ret; 247} 248 249static void 250stvii_worker(void *cookie) 251{ 252 struct stvii_softc *sc = cookie; 253 int status = 0, st, cnt = 4; 254 int bl; 255 int charging = 0; 256 int ok = TRUE; 257 uint8_t nctrl; 258 259 /* if we were charging when we took over, keep charging */ 260 if (sc->sc_control & STC_CHARGE_ENABLE) 261 charging = 1; 262 263 while (ok) { 264 st = stvii_readreg(sc, ST7_STATUS); 265 /* 266 * I get i2c timeouts when the power button is pressed. 267 * According to the linux driver this happens on firmware 268 * version 0x13 and newer, mine is 0x16. 269 * So, when we see read errors on the right version we assume 270 * it's the power button as long as the lid is open 271 * ( the button is inside the lid ) 272 */ 273 if ((st == -1) && (sc->sc_version >= 0x13)) { 274 if ((status & (STS_LID_CLOSED | STS_POWER_BTN_DOWN) ) 275 == 0) { 276 st = status | STS_POWER_BTN_DOWN; 277 } 278 } 279 if ((st != -1) && (st != status)) { 280 if ((status ^ st) & STS_LID_CLOSED) { 281 sysmon_pswitch_event(&sc->sc_sm_lid, 282 ((st & STS_LID_CLOSED) ? 283 PSWITCH_EVENT_PRESSED : 284 PSWITCH_EVENT_RELEASED)); 285 } 286 if ((status ^ st) & STS_AC_AVAILABLE) { 287 sysmon_pswitch_event(&sc->sc_sm_acpower, 288 ((st & STS_AC_AVAILABLE) ? 289 PSWITCH_EVENT_PRESSED : 290 PSWITCH_EVENT_RELEASED)); 291 } 292 if ((status ^ st) & STS_POWER_BTN_DOWN) { 293 sysmon_pswitch_event(&sc->sc_sm_powerbutton, 294 ((st & STS_POWER_BTN_DOWN) ? 295 PSWITCH_EVENT_PRESSED : 296 PSWITCH_EVENT_RELEASED)); 297 } 298 status = st; 299 } 300 sc->sc_flags = status; 301 if (cnt >= 4) { 302 nctrl = sc->sc_control & ~(STC_TRICKLE | STC_CHARGE_ENABLE); 303 bl = stvii_battery_level(sc); 304 sc->sc_bat_level = bl; 305 if (charging && (bl > BAT_FULL)) { 306 /* stop charging, we're full */ 307 charging = 0; 308 } else if (!charging && (bl < BAT_LOW)) { 309 charging = 1; 310 } 311 if (st & STS_AC_AVAILABLE) { 312 if (charging) { 313 nctrl |= STC_CHARGE_ENABLE; 314 } else 315 nctrl |= STC_TRICKLE; 316 } 317 if (nctrl != sc->sc_control) { 318 sc->sc_control = nctrl; 319 stvii_writereg(sc, ST7_CONTROL, sc->sc_control); 320 } 321 cnt = 0; 322 } else 323 cnt++; 324 tsleep(&sc->sc_sleep, 0, "stvii", hz / 2); 325 } 326} 327 328#define INITDATA(index, unit, string) \ 329 sc->sc_sensor[index].units = unit; \ 330 sc->sc_sensor[index].state = ENVSYS_SINVALID; \ 331 snprintf(sc->sc_sensor[index].desc, \ 332 sizeof(sc->sc_sensor[index].desc), "%s", string); 333 334static void 335stvii_setup_envsys(struct stvii_softc *sc) 336{ 337 int i; 338 339 sc->sc_sme = sysmon_envsys_create(); 340 341 INITDATA(BAT_AC_PRESENT, ENVSYS_INDICATOR, "AC present"); 342 INITDATA(BAT_BATTERY_PRESENT, ENVSYS_INDICATOR, "Battery present"); 343 INITDATA(BAT_CHARGING, ENVSYS_BATTERY_CHARGE, "Battery charging"); 344 INITDATA(BAT_CHARGE, ENVSYS_SVOLTS_DC, "Battery voltage"); 345 INITDATA(BAT_MAX_CHARGE, ENVSYS_SVOLTS_DC, "Battery design cap"); 346#undef INITDATA 347 348 for (i = 0; i < BAT_NSENSORS; i++) { 349 if (sysmon_envsys_sensor_attach(sc->sc_sme, 350 &sc->sc_sensor[i])) { 351 sysmon_envsys_destroy(sc->sc_sme); 352 return; 353 } 354 } 355 356 sc->sc_sme->sme_name = device_xname(sc->sc_dev); 357 sc->sc_sme->sme_cookie = sc; 358 sc->sc_sme->sme_refresh = stvii_refresh; 359 360 if (sysmon_envsys_register(sc->sc_sme)) { 361 aprint_error_dev(sc->sc_dev, 362 "unable to register with sysmon\n"); 363 sysmon_envsys_destroy(sc->sc_sme); 364 } 365} 366 367static void 368stvii_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 369{ 370 struct stvii_softc *sc = sme->sme_cookie; 371 int which = edata->sensor; 372 373 edata->state = ENVSYS_SINVALID; 374 switch (which) { 375 case BAT_AC_PRESENT: 376 edata->value_cur = (sc->sc_flags & STS_AC_AVAILABLE); 377 edata->state = ENVSYS_SVALID; 378 break; 379 case BAT_BATTERY_PRESENT: 380 edata->value_cur = (sc->sc_flags & STS_BATTERY_PRESENT); 381 edata->state = ENVSYS_SVALID; 382 break; 383 case BAT_CHARGE: 384 if (sc->sc_flags & STS_BATTERY_PRESENT) { 385 edata->value_cur = sc->sc_bat_level; 386 edata->state = ENVSYS_SVALID; 387 } 388 break; 389 case BAT_MAX_CHARGE: 390 if (sc->sc_flags & STS_BATTERY_PRESENT) { 391 edata->value_cur = 8000000; 392 /*edata->state = ENVSYS_SVALID;*/ 393 } 394 break; 395 case BAT_CHARGING: 396 edata->value_cur = sc->sc_control & STC_CHARGE_ENABLE; 397 edata->state = ENVSYS_SVALID; 398 break; 399 } 400} 401 402void 403stvii_poweroff(void) 404{ 405 struct stvii_softc *sc = device_private(stvii_dev); 406 int ctl; 407 408 if (sc == NULL) 409 return; 410 ctl = stvii_readreg(sc, ST7_CONTROL); 411 if (ctl == -1) 412 return; 413 stvii_writereg(sc, ST7_CONTROL, ctl & ~(STC_MAIN_POWER | STC_DDR_POWER)); 414} 415