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