1 /* $NetBSD: acpi_tz.c,v 1.46 2009/08/25 10:34:08 jmcneill Exp $ */
4 * Copyright (c) 2003 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. The name of the author may not be used to endorse or promote products
13 * derived from this software without specific prior written permission.
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
22 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * ACPI Thermal Zone driver
32 #include <sys/cdefs.h>
33 __KERNEL_RCSID(0, "$NetBSD: acpi_tz.c,v 1.46 2009/08/25 10:34:08 jmcneill Exp $");
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/kernel.h>
38 #include <sys/errno.h>
39 #include <sys/ioctl.h>
40 #include <sys/syslog.h>
41 #include <sys/device.h>
42 #include <sys/callout.h>
44 #include <dev/sysmon/sysmonvar.h>
46 #include <dev/acpi/acpica.h>
47 #include <dev/acpi/acpireg.h>
48 #include <dev/acpi/acpivar.h>
50 #define _COMPONENT ACPI_TZ_COMPONENT
51 ACPI_MODULE_NAME ("acpi_tz")
54 #define ATZ_F_VERBOSE 0x01 /* show events to console */
55 #define ATZ_F_CRITICAL 0x02 /* zone critical */
56 #define ATZ_F_HOT 0x04 /* zone hot */
57 #define ATZ_F_PASSIVE 0x08 /* zone passive cooling */
58 #define ATZ_F_PASSIVEONLY 0x10 /* zone is passive cooling only */
60 /* no active cooling level */
61 #define ATZ_ACTIVE_NONE -1
64 #define ATZ_TZP_RATE 300 /* default if no _TZP CM present (30 secs) */
65 #define ATZ_NLEVELS 10 /* number of cooling levels, from ACPI spec */
66 #define ATZ_ZEROC 2732 /* 0C in tenths degrees Kelvin */
67 #define ATZ_TMP_INVALID 0xffffffff /* invalid temperature */
68 #define ATZ_ZONE_EXPIRE 9000 /* zone info refetch interval (15min) */
71 #define ATZ_SENSOR_TEMP 0 /* thermal zone temperature */
73 static int acpitz_match(device_t
, cfdata_t
, void *);
74 static void acpitz_attach(device_t
, device_t
, void *);
77 * ACPI Temperature Zone information. Note all temperatures are reported
78 * in tenths of degrees Kelvin, and that the ACPI specification assumes
79 * that K = C + 273.2 rather than the nominal 273.15 used by envsys(4).
80 * So define an appropriate conversion.
83 #define ATZ2UKELVIN(t) ((t) * 100000 - 50000)
86 /* Active cooling temperature threshold */
87 UINT32 ac
[ATZ_NLEVELS
];
88 /* Package of references to all active cooling devices for a level */
89 ACPI_BUFFER al
[ATZ_NLEVELS
];
90 /* Critical temperature threshold for system shutdown */
92 /* Critical temperature threshold for S4 sleep */
94 /* Package of references to processor objects for passive cooling */
96 /* Passive cooling temperature threshold */
98 /* Thermal constants for use in passive cooling formulas */
100 /* Current temperature of the thermal zone */
102 /* Thermal sampling period for passive cooling, in tenths of seconds */
104 /* Package of references to devices in this TZ (optional) */
106 /* Recommended TZ polling frequency, in tenths of seconds */
110 struct acpitz_softc
{
111 struct acpi_devnode
*sc_devnode
;
112 struct acpitz_zone sc_zone
;
113 struct callout sc_callout
;
114 struct sysmon_envsys
*sc_sme
;
115 envsys_data_t sc_sensor
;
116 int sc_active
; /* active cooling level */
118 int sc_rate
; /* tz poll rate */
124 static void acpitz_get_status(void *);
125 static void acpitz_get_zone(void *, int);
126 static void acpitz_get_zone_quiet(void *);
127 static char *acpitz_celcius_string(int);
128 static void acpitz_print_status(device_t
);
129 static void acpitz_power_off(struct acpitz_softc
*);
130 static void acpitz_power_zone(struct acpitz_softc
*, int, int);
131 static void acpitz_sane_temp(UINT32
*tmp
);
133 acpitz_switch_cooler(ACPI_OBJECT
*, void *);
134 static void acpitz_notify_handler(ACPI_HANDLE
, UINT32
, void *);
135 static int acpitz_get_integer(device_t
, const char *, UINT32
*);
136 static void acpitz_tick(void *);
137 static void acpitz_init_envsys(device_t
);
138 static void acpitz_get_limits(struct sysmon_envsys
*, envsys_data_t
*,
139 sysmon_envsys_lim_t
*);
141 CFATTACH_DECL_NEW(acpitz
, sizeof(struct acpitz_softc
), acpitz_match
,
142 acpitz_attach
, NULL
, NULL
);
145 * acpitz_match: autoconf(9) match routine
148 acpitz_match(device_t parent
, cfdata_t match
, void *aux
)
150 struct acpi_attach_args
*aa
= aux
;
152 if (aa
->aa_node
->ad_type
!= ACPI_TYPE_THERMAL
)
159 * acpitz_attach: autoconf(9) attach routine
162 acpitz_attach(device_t parent
, device_t self
, void *aux
)
164 struct acpitz_softc
*sc
= device_private(self
);
165 struct acpi_attach_args
*aa
= aux
;
170 sc
->sc_flags
= ATZ_F_VERBOSE
;
172 sc
->sc_devnode
= aa
->aa_node
;
176 rv
= acpi_eval_integer(sc
->sc_devnode
->ad_handle
, "_TZP", &v
);
177 if (ACPI_FAILURE(rv
))
178 sc
->sc_zone
.tzp
= ATZ_TZP_RATE
;
182 aprint_debug(" sample rate %d.%ds\n",
183 sc
->sc_zone
.tzp
/ 10, sc
->sc_zone
.tzp
% 10);
185 /* XXX a value of 0 means "polling is not necessary" */
186 if (sc
->sc_zone
.tzp
== 0)
187 sc
->sc_zone
.tzp
= ATZ_TZP_RATE
;
189 sc
->sc_zone_expire
= ATZ_ZONE_EXPIRE
/ sc
->sc_zone
.tzp
;
192 acpitz_get_zone(self
, 1);
193 acpitz_get_status(self
);
195 rv
= AcpiInstallNotifyHandler(sc
->sc_devnode
->ad_handle
,
196 ACPI_SYSTEM_NOTIFY
, acpitz_notify_handler
, self
);
197 if (ACPI_FAILURE(rv
)) {
198 aprint_error(": unable to install SYSTEM NOTIFY handler: %s\n",
199 AcpiFormatException(rv
));
203 callout_init(&sc
->sc_callout
, CALLOUT_MPSAFE
);
204 callout_setfunc(&sc
->sc_callout
, acpitz_tick
, self
);
206 acpitz_init_envsys(self
);
208 if (!pmf_device_register(self
, NULL
, NULL
))
209 aprint_error(": couldn't establish power handler\n");
211 callout_schedule(&sc
->sc_callout
, sc
->sc_zone
.tzp
* hz
/ 10);
215 acpitz_get_zone_quiet(void *opaque
)
217 acpitz_get_zone(opaque
, 0);
221 acpitz_get_status(void *opaque
)
223 device_t dv
= opaque
;
224 struct acpitz_softc
*sc
= device_private(dv
);
228 sc
->sc_zone_expire
--;
229 if (sc
->sc_zone_expire
<= 0) {
230 sc
->sc_zone_expire
= ATZ_ZONE_EXPIRE
/ sc
->sc_zone
.tzp
;
231 if (sc
->sc_flags
& ATZ_F_VERBOSE
)
232 printf("%s: force refetch zone\n", device_xname(dv
));
233 acpitz_get_zone(dv
, 0);
236 if (acpitz_get_integer(dv
, "_TMP", &tmp
)) {
237 aprint_error_dev(dv
, "failed to evaluate _TMP\n");
240 sc
->sc_zone
.tmp
= tmp
;
241 /* XXX sanity check for tmp here? */
244 * The temperature unit for envsys(4) is microKelvin, so convert to
245 * that from ACPI's microKelvin. Also, the ACPI specification assumes
246 * that K = C + 273.2 rather than the nominal 273.15 used by envsys(4),
247 * so we correct for that too.
249 sc
->sc_sensor
.value_cur
= ATZ2UKELVIN(sc
->sc_zone
.tmp
);
250 sc
->sc_sensor
.state
= ENVSYS_SVALID
;
252 if (sc
->sc_flags
& ATZ_F_VERBOSE
)
253 acpitz_print_status(dv
);
255 if (sc
->sc_flags
& ATZ_F_PASSIVEONLY
) {
256 /* Passive Cooling: XXX not yet */
261 /* temperature threshold: _AC0 > ... > _AC9 */
262 active
= ATZ_ACTIVE_NONE
;
263 for (i
= ATZ_NLEVELS
- 1; i
>= 0; i
--) {
264 if (sc
->sc_zone
.ac
[i
] == ATZ_TMP_INVALID
)
267 /* we want to keep highest cooling mode in 'active' */
268 if (sc
->sc_zone
.ac
[i
] <= tmp
)
271 if (active
!= ATZ_ACTIVE_NONE
)
272 sc
->sc_sensor
.state
= ENVSYS_SWARNOVER
;
274 flags
= sc
->sc_flags
&
275 ~(ATZ_F_CRITICAL
|ATZ_F_HOT
|ATZ_F_PASSIVE
);
276 if (sc
->sc_zone
.psv
!= ATZ_TMP_INVALID
&&
277 tmp
>= sc
->sc_zone
.psv
)
278 flags
|= ATZ_F_PASSIVE
;
279 if (sc
->sc_zone
.hot
!= ATZ_TMP_INVALID
&&
280 tmp
>= sc
->sc_zone
.hot
)
282 if (sc
->sc_zone
.crt
!= ATZ_TMP_INVALID
&&
283 tmp
>= sc
->sc_zone
.crt
)
284 flags
|= ATZ_F_CRITICAL
;
286 if (flags
!= sc
->sc_flags
) {
287 int changed
= (sc
->sc_flags
^ flags
) & flags
;
288 sc
->sc_flags
= flags
;
289 if (changed
& ATZ_F_CRITICAL
) {
290 sc
->sc_sensor
.state
= ENVSYS_SCRITOVER
;
292 "zone went critical at temp %sC\n",
293 acpitz_celcius_string(tmp
));
294 } else if (changed
& ATZ_F_HOT
) {
295 sc
->sc_sensor
.state
= ENVSYS_SCRITOVER
;
297 "zone went hot at temp %sC\n",
298 acpitz_celcius_string(tmp
));
303 if (sc
->sc_active
!= active
) {
304 if (sc
->sc_active
!= ATZ_ACTIVE_NONE
)
305 acpitz_power_zone(sc
, sc
->sc_active
, 0);
307 if (active
!= ATZ_ACTIVE_NONE
) {
308 if (sc
->sc_flags
& ATZ_F_VERBOSE
)
309 printf("%s: active cooling level %u\n",
310 device_xname(dv
), active
);
311 acpitz_power_zone(sc
, active
, 1);
314 sc
->sc_active
= active
;
322 acpitz_celcius_string(int dk
)
326 snprintf(buf
, sizeof(buf
), "%d.%d", (dk
- ATZ_ZEROC
) / 10,
327 (dk
- ATZ_ZEROC
) % 10);
333 acpitz_print_status(device_t dv
)
335 struct acpitz_softc
*sc
= device_private(dv
);
337 printf("%s: zone temperature is now %sC\n", device_xname(dv
),
338 acpitz_celcius_string(sc
->sc_zone
.tmp
));
344 acpitz_switch_cooler(ACPI_OBJECT
*obj
, void *arg
)
353 pwr_state
= ACPI_STATE_D0
;
355 pwr_state
= ACPI_STATE_D3
;
358 case ACPI_TYPE_LOCAL_REFERENCE
:
360 cooler
= obj
->Reference
.Handle
;
362 case ACPI_TYPE_STRING
:
363 rv
= AcpiGetHandle(NULL
, obj
->String
.Pointer
, &cooler
);
364 if (ACPI_FAILURE(rv
)) {
365 printf("acpitz_switch_cooler: "
366 "failed to get handler from %s\n",
367 obj
->String
.Pointer
);
372 printf("acpitz_switch_cooler: "
373 "unknown power type: %d\n", obj
->Type
);
377 rv
= acpi_pwr_switch_consumer(cooler
, pwr_state
);
378 if (rv
!= AE_BAD_PARAMETER
&& ACPI_FAILURE(rv
)) {
379 printf("acpitz_switch_cooler: "
380 "failed to change state for %s: %s\n",
381 acpi_name(obj
->Reference
.Handle
),
382 AcpiFormatException(rv
));
390 * power on or off the i:th part of the zone zone
393 acpitz_power_zone(struct acpitz_softc
*sc
, int i
, int on
)
395 KASSERT(i
>= 0 && i
< ATZ_NLEVELS
);
397 acpi_foreach_package_object(sc
->sc_zone
.al
[i
].Pointer
,
398 acpitz_switch_cooler
, &on
);
404 * power off parts of the zone
407 acpitz_power_off(struct acpitz_softc
*sc
)
411 for (i
= 0 ; i
< ATZ_NLEVELS
; i
++) {
412 if (sc
->sc_zone
.al
[i
].Pointer
== NULL
)
414 acpitz_power_zone(sc
, i
, 0);
416 sc
->sc_active
= ATZ_ACTIVE_NONE
;
417 sc
->sc_flags
&= ~(ATZ_F_CRITICAL
|ATZ_F_HOT
|ATZ_F_PASSIVE
);
421 acpitz_get_zone(void *opaque
, int verbose
)
423 device_t dv
= opaque
;
424 struct acpitz_softc
*sc
= device_private(dv
);
430 acpitz_power_off(sc
);
432 for (i
= 0; i
< ATZ_NLEVELS
; i
++) {
433 if (sc
->sc_zone
.al
[i
].Pointer
!= NULL
)
434 ACPI_FREE(sc
->sc_zone
.al
[i
].Pointer
);
435 sc
->sc_zone
.al
[i
].Pointer
= NULL
;
442 for (i
= 0; i
< ATZ_NLEVELS
; i
++) {
445 snprintf(buf
, sizeof(buf
), "_AC%d", i
);
446 if (acpitz_get_integer(dv
, buf
, &sc
->sc_zone
.ac
[i
]))
449 snprintf(buf
, sizeof(buf
), "_AL%d", i
);
450 rv
= acpi_eval_struct(sc
->sc_devnode
->ad_handle
, buf
,
452 if (ACPI_FAILURE(rv
)) {
453 sc
->sc_zone
.al
[i
].Pointer
= NULL
;
457 obj
= sc
->sc_zone
.al
[i
].Pointer
;
459 if (obj
->Type
!= ACPI_TYPE_PACKAGE
) {
460 aprint_error("%d not package\n", i
);
462 sc
->sc_zone
.al
[i
].Pointer
= NULL
;
468 aprint_normal(" active cooling level %d: %sC", i
,
469 acpitz_celcius_string(sc
->sc_zone
.ac
[i
]));
474 acpitz_get_integer(dv
, "_TMP", &sc
->sc_zone
.tmp
);
475 acpitz_get_integer(dv
, "_CRT", &sc
->sc_zone
.crt
);
476 acpitz_get_integer(dv
, "_HOT", &sc
->sc_zone
.hot
);
477 sc
->sc_zone
.psl
.Length
= ACPI_ALLOCATE_LOCAL_BUFFER
;
478 sc
->sc_zone
.psl
.Pointer
= NULL
;
479 AcpiEvaluateObject(sc
, "_PSL", NULL
, &sc
->sc_zone
.psl
);
480 acpitz_get_integer(dv
, "_PSV", &sc
->sc_zone
.psv
);
481 acpitz_get_integer(dv
, "_TC1", &sc
->sc_zone
.tc1
);
482 acpitz_get_integer(dv
, "_TC2", &sc
->sc_zone
.tc2
);
484 acpitz_sane_temp(&sc
->sc_zone
.tmp
);
485 acpitz_sane_temp(&sc
->sc_zone
.crt
);
486 acpitz_sane_temp(&sc
->sc_zone
.hot
);
487 acpitz_sane_temp(&sc
->sc_zone
.psv
);
490 if (sc
->sc_zone
.crt
!= ATZ_TMP_INVALID
)
491 aprint_normal(" critical %sC",
492 acpitz_celcius_string(sc
->sc_zone
.crt
));
493 if (sc
->sc_zone
.hot
!= ATZ_TMP_INVALID
)
494 aprint_normal(" hot %sC",
495 acpitz_celcius_string(sc
->sc_zone
.hot
));
496 if (sc
->sc_zone
.psv
!= ATZ_TMP_INVALID
)
497 aprint_normal(" passive %sC",
498 acpitz_celcius_string(sc
->sc_zone
.tmp
));
501 if (valid_levels
== 0) {
502 sc
->sc_flags
|= ATZ_F_PASSIVEONLY
;
504 aprint_normal(", passive cooling");
509 for (i
= 0; i
< ATZ_NLEVELS
; i
++)
510 acpitz_sane_temp(&sc
->sc_zone
.ac
[i
]);
512 acpitz_power_off(sc
);
518 acpitz_notify_handler(ACPI_HANDLE hdl
, UINT32 notify
, void *opaque
)
520 device_t dv
= opaque
;
521 ACPI_OSD_EXEC_CALLBACK func
= NULL
;
526 case ACPI_NOTIFY_ThermalZoneStatusChanged
:
527 func
= acpitz_get_status
;
528 name
= "status check";
530 case ACPI_NOTIFY_ThermalZoneTripPointsChanged
:
531 case ACPI_NOTIFY_DeviceListsChanged
:
532 func
= acpitz_get_zone_quiet
;
537 "received unhandled notify message 0x%x\n", notify
);
541 KASSERT(func
!= NULL
);
543 rv
= AcpiOsExecute(OSL_NOTIFY_HANDLER
, func
, dv
);
544 if (ACPI_FAILURE(rv
))
545 aprint_debug_dev(dv
, "unable to queue %s\n", name
);
549 acpitz_sane_temp(UINT32
*tmp
)
551 /* Sane temperatures are beteen 0 and 150 C */
552 if (*tmp
< ATZ_ZEROC
|| *tmp
> ATZ_ZEROC
+ 1500)
553 *tmp
= ATZ_TMP_INVALID
;
557 acpitz_get_integer(device_t dv
, const char *cm
, UINT32
*val
)
559 struct acpitz_softc
*sc
= device_private(dv
);
563 rv
= acpi_eval_integer(sc
->sc_devnode
->ad_handle
, cm
, &tmp
);
564 if (ACPI_FAILURE(rv
)) {
566 aprint_debug_dev(dv
, "failed to evaluate %s: %s\n",
567 cm
, AcpiFormatException(rv
));
569 *val
= ATZ_TMP_INVALID
;
579 acpitz_tick(void *opaque
)
581 device_t dv
= opaque
;
582 struct acpitz_softc
*sc
= device_private(dv
);
584 AcpiOsExecute(OSL_NOTIFY_HANDLER
, acpitz_get_status
, dv
);
586 callout_schedule(&sc
->sc_callout
, sc
->sc_zone
.tzp
* hz
/ 10);
590 acpitz_init_envsys(device_t dv
)
592 struct acpitz_softc
*sc
= device_private(dv
);
594 sc
->sc_sme
= sysmon_envsys_create();
595 sc
->sc_sme
->sme_get_limits
= acpitz_get_limits
;
596 sc
->sc_sme
->sme_cookie
= sc
;
597 sc
->sc_sme
->sme_name
= device_xname(dv
);
598 sc
->sc_sme
->sme_flags
= SME_DISABLE_REFRESH
;
599 sc
->sc_sensor
.monitor
= true;
600 sc
->sc_sensor
.flags
= ENVSYS_FMONLIMITS
| ENVSYS_FMONNOTSUPP
;
601 sc
->sc_sensor
.units
= ENVSYS_STEMP
;
602 strlcpy(sc
->sc_sensor
.desc
, "temperature", sizeof(sc
->sc_sensor
.desc
));
603 if (sysmon_envsys_sensor_attach(sc
->sc_sme
, &sc
->sc_sensor
))
606 /* hook into sysmon */
607 if (sysmon_envsys_register(sc
->sc_sme
) == 0)
610 aprint_error_dev(dv
, "unable to register with sysmon\n");
611 sysmon_envsys_destroy(sc
->sc_sme
);
615 acpitz_get_limits(struct sysmon_envsys
*sme
, envsys_data_t
*edata
,
616 sysmon_envsys_lim_t
*limits
)
618 struct acpitz_softc
*sc
= sme
->sme_cookie
;
621 limits
->sel_flags
= 0;
623 if (sc
->sc_zone
.hot
!= ATZ_TMP_INVALID
) {
624 limits
->sel_flags
|= PROP_CRITMAX
;
625 limits
->sel_critmax
= ATZ2UKELVIN(sc
->sc_zone
.hot
);
626 } else if (sc
->sc_zone
.crt
!= ATZ_TMP_INVALID
) {
627 limits
->sel_flags
|= PROP_CRITMAX
;
628 limits
->sel_critmax
= ATZ2UKELVIN(sc
->sc_zone
.crt
);
630 for (i
= 0; i
< ATZ_NLEVELS
; i
++)
631 if (sc
->sc_zone
.ac
[i
] != ATZ_TMP_INVALID
) {
632 limits
->sel_critmax
= ATZ2UKELVIN(sc
->sc_zone
.ac
[i
]);
633 limits
->sel_flags
|= PROP_WARNMAX
;