xref: /openbsd-src/sys/dev/acpi/acpithinkpad.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1 /*	$OpenBSD: acpithinkpad.c,v 1.28 2011/06/06 06:13:46 deraadt 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 #include <sys/proc.h>
21 #include <sys/workq.h>
22 
23 #include <dev/acpi/acpireg.h>
24 #include <dev/acpi/acpivar.h>
25 #include <dev/acpi/acpidev.h>
26 #include <dev/acpi/amltypes.h>
27 #include <dev/acpi/dsdt.h>
28 
29 #include <machine/apmvar.h>
30 
31 #include "audio.h"
32 #include "wskbd.h"
33 
34 #define	THINKPAD_HKEY_VERSION		0x0100
35 
36 #define	THINKPAD_CMOS_VOLUME_DOWN	0x00
37 #define	THINKPAD_CMOS_VOLUME_UP		0x01
38 #define	THINKPAD_CMOS_VOLUME_MUTE	0x02
39 #define	THINKPAD_CMOS_BRIGHTNESS_UP	0x04
40 #define	THINKPAD_CMOS_BRIGHTNESS_DOWN	0x05
41 
42 #define	THINKPAD_BLUETOOTH_PRESENT	0x01
43 #define	THINKPAD_BLUETOOTH_ENABLED	0x02
44 
45 /* wan (not wifi) card */
46 #define	THINKPAD_WAN_PRESENT		0x01
47 #define	THINKPAD_WAN_ENABLED		0x02
48 
49 #define	THINKPAD_BUTTON_FN_F1		0x1001
50 #define	THINKPAD_BUTTON_LOCK_SCREEN	0x1002
51 #define	THINKPAD_BUTTON_BATTERY_INFO	0x1003
52 #define	THINKPAD_BUTTON_SUSPEND		0x1004
53 #define	THINKPAD_BUTTON_WIRELESS	0x1005
54 #define	THINKPAD_BUTTON_FN_F6		0x1006
55 #define	THINKPAD_BUTTON_EXTERNAL_SCREEN	0x1007
56 #define	THINKPAD_BUTTON_POINTER_SWITCH	0x1008
57 #define	THINKPAD_BUTTON_EJECT		0x1009
58 #define	THINKPAD_BUTTON_BRIGHTNESS_UP	0x1010
59 #define	THINKPAD_BUTTON_BRIGHTNESS_DOWN	0x1011
60 #define	THINKPAD_BUTTON_THINKLIGHT	0x1012
61 #define	THINKPAD_BUTTON_FN_SPACE	0x1014
62 #define	THINKPAD_BUTTON_VOLUME_UP	0x1015
63 #define	THINKPAD_BUTTON_VOLUME_DOWN	0x1016
64 #define	THINKPAD_BUTTON_VOLUME_MUTE	0x1017
65 #define	THINKPAD_BUTTON_THINKVANTAGE	0x1018
66 #define	THINKPAD_BUTTON_MICROPHONE_MUTE	0x101b
67 #define	THINKPAD_BUTTON_FN_F11		0x100b
68 #define	THINKPAD_BUTTON_HIBERNATE	0x100c
69 #define	THINKPAD_LID_OPEN		0x5001
70 #define	THINKPAD_LID_CLOSED		0x5002
71 #define	THINKPAD_TABLET_SCREEN_NORMAL	0x500a
72 #define	THINKPAD_TABLET_SCREEN_ROTATED	0x5009
73 #define	THINKPAD_BRIGHTNESS_CHANGED	0x5010
74 #define	THINKPAD_TABLET_PEN_INSERTED	0x500b
75 #define	THINKPAD_TABLET_PEN_REMOVED	0x500c
76 #define	THINKPAD_POWER_CHANGED		0x6030
77 #define	THINKPAD_SWITCH_WIRELESS	0x7000
78 
79 #define THINKPAD_NSENSORS 9
80 #define THINKPAD_NTEMPSENSORS 8
81 
82 #define THINKPAD_ECOFFSET_FANLO		0x84
83 #define THINKPAD_ECOFFSET_FANHI		0x85
84 
85 struct acpithinkpad_softc {
86 	struct device		 sc_dev;
87 
88 	struct acpiec_softc     *sc_ec;
89 	struct acpi_softc	*sc_acpi;
90 	struct aml_node		*sc_devnode;
91 
92 	struct ksensor		 sc_sens[THINKPAD_NSENSORS];
93 	struct ksensordev	 sc_sensdev;
94 };
95 
96 extern void acpiec_read(struct acpiec_softc *, u_int8_t, int, u_int8_t *);
97 
98 int	thinkpad_match(struct device *, void *, void *);
99 void	thinkpad_attach(struct device *, struct device *, void *);
100 int	thinkpad_hotkey(struct aml_node *, int, void *);
101 int	thinkpad_enable_events(struct acpithinkpad_softc *);
102 int	thinkpad_toggle_bluetooth(struct acpithinkpad_softc *);
103 int	thinkpad_toggle_wan(struct acpithinkpad_softc *);
104 int	thinkpad_cmos(struct acpithinkpad_softc *sc, uint8_t);
105 int	thinkpad_volume_down(struct acpithinkpad_softc *);
106 int	thinkpad_volume_up(struct acpithinkpad_softc *);
107 int	thinkpad_volume_mute(struct acpithinkpad_softc *);
108 int	thinkpad_brightness_up(struct acpithinkpad_softc *);
109 int	thinkpad_brightness_down(struct acpithinkpad_softc *);
110 
111 void    thinkpad_sensor_attach(struct acpithinkpad_softc *sc);
112 void    thinkpad_sensor_refresh(void *);
113 
114 #if NAUDIO > 0 && NWSKBD > 0
115 extern int wskbd_set_mixervolume(long dir, int out);
116 #endif
117 
118 struct cfattach acpithinkpad_ca = {
119 	sizeof(struct acpithinkpad_softc), thinkpad_match, thinkpad_attach
120 };
121 
122 struct cfdriver acpithinkpad_cd = {
123 	NULL, "acpithinkpad", DV_DULL
124 };
125 
126 const char *acpithinkpad_hids[] = {
127 	ACPI_DEV_IBM, ACPI_DEV_LENOVO, 0
128 };
129 
130 int
131 thinkpad_match(struct device *parent, void *match, void *aux)
132 {
133 	struct acpi_attach_args	*aa = aux;
134 	struct cfdata *cf = match;
135 	int64_t	res;
136 
137 	if (!acpi_matchhids(aa, acpithinkpad_hids, cf->cf_driver->cd_name))
138 		return (0);
139 
140 	if (aml_evalinteger((struct acpi_softc *)parent, aa->aaa_node,
141 	    "MHKV", 0, NULL, &res))
142 		return (0);
143 
144 	if (res != THINKPAD_HKEY_VERSION)
145 		return (0);
146 
147 	return (1);
148 }
149 
150 void
151 thinkpad_sensor_attach(struct acpithinkpad_softc *sc)
152 {
153 	int i;
154 
155 	if (sc->sc_acpi->sc_ec == NULL)
156 		return;
157 	sc->sc_ec = sc->sc_acpi->sc_ec;
158 
159 	/* Add temperature probes */
160 	strlcpy(sc->sc_sensdev.xname, DEVNAME(sc),
161 	    sizeof(sc->sc_sensdev.xname));
162 	for (i=0; i<THINKPAD_NTEMPSENSORS; i++) {
163 		sc->sc_sens[i].type = SENSOR_TEMP;
164 		sensor_attach(&sc->sc_sensdev, &sc->sc_sens[i]);
165 	}
166 
167 	/* Add fan probe */
168 	sc->sc_sens[i].type = SENSOR_FANRPM;
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 	char sname[5];
181 
182 	/* Refresh sensor readings */
183 	for (i=0; i<THINKPAD_NTEMPSENSORS; i++) {
184 		snprintf(sname, sizeof(sname), "TMP%d", i);
185 		aml_evalinteger(sc->sc_acpi, sc->sc_ec->sc_devnode,
186 		    sname, 0, 0, &tmp);
187 		sc->sc_sens[i].value = (tmp * 1000000) + 273150000;
188 		if (tmp > 127 || tmp < -127)
189 			sc->sc_sens[i].flags = SENSOR_FINVALID;
190 	}
191 
192 	/* Read fan RPM */
193 	acpiec_read(sc->sc_ec, THINKPAD_ECOFFSET_FANLO, 1, &lo);
194 	acpiec_read(sc->sc_ec, THINKPAD_ECOFFSET_FANHI, 1, &hi);
195 	sc->sc_sens[i].value = ((hi << 8L) + lo);
196 }
197 
198 void
199 thinkpad_attach(struct device *parent, struct device *self, void *aux)
200 {
201 	struct acpithinkpad_softc *sc = (struct acpithinkpad_softc *)self;
202 	struct acpi_attach_args	*aa = aux;
203 
204 	sc->sc_acpi = (struct acpi_softc *)parent;
205 	sc->sc_devnode = aa->aaa_node;
206 
207 	printf("\n");
208 
209 	/* Set event mask to receive everything */
210 	thinkpad_enable_events(sc);
211 	thinkpad_sensor_attach(sc);
212 
213 	/* Run thinkpad_hotkey on button presses */
214 	aml_register_notify(sc->sc_devnode, aa->aaa_dev,
215 	    thinkpad_hotkey, sc, ACPIDEV_POLL);
216 }
217 
218 int
219 thinkpad_enable_events(struct acpithinkpad_softc *sc)
220 {
221 	struct aml_value arg, args[2];
222 	int64_t	mask;
223 	int i;
224 
225 	/* Get the supported event mask */
226 	if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "MHKA",
227 	    0, NULL, &mask)) {
228 		printf("%s: no MHKA\n", DEVNAME(sc));
229 		return (1);
230 	}
231 
232 	/* Update hotkey mask */
233 	bzero(args, sizeof(args));
234 	args[0].type = args[1].type = AML_OBJTYPE_INTEGER;
235 	for (i = 0; i < 32; i++) {
236 		args[0].v_integer = i + 1;
237 		args[1].v_integer = (((1 << i) & mask) != 0);
238 
239 		if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "MHKM",
240 		    2, args, NULL)) {
241 			printf("%s: couldn't toggle MHKM\n", DEVNAME(sc));
242 			return (1);
243 		}
244 	}
245 
246 	/* Enable hotkeys */
247 	bzero(&arg, sizeof(arg));
248 	arg.type = AML_OBJTYPE_INTEGER;
249 	arg.v_integer = 1;
250 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "MHKC",
251 	    1, &arg, NULL)) {
252 		printf("%s: couldn't enable hotkeys\n", DEVNAME(sc));
253 		return (1);
254 	}
255 
256 	return (0);
257 }
258 
259 int
260 thinkpad_hotkey(struct aml_node *node, int notify_type, void *arg)
261 {
262 	struct acpithinkpad_softc *sc = arg;
263 	int handled = 0;
264 	int64_t	event;
265 
266 	if (notify_type == 0x00) {
267 		/* Poll sensors */
268 		thinkpad_sensor_refresh(sc);
269 		return (0);
270 	}
271 
272 	if (notify_type != 0x80)
273 		return (1);
274 
275 	for (;;) {
276 		if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "MHKP",
277 		    0, NULL, &event))
278 			break;
279 		if (event == 0)
280 			break;
281 
282 		switch (event) {
283 		case THINKPAD_BUTTON_BRIGHTNESS_UP:
284 			thinkpad_brightness_up(sc);
285 			handled = 1;
286 			break;
287 		case THINKPAD_BUTTON_BRIGHTNESS_DOWN:
288 			thinkpad_brightness_down(sc);
289 			handled = 1;
290 			break;
291 		case THINKPAD_BUTTON_WIRELESS:
292 			thinkpad_toggle_bluetooth(sc);
293 			handled = 1;
294 			break;
295 		case THINKPAD_BUTTON_SUSPEND:
296 #ifndef SMALL_KERNEL
297 			if (acpi_record_event(sc->sc_acpi, APM_USER_SUSPEND_REQ))
298 				acpi_addtask(sc->sc_acpi, acpi_sleep_task,
299 				    sc->sc_acpi, ACPI_STATE_S3);
300 #endif
301 			handled = 1;
302 			break;
303 		case THINKPAD_BUTTON_HIBERNATE:
304 		case THINKPAD_BUTTON_FN_F1:
305 		case THINKPAD_BUTTON_LOCK_SCREEN:
306 		case THINKPAD_BUTTON_BATTERY_INFO:
307 		case THINKPAD_BUTTON_FN_F6:
308 		case THINKPAD_BUTTON_EXTERNAL_SCREEN:
309 		case THINKPAD_BUTTON_POINTER_SWITCH:
310 		case THINKPAD_BUTTON_EJECT:
311 		case THINKPAD_BUTTON_THINKLIGHT:
312 		case THINKPAD_BUTTON_FN_SPACE:
313 			handled = 1;
314 			break;
315 		case THINKPAD_BUTTON_VOLUME_MUTE:
316 			thinkpad_volume_mute(sc);
317 			handled = 1;
318 			break;
319 		case THINKPAD_BUTTON_VOLUME_DOWN:
320 			thinkpad_volume_down(sc);
321 			handled = 1;
322 			break;
323 		case THINKPAD_BUTTON_VOLUME_UP:
324 			thinkpad_volume_up(sc);
325 			handled = 1;
326 			break;
327 		case THINKPAD_BUTTON_MICROPHONE_MUTE:
328 #if NAUDIO > 0 && NWSKBD > 0
329 			workq_add_task(NULL, 0, (workq_fn)wskbd_set_mixervolume,
330 			    (void *)(long)0, (void *)(int)0);
331 #endif
332 			handled = 1;
333 			break;
334 		case THINKPAD_BUTTON_THINKVANTAGE:
335 		case THINKPAD_BUTTON_FN_F11:
336 			handled = 1;
337 			break;
338 		case THINKPAD_LID_OPEN:
339 		case THINKPAD_LID_CLOSED:
340 		case THINKPAD_TABLET_SCREEN_NORMAL:
341 		case THINKPAD_TABLET_SCREEN_ROTATED:
342 		case THINKPAD_BRIGHTNESS_CHANGED:
343 		case THINKPAD_TABLET_PEN_INSERTED:
344 		case THINKPAD_TABLET_PEN_REMOVED:
345 			handled = 1;
346 			break;
347 		case THINKPAD_POWER_CHANGED:
348 			handled = 1;
349 			break;
350 		case THINKPAD_SWITCH_WIRELESS:
351 			handled = 1;
352 			break;
353 		default:
354 			printf("%s: unknown event 0x%03x\n",
355 			    DEVNAME(sc), event);
356 		}
357 	}
358 
359 	return (handled);
360 }
361 
362 int
363 thinkpad_toggle_bluetooth(struct acpithinkpad_softc *sc)
364 {
365 	struct aml_value arg;
366 	int64_t	bluetooth;
367 
368 	if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "GBDC",
369 	    0, NULL, &bluetooth))
370 		return (1);
371 
372 	if (!(bluetooth & THINKPAD_BLUETOOTH_PRESENT))
373 		return (1);
374 
375 	bzero(&arg, sizeof(arg));
376 	arg.type = AML_OBJTYPE_INTEGER;
377 	arg.v_integer = bluetooth ^ THINKPAD_BLUETOOTH_ENABLED;
378 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "SBDC",
379 	    1, &arg, NULL)) {
380 		printf("%s: couldn't toggle bluetooth\n", DEVNAME(sc));
381 		return (1);
382 	}
383 
384 	return (0);
385 }
386 
387 int
388 thinkpad_toggle_wan(struct acpithinkpad_softc *sc)
389 {
390 	struct aml_value arg;
391 	int64_t wan;
392 
393 	if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "GWAN",
394 	    0, NULL, &wan))
395 		return (1);
396 
397 	if (!(wan & THINKPAD_WAN_PRESENT))
398 		return (1);
399 
400 	bzero(&arg, sizeof(arg));
401 	arg.type = AML_OBJTYPE_INTEGER;
402 	arg.v_integer = wan ^ THINKPAD_WAN_ENABLED;
403 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "SWAN",
404 	    1, &arg, NULL)) {
405 		printf("%s: couldn't toggle wan\n", DEVNAME(sc));
406 		return (1);
407 	}
408 
409 	return (0);
410 }
411 
412 int
413 thinkpad_cmos(struct acpithinkpad_softc *sc, uint8_t cmd)
414 {
415 	struct aml_value arg;
416 
417 	bzero(&arg, sizeof(arg));
418 	arg.type = AML_OBJTYPE_INTEGER;
419 	arg.v_integer = cmd;
420 	aml_evalname(sc->sc_acpi, sc->sc_devnode, "\\UCMS", 1, &arg, NULL);
421 	return (0);
422 }
423 
424 int
425 thinkpad_volume_down(struct acpithinkpad_softc *sc)
426 {
427 	return (thinkpad_cmos(sc, THINKPAD_CMOS_VOLUME_DOWN));
428 }
429 
430 int
431 thinkpad_volume_up(struct acpithinkpad_softc *sc)
432 {
433 	return (thinkpad_cmos(sc, THINKPAD_CMOS_VOLUME_UP));
434 }
435 
436 int
437 thinkpad_volume_mute(struct acpithinkpad_softc *sc)
438 {
439 	return (thinkpad_cmos(sc, THINKPAD_CMOS_VOLUME_MUTE));
440 }
441 
442 int
443 thinkpad_brightness_up(struct acpithinkpad_softc *sc)
444 {
445 	return (thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_UP));
446 }
447 
448 int
449 thinkpad_brightness_down(struct acpithinkpad_softc *sc)
450 {
451 	return (thinkpad_cmos(sc, THINKPAD_CMOS_BRIGHTNESS_DOWN));
452 }
453