xref: /openbsd-src/sys/dev/acpi/acpithinkpad.c (revision 2b0358df1d88d06ef4139321dd05bd5e05d91eaf)
1 /* $OpenBSD: acpithinkpad.c,v 1.18 2009/03/11 20:52:11 jordan 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 		snprintf(sc->sc_sens[i].desc, sizeof(sc->sc_sens[i].desc),
158 		    "TMP%d", i);
159 		sc->sc_sens[i].type = SENSOR_TEMP;
160 		sc->sc_sens[i].value = 0;
161 		sensor_attach(&sc->sc_sensdev, &sc->sc_sens[i]);
162 	}
163 
164 	/* Add fan probe */
165 	strlcpy(sc->sc_sens[i].desc, "fan",
166 	    sizeof(sc->sc_sens[i].desc));
167 	sc->sc_sens[i].type = SENSOR_FANRPM;
168 	sc->sc_sens[i].value = 0;
169 	sensor_attach(&sc->sc_sensdev, &sc->sc_sens[i]);
170 
171 	sensordev_install(&sc->sc_sensdev);
172 }
173 
174 void
175 thinkpad_sensor_refresh(void *arg)
176 {
177 	struct acpithinkpad_softc *sc = arg;
178 	u_int8_t lo, hi, i;
179 	int64_t tmp;
180 
181 	/* Refresh sensor readings */
182 	for (i=0; i<THINKPAD_NTEMPSENSORS; i++) {
183 		aml_evalinteger(sc->sc_acpi, sc->sc_ec->sc_devnode,
184 		    sc->sc_sens[i].desc, 0, 0, &tmp);
185 		sc->sc_sens[i].value = (tmp * 1000000) + 273150000;
186 		if (tmp > 127 || tmp < -127)
187 			sc->sc_sens[i].flags = SENSOR_FINVALID;
188 	}
189 
190 	/* Read fan RPM */
191 	acpiec_read(sc->sc_ec, THINKPAD_ECOFFSET_FANLO, 1, &lo);
192 	acpiec_read(sc->sc_ec, THINKPAD_ECOFFSET_FANHI, 1, &hi);
193 	sc->sc_sens[i].value = ((hi << 8L) + lo);
194 }
195 
196 void
197 thinkpad_attach(struct device *parent, struct device *self, void *aux)
198 {
199 	struct acpithinkpad_softc *sc = (struct acpithinkpad_softc *)self;
200 	struct acpi_attach_args	*aa = aux;
201 
202 	sc->sc_acpi = (struct acpi_softc *)parent;
203 	sc->sc_devnode = aa->aaa_node;
204 
205 	printf("\n");
206 
207 	/* set event mask to receive everything */
208 	thinkpad_enable_events(sc);
209 	thinkpad_sensor_attach(sc);
210 
211 	/* run thinkpad_hotkey on button presses */
212 	aml_register_notify(sc->sc_devnode, aa->aaa_dev,
213 	    thinkpad_hotkey, sc, ACPIDEV_POLL);
214 }
215 
216 int
217 thinkpad_enable_events(struct acpithinkpad_softc *sc)
218 {
219 	struct aml_value	arg, args[2];
220 	int64_t			mask;
221 	int			i, rv = 1;
222 
223 	/* get the supported event mask */
224 	if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "MHKA", 0, NULL, &mask)) {
225 		printf("%s: no MHKA\n", DEVNAME(sc));
226 		goto fail;
227 	}
228 
229 	/* update hotkey mask */
230 	bzero(args, sizeof(args));
231 	args[0].type = args[1].type = AML_OBJTYPE_INTEGER;
232 	for (i = 0; i < 32; i++) {
233 		args[0].v_integer = i + 1;
234 		args[1].v_integer = (((1 << i) & mask) != 0);
235 
236 		if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "MHKM", 2, args,
237 		    NULL)) {
238 			printf("%s: couldn't toggle MHKM\n", DEVNAME(sc));
239 			goto fail;
240 		}
241 	}
242 
243 	/* enable hotkeys */
244 	bzero(&arg, sizeof(arg));
245 	arg.type = AML_OBJTYPE_INTEGER;
246 	arg.v_integer = 1;
247 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "MHKC", 1, &arg, NULL)) {
248 		printf("%s: couldn't enable hotkeys\n", DEVNAME(sc));
249 		goto fail;
250 	}
251 
252 	rv = 0;
253 fail:
254 	return (rv);
255 }
256 
257 int
258 thinkpad_hotkey(struct aml_node *node, int notify_type, void *arg)
259 {
260 	struct acpithinkpad_softc *sc = arg;
261 	int			type, event, handled, rv = 1, tot = 0;
262 	int64_t			val;
263 
264 	if (notify_type == 0x00) {
265 		/* poll sensors */
266 		thinkpad_sensor_refresh(sc);
267 		return 0;
268 	}
269 	if (notify_type != 0x80)
270 		goto fail;
271 
272 	for (;;) {
273 		if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "MHKP", 0, NULL,
274 		    &val))
275 			goto done;
276 		if (val == 0)
277 			goto done;
278 
279 		type = (val & 0xf000) >> 12;
280 		event = val & 0x0fff;
281 		handled = 0;
282 
283 		switch (type) {
284 		case 1:
285 			switch (event) {
286 			case THINKPAD_BUTTON_BRIGHTNESS_UP:
287 				thinkpad_brightness_up(sc);
288 				handled = 1;
289 				break;
290 			case THINKPAD_BUTTON_BRIGHTNESS_DOWN:
291 				thinkpad_brightness_down(sc);
292 				handled = 1;
293 				break;
294 			case THINKPAD_BUTTON_WIRELESS:
295 				thinkpad_toggle_bluetooth(sc);
296 				handled = 1;
297 				break;
298 			case THINKPAD_BUTTON_SUSPEND:
299 				handled = 1;
300 				/*
301 				acpi_enter_sleep_state(sc->sc_acpi,
302 				    ACPI_STATE_S3);
303 				*/
304 				break;
305 			case THINKPAD_BUTTON_HIBERNATE:
306 			case THINKPAD_BUTTON_FN_F1:
307 			case THINKPAD_BUTTON_LOCK_SCREEN:
308 			case THINKPAD_BUTTON_BATTERY_INFO:
309 			case THINKPAD_BUTTON_FN_F6:
310 			case THINKPAD_BUTTON_EXTERNAL_SCREEN:
311 			case THINKPAD_BUTTON_POINTER_SWITCH:
312 			case THINKPAD_BUTTON_EJECT:
313 			case THINKPAD_BUTTON_THINKLIGHT:
314 			case THINKPAD_BUTTON_FN_SPACE:
315 				handled = 1;
316 				break;
317 			case THINKPAD_BUTTON_VOLUME_DOWN:
318 				thinkpad_volume_down(sc);
319 				handled = 1;
320 				break;
321 			case THINKPAD_BUTTON_VOLUME_UP:
322 				thinkpad_volume_up(sc);
323 				handled = 1;
324 				break;
325 			case THINKPAD_BUTTON_VOLUME_MUTE:
326 				thinkpad_volume_mute(sc);
327 				handled = 1;
328 				break;
329 			case THINKPAD_BUTTON_THINKVANTAGE:
330 			case THINKPAD_BUTTON_FN_F11:
331 				handled = 1;
332 				break;
333 			}
334 			break;
335 		case 5:
336 			switch (event) {
337 			case THINKPAD_LID_OPEN:
338 			case THINKPAD_LID_CLOSED:
339 			case THINKPAD_TABLET_SCREEN_NORMAL:
340 			case THINKPAD_TABLET_SCREEN_ROTATED:
341 			case THINKPAD_BRIGHTNESS_CHANGED:
342 			case THINKPAD_TABLET_PEN_INSERTED:
343 			case THINKPAD_TABLET_PEN_REMOVED:
344 				handled = 1;
345 				break;
346 			}
347 			break;
348 		case 6:
349 			switch (event) {
350 			case THINKPAD_POWER_CHANGED:
351 				handled = 1;
352 				break;
353 			}
354 			break;
355 		case 7:
356 			switch (event) {
357 			case THINKPAD_SWITCH_WIRELESS:
358 				handled = 1;
359 				break;
360 			}
361 			break;
362 		}
363 
364 		if (handled)
365 			tot++;
366 		else
367 			printf("%s: unknown type %d event 0x%03x\n",
368 			    DEVNAME(sc), type, event);
369 	}
370 done:
371 	if (tot)
372 		rv = 0;
373 fail:
374 	return (rv);
375 }
376 
377 int
378 thinkpad_toggle_bluetooth(struct acpithinkpad_softc *sc)
379 {
380 	struct aml_value	arg;
381 	int			rv = 1;
382 	int64_t			bluetooth;
383 
384 	if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "GBDC", 0, NULL, &bluetooth))
385 		goto fail;
386 
387 	if (!(bluetooth & THINKPAD_BLUETOOTH_PRESENT))
388 		goto fail;
389 
390 	bzero(&arg, sizeof(arg));
391 	arg.type = AML_OBJTYPE_INTEGER;
392 	arg.v_integer = bluetooth ^= THINKPAD_BLUETOOTH_ENABLED;
393 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "SBDC", 1, &arg, NULL)) {
394 		printf("%s: couldn't toggle bluetooth\n", DEVNAME(sc));
395 		goto fail;
396 	}
397 
398 	rv = 0;
399 fail:
400 	return (rv);
401 }
402 
403 int
404 thinkpad_toggle_wan(struct acpithinkpad_softc *sc)
405 {
406 	struct aml_value	arg;
407 	int			rv = 1;
408 	int64_t			wan;
409 
410 	if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "GWAN", 0, NULL, &wan))
411 		goto fail;
412 
413 	if (!(wan & THINKPAD_WAN_PRESENT))
414 		goto fail;
415 
416 	bzero(&arg, sizeof(arg));
417 	arg.type = AML_OBJTYPE_INTEGER;
418 	arg.v_integer = (wan ^= THINKPAD_WAN_ENABLED);
419 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "SWAN", 1, &arg, NULL)) {
420 		printf("%s: couldn't toggle wan\n", DEVNAME(sc));
421 		goto fail;
422 	}
423 
424 	rv = 0;
425 fail:
426 	return (rv);
427 }
428 
429 int
430 thinkpad_cmos(struct acpithinkpad_softc *sc, uint8_t cmd)
431 {
432 	struct aml_value	arg;
433 
434 	bzero(&arg, sizeof(arg));
435 	arg.type = AML_OBJTYPE_INTEGER;
436 	arg.v_integer = cmd;
437 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "\\UCMS", 1, &arg,
438 	    NULL)) {
439 		printf("%s: cmos command 0x%x failed\n", DEVNAME(sc), cmd);
440 		return (1);
441 	}
442 
443 	return (0);
444 }
445 
446 int
447 thinkpad_volume_down(struct acpithinkpad_softc *sc)
448 {
449 	return (thinkpad_cmos(sc, THINKPAD_CMOS_VOLUME_DOWN));
450 }
451 
452 int
453 thinkpad_volume_up(struct acpithinkpad_softc *sc)
454 {
455 	return (thinkpad_cmos(sc, THINKPAD_CMOS_VOLUME_UP));
456 }
457 
458 int
459 thinkpad_volume_mute(struct acpithinkpad_softc *sc)
460 {
461 	return (thinkpad_cmos(sc, THINKPAD_CMOS_VOLUME_MUTE));
462 }
463 
464 int
465 thinkpad_brightness_up(struct acpithinkpad_softc *sc)
466 {
467 	return (thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_UP));
468 }
469 
470 int
471 thinkpad_brightness_down(struct acpithinkpad_softc *sc)
472 {
473 	return (thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_DOWN));
474 }
475