1 /*-
2 * Copyright (c) 2004 Philip Paeps <philip@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 */
27
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD: releng/5.3/sys/i386/acpica/acpi_asus.c 133628 2004-08-13 06:22:29Z njl $");
30
31 /*
32 * Driver for extra ACPI-controlled gadgets (hotkeys, leds, etc) found on
33 * recent Asus (and Medion) laptops. Inspired by the acpi4asus project which
34 * implements these features in the Linux kernel.
35 *
36 * <http://sourceforge.net/projects/acpi4asus/>
37 *
38 * Currently should support most features, but could use some more testing.
39 * Particularly the display-switching stuff is a bit hairy. If you have an
40 * Asus laptop which doesn't appear to be supported, or strange things happen
41 * when using this driver, please report to <acpi@FreeBSD.org>.
42 *
43 */
44
45 #include "opt_acpi.h"
46 #include <sys/param.h>
47 #include <sys/kernel.h>
48 #include <sys/module.h>
49 #include <sys/bus.h>
50 #include <sys/sbuf.h>
51
52 #include "acpi.h"
53 #include <dev/acpica/acpivar.h>
54 #include <dev/led/led.h>
55
56 #define _COMPONENT ACPI_ASUS
57 ACPI_MODULE_NAME("ASUS")
58
59 struct acpi_asus_model {
60 char *name;
61
62 char *mled_set;
63 char *tled_set;
64 char *wled_set;
65
66 char *brn_get;
67 char *brn_set;
68 char *brn_up;
69 char *brn_dn;
70
71 char *lcd_get;
72 char *lcd_set;
73
74 char *disp_get;
75 char *disp_set;
76 };
77
78 struct acpi_asus_led {
79 struct cdev *cdev;
80 device_t dev;
81 enum {
82 ACPI_ASUS_LED_MLED,
83 ACPI_ASUS_LED_TLED,
84 ACPI_ASUS_LED_WLED,
85 } type;
86 };
87
88 struct acpi_asus_softc {
89 device_t dev;
90 ACPI_HANDLE handle;
91
92 struct acpi_asus_model *model;
93 struct sysctl_ctx_list sysctl_ctx;
94 struct sysctl_oid *sysctl_tree;
95
96 struct acpi_asus_led s_mled;
97 struct acpi_asus_led s_tled;
98 struct acpi_asus_led s_wled;
99
100 int s_brn;
101 int s_disp;
102 int s_lcd;
103 };
104
105 /* Models we know about */
106 static struct acpi_asus_model acpi_asus_models[] = {
107 {
108 .name = "L2D",
109 .mled_set = "MLED",
110 .wled_set = "WLED",
111 .brn_up = "\\Q0E",
112 .brn_dn = "\\Q0F",
113 .lcd_get = "\\SGP0",
114 .lcd_set = "\\Q10"
115 },
116 {
117 .name = "L3C",
118 .mled_set = "MLED",
119 .wled_set = "WLED",
120 .brn_get = "GPLV",
121 .brn_set = "SPLV",
122 .lcd_get = "\\GL32",
123 .lcd_set = "\\_SB.PCI0.PX40.ECD0._Q10"
124 },
125 {
126 .name = "L3D",
127 .mled_set = "MLED",
128 .wled_set = "WLED",
129 .brn_get = "GPLV",
130 .brn_set = "SPLV",
131 .lcd_get = "\\BKLG",
132 .lcd_set = "\\Q10"
133 },
134 {
135 .name = "L3H",
136 .mled_set = "MLED",
137 .wled_set = "WLED",
138 .brn_get = "GPLV",
139 .brn_set = "SPLV",
140 .lcd_get = "\\_SB.PCI0.PM.PBC",
141 .lcd_set = "EHK",
142 .disp_get = "\\_SB.INFB",
143 .disp_set = "SDSP"
144 },
145 {
146 .name = "L8L"
147 /* Only has hotkeys, apparantly */
148 },
149 {
150 .name = "M1A",
151 .mled_set = "MLED",
152 .brn_up = "\\_SB.PCI0.PX40.EC0.Q0E",
153 .brn_dn = "\\_SB.PCI0.PX40.EC0.Q0F",
154 .lcd_get = "\\PNOF",
155 .lcd_set = "\\_SB.PCI0.PX40.EC0.Q10"
156 },
157 {
158 .name = "M2E",
159 .mled_set = "MLED",
160 .wled_set = "WLED",
161 .brn_get = "GPLV",
162 .brn_set = "SPLV",
163 .lcd_get = "\\GP06",
164 .lcd_set = "\\Q10"
165 },
166 {
167 .name = "P30",
168 .wled_set = "WLED",
169 .brn_up = "\\_SB.PCI0.LPCB.EC0._Q68",
170 .brn_dn = "\\_SB.PCI0.LPCB.EC0._Q69",
171 .lcd_get = "\\BKLT",
172 .lcd_set = "\\_SB.PCI0.LPCB.EC0._Q0E"
173 },
174
175 { .name = NULL }
176 };
177
178 ACPI_SERIAL_DECL(asus, "ACPI ASUS extras");
179
180 /* Function prototypes */
181 static int acpi_asus_probe(device_t dev);
182 static int acpi_asus_attach(device_t dev);
183 static int acpi_asus_detach(device_t dev);
184
185 static void acpi_asus_led(struct acpi_asus_led *led, int state);
186
187 static int acpi_asus_sysctl_brn(SYSCTL_HANDLER_ARGS);
188 static int acpi_asus_sysctl_lcd(SYSCTL_HANDLER_ARGS);
189 static int acpi_asus_sysctl_disp(SYSCTL_HANDLER_ARGS);
190
191 static void acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context);
192
193 static device_method_t acpi_asus_methods[] = {
194 DEVMETHOD(device_probe, acpi_asus_probe),
195 DEVMETHOD(device_attach, acpi_asus_attach),
196 DEVMETHOD(device_detach, acpi_asus_detach),
197
198 { 0, 0 }
199 };
200
201 static driver_t acpi_asus_driver = {
202 "acpi_asus",
203 acpi_asus_methods,
204 sizeof(struct acpi_asus_softc)
205 };
206
207 static devclass_t acpi_asus_devclass;
208
209 DRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver, acpi_asus_devclass, 0, 0);
210 MODULE_DEPEND(acpi_asus, acpi, 1, 1, 1);
211
212 static int
213 acpi_asus_probe(device_t dev)
214 {
215 struct acpi_asus_model *model;
216 struct acpi_asus_softc *sc;
217 struct sbuf *sb;
218 ACPI_BUFFER Buf;
219 ACPI_OBJECT Arg, *Obj;
220 ACPI_OBJECT_LIST Args;
221 static char *asus_ids[] = { "ATK0100", NULL };
222
223 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
224
225 if (!acpi_disabled("asus") &&
226 ACPI_ID_PROBE(device_get_parent(dev), dev, asus_ids)) {
227 sc = device_get_softc(dev);
228 sc->dev = dev;
229 sc->handle = acpi_get_handle(dev);
230
231 sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND);
232
233 if (sb == NULL)
234 return (ENOMEM);
235
236 Arg.Type = ACPI_TYPE_INTEGER;
237 Arg.Integer.Value = 0;
238
239 Args.Count = 1;
240 Args.Pointer = &Arg;
241
242 Buf.Pointer = NULL;
243 Buf.Length = ACPI_ALLOCATE_BUFFER;
244
245 AcpiEvaluateObject(sc->handle, "INIT", &Args, &Buf);
246
247 Obj = Buf.Pointer;
248
249 for (model = acpi_asus_models; model->name != NULL; model++)
250 if (strcmp(Obj->String.Pointer, model->name) == 0) {
251 sbuf_printf(sb, "Asus %s Laptop Extras",
252 Obj->String.Pointer);
253 sbuf_finish(sb);
254
255 sc->model = model;
256 device_set_desc(dev, sbuf_data(sb));
257
258 sbuf_delete(sb);
259 AcpiOsFree(Buf.Pointer);
260 return (0);
261 }
262
263 sbuf_printf(sb, "Unsupported Asus laptop detected: %s\n",
264 Obj->String.Pointer);
265 sbuf_finish(sb);
266
267 device_printf(dev, sbuf_data(sb));
268
269 sbuf_delete(sb);
270 AcpiOsFree(Buf.Pointer);
271 }
272
273 return (ENXIO);
274 }
275
276 static int
277 acpi_asus_attach(device_t dev)
278 {
279 struct acpi_asus_softc *sc;
280 struct acpi_softc *acpi_sc;
281
282 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
283
284 sc = device_get_softc(dev);
285 acpi_sc = acpi_device_get_parent_softc(dev);
286
287 /* Build sysctl tree */
288 sysctl_ctx_init(&sc->sysctl_ctx);
289 sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
290 SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree),
291 OID_AUTO, "asus", CTLFLAG_RD, 0, "");
292
293 /* Attach leds */
294 if (sc->model->mled_set) {
295 sc->s_mled.dev = dev;
296 sc->s_mled.type = ACPI_ASUS_LED_MLED;
297 sc->s_mled.cdev =
298 led_create((led_t *)acpi_asus_led, &sc->s_mled, "mled");
299 }
300
301 if (sc->model->tled_set) {
302 sc->s_tled.dev = dev;
303 sc->s_tled.type = ACPI_ASUS_LED_TLED;
304 sc->s_tled.cdev =
305 led_create((led_t *)acpi_asus_led, &sc->s_tled, "tled");
306 }
307
308 if (sc->model->wled_set) {
309 sc->s_wled.dev = dev;
310 sc->s_wled.type = ACPI_ASUS_LED_WLED;
311 sc->s_wled.cdev =
312 led_create((led_t *)acpi_asus_led, &sc->s_wled, "wled");
313 }
314
315 /* Attach brightness for GPLV/SPLV models */
316 if (sc->model->brn_get && ACPI_SUCCESS(acpi_GetInteger(sc->handle,
317 sc->model->brn_get, &sc->s_brn)))
318 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
319 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
320 "lcd_brightness", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
321 acpi_asus_sysctl_brn, "I", "brightness of the lcd panel");
322
323 /* Attach brightness for other models */
324 if (sc->model->brn_up &&
325 ACPI_SUCCESS(AcpiEvaluateObject(sc->handle, sc->model->brn_up,
326 NULL, NULL)) &&
327 ACPI_SUCCESS(AcpiEvaluateObject(sc->handle, sc->model->brn_dn,
328 NULL, NULL)))
329 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
330 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
331 "lcd_brightness", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
332 acpi_asus_sysctl_brn, "I", "brightness of the lcd panel");
333
334 /* Attach display switching */
335 if (sc->model->disp_get && ACPI_SUCCESS(acpi_GetInteger(sc->handle,
336 sc->model->disp_get, &sc->s_disp)))
337 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
338 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
339 "video_output", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
340 acpi_asus_sysctl_disp, "I", "display output state");
341
342 /* Attach LCD state, easy for most models... */
343 if (sc->model->lcd_get && strncmp(sc->model->name, "L3H", 3) != 0 &&
344 ACPI_SUCCESS(acpi_GetInteger(sc->handle, sc->model->lcd_get,
345 &sc->s_lcd))) {
346 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
347 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
348 "lcd_backlight", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
349 acpi_asus_sysctl_lcd, "I", "state of the lcd backlight");
350 } else if (sc->model->lcd_get) {
351 ACPI_BUFFER Buf;
352 ACPI_OBJECT Arg[2], Obj;
353 ACPI_OBJECT_LIST Args;
354
355 /* ...a nightmare for the L3H */
356 Arg[0].Type = ACPI_TYPE_INTEGER;
357 Arg[0].Integer.Value = 0x02;
358 Arg[1].Type = ACPI_TYPE_INTEGER;
359 Arg[1].Integer.Value = 0x03;
360
361 Args.Count = 2;
362 Args.Pointer = Arg;
363
364 Buf.Length = sizeof(Obj);
365 Buf.Pointer = &Obj;
366
367 if (ACPI_SUCCESS(AcpiEvaluateObject(sc->handle,
368 sc->model->lcd_get, &Args, &Buf)) &&
369 Obj.Type == ACPI_TYPE_INTEGER) {
370 sc->s_lcd = Obj.Integer.Value >> 8;
371
372 SYSCTL_ADD_PROC(&sc->sysctl_ctx,
373 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
374 "lcd_backlight", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
375 acpi_asus_sysctl_lcd, "I",
376 "state of the lcd backlight");
377 }
378 }
379
380 /* Activate hotkeys */
381 AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL);
382
383 /* Handle notifies */
384 AcpiInstallNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
385 acpi_asus_notify, dev);
386
387 return (0);
388 }
389
390 static int
391 acpi_asus_detach(device_t dev)
392 {
393 struct acpi_asus_softc *sc;
394
395 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
396
397 sc = device_get_softc(dev);
398
399 /* Turn the lights off */
400 if (sc->model->mled_set)
401 led_destroy(sc->s_mled.cdev);
402
403 if (sc->model->tled_set)
404 led_destroy(sc->s_tled.cdev);
405
406 if (sc->model->wled_set)
407 led_destroy(sc->s_wled.cdev);
408
409 /* Remove notify handler */
410 AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,
411 acpi_asus_notify);
412
413 /* Free sysctl tree */
414 sysctl_ctx_free(&sc->sysctl_ctx);
415
416 return (0);
417 }
418
419 static void
420 acpi_asus_led(struct acpi_asus_led *led, int state)
421 {
422 struct acpi_asus_softc *sc;
423 char *method;
424
425 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
426
427 sc = device_get_softc(led->dev);
428
429 switch (led->type) {
430 case ACPI_ASUS_LED_MLED:
431 method = sc->model->mled_set;
432
433 /* Note: inverted */
434 state = !state;
435 break;
436 case ACPI_ASUS_LED_TLED:
437 method = sc->model->tled_set;
438 break;
439 case ACPI_ASUS_LED_WLED:
440 method = sc->model->wled_set;
441 break;
442 default:
443 printf("acpi_asus_led: invalid LED type %d\n",
444 (int)led->type);
445 return;
446 }
447
448 acpi_SetInteger(sc->handle, method, state);
449 }
450
451 static int
452 acpi_asus_sysctl_brn(SYSCTL_HANDLER_ARGS)
453 {
454 struct acpi_asus_softc *sc;
455 int brn, err;
456
457 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
458
459 sc = (struct acpi_asus_softc *)oidp->oid_arg1;
460 ACPI_SERIAL_BEGIN(asus);
461
462 /* Sanity check */
463 brn = sc->s_brn;
464 err = sysctl_handle_int(oidp, &brn, 0, req);
465
466 if (err != 0 || req->newptr == NULL)
467 goto out;
468
469 if (brn < 0 || brn > 15) {
470 err = EINVAL;
471 goto out;
472 }
473
474 /* Keep track and update */
475 sc->s_brn = brn;
476
477 if (sc->model->brn_set)
478 acpi_SetInteger(sc->handle, sc->model->brn_set, brn);
479 else {
480 brn -= sc->s_brn;
481
482 while (brn != 0) {
483 AcpiEvaluateObject(sc->handle, (brn > 0) ?
484 sc->model->brn_up : sc->model->brn_dn,
485 NULL, NULL);
486 (brn > 0) ? brn-- : brn++;
487 }
488 }
489
490 out:
491 ACPI_SERIAL_END(asus);
492 return (err);
493 }
494
495 static int
496 acpi_asus_sysctl_lcd(SYSCTL_HANDLER_ARGS)
497 {
498 struct acpi_asus_softc *sc;
499 int lcd, err;
500
501 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
502
503 sc = (struct acpi_asus_softc *)oidp->oid_arg1;
504 ACPI_SERIAL_BEGIN(asus);
505
506 /* Sanity check */
507 lcd = sc->s_lcd;
508 err = sysctl_handle_int(oidp, &lcd, 0, req);
509
510 if (err != 0 || req->newptr == NULL)
511 goto out;
512
513 if (lcd < 0 || lcd > 1) {
514 err = EINVAL;
515 goto out;
516 }
517
518 /* Keep track and update */
519 sc->s_lcd = lcd;
520
521 /* Most models just need a lcd_set evaluated, the L3H is trickier */
522 if (strncmp(sc->model->name, "L3H", 3) != 0)
523 AcpiEvaluateObject(sc->handle, sc->model->lcd_set, NULL, NULL);
524 else
525 acpi_SetInteger(sc->handle, sc->model->lcd_set, 0x7);
526
527 out:
528 ACPI_SERIAL_END(asus);
529 return (err);
530 }
531
532 static int
533 acpi_asus_sysctl_disp(SYSCTL_HANDLER_ARGS)
534 {
535 struct acpi_asus_softc *sc;
536 int disp, err;
537
538 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
539
540 sc = (struct acpi_asus_softc *)oidp->oid_arg1;
541
542 /* Sanity check */
543 ACPI_SERIAL_BEGIN(asus);
544 disp = sc->s_disp;
545 err = sysctl_handle_int(oidp, &disp, 0, req);
546
547 if (err != 0 || req->newptr == NULL)
548 goto out;
549
550 if (disp < 0 || disp > 7) {
551 err = EINVAL;
552 goto out;
553 }
554
555 /* Keep track and update */
556 sc->s_disp = disp;
557 acpi_SetInteger(sc->handle, sc->model->disp_set, disp);
558
559 out:
560 ACPI_SERIAL_END(asus);
561 return (err);
562 }
563
564 static void
565 acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context)
566 {
567 struct acpi_asus_softc *sc;
568 struct acpi_softc *acpi_sc;
569
570 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
571
572 sc = device_get_softc((device_t)context);
573 acpi_sc = acpi_device_get_parent_softc(sc->dev);
574
575 ACPI_SERIAL_BEGIN(asus);
576 if ((notify & ~0x10) <= 15) {
577 sc->s_brn = notify & ~0x10;
578 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n");
579 } else if ((notify & ~0x20) <= 15) {
580 sc->s_brn = notify & ~0x20;
581 ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n");
582 } else if (notify == 0x33) {
583 sc->s_lcd = 1;
584 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n");
585 } else if (notify == 0x34) {
586 sc->s_lcd = 0;
587 ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n");
588 } else {
589 /* Notify devd(8) */
590 acpi_UserNotify("ASUS", h, notify);
591 }
592 ACPI_SERIAL_END(asus);
593 }
Cache object: dbf126069f2ff52f9e391b9e3007ee04
|