1 /* $NetBSD: aps.c,v 1.8 2008/04/04 09:41:40 xtraeme Exp $ */
2 /* $OpenBSD: aps.c,v 1.15 2007/05/19 19:14:11 tedu Exp $ */
5 * Copyright (c) 2005 Jonathan Gray <jsg@openbsd.org>
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 * A driver for the ThinkPad Active Protection System based on notes from
22 * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html
25 #include <sys/cdefs.h>
26 __KERNEL_RCSID(0, "$NetBSD: aps.c,v 1.8 2008/04/04 09:41:40 xtraeme Exp $");
28 #include <sys/param.h>
29 #include <sys/systm.h>
30 #include <sys/device.h>
31 #include <sys/kernel.h>
32 #include <sys/callout.h>
36 #include <dev/sysmon/sysmonvar.h>
38 #include <dev/isa/isareg.h>
39 #include <dev/isa/isavar.h>
42 #define DPRINTF(x) do { printf x; } while (0)
47 #define APS_ACCEL_STATE 0x04
49 #define APS_STATE 0x11
50 #define APS_XACCEL 0x12
51 #define APS_YACCEL 0x14
55 #define APS_TEMP2 0x1b
56 #define APS_UNKNOWN 0x1c
57 #define APS_INPUT 0x1d
60 #define APS_STATE_NEWDATA 0x50
62 #define APS_CMD_START 0x01
64 #define APS_INPUT_KB (1 << 5)
65 #define APS_INPUT_MS (1 << 6)
66 #define APS_INPUT_LIDOPEN (1 << 7)
68 #define APS_ADDR_SIZE 0x1f
83 APS_SENSOR_XACCEL
= 0,
96 bus_space_tag_t sc_iot
;
97 bus_space_handle_t sc_ioh
;
99 struct sysmon_envsys
*sc_sme
;
100 envsys_data_t sc_sensor
[APS_NUM_SENSORS
];
101 struct callout sc_callout
;
103 struct sensor_rec aps_data
;
106 static int aps_match(device_t
, cfdata_t
, void *);
107 static void aps_attach(device_t
, device_t
, void *);
108 static int aps_detach(device_t
, int);
110 static int aps_init(struct aps_softc
*);
111 static uint8_t aps_mem_read_1(bus_space_tag_t
, bus_space_handle_t
,
113 static void aps_refresh_sensor_data(struct aps_softc
*sc
);
114 static void aps_refresh(void *);
115 static bool aps_suspend(device_t
, pmf_qual_t
);
116 static bool aps_resume(device_t
, pmf_qual_t
);
118 CFATTACH_DECL_NEW(aps
, sizeof(struct aps_softc
),
119 aps_match
, aps_attach
, aps_detach
, NULL
);
122 aps_match(device_t parent
, cfdata_t match
, void *aux
)
124 struct isa_attach_args
*ia
= aux
;
125 bus_space_tag_t iot
= ia
->ia_iot
;
126 bus_space_handle_t ioh
;
130 /* Must supply an address */
134 if (ISA_DIRECT_CONFIG(ia
))
137 if (ia
->ia_io
[0].ir_addr
== ISA_UNKNOWN_PORT
)
140 iobase
= ia
->ia_io
[0].ir_addr
;
142 if (bus_space_map(iot
, iobase
, APS_ADDR_SIZE
, 0, &ioh
)) {
143 aprint_error("aps: can't map i/o space\n");
147 /* See if this machine has APS */
148 bus_space_write_1(iot
, ioh
, APS_INIT
, 0x13);
149 bus_space_write_1(iot
, ioh
, APS_CMD
, 0x01);
151 /* ask again as the X40 is slightly deaf in one ear */
152 bus_space_read_1(iot
, ioh
, APS_CMD
);
153 bus_space_write_1(iot
, ioh
, APS_INIT
, 0x13);
154 bus_space_write_1(iot
, ioh
, APS_CMD
, 0x01);
156 if (!aps_mem_read_1(iot
, ioh
, APS_CMD
, 0x00)) {
157 bus_space_unmap(iot
, ioh
, APS_ADDR_SIZE
);
162 * Observed values from Linux driver:
164 * 0x02: chip already initialised
167 for (i
= 0; i
< 10; i
++) {
168 cr
= bus_space_read_1(iot
, ioh
, APS_STATE
);
169 if (cr
> 0 && cr
< 6)
174 bus_space_unmap(iot
, ioh
, APS_ADDR_SIZE
);
175 DPRINTF(("aps: state register 0x%x\n", cr
));
176 if (cr
< 1 || cr
> 5) {
177 DPRINTF(("aps0: unsupported state %d\n", cr
));
182 ia
->ia_io
[0].ir_size
= APS_ADDR_SIZE
;
191 aps_attach(device_t parent
, device_t self
, void *aux
)
193 struct aps_softc
*sc
= device_private(self
);
194 struct isa_attach_args
*ia
= aux
;
197 sc
->sc_iot
= ia
->ia_iot
;
198 iobase
= ia
->ia_io
[0].ir_addr
;
200 if (bus_space_map(sc
->sc_iot
, iobase
, APS_ADDR_SIZE
, 0, &sc
->sc_ioh
)) {
201 aprint_error(": can't map i/o space\n");
209 aprint_error_dev(self
, "failed to initialise\n");
213 /* Initialize sensors */
214 #define INITDATA(idx, unit, string) \
215 sc->sc_sensor[idx].units = unit; \
216 strlcpy(sc->sc_sensor[idx].desc, string, \
217 sizeof(sc->sc_sensor[idx].desc));
219 INITDATA(APS_SENSOR_XACCEL
, ENVSYS_INTEGER
, "X_ACCEL");
220 INITDATA(APS_SENSOR_YACCEL
, ENVSYS_INTEGER
, "Y_ACCEL");
221 INITDATA(APS_SENSOR_TEMP1
, ENVSYS_STEMP
, "TEMP_1");
222 INITDATA(APS_SENSOR_TEMP2
, ENVSYS_STEMP
, "TEMP_2");
223 INITDATA(APS_SENSOR_XVAR
, ENVSYS_INTEGER
, "X_VAR");
224 INITDATA(APS_SENSOR_YVAR
, ENVSYS_INTEGER
, "Y_VAR");
225 INITDATA(APS_SENSOR_KBACT
, ENVSYS_INDICATOR
, "Keyboard Active");
226 INITDATA(APS_SENSOR_MSACT
, ENVSYS_INDICATOR
, "Mouse Active");
227 INITDATA(APS_SENSOR_LIDOPEN
, ENVSYS_INDICATOR
, "Lid Open");
229 sc
->sc_sme
= sysmon_envsys_create();
230 for (i
= 0; i
< APS_NUM_SENSORS
; i
++) {
231 sc
->sc_sensor
[i
].state
= ENVSYS_SVALID
;
232 if (sysmon_envsys_sensor_attach(sc
->sc_sme
,
233 &sc
->sc_sensor
[i
])) {
234 sysmon_envsys_destroy(sc
->sc_sme
);
239 * Register with the sysmon_envsys(9) framework.
241 sc
->sc_sme
->sme_name
= device_xname(self
);
242 sc
->sc_sme
->sme_flags
= SME_DISABLE_REFRESH
;
244 if ((i
= sysmon_envsys_register(sc
->sc_sme
))) {
245 aprint_error_dev(self
,
246 "unable to register with sysmon (%d)\n", i
);
247 sysmon_envsys_destroy(sc
->sc_sme
);
251 if (!pmf_device_register(self
, aps_suspend
, aps_resume
))
252 aprint_error_dev(self
, "couldn't establish power handler\n");
254 /* Refresh sensor data every 0.5 seconds */
255 callout_init(&sc
->sc_callout
, 0);
256 callout_setfunc(&sc
->sc_callout
, aps_refresh
, sc
);
257 callout_schedule(&sc
->sc_callout
, (hz
) / 2);
259 aprint_normal_dev(self
, "Thinkpad Active Protection System\n");
263 bus_space_unmap(sc
->sc_iot
, sc
->sc_ioh
, APS_ADDR_SIZE
);
267 aps_init(struct aps_softc
*sc
)
269 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_INIT
, 0x17);
270 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_STATE
, 0x81);
271 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
, 0x01);
272 if (!aps_mem_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
, 0x00))
274 if (!aps_mem_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_STATE
, 0x00))
276 if (!aps_mem_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_XACCEL
, 0x60))
278 if (!aps_mem_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_XACCEL
+ 1, 0x00))
280 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_INIT
, 0x14);
281 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_STATE
, 0x01);
282 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
, 0x01);
283 if (!aps_mem_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
, 0x00))
285 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_INIT
, 0x10);
286 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_STATE
, 0xc8);
287 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_XACCEL
, 0x00);
288 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_XACCEL
+ 1, 0x02);
289 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
, 0x01);
290 if (!aps_mem_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
, 0x00))
293 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_INIT
, 0x11);
294 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
, 0x01);
295 if (!aps_mem_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_ACCEL_STATE
, 0x50))
297 if (!aps_mem_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_STATE
, 0x00))
304 aps_detach(device_t self
, int flags
)
306 struct aps_softc
*sc
= device_private(self
);
308 callout_stop(&sc
->sc_callout
);
309 callout_destroy(&sc
->sc_callout
);
310 sysmon_envsys_unregister(sc
->sc_sme
);
311 bus_space_unmap(sc
->sc_iot
, sc
->sc_ioh
, APS_ADDR_SIZE
);
317 aps_mem_read_1(bus_space_tag_t iot
, bus_space_handle_t ioh
, int reg
,
322 /* should take no longer than 50 microseconds */
323 for (i
= 0; i
< 10; i
++) {
324 cr
= bus_space_read_1(iot
, ioh
, reg
);
330 DPRINTF(("aps: reg 0x%x not val 0x%x!\n", reg
, val
));
335 aps_refresh_sensor_data(struct aps_softc
*sc
)
339 /* ask for new data */
340 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_INIT
, 0x11);
341 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
, 0x01);
342 if (!aps_mem_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_ACCEL_STATE
, 0x50))
346 bus_space_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_STATE
);
347 sc
->aps_data
.x_accel
=
348 bus_space_read_2(sc
->sc_iot
, sc
->sc_ioh
, APS_XACCEL
);
349 sc
->aps_data
.y_accel
=
350 bus_space_read_2(sc
->sc_iot
, sc
->sc_ioh
, APS_YACCEL
);
352 bus_space_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_TEMP
);
354 bus_space_read_2(sc
->sc_iot
, sc
->sc_ioh
, APS_XVAR
);
356 bus_space_read_2(sc
->sc_iot
, sc
->sc_ioh
, APS_YVAR
);
358 bus_space_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_TEMP2
);
360 bus_space_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_INPUT
);
362 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_INIT
, 0x11);
363 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
, 0x01);
365 /* tell accelerometer we're done reading from it */
366 bus_space_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
);
367 bus_space_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_ACCEL_STATE
);
369 sc
->sc_sensor
[APS_SENSOR_XACCEL
].value_cur
= sc
->aps_data
.x_accel
;
370 sc
->sc_sensor
[APS_SENSOR_YACCEL
].value_cur
= sc
->aps_data
.y_accel
;
372 /* convert to micro (mu) degrees */
373 temp
= sc
->aps_data
.temp1
* 1000000;
374 /* convert to kelvin */
376 sc
->sc_sensor
[APS_SENSOR_TEMP1
].value_cur
= temp
;
378 /* convert to micro (mu) degrees */
379 temp
= sc
->aps_data
.temp2
* 1000000;
380 /* convert to kelvin */
382 sc
->sc_sensor
[APS_SENSOR_TEMP2
].value_cur
= temp
;
384 sc
->sc_sensor
[APS_SENSOR_XVAR
].value_cur
= sc
->aps_data
.x_var
;
385 sc
->sc_sensor
[APS_SENSOR_YVAR
].value_cur
= sc
->aps_data
.y_var
;
386 sc
->sc_sensor
[APS_SENSOR_KBACT
].value_cur
=
387 (sc
->aps_data
.input
& APS_INPUT_KB
) ? 1 : 0;
388 sc
->sc_sensor
[APS_SENSOR_MSACT
].value_cur
=
389 (sc
->aps_data
.input
& APS_INPUT_MS
) ? 1 : 0;
390 sc
->sc_sensor
[APS_SENSOR_LIDOPEN
].value_cur
=
391 (sc
->aps_data
.input
& APS_INPUT_LIDOPEN
) ? 1 : 0;
395 aps_refresh(void *arg
)
397 struct aps_softc
*sc
= arg
;
399 aps_refresh_sensor_data(sc
);
400 callout_schedule(&sc
->sc_callout
, (hz
) / 2);
404 aps_suspend(device_t dv
, pmf_qual_t qual
)
406 struct aps_softc
*sc
= device_private(dv
);
408 callout_stop(&sc
->sc_callout
);
414 aps_resume(device_t dv
, pmf_qual_t qual
)
416 struct aps_softc
*sc
= device_private(dv
);
419 * Redo the init sequence on resume, because APS is
420 * as forgetful as it is deaf.
422 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_INIT
, 0x13);
423 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
, 0x01);
424 bus_space_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
);
425 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_INIT
, 0x13);
426 bus_space_write_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
, 0x01);
428 if (aps_mem_read_1(sc
->sc_iot
, sc
->sc_ioh
, APS_CMD
, 0x00) &&
430 callout_schedule(&sc
->sc_callout
, (hz
) / 2);
432 aprint_error_dev(dv
, "failed to wake up\n");