1 /*        $NetBSD: aps.c,v 1.20 2025/02/17 22:56:46 andvar 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.20 2025/02/17 22:56:46 andvar 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 Renesas 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 byte 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                               sc->sc_sme = NULL;
352                               goto out;
353                     }
354           }
355         /*
356          * Register with the sysmon_envsys(9) framework.
357          */
358           sc->sc_sme->sme_name = device_xname(self);
359           sc->sc_sme->sme_flags = SME_DISABLE_REFRESH;
360 
361           if ((i = sysmon_envsys_register(sc->sc_sme))) {
362                     aprint_error_dev(self,
363                         "unable to register with sysmon (%d)\n", i);
364                     sysmon_envsys_destroy(sc->sc_sme);
365                     sc->sc_sme = NULL;
366                     goto out;
367           }
368 
369           if (!pmf_device_register(self, aps_suspend, aps_resume))
370                     aprint_error_dev(self, "couldn't establish power handler\n");
371 
372           /* Refresh sensor data every 0.5 seconds */
373           callout_schedule(&sc->sc_callout, (hz) / 2);
374 
375           return;
376 
377 out:
378           bus_space_unmap(sc->sc_iot, sc->sc_ioh, APS_ADDR_SIZE);
379 }
380 
381 static int
aps_init(struct aps_softc * sc)382 aps_init(struct aps_softc *sc)
383 {
384           unsigned char iobuf[16];
385 
386           /* command 0x17/0x81: check EC */
387           iobuf[APS_CMD] = 0x17;
388           iobuf[APS_ARG1] = 0x81;
389 
390           if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_1, APS_READ_3))
391                     return 1;
392           if (iobuf[APS_RET] != 0 ||iobuf[APS_ARG3] != 0)
393                     return 1;
394 
395           /* Test values from the Linux driver */
396           if ((iobuf[APS_ARG1] != 0 || iobuf[APS_ARG2] != 0x60) &&
397               (iobuf[APS_ARG1] != 1 || iobuf[APS_ARG2] != 0))
398                     return 1;
399 
400           /* command 0x14: set power */
401           iobuf[APS_CMD] = 0x14;
402           iobuf[APS_ARG1] = 0x01;
403 
404           if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_1, APS_READ_0))
405                     return 1;
406 
407           if (iobuf[APS_RET] != 0)
408                     return 1;
409 
410           /* command 0x10: set config (sample rate and order) */
411           iobuf[APS_CMD] = 0x10;
412           iobuf[APS_ARG1] = 0xc8;
413           iobuf[APS_ARG2] = 0x00;
414           iobuf[APS_ARG3] = 0x02;
415 
416           if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_3, APS_READ_0))
417                     return 1;
418 
419           /* command 0x11: refresh data */
420           iobuf[APS_CMD] = 0x11;
421           if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_0, APS_READ_1))
422                     return 1;
423           if (iobuf[APS_ARG1] != 0)
424                     return 1;
425 
426           return 0;
427 }
428 
429 static int
aps_detach(device_t self,int flags)430 aps_detach(device_t self, int flags)
431 {
432           struct aps_softc *sc = device_private(self);
433 
434         callout_halt(&sc->sc_callout, NULL);
435         callout_destroy(&sc->sc_callout);
436 
437           if (sc->sc_sme)
438                     sysmon_envsys_unregister(sc->sc_sme);
439           if (sc->sc_bus_space_valid == true)
440                     bus_space_unmap(sc->sc_iot, sc->sc_ioh, APS_ADDR_SIZE);
441 
442           return 0;
443 }
444 
445 static int
aps_read_data(struct aps_softc * sc)446 aps_read_data(struct aps_softc *sc)
447 {
448           unsigned char iobuf[16];
449 
450           iobuf[APS_CMD] = 0x11;
451           if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_0, APS_READ_ALL))
452                     return 1;
453 
454           sc->aps_data.state = iobuf[APS_STATE];
455           sc->aps_data.x_accel = iobuf[APS_XACCEL] + 256 * iobuf[APS_XACCEL + 1];
456           sc->aps_data.y_accel = iobuf[APS_YACCEL] + 256 * iobuf[APS_YACCEL + 1];
457           sc->aps_data.temp1 = iobuf[APS_TEMP];
458           sc->aps_data.x_var = iobuf[APS_XVAR] + 256 * iobuf[APS_XVAR + 1];
459           sc->aps_data.y_var = iobuf[APS_YVAR] + 256 * iobuf[APS_YVAR + 1];
460           sc->aps_data.temp2 = iobuf[APS_TEMP2];
461           sc->aps_data.input = iobuf[APS_INPUT];
462 
463           return 0;
464 }
465 
466 static void
aps_refresh_sensor_data(struct aps_softc * sc)467 aps_refresh_sensor_data(struct aps_softc *sc)
468 {
469           int64_t temp;
470 
471           if (aps_read_data(sc)) {
472                     printf("aps0: read data failed\n");
473                     return;
474           }
475 
476           sc->sc_sensor[APS_SENSOR_XACCEL].value_cur = sc->aps_data.x_accel;
477           sc->sc_sensor[APS_SENSOR_YACCEL].value_cur = sc->aps_data.y_accel;
478 
479           if (sc->aps_data.temp1 == 0xff)
480                     sc->sc_sensor[APS_SENSOR_TEMP1].state = ENVSYS_SINVALID;
481           else {
482                     /* convert to micro (mu) degrees */
483                     temp = sc->aps_data.temp1 * 1000000;
484                     /* convert to kelvin */
485                     temp += 273150000;
486                     sc->sc_sensor[APS_SENSOR_TEMP1].value_cur = temp;
487                     sc->sc_sensor[APS_SENSOR_TEMP1].state = ENVSYS_SVALID;
488           }
489 
490           if (sc->aps_data.temp2 == 0xff)
491                     sc->sc_sensor[APS_SENSOR_TEMP2].state = ENVSYS_SINVALID;
492           else {
493                     /* convert to micro (mu) degrees */
494                     temp = sc->aps_data.temp2 * 1000000;
495                     /* convert to kelvin */
496                     temp += 273150000;
497                     sc->sc_sensor[APS_SENSOR_TEMP2].value_cur = temp;
498                     sc->sc_sensor[APS_SENSOR_TEMP2].state = ENVSYS_SVALID;
499           }
500 
501           sc->sc_sensor[APS_SENSOR_XVAR].value_cur = sc->aps_data.x_var;
502           sc->sc_sensor[APS_SENSOR_YVAR].value_cur = sc->aps_data.y_var;
503           sc->sc_sensor[APS_SENSOR_KBACT].value_cur =
504               (sc->aps_data.input &  APS_INPUT_KB) ? 1 : 0;
505           sc->sc_sensor[APS_SENSOR_MSACT].value_cur =
506               (sc->aps_data.input & APS_INPUT_MS) ? 1 : 0;
507           sc->sc_sensor[APS_SENSOR_LIDOPEN].value_cur =
508               (sc->aps_data.input & APS_INPUT_LIDOPEN) ? 1 : 0;
509 }
510 
511 static void
aps_refresh(void * arg)512 aps_refresh(void *arg)
513 {
514           struct aps_softc *sc = arg;
515 
516           aps_refresh_sensor_data(sc);
517           callout_schedule(&sc->sc_callout, (hz) / 2);
518 }
519 
520 static bool
aps_suspend(device_t dv,const pmf_qual_t * qual)521 aps_suspend(device_t dv, const pmf_qual_t *qual)
522 {
523           struct aps_softc *sc = device_private(dv);
524 
525           callout_stop(&sc->sc_callout);
526 
527           return true;
528 }
529 
530 static bool
aps_resume(device_t dv,const pmf_qual_t * qual)531 aps_resume(device_t dv, const pmf_qual_t *qual)
532 {
533           struct aps_softc *sc = device_private(dv);
534           unsigned char iobuf[16];
535 
536           /*
537            * Redo the init sequence on resume, because APS is
538            * as forgetful as it is deaf.
539            */
540 
541           /* get APS mode */
542           iobuf[APS_CMD] = 0x13;
543           if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_0, APS_READ_1)
544               || aps_init(sc))
545                     aprint_error_dev(dv, "failed to wake up\n");
546           else
547                     callout_schedule(&sc->sc_callout, (hz) / 2);
548 
549           return true;
550 }
551 
552 MODULE(MODULE_CLASS_DRIVER, aps, "sysmon_envsys");
553 
554 #ifdef _MODULE
555 #include "ioconf.c"
556 #endif
557 
558 static int
aps_modcmd(modcmd_t cmd,void * opaque)559 aps_modcmd(modcmd_t cmd, void *opaque)
560 {
561           switch (cmd) {
562           case MODULE_CMD_INIT:
563 #ifdef _MODULE
564                     return config_init_component(cfdriver_ioconf_aps,
565                         cfattach_ioconf_aps, cfdata_ioconf_aps);
566 #else
567                     return 0;
568 #endif
569           case MODULE_CMD_FINI:
570 #ifdef _MODULE
571                     return config_fini_component(cfdriver_ioconf_aps,
572                         cfattach_ioconf_aps, cfdata_ioconf_aps);
573 #else
574                     return 0;
575 #endif
576           default:
577                     return ENOTTY;
578           }
579 }
580