1 /* $OpenBSD: stsec.c,v 1.5 2016/12/05 15:04:15 fcambus Exp $ */ 2 3 /* 4 * Copyright (c) 2010 Miodrag Vallat. 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 /* 20 * Gdium ST7 Embedded Controller I2C interface 21 */ 22 23 #include <sys/param.h> 24 #include <sys/kernel.h> 25 #include <sys/systm.h> 26 #include <sys/device.h> 27 #include <sys/sensors.h> 28 29 #include <machine/apmvar.h> 30 #include <dev/i2c/i2cvar.h> 31 32 #include "apm.h" 33 34 extern int gdium_revision; 35 36 #define ST7_VERSION 0x00 /* only on later mobos */ 37 38 #define ST7_STATUS 0x00 39 #define STS_LID_CLOSED 0x01 40 #define STS_POWER_BTN_DOWN 0x02 41 #define STS_BATTERY_PRESENT 0x04 /* not available on old mobo */ 42 #define STS_POWER_AVAILABLE 0x08 43 #define STS_WAVELAN_BTN_DOWN 0x10 /* ``enable'' on old mobo */ 44 #define STS_AC_AVAILABLE 0x20 45 #define ST7_CONTROL 0x01 46 #define STC_DDR_CLOCK 0x01 47 #define STC_CHARGE_LED_LIT 0x02 48 #define STC_BEEP 0x04 49 #define STC_DDR_POWER 0x08 50 #define STC_TRICKLE 0x10 /* trickle charge rate */ 51 #define STC_RADIO_ENABLE 0x20 /* enable wavelan rf, later mobos */ 52 #define STC_MAIN_POWER 0x40 53 #define STC_CHARGE_ENABLE 0x80 54 #define ST7_BATTERY_L 0x02 55 #define ST7_BATTERY_H 0x03 56 #define STB_VALUE(h,l) (((h) << 2) | ((l) & 0x03)) 57 #define ST7_SIGNATURE 0x04 58 #define STSIG_EC_CONTROL 0x00 59 #define STSIG_OS_CONTROL 0xae 60 /* rough battery operating state limits */ 61 #define STSEC_BAT_MIN_VOLT 7000000 /* 7V */ 62 #define STSEC_BAT_MAX_VOLT 8000000 /* 8V */ 63 64 const struct { 65 const char *desc; 66 enum sensor_type type; 67 } stsec_sensors_template[] = { 68 #define STSEC_SENSOR_AC_PRESENCE 0 69 { 70 .desc = "AC present", 71 .type = SENSOR_INDICATOR, 72 }, 73 #define STSEC_SENSOR_BATTERY_PRESENCE 1 74 { 75 .desc = "Battery present", 76 .type = SENSOR_INDICATOR, 77 }, 78 #define STSEC_SENSOR_BATTERY_STATE 2 79 { 80 .desc = "Battery charging", 81 .type = SENSOR_INDICATOR, 82 }, 83 #define STSEC_SENSOR_BATTERY_VOLTAGE 3 84 { 85 .desc = "Battery voltage", 86 .type = SENSOR_VOLTS_DC, 87 } 88 }; 89 90 struct stsec_softc { 91 struct device sc_dev; 92 i2c_tag_t sc_tag; 93 i2c_addr_t sc_addr; 94 uint sc_base; 95 96 struct ksensor sc_sensors[nitems(stsec_sensors_template)]; 97 struct ksensordev sc_sensordev; 98 struct sensor_task *sc_sensors_update_task; 99 }; 100 101 int stsec_match(struct device *, void *, void *); 102 void stsec_attach(struct device *, struct device *, void *); 103 104 const struct cfattach stsec_ca = { 105 sizeof(struct stsec_softc), stsec_match, stsec_attach 106 }; 107 108 struct cfdriver stsec_cd = { 109 NULL, "stsec", DV_DULL 110 }; 111 112 int stsec_apminfo(struct apm_power_info *); 113 int stsec_read(struct stsec_softc *, uint, int *); 114 int stsec_write(struct stsec_softc *, uint, int); 115 void stsec_sensors_update(void *); 116 117 #if NAPM > 0 118 struct apm_power_info stsec_apmdata; 119 const char *stsec_batstate[] = { 120 "high", 121 "low", 122 "critical", 123 "charging", 124 "unknown" 125 }; 126 #define BATTERY_STRING(x) ((x) < nitems(stsec_batstate) ? \ 127 stsec_batstate[x] : stsec_batstate[4]) 128 #endif 129 130 int 131 stsec_match(struct device *parent, void *vcf, void *aux) 132 { 133 struct i2c_attach_args *ia = (struct i2c_attach_args *)aux; 134 struct cfdata *cf = (struct cfdata *)vcf; 135 136 return strcmp(ia->ia_name, cf->cf_driver->cd_name) == 0; 137 } 138 139 void 140 stsec_attach(struct device *parent, struct device *self, void *aux) 141 { 142 struct stsec_softc *sc = (struct stsec_softc *)self; 143 struct i2c_attach_args *ia = (struct i2c_attach_args *)aux; 144 int rev, sig; 145 int rc; 146 uint i; 147 148 sc->sc_tag = ia->ia_tag; 149 sc->sc_addr = ia->ia_addr; 150 151 /* 152 * Figure out where to get our information, and display microcode 153 * version for geek value if available. 154 */ 155 156 sc->sc_base = 0; 157 switch (gdium_revision) { 158 case 0: 159 break; 160 default: 161 /* read version before sc_base is set */ 162 rc = stsec_read(sc, ST7_VERSION, &rev); 163 if (rc != 0) { 164 printf(": can't read microcode revision\n"); 165 return; 166 } 167 printf(": revision %d.%d", (rev >> 4) & 0x0f, rev & 0x0f); 168 sc->sc_base = ST7_VERSION + 1; 169 break; 170 } 171 172 printf("\n"); 173 174 /* 175 * Better trust the ST7 firmware to control charge operation. 176 */ 177 178 rc = stsec_read(sc, ST7_SIGNATURE, &sig); 179 if (rc != 0) { 180 printf("%s: can't verify charge policy\n", self->dv_xname); 181 /* not fatal */ 182 } else { 183 if (sig != STSIG_EC_CONTROL) 184 stsec_write(sc, ST7_SIGNATURE, STSIG_EC_CONTROL); 185 } 186 187 /* 188 * Setup sensors. We use a quite short refresh interval to react 189 * quickly enough to button presses. 190 * XXX but we don't do anything on lid or button presses... yet 191 */ 192 193 strlcpy(sc->sc_sensordev.xname, self->dv_xname, 194 sizeof(sc->sc_sensordev.xname)); 195 sc->sc_sensors_update_task = 196 sensor_task_register(sc, stsec_sensors_update, 2); 197 if (sc->sc_sensors_update_task == NULL) { 198 printf("%s: can't initialize refresh task\n", self->dv_xname); 199 return; 200 } 201 202 for (i = 0; i < nitems(sc->sc_sensors); i++) { 203 sc->sc_sensors[i].type = stsec_sensors_template[i].type; 204 strlcpy(sc->sc_sensors[i].desc, stsec_sensors_template[i].desc, 205 sizeof(sc->sc_sensors[i].desc)); 206 sensor_attach(&sc->sc_sensordev, &sc->sc_sensors[i]); 207 } 208 sensordev_install(&sc->sc_sensordev); 209 #if NAPM > 0 210 /* make sure we have the apm state initialized before apm attaches */ 211 stsec_sensors_update(sc); 212 apm_setinfohook(stsec_apminfo); 213 #endif 214 } 215 216 int 217 stsec_read(struct stsec_softc *sc, uint reg, int *value) 218 { 219 uint8_t regno, data; 220 int rc; 221 222 regno = sc->sc_base + reg; 223 iic_acquire_bus(sc->sc_tag, 0); 224 rc = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, 225 ®no, sizeof regno, &data, sizeof data, 0); 226 iic_release_bus(sc->sc_tag, 0); 227 228 if (rc == 0) 229 *value = (int)data; 230 return rc; 231 } 232 233 int 234 stsec_write(struct stsec_softc *sc, uint reg, int val) 235 { 236 uint8_t regno, data[1]; 237 int rc; 238 239 regno = sc->sc_base + reg; 240 data[0] = (uint8_t)val; 241 iic_acquire_bus(sc->sc_tag, 0); 242 rc = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, 243 ®no, sizeof regno, data, sizeof data[0], 0); 244 iic_release_bus(sc->sc_tag, 0); 245 return rc; 246 } 247 248 void 249 stsec_sensors_update(void *vsc) 250 { 251 struct stsec_softc *sc = (struct stsec_softc *)vsc; 252 int status, control, batl, bath; 253 ulong batuv; 254 struct ksensor *ks; 255 uint i; 256 #if NAPM > 0 257 struct apm_power_info old; 258 uint cap_pct; 259 #endif 260 261 for (i = 0; i < nitems(sc->sc_sensors); i++) 262 sc->sc_sensors[i].flags |= SENSOR_FINVALID; 263 264 if (stsec_read(sc, ST7_STATUS, &status) != 0 || 265 stsec_read(sc, ST7_CONTROL, &control) != 0 || 266 stsec_read(sc, ST7_BATTERY_L, &batl) != 0 || 267 stsec_read(sc, ST7_BATTERY_H, &bath) != 0) 268 return; 269 270 /* 271 * Battery voltage is in 10/1024V units, in the 0-1023 range. 272 */ 273 batuv = ((ulong)STB_VALUE(bath, batl) * 10 * 1000000) / 1024; 274 275 ks = &sc->sc_sensors[STSEC_SENSOR_AC_PRESENCE]; 276 ks->value = !!ISSET(status, STS_AC_AVAILABLE); 277 ks->flags &= ~SENSOR_FINVALID; 278 279 /* 280 * Old mobo design does not have a battery presence bit; the Linux 281 * code decides there is no battery if the reported battery voltage 282 * is too low, we'll do the same. 283 */ 284 ks = &sc->sc_sensors[STSEC_SENSOR_BATTERY_PRESENCE]; 285 switch (gdium_revision) { 286 case 0: 287 if (ISSET(status, STS_AC_AVAILABLE)) 288 ks->value = batuv > 500000; 289 else 290 ks->value = 1; 291 break; 292 default: 293 ks->value = !!ISSET(status, STS_BATTERY_PRESENT); 294 break; 295 } 296 ks->flags &= ~SENSOR_FINVALID; 297 298 ks = &sc->sc_sensors[STSEC_SENSOR_BATTERY_STATE]; 299 ks->value = !!ISSET(control, STC_CHARGE_ENABLE); 300 ks->flags &= ~SENSOR_FINVALID; 301 302 ks = &sc->sc_sensors[STSEC_SENSOR_BATTERY_VOLTAGE]; 303 ks->value = (int64_t)batuv; 304 ks->flags &= ~SENSOR_FINVALID; 305 306 #if NAPM > 0 307 bcopy(&stsec_apmdata, &old, sizeof(old)); 308 309 if (batuv < STSEC_BAT_MIN_VOLT) 310 batuv = STSEC_BAT_MIN_VOLT; 311 else if (batuv > STSEC_BAT_MAX_VOLT) 312 batuv = STSEC_BAT_MAX_VOLT; 313 cap_pct = (batuv - STSEC_BAT_MIN_VOLT) * 100 / (STSEC_BAT_MAX_VOLT - 314 STSEC_BAT_MIN_VOLT); 315 stsec_apmdata.battery_life = cap_pct; 316 317 stsec_apmdata.ac_state = ISSET(status, STS_AC_AVAILABLE) ? APM_AC_ON : 318 APM_AC_OFF; 319 if (!sc->sc_sensors[STSEC_SENSOR_BATTERY_PRESENCE].value) { 320 stsec_apmdata.battery_state = APM_BATTERY_ABSENT; 321 stsec_apmdata.minutes_left = 0; 322 stsec_apmdata.battery_life = 0; 323 } else { 324 if (ISSET(control, STC_CHARGE_ENABLE)) 325 stsec_apmdata.battery_state = APM_BATT_CHARGING; 326 else if (cap_pct > 50) 327 stsec_apmdata.battery_state = APM_BATT_HIGH; 328 else if (cap_pct > 25) 329 stsec_apmdata.battery_state = APM_BATT_LOW; 330 else 331 stsec_apmdata.battery_state = APM_BATT_CRITICAL; 332 333 stsec_apmdata.minutes_left = -1; /* unknown */ 334 } 335 if (old.ac_state != stsec_apmdata.ac_state) 336 apm_record_event(APM_POWER_CHANGE, "AC power", 337 stsec_apmdata.ac_state ? "restored" : "lost"); 338 if (old.battery_state != stsec_apmdata.battery_state) 339 apm_record_event(APM_POWER_CHANGE, "battery", 340 BATTERY_STRING(stsec_apmdata.battery_state)); 341 #endif 342 } 343 344 #if NAPM > 0 345 int 346 stsec_apminfo(struct apm_power_info *info) 347 { 348 bcopy(&stsec_apmdata, info, sizeof(struct apm_power_info)); 349 return 0; 350 } 351 #endif 352