xref: /openbsd-src/sys/dev/isa/aps.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: aps.c,v 1.18 2009/04/21 19:36:17 mk Exp $	*/
2 /*
3  * Copyright (c) 2005 Jonathan Gray <jsg@openbsd.org>
4  * Copyright (c) 2008 Can Erkin Acar <canacar@openbsd.org>
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  * A driver for the ThinkPad Active Protection System based on notes from
21  * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html
22  */
23 
24 #include <sys/param.h>
25 #include <sys/systm.h>
26 #include <sys/device.h>
27 #include <sys/kernel.h>
28 #include <sys/sensors.h>
29 #include <sys/timeout.h>
30 #include <machine/bus.h>
31 
32 #include <dev/isa/isareg.h>
33 #include <dev/isa/isavar.h>
34 
35 #if defined(APSDEBUG)
36 #define DPRINTF(x)		do { printf x; } while (0)
37 #else
38 #define DPRINTF(x)
39 #endif
40 
41 
42 /*
43  * EC interface on Thinkpad Laptops, from Linux HDAPS driver notes.
44  * From Renesans H8S/2140B Group Hardware Manual
45  * http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf
46  *
47  * EC uses LPC Channel 3 registers TWR0..15
48  */
49 
50 /* STR3 status register */
51 #define APS_STR3		0x04
52 
53 #define APS_STR3_IBF3B	0x80	/* Input buffer full (host->slave) */
54 #define APS_STR3_OBF3B	0x40	/* Output buffer full (slave->host)*/
55 #define APS_STR3_MWMF	0x20	/* Master write mode */
56 #define APS_STR3_SWMF	0x10	/* Slave write mode */
57 
58 
59 /* Base address of TWR registers */
60 #define APS_TWR_BASE		0x10
61 #define APS_TWR_RET		0x1f
62 
63 /* TWR registers */
64 #define APS_CMD			0x00
65 #define APS_ARG1		0x01
66 #define APS_ARG2		0x02
67 #define APS_ARG3		0x03
68 #define APS_RET			0x0f
69 
70 /* Sensor values */
71 #define APS_STATE		0x01
72 #define	APS_XACCEL		0x02
73 #define APS_YACCEL		0x04
74 #define APS_TEMP		0x06
75 #define	APS_XVAR		0x07
76 #define APS_YVAR		0x09
77 #define APS_TEMP2		0x0b
78 #define APS_UNKNOWN		0x0c
79 #define APS_INPUT		0x0d
80 
81 /* write masks for I/O, send command + 0-3 arguments*/
82 #define APS_WRITE_0		0x0001
83 #define APS_WRITE_1		0x0003
84 #define APS_WRITE_2		0x0007
85 #define APS_WRITE_3		0x000f
86 
87 /* read masks for I/O, read 0-3 values (skip command byte) */
88 #define APS_READ_0		0x0000
89 #define APS_READ_1		0x0002
90 #define APS_READ_2		0x0006
91 #define APS_READ_3		0x000e
92 
93 #define APS_READ_RET		0x8000
94 #define APS_READ_ALL		0xffff
95 
96 /* Bit definitions for APS_INPUT value */
97 #define APS_INPUT_KB		(1 << 5)
98 #define APS_INPUT_MS		(1 << 6)
99 #define APS_INPUT_LIDOPEN	(1 << 7)
100 
101 #define APS_ADDR_SIZE		0x1f
102 
103 struct sensor_rec {
104 	u_int8_t	state;
105 	u_int16_t	x_accel;
106 	u_int16_t	y_accel;
107 	u_int8_t	temp1;
108 	u_int16_t	x_var;
109 	u_int16_t	y_var;
110 	u_int8_t	temp2;
111 	u_int8_t	unk;
112 	u_int8_t	input;
113 };
114 
115 #define APS_NUM_SENSORS		9
116 
117 #define APS_SENSOR_XACCEL	0
118 #define APS_SENSOR_YACCEL	1
119 #define APS_SENSOR_XVAR		2
120 #define APS_SENSOR_YVAR		3
121 #define APS_SENSOR_TEMP1	4
122 #define APS_SENSOR_TEMP2	5
123 #define APS_SENSOR_KBACT	6
124 #define APS_SENSOR_MSACT	7
125 #define APS_SENSOR_LIDOPEN	8
126 
127 struct aps_softc {
128 	struct device sc_dev;
129 
130 	bus_space_tag_t aps_iot;
131 	bus_space_handle_t aps_ioh;
132 
133 	struct ksensor sensors[APS_NUM_SENSORS];
134 	struct ksensordev sensordev;
135 	void (*refresh_sensor_data)(struct aps_softc *);
136 
137 	struct sensor_rec aps_data;
138 };
139 
140 int	 aps_match(struct device *, void *, void *);
141 void	 aps_attach(struct device *, struct device *, void *);
142 
143 int	 aps_init(bus_space_tag_t, bus_space_handle_t);
144 int	 aps_read_data(struct aps_softc *);
145 void	 aps_refresh_sensor_data(struct aps_softc *);
146 void	 aps_refresh(void *);
147 void	 aps_power(int, void *);
148 int	 aps_do_io(bus_space_tag_t, bus_space_handle_t,
149 		   unsigned char *, int, int);
150 
151 struct cfattach aps_ca = {
152 	sizeof(struct aps_softc),
153 	aps_match,
154 	aps_attach
155 };
156 
157 struct cfdriver aps_cd = {
158 	NULL, "aps", DV_DULL
159 };
160 
161 struct timeout aps_timeout;
162 
163 
164 
165 /* properly communicate with the controller, writing a set of memory
166  * locations and reading back another set  */
167 int
168 aps_do_io(bus_space_tag_t iot, bus_space_handle_t ioh,
169 	  unsigned char *buf, int wmask, int rmask)
170 {
171 	int bp, stat, n;
172 
173 	DPRINTF(("aps_do_io: CMD: 0x%02x, wmask: 0x%04x, rmask: 0x%04x\n",
174 	       buf[0], wmask, rmask));
175 
176 	/* write init byte using arbitration */
177 	for (n = 0; n < 100; n++) {
178 		stat = bus_space_read_1(iot, ioh, APS_STR3);
179 		if (stat & (APS_STR3_OBF3B | APS_STR3_SWMF)) {
180 			bus_space_read_1(iot, ioh, APS_TWR_RET);
181 			continue;
182 		}
183 		bus_space_write_1(iot, ioh, APS_TWR_BASE, buf[0]);
184 		stat = bus_space_read_1(iot, ioh, APS_STR3);
185 		if (stat & (APS_STR3_MWMF))
186 			break;
187 		delay(1);
188 	}
189 
190 	if (n == 100) {
191 		DPRINTF(("aps_do_io: Failed to get bus\n"));
192 		return (1);
193 	}
194 
195 	/* write data bytes, init already sent */
196 	/* make sure last bye is always written as this will trigger slave */
197 	wmask |= APS_READ_RET;
198 	buf[APS_RET] = 0x01;
199 
200 	for (n = 1, bp = 2; n < 16; bp <<= 1, n++) {
201 		if (wmask & bp) {
202 			bus_space_write_1(iot, ioh, APS_TWR_BASE + n, buf[n]);
203 			DPRINTF(("aps_do_io:  write %2d 0x%02x\n", n, buf[n]));
204 		}
205 	}
206 
207 	for (n = 0; n < 100; n++) {
208 		stat = bus_space_read_1(iot, ioh, APS_STR3);
209 		if (stat & (APS_STR3_OBF3B))
210 			break;
211 		delay(5 * 100);
212 	}
213 
214 	if (n == 100) {
215 		DPRINTF(("aps_do_io: timeout waiting response\n"));
216 		return (1);
217 	}
218 	/* wait for data available */
219 	/* make sure to read the final byte to clear status */
220 	rmask |= APS_READ_RET;
221 
222 	/* read cmd and data bytes */
223 	for (n = 0, bp = 1; n < 16; bp <<= 1, n++) {
224 		if (rmask & bp) {
225 			buf[n] = bus_space_read_1(iot, ioh, APS_TWR_BASE + n);
226 			DPRINTF(("aps_do_io:  read %2d 0x%02x\n", n, buf[n]));
227 		}
228 	}
229 
230 	return (0);
231 }
232 
233 int
234 aps_match(struct device *parent, void *match, void *aux)
235 {
236 	bus_space_tag_t iot;
237 	bus_space_handle_t ioh;
238 	struct isa_attach_args *ia = aux;
239 	int iobase;
240 	u_int8_t cr;
241 
242 	char iobuf[16];
243 
244 	iot = ia->ia_iot;
245 	iobase = ia->ipa_io[0].base;
246 
247 	if (bus_space_map(iot, iobase, APS_ADDR_SIZE, 0, &ioh)) {
248 		DPRINTF(("aps: can't map i/o space\n"));
249 		return (0);
250 	}
251 
252 
253 	/* See if this machine has APS */
254 
255 	/* get APS mode */
256 	iobuf[APS_CMD] = 0x13;
257 	if (aps_do_io(iot, ioh, iobuf, APS_WRITE_0, APS_READ_1)) {
258 		bus_space_unmap(iot, ioh, APS_ADDR_SIZE);
259 		return (0);
260 	}
261 
262 	/*
263 	 * Observed values from Linux driver:
264 	 * 0x01: T42
265 	 * 0x02: chip already initialised
266 	 * 0x03: T41
267 	 * 0x05: T61
268 	 */
269 
270 	cr = iobuf[APS_ARG1];
271 
272 	bus_space_unmap(iot, ioh, APS_ADDR_SIZE);
273 	DPRINTF(("aps: state register 0x%x\n", cr));
274 
275 	if (iobuf[APS_RET] != 0 || cr < 1 || cr > 5) {
276 		DPRINTF(("aps0: unsupported state %d\n", cr));
277 		return (0);
278 	}
279 
280 	ia->ipa_nio = 1;
281 	ia->ipa_io[0].length = APS_ADDR_SIZE;
282 	ia->ipa_nmem = 0;
283 	ia->ipa_nirq = 0;
284 	ia->ipa_ndrq = 0;
285 
286 	return (1);
287 }
288 
289 void
290 aps_attach(struct device *parent, struct device *self, void *aux)
291 {
292 	struct aps_softc *sc = (void *)self;
293 	int iobase, i;
294 	bus_space_tag_t iot;
295 	bus_space_handle_t ioh;
296 	struct isa_attach_args *ia = aux;
297 
298 	iobase = ia->ipa_io[0].base;
299 	iot = sc->aps_iot = ia->ia_iot;
300 
301 	if (bus_space_map(iot, iobase, APS_ADDR_SIZE, 0, &sc->aps_ioh)) {
302 		printf(": can't map i/o space\n");
303 		return;
304 	}
305 
306 	ioh = sc->aps_ioh;
307 
308 	printf("\n");
309 
310 	if (aps_init(iot, ioh))
311 		goto out;
312 
313 	sc->sensors[APS_SENSOR_XACCEL].type = SENSOR_INTEGER;
314 	snprintf(sc->sensors[APS_SENSOR_XACCEL].desc,
315 	    sizeof(sc->sensors[APS_SENSOR_XACCEL].desc), "X_ACCEL");
316 
317 	sc->sensors[APS_SENSOR_YACCEL].type = SENSOR_INTEGER;
318 	snprintf(sc->sensors[APS_SENSOR_YACCEL].desc,
319 	    sizeof(sc->sensors[APS_SENSOR_YACCEL].desc), "Y_ACCEL");
320 
321 	sc->sensors[APS_SENSOR_TEMP1].type = SENSOR_TEMP;
322 	sc->sensors[APS_SENSOR_TEMP2].type = SENSOR_TEMP;
323 
324 	sc->sensors[APS_SENSOR_XVAR].type = SENSOR_INTEGER;
325 	snprintf(sc->sensors[APS_SENSOR_XVAR].desc,
326 	    sizeof(sc->sensors[APS_SENSOR_XVAR].desc), "X_VAR");
327 
328 	sc->sensors[APS_SENSOR_YVAR].type = SENSOR_INTEGER;
329 	snprintf(sc->sensors[APS_SENSOR_YVAR].desc,
330 	    sizeof(sc->sensors[APS_SENSOR_YVAR].desc), "Y_VAR");
331 
332 	sc->sensors[APS_SENSOR_KBACT].type = SENSOR_INDICATOR;
333 	snprintf(sc->sensors[APS_SENSOR_KBACT].desc,
334 	    sizeof(sc->sensors[APS_SENSOR_KBACT].desc), "Keyboard Active");
335 
336 	sc->sensors[APS_SENSOR_MSACT].type = SENSOR_INDICATOR;
337 	snprintf(sc->sensors[APS_SENSOR_MSACT].desc,
338 	    sizeof(sc->sensors[APS_SENSOR_MSACT].desc), "Mouse Active");
339 
340 	sc->sensors[APS_SENSOR_LIDOPEN].type = SENSOR_INDICATOR;
341 	snprintf(sc->sensors[APS_SENSOR_LIDOPEN].desc,
342 	    sizeof(sc->sensors[APS_SENSOR_LIDOPEN].desc), "Lid Open");
343 
344 	/* stop hiding and report to the authorities */
345 	strlcpy(sc->sensordev.xname, sc->sc_dev.dv_xname,
346 	    sizeof(sc->sensordev.xname));
347 	for (i = 0; i < APS_NUM_SENSORS ; i++) {
348 		sensor_attach(&sc->sensordev, &sc->sensors[i]);
349 	}
350 	sensordev_install(&sc->sensordev);
351 
352 	powerhook_establish(aps_power, (void *)sc);
353 
354 	/* Refresh sensor data every 0.5 seconds */
355 	timeout_set(&aps_timeout, aps_refresh, sc);
356 	timeout_add_msec(&aps_timeout, 500);
357 	return;
358 out:
359 	printf("%s: failed to initialize\n", sc->sc_dev.dv_xname);
360 	return;
361 }
362 
363 int
364 aps_init(bus_space_tag_t iot, bus_space_handle_t ioh)
365 {
366 	unsigned char iobuf[16];
367 
368 
369 	/* command 0x17/0x81: check EC */
370 	iobuf[APS_CMD] = 0x17;
371 	iobuf[APS_ARG1] = 0x81;
372 
373 	if (aps_do_io(iot, ioh, iobuf, APS_WRITE_1, APS_READ_3))
374 		return (1);
375 
376 	if (iobuf[APS_RET] != 0 ||iobuf[APS_ARG3] != 0)
377 		return (1);
378 
379 	/* Test values from the Linux driver */
380 	if ((iobuf[APS_ARG1] != 0 || iobuf[APS_ARG2] != 0x60) &&
381 	    (iobuf[APS_ARG1] != 1 || iobuf[APS_ARG2] != 0))
382 		return (1);
383 
384 	/* command 0x14: set power */
385 	iobuf[APS_CMD] = 0x14;
386 	iobuf[APS_ARG1] = 0x01;
387 
388 	if (aps_do_io(iot, ioh, iobuf, APS_WRITE_1, APS_READ_0))
389 		return (1);
390 
391 	if (iobuf[APS_RET] != 0)
392 		return (1);
393 
394 	/* command 0x10: set config (sample rate and order) */
395 	iobuf[APS_CMD] = 0x10;
396 	iobuf[APS_ARG1] = 0xc8;
397 	iobuf[APS_ARG2] = 0x00;
398 	iobuf[APS_ARG3] = 0x02;
399 
400 	if (aps_do_io(iot, ioh, iobuf, APS_WRITE_3, APS_READ_0))
401 		return (1);
402 
403 	if (iobuf[APS_RET] != 0)
404 		return (1);
405 
406 	/* command 0x11: refresh data */
407 	iobuf[APS_CMD] = 0x11;
408 	if (aps_do_io(iot, ioh, iobuf, APS_WRITE_0, APS_READ_1))
409 		return (1);
410 	if (iobuf[APS_ARG1] != 0)
411 		return (1);
412 
413 	return (0);
414 }
415 
416 int
417 aps_read_data(struct aps_softc *sc)
418 {
419 	bus_space_tag_t iot = sc->aps_iot;
420 	bus_space_handle_t ioh = sc->aps_ioh;
421 	unsigned char iobuf[16];
422 
423 	/* command 0x11: refresh data */
424 	iobuf[APS_CMD] = 0x11;
425 	if (aps_do_io(iot, ioh, iobuf, APS_WRITE_0, APS_READ_ALL))
426 		return (1);
427 
428 	sc->aps_data.state = iobuf[APS_STATE];
429 	sc->aps_data.x_accel = iobuf[APS_XACCEL] + 256 * iobuf[APS_XACCEL + 1];
430 	sc->aps_data.y_accel = iobuf[APS_YACCEL] + 256 * iobuf[APS_YACCEL + 1];
431 	sc->aps_data.temp1 = iobuf[APS_TEMP];
432 	sc->aps_data.x_var = iobuf[APS_XVAR] + 256 * iobuf[APS_XVAR + 1];
433 	sc->aps_data.y_var = iobuf[APS_YVAR] + 256 * iobuf[APS_YVAR + 1];
434 	sc->aps_data.temp2 = iobuf[APS_TEMP2];
435 	sc->aps_data.input = iobuf[APS_INPUT];
436 
437 	return (0);
438 }
439 
440 void
441 aps_refresh_sensor_data(struct aps_softc *sc)
442 {
443 	int64_t temp;
444 	int i;
445 
446 	if (aps_read_data(sc))
447 		return;
448 
449 	for (i = 0; i < APS_NUM_SENSORS; i++) {
450 		sc->sensors[i].flags &= ~SENSOR_FINVALID;
451 	}
452 
453 	sc->sensors[APS_SENSOR_XACCEL].value = sc->aps_data.x_accel;
454 	sc->sensors[APS_SENSOR_YACCEL].value = sc->aps_data.y_accel;
455 
456 	/* convert to micro (mu) degrees */
457 	temp = sc->aps_data.temp1 * 1000000;
458 	/* convert to kelvin */
459 	temp += 273150000;
460 	sc->sensors[APS_SENSOR_TEMP1].value = temp;
461 
462 	/* convert to micro (mu) degrees */
463 	temp = sc->aps_data.temp2 * 1000000;
464 	/* convert to kelvin */
465 	temp += 273150000;
466 	sc->sensors[APS_SENSOR_TEMP2].value = temp;
467 
468 	sc->sensors[APS_SENSOR_XVAR].value = sc->aps_data.x_var;
469 	sc->sensors[APS_SENSOR_YVAR].value = sc->aps_data.y_var;
470 	sc->sensors[APS_SENSOR_KBACT].value =
471 	    (sc->aps_data.input &  APS_INPUT_KB) ? 1 : 0;
472 	sc->sensors[APS_SENSOR_MSACT].value =
473 	    (sc->aps_data.input & APS_INPUT_MS) ? 1 : 0;
474 	sc->sensors[APS_SENSOR_LIDOPEN].value =
475 	    (sc->aps_data.input & APS_INPUT_LIDOPEN) ? 1 : 0;
476 }
477 
478 void
479 aps_refresh(void *arg)
480 {
481 	struct aps_softc *sc = (struct aps_softc *)arg;
482 
483 	aps_refresh_sensor_data(sc);
484 	timeout_add_msec(&aps_timeout, 500);
485 }
486 
487 void
488 aps_power(int why, void *arg)
489 {
490 	struct aps_softc *sc = (struct aps_softc *)arg;
491 	bus_space_tag_t iot = sc->aps_iot;
492 	bus_space_handle_t ioh = sc->aps_ioh;
493 	unsigned char iobuf[16];
494 
495 	if (why != PWR_RESUME) {
496 		timeout_del(&aps_timeout);
497 		return;
498 	}
499 	/*
500 	 * Redo the init sequence on resume, because APS is
501 	 * as forgetful as it is deaf.
502 	 */
503 
504 	/* get APS mode */
505 	iobuf[APS_CMD] = 0x13;
506 	if (aps_do_io(iot, ioh, iobuf, APS_WRITE_0, APS_READ_1)
507 	    || aps_init(iot, ioh))
508 		printf("aps: failed to wake up\n");
509 	else
510 		timeout_add_msec(&aps_timeout, 500);
511 }
512