tech-kern archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
ACPI Issues on Lifebook P7120 (with fixes)
Hi,
I've been using NetBSD for a few years now, but this is my first attempt
at modifying the kernel. I may have misunderstood some things...
My laptop (Fujitsu Siemens Lifebook P7120) works great with NetBSD 5
beta, but there are a few issues that are fixed with the modifications
detailed below. Well at least on my laptop... The problems are the
following:
1. after suspend/resume the internal LCD remains black (no backlight).
2. brightness controls Fn-F6 (brightness down) and Fn-F7 (brightness up)
do not work: brightness of the internal LCD is not changed.
3. pressing the hotkey button `Eco'' does not trigger a powerd hotkey
event.
I discovered (a bit too late) that problem (1) can be fixed under X.org
with: xrandr --output LVDS --set BACKLIGHT_CONTROL legacy. Still I
believe it would be nice if it worked without X.
The modifications are implemented in (see attached files):
- a patch acpi.c.patch that comments the discarding of ACPI devices
without HID. This patch is required for acpi_display.c, since
otherwise the display adapter device would be discarded in
autoconfiguration of ACPI devices.
- a driver acpi_display.c for display adapters that simply relays
brightness notifications to the PMF framework. This driver is
inspired by: http://www.invisible.ca/~jmcneill/netbsd/acpi_display.c
which focuses on display switching whereas this driver focuses on
brightness control. Since display switching already works with the
standard kernel on my laptop, I chose to write the driver from
scratch.
- a driver fujbl_acpi.c for the Fujitsu ACPI FUJ02B1 device that listens
to PMF brightness events and changes the backlight level accordingly.
This driver also saves and restores the backlight level at
suspend/resume (which fixes (1)).
- a driver fujhk_acpi.c for the Fujitsu ACPI FUJ02E3 device that handles
hotkey buttons. On my laptop there is only one hotkey button, but it
should work with 4 hotkey buttons (according to the source of the
linux driver).
In other laptop specific drivers that are included in NetBSD, ACPI
notifications on the custom device permit the discrimination of
brightness up/down events. I could not find a way to discriminate these
events with the Fujitsu ACPI FUJ02B1 and FUJ02E3 devices. This the
reason for the acpi_display.c driver.
For people interested in using this, please follow the attached INSTALL
file and give feedback :-)
Comments, suggestions, corrections, flames: all welcome :-)
Grégoire
Copy acpi_display.c, fujbl_acpi.c and fujhk_acpi.c into /usr/src/sys/dev/acpi/
Patch /usr/src/sys/dev/acpi/acpi.c with the provided patch
Patch /usr/src/sys/dev/acpi/files.acpi with the provided patch
Add the following lines in the kernel configuration file:
acpidisplay* at acpi?
#options ACPI_DISPLAY_DEBUG
fujbl* at acpi?
fujhk* at acpi?
Build kernel as usual, e.g. with build.sh.
The hotkey buttons are configured in the driver as hotkey buttons. They are
processed by the /etc/powerd/scripts/hotkey_button powerd script.
Index: acpi.c
===================================================================
RCS file: /cvsroot/src/sys/dev/acpi/acpi.c,v
retrieving revision 1.120
diff -u -r1.120 acpi.c
--- acpi.c 19 Sep 2008 11:19:33 -0000 1.120
+++ acpi.c 4 Dec 2008 12:01:36 -0000
@@ -630,9 +630,16 @@
* Do this check only for devices, as e.g.
* a Thermal Zone doesn't have a HID.
*/
+
+ /*
+ * This check must be disabled for acpidisplay since
+ * the ACPI display adapter device may have no HID.
+ */
+ /*
if (ad->ad_devinfo->Type == ACPI_TYPE_DEVICE &&
(ad->ad_devinfo->Valid & ACPI_VALID_HID) == 0)
continue;
+ */
/*
* Handled internally
/* $Id: acpi_display.c 1000 2008-12-04 16:39:34Z sutre $ */
/*
* ACPI Display Adapter Driver.
*
* Appendix B of the ACPI specification (revision 3) specifies ACPI extensions
* for display adapters. Systems containing a built-in display adapter are
* required to implement these extensions.
*
* This basic implementation simply relays brightness notifications to the PMF
* framework.
*
* Note: we use a fixed maximum number of display output devices. It would be
* nicer to get the number of output devices from _DOD and allocate the array
* sc_outdev dynamically...
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$Id: acpi_display.c 1000 2008-12-04 16:39:34Z sutre $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <dev/acpi/acpivar.h>
/* Notifications specific to display output devices (appendix B.7) */
#define ACPI_NOTIFY_CycleBrightness 0x85
#define ACPI_NOTIFY_IncreaseBrightness 0x86
#define ACPI_NOTIFY_DecreaseBrightness 0x87
#define ACPI_NOTIFY_ZeroBrightness 0x88
#define ACPI_NOTIFY_DisplayDeviceOff 0x89
/* Maximum number of display output devices */
#define ACPI_DISPLAY_NOUTDEV_MAX 8
/*
* acpidisplay_outdev:
*
* Description of an ACPI video output device.
*/
struct acpidisplay_outdev {
ACPI_HANDLE od_handle;
/* our ACPI handle */
UINT32 od_attributes;
/* attributes (appendix B.4.2) */
struct acpidisplay_softc *od_softc;
/* backpointer to display softc */
};
/*
* acpidisplay_softc:
*
* Software state of the ACPI display adapter driver.
*/
struct acpidisplay_softc {
device_t sc_dev;
/* base device info */
struct acpi_devnode *sc_node;
/* our ACPI devnode */
struct acpidisplay_outdev sc_outdev[ACPI_DISPLAY_NOUTDEV_MAX];
/* array of output devices */
uint8_t sc_noutdev;
/* number of output devices */
};
static int acpidisplay_match(device_t, struct cfdata *, void *);
static void acpidisplay_attach(device_t, device_t, void *);
CFATTACH_DECL_NEW(acpidisplay, sizeof(struct acpidisplay_softc),
acpidisplay_match, acpidisplay_attach, NULL, NULL);
static ACPI_STATUS acpidisplay_enumerate_output_devices(struct
acpidisplay_softc *);
static ACPI_STATUS acpidisplay_register_output_device(ACPI_OBJECT *,
void *);
static ACPI_STATUS acpidisplay_match_node_with_address(ACPI_HANDLE, UINT32,
void *, void **);
static void acpidisplay_outdev_notify_handler(ACPI_HANDLE, UINT32, void *);
static void acpidisplay_outdev_increase_brightness(void *);
static void acpidisplay_outdev_decrease_brightness(void *);
/*
* acpidisplay_match:
*
* Autoconfiguration `match' routine.
*/
static int
acpidisplay_match(device_t parent, struct cfdata *match, void *aux)
{
struct acpi_attach_args *aa = aux;
struct acpi_devnode *ad = aa->aa_node;
ACPI_HANDLE handle;
/*
* There is no straightforward way to match display adapter devices.
* We assume here that an ACPI device node is a display adapter device
* if (1) it has a valid address and (2) it has methods _DOS and _DOD.
*/
if (ad->ad_type != ACPI_TYPE_DEVICE)
return 0;
if (!(ad->ad_devinfo->Valid & ACPI_VALID_ADR))
return 0;
if (ACPI_FAILURE(AcpiGetHandle(ad->ad_handle, "_DOS", &handle)))
return 0;
if (ACPI_FAILURE(AcpiGetHandle(ad->ad_handle, "_DOD", &handle)))
return 0;
return 1;
}
/*
* acpidisplay_attach:
*
* Autoconfiguration `attach' routine.
*/
static void
acpidisplay_attach(device_t parent, device_t self, void *aux)
{
struct acpidisplay_softc *sc = device_private(self);
struct acpi_attach_args *aa = aux;
ACPI_STATUS rv;
int i;
aprint_naive(": ACPI Display Adapter\n");
aprint_normal(": ACPI Display Adapter\n");
sc->sc_dev = self;
sc->sc_node = aa->aa_node;
sc->sc_noutdev = 0;
rv = acpidisplay_enumerate_output_devices(sc);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(self,
"unable to enumerate (all) output devices: %s\n",
AcpiFormatException(rv));
}
aprint_verbose_dev(self, "%d video output devices\n", sc->sc_noutdev);
#ifdef ACPI_DISPLAY_DEBUG
for (i = 0 ; i < sc->sc_noutdev ; i++) {
ACPI_BUFFER buf;
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_BUFFER;
rv = AcpiGetObjectInfo(sc->sc_outdev[i].od_handle, &buf);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(self, "failed to get info: %s\n",
AcpiFormatException(rv));
continue;
}
ACPI_DEVICE_INFO *info = buf.Pointer; /* shall be non NULL */
char *charp = (char *) &(info->Name);
aprint_debug_dev(self,
"outdev %d: { %s: %c%c%c%c, %s: 0x%x, %s: 0x%llx }\n", i,
"name", charp[0], charp[1], charp[2], charp[3],
"attr", sc->sc_outdev[i].od_attributes,
"addr", info->Address);
AcpiOsFree(buf.Pointer);
}
#endif
/* Install notify handler for output devices */
for (i = 0 ; i < sc->sc_noutdev ; i++) {
rv = AcpiInstallNotifyHandler(sc->sc_outdev[i].od_handle,
ACPI_DEVICE_NOTIFY, acpidisplay_outdev_notify_handler,
sc->sc_outdev + i);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(self,
"unable to register DEVICE NOTIFY handler: %s\n",
AcpiFormatException(rv));
}
}
if (!pmf_device_register(self, NULL, NULL))
aprint_error_dev(self, "couldn't establish power handler\n");
}
static ACPI_STATUS
acpidisplay_enumerate_output_devices(struct acpidisplay_softc *sc)
{
ACPI_STATUS rv;
ACPI_BUFFER buf;
rv = acpi_eval_struct(sc->sc_node->ad_handle, "_DOD", &buf);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(sc->sc_dev, "failed to evaluate _DOD: %s\n",
AcpiFormatException(rv));
return rv;
}
rv = acpi_foreach_package_object(buf.Pointer,
acpidisplay_register_output_device, sc);
AcpiOsFree(buf.Pointer);
return rv;
}
static ACPI_STATUS
acpidisplay_register_output_device(ACPI_OBJECT *obj, void *arg)
{
struct acpidisplay_softc *sc = arg;
ACPI_STATUS rv;
ACPI_INTEGER attr, addr;
ACPI_HANDLE hdl;
if (sc->sc_noutdev >= ACPI_DISPLAY_NOUTDEV_MAX) {
aprint_error_dev(sc->sc_dev,
"maximum number of output devices reached\n");
/* Stop foreach iteration in enumerate_output_devices */
return AE_CTRL_TERMINATE;
}
if (obj == NULL || obj->Type != ACPI_TYPE_INTEGER) {
aprint_error_dev(sc->sc_dev,
"unexpected non-integer ACPI object\n");
/* Continue foreach iteration in enumerate_output_devices */
return AE_OK;
}
attr = obj->Integer.Value;
addr = attr & 0xffff;
hdl = NULL;
rv = AcpiWalkNamespace(ACPI_TYPE_DEVICE, sc->sc_node->ad_handle, 100,
acpidisplay_match_node_with_address, &addr, &hdl);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(sc->sc_dev,
"failed to walk ACPI namespace: %s\n",
AcpiFormatException(rv));
/* Continue foreach iteration in enumerate_output_devices */
return AE_OK;
}
if (hdl == NULL) {
aprint_error_dev(sc->sc_dev,
"failed to find output device : %llx\n", attr);
/* Continue foreach iteration in enumerate_output_devices */
return AE_OK;
}
sc->sc_outdev[sc->sc_noutdev].od_handle = hdl;
sc->sc_outdev[sc->sc_noutdev].od_attributes = attr;
sc->sc_outdev[sc->sc_noutdev].od_softc = sc;
sc->sc_noutdev++;
/* Continue foreach iteration in enumerate_output_devices */
return AE_OK;
}
/*
* acpidisplay_outdev_notify_handler:
*
* Callback from ACPI interrupt handler to notify us of an event.
*/
static void
acpidisplay_outdev_notify_handler(ACPI_HANDLE handle, UINT32 notify,
void *context)
{
struct acpidisplay_outdev *od = context;
ACPI_STATUS rv;
ACPI_OSD_EXEC_CALLBACK callback = NULL;
const char *callback_name = NULL;
#ifdef ACPI_DISPLAY_DEBUG
aprint_debug_dev(od->od_softc->sc_dev,
"received notify message: 0x%x on outdev: { %s: 0x%x }\n", notify,
"attr", od->od_attributes);
#endif
switch (notify) {
case ACPI_NOTIFY_IncreaseBrightness:
callback = acpidisplay_outdev_increase_brightness;
callback_name = "increase brightness";
break;
case ACPI_NOTIFY_DecreaseBrightness:
callback = acpidisplay_outdev_decrease_brightness;
callback_name = "decrease brightness";
break;
case ACPI_NOTIFY_CycleBrightness:
case ACPI_NOTIFY_ZeroBrightness:
case ACPI_NOTIFY_DisplayDeviceOff:
aprint_debug_dev(od->od_softc->sc_dev,
"received unhandled notify message: 0x%x\n", notify);
return;
default:
aprint_error_dev(od->od_softc->sc_dev,
"received unknown notify message: 0x%x\n", notify);
return;
}
KASSERT((callback != NULL) && (callback_name != NULL));
rv = AcpiOsExecute(OSL_NOTIFY_HANDLER, callback, od);
if (ACPI_FAILURE(rv))
aprint_error_dev(od->od_softc->sc_dev,
"unable to queue %s callback: %s\n", callback_name,
AcpiFormatException(rv));
}
static void
acpidisplay_outdev_increase_brightness(void *arg)
{
/*
* Here we should first try _BCM when it is available, and only resort
* to pmf_event_inject otherwise. Let's keep it simple for now.
*/
pmf_event_inject(NULL, PMFE_DISPLAY_BRIGHTNESS_UP);
}
static void
acpidisplay_outdev_decrease_brightness(void *arg)
{
/*
* Here we should first try _BCM when it is available, and only resort
* to pmf_event_inject otherwise. Let's keep it simple for now.
*/
pmf_event_inject(NULL, PMFE_DISPLAY_BRIGHTNESS_DOWN);
}
/*
* acpidisplay_match_node_with_address:
*
* Walker to find an ACPI node having a given address (_ADR).
*/
static ACPI_STATUS
acpidisplay_match_node_with_address(ACPI_HANDLE handle, UINT32 level,
void *context, void **retval)
{
ACPI_INTEGER *addr = context;
ACPI_HANDLE *hdlp = retval;
ACPI_STATUS rv;
ACPI_INTEGER val;
rv = acpi_eval_integer(handle, "_ADR", &val);
if (ACPI_FAILURE(rv))
return AE_OK; /* Continue walk */
if (val != *addr)
return AE_OK; /* Continue walk */
/* Match found */
*hdlp = handle;
return AE_CTRL_TERMINATE; /* Stop walk */
}
Index: files.acpi
===================================================================
RCS file: /cvsroot/src/sys/dev/acpi/files.acpi,v
retrieving revision 1.52
diff -u -r1.52 files.acpi
--- files.acpi 18 May 2008 22:05:59 -0000 1.52
+++ files.acpi 4 Dec 2008 16:18:23 -0000
@@ -141,3 +141,13 @@
device acpidalb
attach acpidalb at acpinodebus
file dev/acpi/dalb_acpi.c acpidalb
+
+# Internal LCD Backlight in Fujitsu Lifebooks
+device fujbl
+attach fujbl at acpinodebus
+file dev/acpi/fujbl_acpi.c fujbl
+
+# Hotkey Button in Fujitsu Lifebooks
+device fujhk
+attach fujhk at acpinodebus
+file dev/acpi/fujhk_acpi.c fujhk
/* $Id: fujbl_acpi.c 1010 2008-12-05 10:35:40Z sutre $ */
/*
* Internal LCD Backlight Driver for Fujitsu Lifebooks.
*
* The Fujitsu FUJ02B1 device provides methods to control the backlight level:
* - GBLL and/or GBLS to get the value of the current backlight level.
* - SBLL and/or SBL2 to set the backlight level to some given value.
* - RBLL to get the number of backlight levels.
*
* We assume that valid backlight levels range from 0 to n-1 where n is the
* number of backlight levels returned by the method RBLL.
*
* The driver does not listen to notification messages sent by the FUJ02B1
* device, but it listens to PMF brightness events and changes the backlight
* level accordingly. Such PMF brightness events would typically be sent by the
* acpidisplay driver in response to brightness notifications on ACPI display
* output devices.
*
* Other methods are provided by the FUJ02B1 device (to control the internal
* mouse and/or audio volume), but this driver does not use them.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$Id: fujbl_acpi.c 1010 2008-12-05 10:35:40Z sutre $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <dev/acpi/acpivar.h>
#include <dev/sysmon/sysmonvar.h>
struct fujbl_softc {
struct acpi_devnode *sc_node; /* our ACPI devnode */
uint32_t sc_nlevels; /* number of backlight levels */
uint32_t sc_level; /* current backlight level */
};
/*
* We use sc_nlevels to check for validity of a desired backlight level before
* calling SBLL/SBL2. This check might be unnecessary (it is indeed unnecessary
* on the Lifebook P7120), but we keep it for safety.
*
* We store the current backlight level into sc_level at suspend and restore the
* backlight level at resume. Without these suspend/resume hooks, the internal
* LCD screen remains black (no backlight) after resume on the Lifebook P7120.
*/
static const char * const fujbl_ids[] = {
"FUJ02B1",
NULL
};
static int fujbl_match(device_t, struct cfdata *, void *);
static void fujbl_attach(device_t, device_t, void *);
CFATTACH_DECL_NEW(fujbl, sizeof(struct fujbl_softc),
fujbl_match, fujbl_attach, NULL, NULL);
static bool fujbl_suspend(device_t PMF_FN_PROTO);
static bool fujbl_resume(device_t PMF_FN_PROTO);
static void fujbl_init(device_t);
static void fujbl_brightness_up(device_t);
static void fujbl_brightness_down(device_t);
static ACPI_STATUS fujbl_get_level(device_t, ACPI_INTEGER *);
static ACPI_STATUS fujbl_set_level(device_t, ACPI_INTEGER);
static ACPI_STATUS fujbl_acpi_eval_set_integer(ACPI_HANDLE, const char *,
ACPI_INTEGER);
static int
fujbl_match(device_t parent, struct cfdata *match, void *aux)
{
struct acpi_attach_args *aa = aux;
if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE)
return 0;
return acpi_match_hid(aa->aa_node->ad_devinfo, fujbl_ids);
}
static void
fujbl_attach(device_t parent, device_t self, void *aux)
{
struct fujbl_softc *sc = device_private(self);
struct acpi_attach_args *aa = aux;
aprint_naive("\n");
aprint_normal(": Fujitsu Backlight\n");
sc->sc_node = aa->aa_node;
sc->sc_nlevels = 0xffffffff;
fujbl_init(self);
if (!pmf_device_register(self, fujbl_suspend, fujbl_resume))
aprint_error_dev(self, "couldn't establish power handler\n");
if (!pmf_event_register(self, PMFE_DISPLAY_BRIGHTNESS_UP,
fujbl_brightness_up, true))
aprint_error_dev(self, "couldn't establish power handler\n");
if (!pmf_event_register(self, PMFE_DISPLAY_BRIGHTNESS_DOWN,
fujbl_brightness_down, true))
aprint_error_dev(self, "couldn't establish power handler\n");
}
static void
fujbl_init(device_t dv)
{
struct fujbl_softc *sc = device_private(dv);
ACPI_STATUS rv;
ACPI_INTEGER val;
rv = acpi_eval_integer(sc->sc_node->ad_handle, "RBLL", &val);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(dv, "failed to evaluate RBLL: %s\n",
AcpiFormatException(rv));
return ;
}
sc->sc_nlevels = val;
}
static ACPI_STATUS
fujbl_get_level(device_t dv, ACPI_INTEGER *valp)
{
struct fujbl_softc *sc = device_private(dv);
ACPI_STATUS rv;
rv = acpi_eval_integer(sc->sc_node->ad_handle, "GBLL", valp);
if (ACPI_FAILURE(rv))
rv = acpi_eval_integer(sc->sc_node->ad_handle, "GBLS", valp);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(dv,
"failed to evaluate GBLL or GBLS: %s\n",
AcpiFormatException(rv));
}
/* Get rid of the notification bit */
*valp = *valp & 0x7fffffff;
return rv;
}
static ACPI_STATUS
fujbl_set_level(device_t dv, ACPI_INTEGER val)
{
struct fujbl_softc *sc = device_private(dv);
ACPI_STATUS rv;
rv = fujbl_acpi_eval_set_integer(sc->sc_node->ad_handle, "SBLL", val);
if (ACPI_FAILURE(rv))
rv = fujbl_acpi_eval_set_integer(sc->sc_node->ad_handle, "SBL2",
val);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(dv,
"failed to evaluate SBLL or SBL2: %s\n",
AcpiFormatException(rv));
}
return rv;
}
static void
fujbl_brightness_up(device_t dv)
{
struct fujbl_softc *sc = device_private(dv);
ACPI_INTEGER val;
if (ACPI_FAILURE(fujbl_get_level(dv, &val)))
return ;
if (val >= sc->sc_nlevels - 1)
return ; /* Already at max backlight level */
if (ACPI_FAILURE(fujbl_set_level(dv, val+1)))
return ;
}
static void
fujbl_brightness_down(device_t dv)
{
ACPI_INTEGER val;
if (ACPI_FAILURE(fujbl_get_level(dv, &val)))
return ;
if (val <= 0)
return ; /* Already at min backlight level */
if (ACPI_FAILURE(fujbl_set_level(dv, val-1)))
return ;
}
static bool
fujbl_suspend(device_t dv PMF_FN_ARGS)
{
struct fujbl_softc *sc = device_private(dv);
ACPI_INTEGER val;
if (ACPI_SUCCESS(fujbl_get_level(dv, &val)))
sc->sc_level = val;
else
sc->sc_level = 0xffffffff;
return true;
}
static bool
fujbl_resume(device_t dv PMF_FN_ARGS)
{
struct fujbl_softc *sc = device_private(dv);
if (sc->sc_level != 0xffffffff)
fujbl_set_level(dv, sc->sc_level);
return true;
}
static ACPI_STATUS
fujbl_acpi_eval_set_integer(ACPI_HANDLE handle, const char *path,
ACPI_INTEGER arg)
{
ACPI_STATUS rv;
ACPI_BUFFER buf;
ACPI_OBJECT param_ret, param_arg;
ACPI_OBJECT_LIST param_args;
if (handle == NULL)
handle = ACPI_ROOT_OBJECT;
param_arg.Type = ACPI_TYPE_INTEGER;
param_arg.Integer.Value = arg;
param_args.Count = 1;
param_args.Pointer = ¶m_arg;
buf.Pointer = ¶m_ret;
buf.Length = sizeof(param_ret);
rv = AcpiEvaluateObjectTyped(handle, path, ¶m_args, &buf,
ACPI_TYPE_INTEGER);
return rv;
}
/* $Id: fujhk_acpi.c 1005 2008-12-05 08:59:26Z sutre $ */
/*
* Hotkey Button Driver for Fujitsu Lifebooks.
*
* This driver currently only supports a single hotkey button.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$Id: fujhk_acpi.c 1005 2008-12-05 08:59:26Z sutre $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <dev/acpi/acpivar.h>
#include <dev/sysmon/sysmonvar.h>
/* Notification code (only one type of notifications) */
#define FUJHK_ACPI_NOTIFY 0x80
/* Number and names of hotkey buttons */
#define FUJHK_NHOTKEYS 4
/* Names of hotkey buttons */
static const char * const fujhk_hotkey_names[] = {
"fujitsu-hotkey-0",
"fujitsu-hotkey-1",
"fujitsu-hotkey-2",
"fujitsu-hotkey-3"
};
struct fujhk_softc {
struct acpi_devnode *sc_node;
/* our ACPI devnode */
struct sysmon_pswitch sc_smpsw[FUJHK_NHOTKEYS];
/* our sysmon glue */
};
static const char * const fujhk_ids[] = {
"FUJ02E3",
NULL
};
static int fujhk_match(device_t, struct cfdata *, void *);
static void fujhk_attach(device_t, device_t, void *);
CFATTACH_DECL_NEW(fujhk, sizeof(struct fujhk_softc),
fujhk_match, fujhk_attach, NULL, NULL);
static void fujhk_button_event(void *);
static void fujhk_notify_handler(ACPI_HANDLE, UINT32, void *);
static int
fujhk_match(device_t parent, struct cfdata *match, void *aux)
{
struct acpi_attach_args *aa = aux;
if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE)
return 0;
return acpi_match_hid(aa->aa_node->ad_devinfo, fujhk_ids);
}
static void
fujhk_attach(device_t parent, device_t self, void *aux)
{
struct fujhk_softc *sc = device_private(self);
struct acpi_attach_args *aa = aux;
ACPI_STATUS rv;
int i;
aprint_naive("\n");
aprint_normal(": Fujitsu Hotkeys\n");
sc->sc_node = aa->aa_node;
/* Register power switches with sysmon */
for (i = 0 ; i < FUJHK_NHOTKEYS ; i++) {
sc->sc_smpsw[i].smpsw_name = fujhk_hotkey_names[i];
sc->sc_smpsw[i].smpsw_type = PSWITCH_TYPE_HOTKEY;
if (sysmon_pswitch_register(&sc->sc_smpsw[i]) != 0) {
aprint_error_dev(self,
"unable to register with sysmon\n");
return ;
}
}
/* Install notify handler for events */
rv = AcpiInstallNotifyHandler(sc->sc_node->ad_handle,
ACPI_DEVICE_NOTIFY, fujhk_notify_handler, self);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(self,
"unable to register DEVICE NOTIFY handler: %s\n",
AcpiFormatException(rv));
}
if (!pmf_device_register(self, NULL, NULL))
aprint_error_dev(self, "couldn't establish power handler\n");
}
static void
fujhk_button_event(void *arg)
{
device_t dv = arg;
struct fujhk_softc *sc = device_private(dv);
ACPI_STATUS rv;
ACPI_INTEGER girb;
int i;
rv = acpi_eval_integer(sc->sc_node->ad_handle, "GIRB", &girb);
if (ACPI_FAILURE(rv)) {
aprint_error_dev(dv, "failed to evaluate GIRB: %s\n",
AcpiFormatException(rv));
return ;
}
if (girb == 0x40000000)
/* Hotkey button release event, nothing to do */
return ;
if ((girb & 0xfffffffc) != 0x40000410) {
/* This should not happen */
aprint_error_dev(dv,
"unexpected GIRB value: 0x%llx\n", girb);
return ;
}
/* We're good: GIRB of the form 0x4000041N with N in [0, 3] */
i = girb & 0x3;
sysmon_pswitch_event(&sc->sc_smpsw[i], PSWITCH_EVENT_PRESSED);
}
static void
fujhk_notify_handler(ACPI_HANDLE handle, UINT32 notify, void *context)
{
device_t dv = context;
ACPI_STATUS rv;
if (notify != FUJHK_ACPI_NOTIFY) {
aprint_error_dev(dv, "received unknown notify message: 0x%x\n",
notify);
return ;
}
rv = AcpiOsExecute(OSL_NOTIFY_HANDLER, fujhk_button_event, dv);
if (ACPI_FAILURE(rv))
aprint_error_dev(dv,
"unable to queue button event callback: %s\n",
AcpiFormatException(rv));
}
Home |
Main Index |
Thread Index |
Old Index