1 /* $NetBSD: thinkpad_acpi.c,v 1.20 2009/09/25 20:26:26 dyoung Exp $ */
4 * Copyright (c) 2007 Jared D. McNeill <jmcneill@invisible.ca>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: thinkpad_acpi.c,v 1.20 2009/09/25 20:26:26 dyoung Exp $");
32 #include <sys/types.h>
33 #include <sys/param.h>
34 #include <sys/malloc.h>
36 #include <sys/callout.h>
37 #include <sys/kernel.h>
38 #include <sys/device.h>
40 #include <sys/queue.h>
43 #include <dev/acpi/acpivar.h>
44 #include <dev/acpi/acpi_ecvar.h>
46 #if defined(__i386__) || defined(__amd64__)
47 #include <dev/isa/isareg.h>
48 #include <machine/pio.h>
51 #define THINKPAD_NTEMPSENSORS 8
52 #define THINKPAD_NFANSENSORS 1
53 #define THINKPAD_NSENSORS (THINKPAD_NTEMPSENSORS + THINKPAD_NFANSENSORS)
55 typedef struct thinkpad_softc
{
58 struct acpi_devnode
*sc_node
;
59 ACPI_HANDLE sc_cmoshdl
;
60 bool sc_cmoshdl_valid
;
62 #define TP_PSW_SLEEP 0
63 #define TP_PSW_HIBERNATE 1
64 #define TP_PSW_DISPLAY_CYCLE 2
65 #define TP_PSW_LOCK_SCREEN 3
66 #define TP_PSW_BATTERY_INFO 4
67 #define TP_PSW_EJECT_BUTTON 5
68 #define TP_PSW_ZOOM_BUTTON 6
69 #define TP_PSW_VENDOR_BUTTON 7
71 struct sysmon_pswitch sc_smpsw
[TP_PSW_LAST
];
74 struct sysmon_envsys
*sc_sme
;
75 envsys_data_t sc_sensor
[THINKPAD_NSENSORS
];
81 #define THINKPAD_NOTIFY_FnF1 0x001
82 #define THINKPAD_NOTIFY_LockScreen 0x002
83 #define THINKPAD_NOTIFY_BatteryInfo 0x003
84 #define THINKPAD_NOTIFY_SleepButton 0x004
85 #define THINKPAD_NOTIFY_WirelessSwitch 0x005
86 #define THINKPAD_NOTIFY_FnF6 0x006
87 #define THINKPAD_NOTIFY_DisplayCycle 0x007
88 #define THINKPAD_NOTIFY_PointerSwitch 0x008
89 #define THINKPAD_NOTIFY_EjectButton 0x009
90 #define THINKPAD_NOTIFY_FnF10 0x00a
91 #define THINKPAD_NOTIFY_FnF11 0x00b
92 #define THINKPAD_NOTIFY_HibernateButton 0x00c
93 #define THINKPAD_NOTIFY_BrightnessUp 0x010
94 #define THINKPAD_NOTIFY_BrightnessDown 0x011
95 #define THINKPAD_NOTIFY_ThinkLight 0x012
96 #define THINKPAD_NOTIFY_Zoom 0x014
97 #define THINKPAD_NOTIFY_ThinkVantage 0x018
99 #define THINKPAD_CMOS_BRIGHTNESS_UP 0x04
100 #define THINKPAD_CMOS_BRIGHTNESS_DOWN 0x05
102 #define THINKPAD_HKEY_VERSION 0x0100
104 #define THINKPAD_DISPLAY_LCD 0x01
105 #define THINKPAD_DISPLAY_CRT 0x02
106 #define THINKPAD_DISPLAY_DVI 0x08
107 #define THINKPAD_DISPLAY_ALL \
108 (THINKPAD_DISPLAY_LCD | THINKPAD_DISPLAY_CRT | THINKPAD_DISPLAY_DVI)
110 static int thinkpad_match(device_t
, cfdata_t
, void *);
111 static void thinkpad_attach(device_t
, device_t
, void *);
113 static ACPI_STATUS
thinkpad_mask_init(thinkpad_softc_t
*, uint32_t);
114 static void thinkpad_notify_handler(ACPI_HANDLE
, UINT32
, void *);
115 static void thinkpad_get_hotkeys(void *);
117 static void thinkpad_sensors_init(thinkpad_softc_t
*);
118 static void thinkpad_sensors_refresh(struct sysmon_envsys
*, envsys_data_t
*);
119 static void thinkpad_temp_refresh(struct sysmon_envsys
*, envsys_data_t
*);
120 static void thinkpad_fan_refresh(struct sysmon_envsys
*, envsys_data_t
*);
122 static void thinkpad_wireless_toggle(thinkpad_softc_t
*);
124 static bool thinkpad_resume(device_t PMF_FN_PROTO
);
125 static void thinkpad_brightness_up(device_t
);
126 static void thinkpad_brightness_down(device_t
);
127 static uint8_t thinkpad_brightness_read(thinkpad_softc_t
*sc
);
128 static void thinkpad_cmos(thinkpad_softc_t
*, uint8_t);
130 CFATTACH_DECL_NEW(thinkpad
, sizeof(thinkpad_softc_t
),
131 thinkpad_match
, thinkpad_attach
, NULL
, NULL
);
133 static const char * const thinkpad_ids
[] = {
139 thinkpad_match(device_t parent
, cfdata_t match
, void *opaque
)
141 struct acpi_attach_args
*aa
= (struct acpi_attach_args
*)opaque
;
144 if (aa
->aa_node
->ad_type
!= ACPI_TYPE_DEVICE
)
147 if (!acpi_match_hid(aa
->aa_node
->ad_devinfo
, thinkpad_ids
))
150 /* We only support hotkey version 0x0100 */
151 if (ACPI_FAILURE(acpi_eval_integer(aa
->aa_node
->ad_handle
, "MHKV",
155 if (ver
!= THINKPAD_HKEY_VERSION
)
158 /* Cool, looks like we're good to go */
163 thinkpad_attach(device_t parent
, device_t self
, void *opaque
)
165 thinkpad_softc_t
*sc
= device_private(self
);
166 struct acpi_attach_args
*aa
= (struct acpi_attach_args
*)opaque
;
167 struct sysmon_pswitch
*psw
;
174 sc
->sc_node
= aa
->aa_node
;
176 sc
->sc_display_state
= THINKPAD_DISPLAY_LCD
;
181 /* T61 uses \UCMS method for issuing CMOS commands */
182 rv
= AcpiGetHandle(NULL
, "\\UCMS", &sc
->sc_cmoshdl
);
183 if (ACPI_FAILURE(rv
))
184 sc
->sc_cmoshdl_valid
= false;
186 aprint_debug_dev(self
, "using CMOS at \\UCMS\n");
187 sc
->sc_cmoshdl_valid
= true;
191 for (curdev
= deviter_first(&di
, DEVITER_F_ROOT_FIRST
);
192 curdev
!= NULL
; curdev
= deviter_next(&di
))
193 if (device_is_a(curdev
, "acpiecdt") ||
194 device_is_a(curdev
, "acpiec")) {
195 sc
->sc_ecdev
= curdev
;
198 deviter_release(&di
);
201 aprint_debug_dev(self
, "using EC at %s\n",
202 device_xname(sc
->sc_ecdev
));
204 /* Get the supported event mask */
205 rv
= acpi_eval_integer(sc
->sc_node
->ad_handle
, "MHKA", &val
);
206 if (ACPI_FAILURE(rv
)) {
207 aprint_error_dev(self
, "couldn't evaluate MHKA: %s\n",
208 AcpiFormatException(rv
));
212 /* Enable all supported events */
213 rv
= thinkpad_mask_init(sc
, val
);
214 if (ACPI_FAILURE(rv
)) {
215 aprint_error_dev(self
, "couldn't set event mask: %s\n",
216 AcpiFormatException(rv
));
220 /* Install notify handler for events */
221 rv
= AcpiInstallNotifyHandler(sc
->sc_node
->ad_handle
,
222 ACPI_DEVICE_NOTIFY
, thinkpad_notify_handler
, sc
);
223 if (ACPI_FAILURE(rv
))
224 aprint_error_dev(self
, "couldn't install notify handler: %s\n",
225 AcpiFormatException(rv
));
227 /* Register power switches with sysmon */
229 sc
->sc_smpsw_valid
= true;
231 psw
[TP_PSW_SLEEP
].smpsw_name
= device_xname(self
);
232 psw
[TP_PSW_SLEEP
].smpsw_type
= PSWITCH_TYPE_SLEEP
;
234 psw
[TP_PSW_HIBERNATE
].smpsw_name
= device_xname(self
);
235 mpsw
[TP_PSW_HIBERNATE
].smpsw_type
= PSWITCH_TYPE_HIBERNATE
;
237 for (i
= TP_PSW_DISPLAY_CYCLE
; i
< TP_PSW_LAST
; i
++)
238 sc
->sc_smpsw
[i
].smpsw_type
= PSWITCH_TYPE_HOTKEY
;
239 psw
[TP_PSW_DISPLAY_CYCLE
].smpsw_name
= PSWITCH_HK_DISPLAY_CYCLE
;
240 psw
[TP_PSW_LOCK_SCREEN
].smpsw_name
= PSWITCH_HK_LOCK_SCREEN
;
241 psw
[TP_PSW_BATTERY_INFO
].smpsw_name
= PSWITCH_HK_BATTERY_INFO
;
242 psw
[TP_PSW_EJECT_BUTTON
].smpsw_name
= PSWITCH_HK_EJECT_BUTTON
;
243 psw
[TP_PSW_ZOOM_BUTTON
].smpsw_name
= PSWITCH_HK_ZOOM_BUTTON
;
244 psw
[TP_PSW_VENDOR_BUTTON
].smpsw_name
= PSWITCH_HK_VENDOR_BUTTON
;
246 for (i
= 0; i
< TP_PSW_LAST
; i
++) {
247 /* not supported yet */
248 if (i
== TP_PSW_HIBERNATE
)
250 if (sysmon_pswitch_register(&sc
->sc_smpsw
[i
]) != 0) {
251 aprint_error_dev(self
,
252 "couldn't register with sysmon\n");
253 sc
->sc_smpsw_valid
= false;
258 /* Register temperature and fan sensors with envsys */
259 thinkpad_sensors_init(sc
);
262 if (!pmf_device_register(self
, NULL
, thinkpad_resume
))
263 aprint_error_dev(self
, "couldn't establish power handler\n");
264 if (!pmf_event_register(self
, PMFE_DISPLAY_BRIGHTNESS_UP
,
265 thinkpad_brightness_up
, true))
266 aprint_error_dev(self
, "couldn't register event handler\n");
267 if (!pmf_event_register(self
, PMFE_DISPLAY_BRIGHTNESS_DOWN
,
268 thinkpad_brightness_down
, true))
269 aprint_error_dev(self
, "couldn't register event handler\n");
273 thinkpad_notify_handler(ACPI_HANDLE hdl
, UINT32 notify
, void *opaque
)
275 thinkpad_softc_t
*sc
= (thinkpad_softc_t
*)opaque
;
276 device_t self
= sc
->sc_dev
;
279 if (notify
!= 0x80) {
280 aprint_debug_dev(self
, "unknown notify 0x%02x\n", notify
);
284 rv
= AcpiOsExecute(OSL_NOTIFY_HANDLER
, thinkpad_get_hotkeys
, sc
);
285 if (ACPI_FAILURE(rv
))
286 aprint_error_dev(self
, "couldn't queue hotkey handler: %s\n",
287 AcpiFormatException(rv
));
291 thinkpad_get_hotkeys(void *opaque
)
293 thinkpad_softc_t
*sc
= (thinkpad_softc_t
*)opaque
;
294 device_t self
= sc
->sc_dev
;
300 rv
= acpi_eval_integer(sc
->sc_node
->ad_handle
, "MHKP", &val
);
301 if (ACPI_FAILURE(rv
)) {
302 aprint_error_dev(self
, "couldn't evaluate MHKP: %s\n",
303 AcpiFormatException(rv
));
310 type
= (val
& 0xf000) >> 12;
311 event
= val
& 0x0fff;
314 /* Only type 1 events are supported for now */
318 case THINKPAD_NOTIFY_BrightnessUp
:
319 thinkpad_brightness_up(self
);
321 case THINKPAD_NOTIFY_BrightnessDown
:
322 thinkpad_brightness_down(self
);
324 case THINKPAD_NOTIFY_WirelessSwitch
:
325 thinkpad_wireless_toggle(sc
);
327 case THINKPAD_NOTIFY_SleepButton
:
328 if (sc
->sc_smpsw_valid
== false)
330 sysmon_pswitch_event(&sc
->sc_smpsw
[TP_PSW_SLEEP
],
331 PSWITCH_EVENT_PRESSED
);
333 case THINKPAD_NOTIFY_HibernateButton
:
335 if (sc
->sc_smpsw_valid
== false)
337 sysmon_pswitch_event(&sc
->sc_smpsw
[TP_PSW_HIBERNATE
],
338 PSWITCH_EVENT_PRESSED
);
341 case THINKPAD_NOTIFY_DisplayCycle
:
342 if (sc
->sc_smpsw_valid
== false)
344 sysmon_pswitch_event(
345 &sc
->sc_smpsw
[TP_PSW_DISPLAY_CYCLE
],
346 PSWITCH_EVENT_PRESSED
);
348 case THINKPAD_NOTIFY_LockScreen
:
349 if (sc
->sc_smpsw_valid
== false)
351 sysmon_pswitch_event(
352 &sc
->sc_smpsw
[TP_PSW_LOCK_SCREEN
],
353 PSWITCH_EVENT_PRESSED
);
355 case THINKPAD_NOTIFY_BatteryInfo
:
356 if (sc
->sc_smpsw_valid
== false)
358 sysmon_pswitch_event(
359 &sc
->sc_smpsw
[TP_PSW_BATTERY_INFO
],
360 PSWITCH_EVENT_PRESSED
);
362 case THINKPAD_NOTIFY_EjectButton
:
363 if (sc
->sc_smpsw_valid
== false)
365 sysmon_pswitch_event(
366 &sc
->sc_smpsw
[TP_PSW_EJECT_BUTTON
],
367 PSWITCH_EVENT_PRESSED
);
369 case THINKPAD_NOTIFY_Zoom
:
370 if (sc
->sc_smpsw_valid
== false)
372 sysmon_pswitch_event(
373 &sc
->sc_smpsw
[TP_PSW_ZOOM_BUTTON
],
374 PSWITCH_EVENT_PRESSED
);
376 case THINKPAD_NOTIFY_ThinkVantage
:
377 if (sc
->sc_smpsw_valid
== false)
379 sysmon_pswitch_event(
380 &sc
->sc_smpsw
[TP_PSW_VENDOR_BUTTON
],
381 PSWITCH_EVENT_PRESSED
);
383 case THINKPAD_NOTIFY_FnF1
:
384 case THINKPAD_NOTIFY_FnF6
:
385 case THINKPAD_NOTIFY_PointerSwitch
:
386 case THINKPAD_NOTIFY_FnF10
:
387 case THINKPAD_NOTIFY_FnF11
:
388 case THINKPAD_NOTIFY_ThinkLight
:
389 /* XXXJDM we should deliver hotkeys as keycodes */
392 aprint_debug_dev(self
, "notify event 0x%03x\n", event
);
399 thinkpad_mask_init(thinkpad_softc_t
*sc
, uint32_t mask
)
401 ACPI_OBJECT param
[2];
402 ACPI_OBJECT_LIST params
;
406 /* Update hotkey mask */
408 params
.Pointer
= param
;
409 param
[0].Type
= param
[1].Type
= ACPI_TYPE_INTEGER
;
411 for (i
= 0; i
< 32; i
++) {
412 param
[0].Integer
.Value
= i
+ 1;
413 param
[1].Integer
.Value
= (((1 << i
) & mask
) != 0);
415 rv
= AcpiEvaluateObject(sc
->sc_node
->ad_handle
, "MHKM",
417 if (ACPI_FAILURE(rv
))
421 /* Enable hotkey events */
422 rv
= acpi_eval_set_integer(sc
->sc_node
->ad_handle
, "MHKC", 1);
423 if (ACPI_FAILURE(rv
)) {
424 aprint_error_dev(sc
->sc_dev
, "couldn't enable hotkeys: %s\n",
425 AcpiFormatException(rv
));
429 /* Claim ownership of brightness control */
430 (void)acpi_eval_set_integer(sc
->sc_node
->ad_handle
, "PWMS", 0);
436 thinkpad_sensors_init(thinkpad_softc_t
*sc
)
438 char sname
[5] = "TMP?";
439 char fname
[5] = "FAN?";
442 if (sc
->sc_ecdev
== NULL
)
443 return; /* no chance of this working */
445 sc
->sc_sme
= sysmon_envsys_create();
446 for (i
= 0; i
< THINKPAD_NTEMPSENSORS
; i
++) {
448 strcpy(sc
->sc_sensor
[i
].desc
, sname
);
449 sc
->sc_sensor
[i
].units
= ENVSYS_STEMP
;
451 if (sysmon_envsys_sensor_attach(sc
->sc_sme
, &sc
->sc_sensor
[i
]))
452 aprint_error_dev(sc
->sc_dev
,
453 "couldn't attach sensor %s\n", sname
);
455 j
= i
; /* THINKPAD_NTEMPSENSORS */
456 for (; i
< (j
+ THINKPAD_NFANSENSORS
); i
++) {
457 fname
[3] = '0' + (i
- j
);
458 strcpy(sc
->sc_sensor
[i
].desc
, fname
);
459 sc
->sc_sensor
[i
].units
= ENVSYS_SFANRPM
;
461 if (sysmon_envsys_sensor_attach(sc
->sc_sme
, &sc
->sc_sensor
[i
]))
462 aprint_error_dev(sc
->sc_dev
,
463 "couldn't attach sensor %s\n", fname
);
466 sc
->sc_sme
->sme_name
= device_xname(sc
->sc_dev
);
467 sc
->sc_sme
->sme_cookie
= sc
;
468 sc
->sc_sme
->sme_refresh
= thinkpad_sensors_refresh
;
470 err
= sysmon_envsys_register(sc
->sc_sme
);
472 aprint_error_dev(sc
->sc_dev
,
473 "couldn't register with sysmon: %d\n", err
);
474 sysmon_envsys_destroy(sc
->sc_sme
);
479 thinkpad_sensors_refresh(struct sysmon_envsys
*sme
, envsys_data_t
*edata
)
481 switch (edata
->units
) {
483 thinkpad_temp_refresh(sme
, edata
);
486 thinkpad_fan_refresh(sme
, edata
);
494 thinkpad_temp_refresh(struct sysmon_envsys
*sme
, envsys_data_t
*edata
)
496 thinkpad_softc_t
*sc
= sme
->sme_cookie
;
497 char sname
[5] = "TMP?";
502 sname
[3] = '0' + edata
->sensor
;
503 rv
= acpi_eval_integer(acpiec_get_handle(sc
->sc_ecdev
), sname
, &val
);
504 if (ACPI_FAILURE(rv
)) {
505 edata
->state
= ENVSYS_SINVALID
;
509 if (temp
> 127 || temp
< -127) {
510 edata
->state
= ENVSYS_SINVALID
;
514 edata
->value_cur
= temp
* 1000000 + 273150000;
515 edata
->state
= ENVSYS_SVALID
;
519 thinkpad_fan_refresh(struct sysmon_envsys
*sme
, envsys_data_t
*edata
)
521 thinkpad_softc_t
*sc
= sme
->sme_cookie
;
528 * Read the low byte first to avoid a firmware bug.
530 rv
= acpiec_bus_read(sc
->sc_ecdev
, 0x84, &lo
, 1);
531 if (ACPI_FAILURE(rv
)) {
532 edata
->state
= ENVSYS_SINVALID
;
535 rv
= acpiec_bus_read(sc
->sc_ecdev
, 0x85, &hi
, 1);
536 if (ACPI_FAILURE(rv
)) {
537 edata
->state
= ENVSYS_SINVALID
;
540 rpm
= ((((int)hi
) << 8) | ((int)lo
));
542 edata
->state
= ENVSYS_SINVALID
;
546 edata
->value_cur
= rpm
;
547 if (rpm
< edata
->value_min
|| edata
->value_min
== -1)
548 edata
->value_min
= rpm
;
549 if (rpm
> edata
->value_max
|| edata
->value_max
== -1)
550 edata
->value_max
= rpm
;
551 edata
->state
= ENVSYS_SVALID
;
555 thinkpad_wireless_toggle(thinkpad_softc_t
*sc
)
557 /* Ignore return value, as the hardware may not support bluetooth */
558 (void)AcpiEvaluateObject(sc
->sc_node
->ad_handle
, "BTGL", NULL
, NULL
);
562 thinkpad_brightness_read(thinkpad_softc_t
*sc
)
564 #if defined(__i386__) || defined(__amd64__)
566 * We have two ways to get the current brightness -- either via
567 * magic RTC registers, or using the EC. Since I don't dare mess
568 * with the EC, and Thinkpads are x86-only, this will have to do
572 return inb(IO_RTC
+1) & 7;
579 thinkpad_brightness_up(device_t self
)
581 thinkpad_softc_t
*sc
= device_private(self
);
583 if (thinkpad_brightness_read(sc
) == 7)
585 thinkpad_cmos(sc
, THINKPAD_CMOS_BRIGHTNESS_UP
);
589 thinkpad_brightness_down(device_t self
)
591 thinkpad_softc_t
*sc
= device_private(self
);
593 if (thinkpad_brightness_read(sc
) == 0)
595 thinkpad_cmos(sc
, THINKPAD_CMOS_BRIGHTNESS_DOWN
);
599 thinkpad_cmos(thinkpad_softc_t
*sc
, uint8_t cmd
)
603 if (sc
->sc_cmoshdl_valid
== false)
606 rv
= acpi_eval_set_integer(sc
->sc_cmoshdl
, NULL
, cmd
);
607 if (ACPI_FAILURE(rv
))
608 aprint_error_dev(sc
->sc_dev
, "couldn't evalute CMOS: %s\n",
609 AcpiFormatException(rv
));
613 thinkpad_resume(device_t dv PMF_FN_ARGS
)
618 rv
= AcpiGetHandle(NULL
, "\\_SB.PCI0.LPC.EC.PUBS", &pubs
);
619 if (ACPI_FAILURE(rv
))
620 return true; /* not fatal */
622 rv
= AcpiEvaluateObject(pubs
, "_ON", NULL
, NULL
);
623 if (ACPI_FAILURE(rv
))
624 aprint_error_dev(dv
, "failed to execute PUBS._ON: %s\n",
625 AcpiFormatException(rv
));