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
stsec_match(struct device * parent,void * vcf,void * aux)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
stsec_attach(struct device * parent,struct device * self,void * aux)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
stsec_read(struct stsec_softc * sc,uint reg,int * value)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
stsec_write(struct stsec_softc * sc,uint reg,int val)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
stsec_sensors_update(void * vsc)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
stsec_apminfo(struct apm_power_info * info)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