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 = &param_arg;

        buf.Pointer = &param_ret;
        buf.Length = sizeof(param_ret);

        rv = AcpiEvaluateObjectTyped(handle, path, &param_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