xref: /openbsd-src/sys/dev/acpi/acpithinkpad.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /* $OpenBSD: acpithinkpad.c,v 1.20 2009/04/26 02:59:05 cnst Exp $ */
2 /*
3  * Copyright (c) 2008 joshua stein <jcs@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 
21 #include <dev/acpi/acpireg.h>
22 #include <dev/acpi/acpivar.h>
23 #include <dev/acpi/acpidev.h>
24 #include <dev/acpi/amltypes.h>
25 #include <dev/acpi/dsdt.h>
26 
27 #define	THINKPAD_HKEY_VERSION		0x0100
28 
29 #define	THINKPAD_CMOS_VOLUME_DOWN	0x00
30 #define	THINKPAD_CMOS_VOLUME_UP		0x01
31 #define	THINKPAD_CMOS_VOLUME_MUTE	0x02
32 #define	THINKPAD_CMOS_BRIGHTNESS_UP	0x04
33 #define	THINKPAD_CMOS_BRIGHTNESS_DOWN	0x05
34 
35 #define	THINKPAD_BLUETOOTH_PRESENT	0x01
36 #define	THINKPAD_BLUETOOTH_ENABLED	0x02
37 
38 /* wan (not wifi) card */
39 #define	THINKPAD_WAN_PRESENT		0x01
40 #define	THINKPAD_WAN_ENABLED		0x02
41 
42 /* type 1 events */
43 #define	THINKPAD_BUTTON_FN_F1		0x001
44 #define	THINKPAD_BUTTON_LOCK_SCREEN	0x002
45 #define	THINKPAD_BUTTON_BATTERY_INFO	0x003
46 #define	THINKPAD_BUTTON_SUSPEND		0x004
47 #define	THINKPAD_BUTTON_WIRELESS	0x005
48 #define	THINKPAD_BUTTON_FN_F6		0x006
49 #define	THINKPAD_BUTTON_EXTERNAL_SCREEN	0x007
50 #define	THINKPAD_BUTTON_POINTER_SWITCH	0x008
51 #define	THINKPAD_BUTTON_EJECT		0x009
52 #define	THINKPAD_BUTTON_BRIGHTNESS_UP	0x010
53 #define	THINKPAD_BUTTON_BRIGHTNESS_DOWN	0x011
54 #define	THINKPAD_BUTTON_THINKLIGHT	0x012
55 #define	THINKPAD_BUTTON_FN_SPACE	0x014
56 #define	THINKPAD_BUTTON_VOLUME_UP	0x015
57 #define	THINKPAD_BUTTON_VOLUME_DOWN	0x016
58 #define	THINKPAD_BUTTON_VOLUME_MUTE	0x017
59 #define	THINKPAD_BUTTON_THINKVANTAGE	0x018
60 #define	THINKPAD_BUTTON_FN_F11		0x00b
61 #define	THINKPAD_BUTTON_HIBERNATE	0x00c
62 
63 /* type 5 events */
64 #define	THINKPAD_LID_OPEN		0x001
65 #define	THINKPAD_LID_CLOSED		0x002
66 #define	THINKPAD_TABLET_SCREEN_NORMAL	0x00a
67 #define	THINKPAD_TABLET_SCREEN_ROTATED	0x009
68 #define	THINKPAD_BRIGHTNESS_CHANGED	0x010
69 #define	THINKPAD_TABLET_PEN_INSERTED	0x00b
70 #define	THINKPAD_TABLET_PEN_REMOVED	0x00c
71 
72 /* type 6 events */
73 #define	THINKPAD_POWER_CHANGED		0x030
74 
75 /* type 7 events */
76 #define	THINKPAD_SWITCH_WIRELESS	0x000
77 
78 #define THINKPAD_NSENSORS 9
79 #define THINKPAD_NTEMPSENSORS 8
80 
81 #define THINKPAD_ECOFFSET_FANLO		0x84
82 #define THINKPAD_ECOFFSET_FANHI		0x85
83 
84 struct acpithinkpad_softc {
85 	struct device		sc_dev;
86 
87 	struct acpiec_softc     *sc_ec;
88 	struct acpi_softc	*sc_acpi;
89 	struct aml_node		*sc_devnode;
90 
91 	struct ksensor           sc_sens[THINKPAD_NSENSORS];
92 	struct ksensordev        sc_sensdev;
93 };
94 
95 extern void acpiec_read(struct acpiec_softc *, u_int8_t, int, u_int8_t *);
96 
97 int	thinkpad_match(struct device *, void *, void *);
98 void	thinkpad_attach(struct device *, struct device *, void *);
99 int	thinkpad_hotkey(struct aml_node *, int, void *);
100 int	thinkpad_enable_events(struct acpithinkpad_softc *);
101 int	thinkpad_toggle_bluetooth(struct acpithinkpad_softc *);
102 int	thinkpad_toggle_wan(struct acpithinkpad_softc *);
103 int	thinkpad_cmos(struct acpithinkpad_softc *sc, uint8_t);
104 int	thinkpad_volume_down(struct acpithinkpad_softc *);
105 int	thinkpad_volume_up(struct acpithinkpad_softc *);
106 int	thinkpad_volume_mute(struct acpithinkpad_softc *);
107 int	thinkpad_brightness_up(struct acpithinkpad_softc *);
108 int	thinkpad_brightness_down(struct acpithinkpad_softc *);
109 
110 void    thinkpad_sensor_attach(struct acpithinkpad_softc *sc);
111 void    thinkpad_sensor_refresh(void *);
112 
113 struct cfattach acpithinkpad_ca = {
114 	sizeof(struct acpithinkpad_softc), thinkpad_match, thinkpad_attach
115 };
116 
117 struct cfdriver acpithinkpad_cd = {
118 	NULL, "acpithinkpad", DV_DULL
119 };
120 
121 const char *acpithinkpad_hids[] = { ACPI_DEV_THINKPAD, 0 };
122 
123 int
124 thinkpad_match(struct device *parent, void *match, void *aux)
125 {
126 	struct acpi_attach_args	*aa = aux;
127 	struct cfdata		*cf = match;
128 	int64_t			res;
129 	int			rv = 0;
130 
131 	if (!acpi_matchhids(aa, acpithinkpad_hids, cf->cf_driver->cd_name))
132 		return (0);
133 
134 	if (aml_evalinteger((struct acpi_softc *)parent, aa->aaa_node,
135 	    "MHKV", 0, NULL, &res))
136 		return (0);
137 
138 	if (res == THINKPAD_HKEY_VERSION)
139 		rv = 1;
140 
141 	return (rv);
142 }
143 
144 void
145 thinkpad_sensor_attach(struct acpithinkpad_softc *sc)
146 {
147 	int i;
148 
149 	if (sc->sc_acpi->sc_ec == NULL)
150 		return;
151 	sc->sc_ec = sc->sc_acpi->sc_ec;
152 
153 	/* Add temperature probes */
154 	strlcpy(sc->sc_sensdev.xname, DEVNAME(sc),
155 	    sizeof(sc->sc_sensdev.xname));
156 	for (i=0; i<THINKPAD_NTEMPSENSORS; i++) {
157 		sc->sc_sens[i].type = SENSOR_TEMP;
158 		sensor_attach(&sc->sc_sensdev, &sc->sc_sens[i]);
159 	}
160 
161 	/* Add fan probe */
162 	sc->sc_sens[i].type = SENSOR_FANRPM;
163 	sensor_attach(&sc->sc_sensdev, &sc->sc_sens[i]);
164 
165 	sensordev_install(&sc->sc_sensdev);
166 }
167 
168 void
169 thinkpad_sensor_refresh(void *arg)
170 {
171 	struct acpithinkpad_softc *sc = arg;
172 	u_int8_t lo, hi, i;
173 	int64_t tmp;
174 	char sname[5];
175 
176 	/* Refresh sensor readings */
177 	for (i=0; i<THINKPAD_NTEMPSENSORS; i++) {
178 		snprintf(sname, sizeof(sname), "TMP%d", i);
179 		aml_evalinteger(sc->sc_acpi, sc->sc_ec->sc_devnode,
180 		    sname, 0, 0, &tmp);
181 		sc->sc_sens[i].value = (tmp * 1000000) + 273150000;
182 		if (tmp > 127 || tmp < -127)
183 			sc->sc_sens[i].flags = SENSOR_FINVALID;
184 	}
185 
186 	/* Read fan RPM */
187 	acpiec_read(sc->sc_ec, THINKPAD_ECOFFSET_FANLO, 1, &lo);
188 	acpiec_read(sc->sc_ec, THINKPAD_ECOFFSET_FANHI, 1, &hi);
189 	sc->sc_sens[i].value = ((hi << 8L) + lo);
190 }
191 
192 void
193 thinkpad_attach(struct device *parent, struct device *self, void *aux)
194 {
195 	struct acpithinkpad_softc *sc = (struct acpithinkpad_softc *)self;
196 	struct acpi_attach_args	*aa = aux;
197 
198 	sc->sc_acpi = (struct acpi_softc *)parent;
199 	sc->sc_devnode = aa->aaa_node;
200 
201 	printf("\n");
202 
203 	/* set event mask to receive everything */
204 	thinkpad_enable_events(sc);
205 	thinkpad_sensor_attach(sc);
206 
207 	/* run thinkpad_hotkey on button presses */
208 	aml_register_notify(sc->sc_devnode, aa->aaa_dev,
209 	    thinkpad_hotkey, sc, ACPIDEV_POLL);
210 }
211 
212 int
213 thinkpad_enable_events(struct acpithinkpad_softc *sc)
214 {
215 	struct aml_value	arg, args[2];
216 	int64_t			mask;
217 	int			i, rv = 1;
218 
219 	/* get the supported event mask */
220 	if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "MHKA", 0, NULL, &mask)) {
221 		printf("%s: no MHKA\n", DEVNAME(sc));
222 		goto fail;
223 	}
224 
225 	/* update hotkey mask */
226 	bzero(args, sizeof(args));
227 	args[0].type = args[1].type = AML_OBJTYPE_INTEGER;
228 	for (i = 0; i < 32; i++) {
229 		args[0].v_integer = i + 1;
230 		args[1].v_integer = (((1 << i) & mask) != 0);
231 
232 		if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "MHKM", 2, args,
233 		    NULL)) {
234 			printf("%s: couldn't toggle MHKM\n", DEVNAME(sc));
235 			goto fail;
236 		}
237 	}
238 
239 	/* enable hotkeys */
240 	bzero(&arg, sizeof(arg));
241 	arg.type = AML_OBJTYPE_INTEGER;
242 	arg.v_integer = 1;
243 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "MHKC", 1, &arg, NULL)) {
244 		printf("%s: couldn't enable hotkeys\n", DEVNAME(sc));
245 		goto fail;
246 	}
247 
248 	rv = 0;
249 fail:
250 	return (rv);
251 }
252 
253 int
254 thinkpad_hotkey(struct aml_node *node, int notify_type, void *arg)
255 {
256 	struct acpithinkpad_softc *sc = arg;
257 	int			type, event, handled, rv = 1, tot = 0;
258 	int64_t			val;
259 
260 	if (notify_type == 0x00) {
261 		/* poll sensors */
262 		thinkpad_sensor_refresh(sc);
263 		return 0;
264 	}
265 	if (notify_type != 0x80)
266 		goto fail;
267 
268 	for (;;) {
269 		if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "MHKP", 0, NULL,
270 		    &val))
271 			goto done;
272 		if (val == 0)
273 			goto done;
274 
275 		type = (val & 0xf000) >> 12;
276 		event = val & 0x0fff;
277 		handled = 0;
278 
279 		switch (type) {
280 		case 1:
281 			switch (event) {
282 			case THINKPAD_BUTTON_BRIGHTNESS_UP:
283 				thinkpad_brightness_up(sc);
284 				handled = 1;
285 				break;
286 			case THINKPAD_BUTTON_BRIGHTNESS_DOWN:
287 				thinkpad_brightness_down(sc);
288 				handled = 1;
289 				break;
290 			case THINKPAD_BUTTON_WIRELESS:
291 				thinkpad_toggle_bluetooth(sc);
292 				handled = 1;
293 				break;
294 			case THINKPAD_BUTTON_SUSPEND:
295 				handled = 1;
296 				/*
297 				acpi_enter_sleep_state(sc->sc_acpi,
298 				    ACPI_STATE_S3);
299 				*/
300 				break;
301 			case THINKPAD_BUTTON_HIBERNATE:
302 			case THINKPAD_BUTTON_FN_F1:
303 			case THINKPAD_BUTTON_LOCK_SCREEN:
304 			case THINKPAD_BUTTON_BATTERY_INFO:
305 			case THINKPAD_BUTTON_FN_F6:
306 			case THINKPAD_BUTTON_EXTERNAL_SCREEN:
307 			case THINKPAD_BUTTON_POINTER_SWITCH:
308 			case THINKPAD_BUTTON_EJECT:
309 			case THINKPAD_BUTTON_THINKLIGHT:
310 			case THINKPAD_BUTTON_FN_SPACE:
311 				handled = 1;
312 				break;
313 			case THINKPAD_BUTTON_VOLUME_DOWN:
314 				thinkpad_volume_down(sc);
315 				handled = 1;
316 				break;
317 			case THINKPAD_BUTTON_VOLUME_UP:
318 				thinkpad_volume_up(sc);
319 				handled = 1;
320 				break;
321 			case THINKPAD_BUTTON_VOLUME_MUTE:
322 				thinkpad_volume_mute(sc);
323 				handled = 1;
324 				break;
325 			case THINKPAD_BUTTON_THINKVANTAGE:
326 			case THINKPAD_BUTTON_FN_F11:
327 				handled = 1;
328 				break;
329 			}
330 			break;
331 		case 5:
332 			switch (event) {
333 			case THINKPAD_LID_OPEN:
334 			case THINKPAD_LID_CLOSED:
335 			case THINKPAD_TABLET_SCREEN_NORMAL:
336 			case THINKPAD_TABLET_SCREEN_ROTATED:
337 			case THINKPAD_BRIGHTNESS_CHANGED:
338 			case THINKPAD_TABLET_PEN_INSERTED:
339 			case THINKPAD_TABLET_PEN_REMOVED:
340 				handled = 1;
341 				break;
342 			}
343 			break;
344 		case 6:
345 			switch (event) {
346 			case THINKPAD_POWER_CHANGED:
347 				handled = 1;
348 				break;
349 			}
350 			break;
351 		case 7:
352 			switch (event) {
353 			case THINKPAD_SWITCH_WIRELESS:
354 				handled = 1;
355 				break;
356 			}
357 			break;
358 		}
359 
360 		if (handled)
361 			tot++;
362 		else
363 			printf("%s: unknown type %d event 0x%03x\n",
364 			    DEVNAME(sc), type, event);
365 	}
366 done:
367 	if (tot)
368 		rv = 0;
369 fail:
370 	return (rv);
371 }
372 
373 int
374 thinkpad_toggle_bluetooth(struct acpithinkpad_softc *sc)
375 {
376 	struct aml_value	arg;
377 	int			rv = 1;
378 	int64_t			bluetooth;
379 
380 	if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "GBDC", 0, NULL, &bluetooth))
381 		goto fail;
382 
383 	if (!(bluetooth & THINKPAD_BLUETOOTH_PRESENT))
384 		goto fail;
385 
386 	bzero(&arg, sizeof(arg));
387 	arg.type = AML_OBJTYPE_INTEGER;
388 	arg.v_integer = bluetooth ^= THINKPAD_BLUETOOTH_ENABLED;
389 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "SBDC", 1, &arg, NULL)) {
390 		printf("%s: couldn't toggle bluetooth\n", DEVNAME(sc));
391 		goto fail;
392 	}
393 
394 	rv = 0;
395 fail:
396 	return (rv);
397 }
398 
399 int
400 thinkpad_toggle_wan(struct acpithinkpad_softc *sc)
401 {
402 	struct aml_value	arg;
403 	int			rv = 1;
404 	int64_t			wan;
405 
406 	if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "GWAN", 0, NULL, &wan))
407 		goto fail;
408 
409 	if (!(wan & THINKPAD_WAN_PRESENT))
410 		goto fail;
411 
412 	bzero(&arg, sizeof(arg));
413 	arg.type = AML_OBJTYPE_INTEGER;
414 	arg.v_integer = (wan ^= THINKPAD_WAN_ENABLED);
415 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "SWAN", 1, &arg, NULL)) {
416 		printf("%s: couldn't toggle wan\n", DEVNAME(sc));
417 		goto fail;
418 	}
419 
420 	rv = 0;
421 fail:
422 	return (rv);
423 }
424 
425 int
426 thinkpad_cmos(struct acpithinkpad_softc *sc, uint8_t cmd)
427 {
428 	struct aml_value	arg;
429 
430 	bzero(&arg, sizeof(arg));
431 	arg.type = AML_OBJTYPE_INTEGER;
432 	arg.v_integer = cmd;
433 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "\\UCMS", 1, &arg,
434 	    NULL)) {
435 		printf("%s: cmos command 0x%x failed\n", DEVNAME(sc), cmd);
436 		return (1);
437 	}
438 
439 	return (0);
440 }
441 
442 int
443 thinkpad_volume_down(struct acpithinkpad_softc *sc)
444 {
445 	return (thinkpad_cmos(sc, THINKPAD_CMOS_VOLUME_DOWN));
446 }
447 
448 int
449 thinkpad_volume_up(struct acpithinkpad_softc *sc)
450 {
451 	return (thinkpad_cmos(sc, THINKPAD_CMOS_VOLUME_UP));
452 }
453 
454 int
455 thinkpad_volume_mute(struct acpithinkpad_softc *sc)
456 {
457 	return (thinkpad_cmos(sc, THINKPAD_CMOS_VOLUME_MUTE));
458 }
459 
460 int
461 thinkpad_brightness_up(struct acpithinkpad_softc *sc)
462 {
463 	return (thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_UP));
464 }
465 
466 int
467 thinkpad_brightness_down(struct acpithinkpad_softc *sc)
468 {
469 	return (thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_DOWN));
470 }
471