1 /* $NetBSD: aps.c,v 1.17 2015/04/23 23:23:00 pgoyette Exp $ */
2 /* $OpenBSD: aps.c,v 1.15 2007/05/19 19:14:11 tedu Exp $ */
3 /* $OpenBSD: aps.c,v 1.17 2008/06/27 06:08:43 canacar Exp $ */
4 /*
5 * Copyright (c) 2005 Jonathan Gray <jsg@openbsd.org>
6 * Copyright (c) 2008 Can Erkin Acar <canacar@openbsd.org>
7 *
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 */
20
21 /*
22 * A driver for the ThinkPad Active Protection System based on notes from
23 * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html
24 */
25
26 #include <sys/cdefs.h>
27 __KERNEL_RCSID(0, "$NetBSD: aps.c,v 1.17 2015/04/23 23:23:00 pgoyette Exp $");
28
29 #include <sys/param.h>
30 #include <sys/systm.h>
31 #include <sys/device.h>
32 #include <sys/kernel.h>
33 #include <sys/callout.h>
34 #include <sys/module.h>
35
36 #include <sys/bus.h>
37
38 #include <dev/sysmon/sysmonvar.h>
39
40 #include <dev/isa/isareg.h>
41 #include <dev/isa/isavar.h>
42
43 #if defined(APSDEBUG)
44 #define DPRINTF(x) do { printf x; } while (0)
45 #else
46 #define DPRINTF(x)
47 #endif
48
49
50 /*
51 * EC interface on Thinkpad Laptops, from Linux HDAPS driver notes.
52 * From Renesans H8S/2140B Group Hardware Manual
53 * http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf
54 *
55 * EC uses LPC Channel 3 registers TWR0..15
56 */
57
58 /* STR3 status register */
59 #define APS_STR3 0x04
60
61 #define APS_STR3_IBF3B 0x80 /* Input buffer full (host->slave) */
62 #define APS_STR3_OBF3B 0x40 /* Output buffer full (slave->host)*/
63 #define APS_STR3_MWMF 0x20 /* Master write mode */
64 #define APS_STR3_SWMF 0x10 /* Slave write mode */
65
66
67 /* Base address of TWR registers */
68 #define APS_TWR_BASE 0x10
69 #define APS_TWR_RET 0x1f
70
71 /* TWR registers */
72 #define APS_CMD 0x00
73 #define APS_ARG1 0x01
74 #define APS_ARG2 0x02
75 #define APS_ARG3 0x03
76 #define APS_RET 0x0f
77
78 /* Sensor values */
79 #define APS_STATE 0x01
80 #define APS_XACCEL 0x02
81 #define APS_YACCEL 0x04
82 #define APS_TEMP 0x06
83 #define APS_XVAR 0x07
84 #define APS_YVAR 0x09
85 #define APS_TEMP2 0x0b
86 #define APS_UNKNOWN 0x0c
87 #define APS_INPUT 0x0d
88
89 /* write masks for I/O, send command + 0-3 arguments*/
90 #define APS_WRITE_0 0x0001
91 #define APS_WRITE_1 0x0003
92 #define APS_WRITE_2 0x0007
93 #define APS_WRITE_3 0x000f
94
95 /* read masks for I/O, read 0-3 values (skip command byte) */
96 #define APS_READ_0 0x0000
97 #define APS_READ_1 0x0002
98 #define APS_READ_2 0x0006
99 #define APS_READ_3 0x000e
100
101 #define APS_READ_RET 0x8000
102 #define APS_READ_ALL 0xffff
103
104 /* Bit definitions for APS_INPUT value */
105 #define APS_INPUT_KB (1 << 5)
106 #define APS_INPUT_MS (1 << 6)
107 #define APS_INPUT_LIDOPEN (1 << 7)
108
109 #define APS_ADDR_SIZE 0x1f
110
111 struct sensor_rec {
112 uint8_t state;
113 uint16_t x_accel;
114 uint16_t y_accel;
115 uint8_t temp1;
116 uint16_t x_var;
117 uint16_t y_var;
118 uint8_t temp2;
119 uint8_t unk;
120 uint8_t input;
121 };
122
123 enum aps_sensors {
124 APS_SENSOR_XACCEL = 0,
125 APS_SENSOR_YACCEL,
126 APS_SENSOR_XVAR,
127 APS_SENSOR_YVAR,
128 APS_SENSOR_TEMP1,
129 APS_SENSOR_TEMP2,
130 APS_SENSOR_KBACT,
131 APS_SENSOR_MSACT,
132 APS_SENSOR_LIDOPEN,
133 APS_NUM_SENSORS
134 };
135
136 struct aps_softc {
137 bus_space_tag_t sc_iot;
138 bus_space_handle_t sc_ioh;
139 bool sc_bus_space_valid;
140
141 struct sysmon_envsys *sc_sme;
142 envsys_data_t sc_sensor[APS_NUM_SENSORS];
143 struct callout sc_callout;
144
145 struct sensor_rec aps_data;
146 };
147
148 static int aps_match(device_t, cfdata_t, void *);
149 static void aps_attach(device_t, device_t, void *);
150 static int aps_detach(device_t, int);
151
152 static int aps_init(struct aps_softc *);
153 static int aps_read_data(struct aps_softc *);
154 static void aps_refresh_sensor_data(struct aps_softc *);
155 static void aps_refresh(void *);
156 static int aps_do_io(bus_space_tag_t, bus_space_handle_t,
157 unsigned char *, int, int);
158 static bool aps_suspend(device_t, const pmf_qual_t *);
159 static bool aps_resume(device_t, const pmf_qual_t *);
160
161 CFATTACH_DECL_NEW(aps, sizeof(struct aps_softc),
162 aps_match, aps_attach, aps_detach, NULL);
163
164 /* properly communicate with the controller, writing a set of memory
165 * locations and reading back another set */
166 static int
aps_do_io(bus_space_tag_t iot,bus_space_handle_t ioh,unsigned char * buf,int wmask,int rmask)167 aps_do_io(bus_space_tag_t iot, bus_space_handle_t ioh,
168 unsigned char *buf, int wmask, int rmask)
169 {
170 int bp, stat, n;
171
172 DPRINTF(("aps_do_io: CMD: 0x%02x, wmask: 0x%04x, rmask: 0x%04x\n",
173 buf[0], wmask, rmask));
174
175 /* write init byte using arbitration */
176 for (n = 0; n < 100; n++) {
177 stat = bus_space_read_1(iot, ioh, APS_STR3);
178 if (stat & (APS_STR3_OBF3B | APS_STR3_SWMF)) {
179 bus_space_read_1(iot, ioh, APS_TWR_RET);
180 continue;
181 }
182 bus_space_write_1(iot, ioh, APS_TWR_BASE, buf[0]);
183 stat = bus_space_read_1(iot, ioh, APS_STR3);
184 if (stat & (APS_STR3_MWMF))
185 break;
186 delay(1);
187 }
188
189 if (n == 100) {
190 DPRINTF(("aps_do_io: Failed to get bus\n"));
191 return 1;
192 }
193
194 /* write data bytes, init already sent */
195 /* make sure last bye is always written as this will trigger slave */
196 wmask |= APS_READ_RET;
197 buf[APS_RET] = 0x01;
198
199 for (n = 1, bp = 2; n < 16; bp <<= 1, n++) {
200 if (wmask & bp) {
201 bus_space_write_1(iot, ioh, APS_TWR_BASE + n, buf[n]);
202 DPRINTF(("aps_do_io: write %2d 0x%02x\n", n, buf[n]));
203 }
204 }
205
206 for (n = 0; n < 100; n++) {
207 stat = bus_space_read_1(iot, ioh, APS_STR3);
208 if (stat & (APS_STR3_OBF3B))
209 break;
210 delay(5 * 100);
211 }
212
213 if (n == 100) {
214 DPRINTF(("aps_do_io: timeout waiting response\n"));
215 return 1;
216 }
217 /* wait for data available */
218 /* make sure to read the final byte to clear status */
219 rmask |= APS_READ_RET;
220
221 /* read cmd and data bytes */
222 for (n = 0, bp = 1; n < 16; bp <<= 1, n++) {
223 if (rmask & bp) {
224 buf[n] = bus_space_read_1(iot, ioh, APS_TWR_BASE + n);
225 DPRINTF(("aps_do_io: read %2d 0x%02x\n", n, buf[n]));
226 }
227 }
228
229 return 0;
230 }
231
232 static int
aps_match(device_t parent,cfdata_t match,void * aux)233 aps_match(device_t parent, cfdata_t match, void *aux)
234 {
235 struct isa_attach_args *ia = aux;
236 bus_space_tag_t iot = ia->ia_iot;
237 bus_space_handle_t ioh;
238 unsigned char iobuf[16];
239 int iobase;
240 uint8_t cr;
241
242 /* Must supply an address */
243 if (ia->ia_nio < 1)
244 return 0;
245
246 if (ISA_DIRECT_CONFIG(ia))
247 return 0;
248
249 if (ia->ia_io[0].ir_addr == ISA_UNKNOWN_PORT)
250 return 0;
251
252 iobase = ia->ia_io[0].ir_addr;
253
254 if (bus_space_map(iot, iobase, APS_ADDR_SIZE, 0, &ioh)) {
255 aprint_error("aps: can't map i/o space\n");
256 return 0;
257 }
258
259
260 /* See if this machine has APS */
261
262 /* get APS mode */
263 iobuf[APS_CMD] = 0x13;
264 if (aps_do_io(iot, ioh, iobuf, APS_WRITE_0, APS_READ_1)) {
265 bus_space_unmap(iot, ioh, APS_ADDR_SIZE);
266 return 0;
267 }
268
269 /*
270 * Observed values from Linux driver:
271 * 0x01: T42
272 * 0x02: chip already initialised
273 * 0x03: T41
274 * 0x05: T61
275 */
276
277 cr = iobuf[APS_ARG1];
278
279 bus_space_unmap(iot, ioh, APS_ADDR_SIZE);
280 DPRINTF(("aps: state register 0x%x\n", cr));
281
282 if (iobuf[APS_RET] != 0 || cr < 1 || cr > 5) {
283 DPRINTF(("aps0: unsupported state %d\n", cr));
284 return 0;
285 }
286
287 ia->ia_nio = 1;
288 ia->ia_io[0].ir_size = APS_ADDR_SIZE;
289 ia->ia_niomem = 0;
290 ia->ia_nirq = 0;
291 ia->ia_ndrq = 0;
292
293 return 1;
294 }
295
296 static void
aps_attach(device_t parent,device_t self,void * aux)297 aps_attach(device_t parent, device_t self, void *aux)
298 {
299 struct aps_softc *sc = device_private(self);
300 struct isa_attach_args *ia = aux;
301 int iobase, i;
302
303 sc->sc_iot = ia->ia_iot;
304 iobase = ia->ia_io[0].ir_addr;
305
306 callout_init(&sc->sc_callout, 0);
307 callout_setfunc(&sc->sc_callout, aps_refresh, sc);
308
309 if (bus_space_map(sc->sc_iot, iobase, APS_ADDR_SIZE, 0, &sc->sc_ioh)) {
310 aprint_error(": can't map i/o space\n");
311 return;
312 }
313 sc->sc_bus_space_valid = true;
314
315 aprint_naive("\n");
316 aprint_normal(": Thinkpad Active Protection System\n");
317
318 if (aps_init(sc)) {
319 aprint_error_dev(self, "failed to initialize\n");
320 goto out;
321 }
322
323 /* Initialize sensors */
324 #define INITDATA(idx, unit, string) \
325 sc->sc_sensor[idx].units = unit; \
326 strlcpy(sc->sc_sensor[idx].desc, string, \
327 sizeof(sc->sc_sensor[idx].desc));
328
329 INITDATA(APS_SENSOR_XACCEL, ENVSYS_INTEGER, "x-acceleration");
330 INITDATA(APS_SENSOR_YACCEL, ENVSYS_INTEGER, "y-acceleration");
331 INITDATA(APS_SENSOR_TEMP1, ENVSYS_STEMP, "temperature 1");
332 INITDATA(APS_SENSOR_TEMP2, ENVSYS_STEMP, "temperature 2");
333 INITDATA(APS_SENSOR_XVAR, ENVSYS_INTEGER, "x-variable");
334 INITDATA(APS_SENSOR_YVAR, ENVSYS_INTEGER, "y-variable");
335 INITDATA(APS_SENSOR_KBACT, ENVSYS_INDICATOR, "keyboard active");
336 INITDATA(APS_SENSOR_MSACT, ENVSYS_INDICATOR, "mouse active");
337 INITDATA(APS_SENSOR_LIDOPEN, ENVSYS_INDICATOR, "lid open");
338
339 sc->sc_sme = sysmon_envsys_create();
340
341 for (i = 0; i < APS_NUM_SENSORS; i++) {
342
343 sc->sc_sensor[i].state = ENVSYS_SVALID;
344
345 if (sc->sc_sensor[i].units == ENVSYS_INTEGER)
346 sc->sc_sensor[i].flags = ENVSYS_FHAS_ENTROPY;
347
348 if (sysmon_envsys_sensor_attach(sc->sc_sme,
349 &sc->sc_sensor[i])) {
350 sysmon_envsys_destroy(sc->sc_sme);
351 goto out;
352 }
353 }
354 /*
355 * Register with the sysmon_envsys(9) framework.
356 */
357 sc->sc_sme->sme_name = device_xname(self);
358 sc->sc_sme->sme_flags = SME_DISABLE_REFRESH;
359
360 if ((i = sysmon_envsys_register(sc->sc_sme))) {
361 aprint_error_dev(self,
362 "unable to register with sysmon (%d)\n", i);
363 sysmon_envsys_destroy(sc->sc_sme);
364 goto out;
365 }
366
367 if (!pmf_device_register(self, aps_suspend, aps_resume))
368 aprint_error_dev(self, "couldn't establish power handler\n");
369
370 /* Refresh sensor data every 0.5 seconds */
371 callout_schedule(&sc->sc_callout, (hz) / 2);
372
373 return;
374
375 out:
376 bus_space_unmap(sc->sc_iot, sc->sc_ioh, APS_ADDR_SIZE);
377 }
378
379 static int
aps_init(struct aps_softc * sc)380 aps_init(struct aps_softc *sc)
381 {
382 unsigned char iobuf[16];
383
384 /* command 0x17/0x81: check EC */
385 iobuf[APS_CMD] = 0x17;
386 iobuf[APS_ARG1] = 0x81;
387
388 if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_1, APS_READ_3))
389 return 1;
390 if (iobuf[APS_RET] != 0 ||iobuf[APS_ARG3] != 0)
391 return 1;
392
393 /* Test values from the Linux driver */
394 if ((iobuf[APS_ARG1] != 0 || iobuf[APS_ARG2] != 0x60) &&
395 (iobuf[APS_ARG1] != 1 || iobuf[APS_ARG2] != 0))
396 return 1;
397
398 /* command 0x14: set power */
399 iobuf[APS_CMD] = 0x14;
400 iobuf[APS_ARG1] = 0x01;
401
402 if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_1, APS_READ_0))
403 return 1;
404
405 if (iobuf[APS_RET] != 0)
406 return 1;
407
408 /* command 0x10: set config (sample rate and order) */
409 iobuf[APS_CMD] = 0x10;
410 iobuf[APS_ARG1] = 0xc8;
411 iobuf[APS_ARG2] = 0x00;
412 iobuf[APS_ARG3] = 0x02;
413
414 if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_3, APS_READ_0))
415 return 1;
416
417 /* command 0x11: refresh data */
418 iobuf[APS_CMD] = 0x11;
419 if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_0, APS_READ_1))
420 return 1;
421 if (iobuf[APS_ARG1] != 0)
422 return 1;
423
424 return 0;
425 }
426
427 static int
aps_detach(device_t self,int flags)428 aps_detach(device_t self, int flags)
429 {
430 struct aps_softc *sc = device_private(self);
431
432 callout_halt(&sc->sc_callout, NULL);
433 callout_destroy(&sc->sc_callout);
434
435 if (sc->sc_sme)
436 sysmon_envsys_unregister(sc->sc_sme);
437 if (sc->sc_bus_space_valid == true)
438 bus_space_unmap(sc->sc_iot, sc->sc_ioh, APS_ADDR_SIZE);
439
440 return 0;
441 }
442
443 static int
aps_read_data(struct aps_softc * sc)444 aps_read_data(struct aps_softc *sc)
445 {
446 unsigned char iobuf[16];
447
448 iobuf[APS_CMD] = 0x11;
449 if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_0, APS_READ_ALL))
450 return 1;
451
452 sc->aps_data.state = iobuf[APS_STATE];
453 sc->aps_data.x_accel = iobuf[APS_XACCEL] + 256 * iobuf[APS_XACCEL + 1];
454 sc->aps_data.y_accel = iobuf[APS_YACCEL] + 256 * iobuf[APS_YACCEL + 1];
455 sc->aps_data.temp1 = iobuf[APS_TEMP];
456 sc->aps_data.x_var = iobuf[APS_XVAR] + 256 * iobuf[APS_XVAR + 1];
457 sc->aps_data.y_var = iobuf[APS_YVAR] + 256 * iobuf[APS_YVAR + 1];
458 sc->aps_data.temp2 = iobuf[APS_TEMP2];
459 sc->aps_data.input = iobuf[APS_INPUT];
460
461 return 0;
462 }
463
464 static void
aps_refresh_sensor_data(struct aps_softc * sc)465 aps_refresh_sensor_data(struct aps_softc *sc)
466 {
467 int64_t temp;
468
469 if (aps_read_data(sc)) {
470 printf("aps0: read data failed\n");
471 return;
472 }
473
474 sc->sc_sensor[APS_SENSOR_XACCEL].value_cur = sc->aps_data.x_accel;
475 sc->sc_sensor[APS_SENSOR_YACCEL].value_cur = sc->aps_data.y_accel;
476
477 if (sc->aps_data.temp1 == 0xff)
478 sc->sc_sensor[APS_SENSOR_TEMP1].state = ENVSYS_SINVALID;
479 else {
480 /* convert to micro (mu) degrees */
481 temp = sc->aps_data.temp1 * 1000000;
482 /* convert to kelvin */
483 temp += 273150000;
484 sc->sc_sensor[APS_SENSOR_TEMP1].value_cur = temp;
485 sc->sc_sensor[APS_SENSOR_TEMP1].state = ENVSYS_SVALID;
486 }
487
488 if (sc->aps_data.temp2 == 0xff)
489 sc->sc_sensor[APS_SENSOR_TEMP2].state = ENVSYS_SINVALID;
490 else {
491 /* convert to micro (mu) degrees */
492 temp = sc->aps_data.temp2 * 1000000;
493 /* convert to kelvin */
494 temp += 273150000;
495 sc->sc_sensor[APS_SENSOR_TEMP2].value_cur = temp;
496 sc->sc_sensor[APS_SENSOR_TEMP2].state = ENVSYS_SVALID;
497 }
498
499 sc->sc_sensor[APS_SENSOR_XVAR].value_cur = sc->aps_data.x_var;
500 sc->sc_sensor[APS_SENSOR_YVAR].value_cur = sc->aps_data.y_var;
501 sc->sc_sensor[APS_SENSOR_KBACT].value_cur =
502 (sc->aps_data.input & APS_INPUT_KB) ? 1 : 0;
503 sc->sc_sensor[APS_SENSOR_MSACT].value_cur =
504 (sc->aps_data.input & APS_INPUT_MS) ? 1 : 0;
505 sc->sc_sensor[APS_SENSOR_LIDOPEN].value_cur =
506 (sc->aps_data.input & APS_INPUT_LIDOPEN) ? 1 : 0;
507 }
508
509 static void
aps_refresh(void * arg)510 aps_refresh(void *arg)
511 {
512 struct aps_softc *sc = arg;
513
514 aps_refresh_sensor_data(sc);
515 callout_schedule(&sc->sc_callout, (hz) / 2);
516 }
517
518 static bool
aps_suspend(device_t dv,const pmf_qual_t * qual)519 aps_suspend(device_t dv, const pmf_qual_t *qual)
520 {
521 struct aps_softc *sc = device_private(dv);
522
523 callout_stop(&sc->sc_callout);
524
525 return true;
526 }
527
528 static bool
aps_resume(device_t dv,const pmf_qual_t * qual)529 aps_resume(device_t dv, const pmf_qual_t *qual)
530 {
531 struct aps_softc *sc = device_private(dv);
532 unsigned char iobuf[16];
533
534 /*
535 * Redo the init sequence on resume, because APS is
536 * as forgetful as it is deaf.
537 */
538
539 /* get APS mode */
540 iobuf[APS_CMD] = 0x13;
541 if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_0, APS_READ_1)
542 || aps_init(sc))
543 aprint_error_dev(dv, "failed to wake up\n");
544 else
545 callout_schedule(&sc->sc_callout, (hz) / 2);
546
547 return true;
548 }
549
550 MODULE(MODULE_CLASS_DRIVER, aps, "sysmon_envsys");
551
552 #ifdef _MODULE
553 #include "ioconf.c"
554 #endif
555
556 static int
aps_modcmd(modcmd_t cmd,void * opaque)557 aps_modcmd(modcmd_t cmd, void *opaque)
558 {
559 switch (cmd) {
560 case MODULE_CMD_INIT:
561 #ifdef _MODULE
562 return config_init_component(cfdriver_ioconf_aps,
563 cfattach_ioconf_aps, cfdata_ioconf_aps);
564 #else
565 return 0;
566 #endif
567 case MODULE_CMD_FINI:
568 #ifdef _MODULE
569 return config_fini_component(cfdriver_ioconf_aps,
570 cfattach_ioconf_aps, cfdata_ioconf_aps);
571 #else
572 return 0;
573 #endif
574 default:
575 return ENOTTY;
576 }
577 }
578