tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: ACPI Issues on Lifebook P7120 (with fixes)



Hi,

I attach the new version of the files. The main changes are:

(1) integration of Jared's display switching code.
(2) the acpidisplay device now attaches as child of vga.
(3) use of PRIx8/16/32/64 conversion macros.

For (1) I took the liberty to adapt the code, but of course I am open for discussion. :-) In particular I left out the resume function.

For (2) I had to introduce an interface attribute (called pcidisplay) to attach as a child of vga. I would be happy to learn that there is another way. A config_found_ia() call is added to vga_pci.c. Maybe this call should also be added to framebuffer devices (e.g. radeonfb) ? To find the ACPI device, I first walk the namespace to find the PCI bus and then walk the sub-tree to find the device with the right _ADR. The implementation tries to be safe: if we find several candidates then the driver does not attach.

It is still possible to attach as a child of acpi (there are two match and attach functions that share a common sub-function where the real work is done).

Grégoire
Copy acpi_display.c, fujbl_acpi.c and fujhk_acpi.c into /usr/src/sys/dev/acpi/

Cd into /usr/src/sys and apply the provided patch autoconf.patch

Add the following lines in the kernel configuration file:

acpidisp*       at pcidisplaybus?
#acpidisp*      at acpi?
#options        ACPI_DISP_DEBUG
#options        ACPI_DISP_SWITCH_OVERRIDE
fujbl*          at acpi?
fujhk*          at acpi?

Build kernel as usual, e.g. with build.sh.

With the above configuration, acpidisp is attached as a child of vga(4).
To have it attached as a child of acpi instead:
1. comment the first line and uncomment the second one.
2. cd into /usr/src/sys/dev/acpi and apply the provided patch acpi.c.patch.

The hotkey buttons are configured in the driver fujhk as PSWITCH_TYPE_HOTKEY
buttons.  They are processed by the /etc/powerd/scripts/hotkey_button powerd
script.
/* $Id: acpi_display.c 1023 2008-12-11 16:42:07Z 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 (well, it's what the spec says).
 *
 * This implementation (1) relays brightness notifications to the PMF framework
 * and (2) performs display switching when a display switch notification (0x80)
 * is received.  Kernel configuration option ACPI_DISP_SWITCH_OVERRIDE controls
 * the display switching method:
 * - without this option, the BIOS shall perform display switching, and no
 *   display switch notification shall be received (hence (2) is disabled).
 * - with this option, the BIOS shall not perform display switching, and instead
 *   display switch notifications shall be received (hence (2) is enabled).
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$Id: acpi_display.c 1023 2008-12-11 16:42:07Z sutre $");

#include <sys/inttypes.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kmem.h>
#include <dev/acpi/acpivar.h>
#include <dev/pci/pcivar.h>

#include "acpi.h"


/* Notifications specific to display adapter devices (appendix B.5) */
#define ACPI_NOTIFY_CycleOutputDevice                   0x80
#define ACPI_NOTIFY_OutputDeviceStatusChange            0x81
#define ACPI_NOTIFY_CycleDisplayOutputHotkeyPressed     0x82
#define ACPI_NOTIFY_NextDisplayOutputHotkeyPressed      0x83
#define ACPI_NOTIFY_PreviousDisplayOutputHotkeyPressed  0x84

/* 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

/* Output device display types (appendix B.4.2, table B-2) */
#define ACPI_DISP_OUTDEV_ATTR_TYPE_OTHER        0
#define ACPI_DISP_OUTDEV_ATTR_TYPE_VGA          1
#define ACPI_DISP_OUTDEV_ATTR_TYPE_TV           2
#define ACPI_DISP_OUTDEV_ATTR_TYPE_EXTDIG       3
#define ACPI_DISP_OUTDEV_ATTR_TYPE_INTDFP       4

/* Format of output device attributes (appendix B.4.2, table B-2) */
union acpidisp_od_attrs {
        UINT32                  raw;
        struct {
                uint8_t         index:4;
                uint8_t         port:4;
                uint8_t         type:4;
                uint8_t         vendor_specific:4;
                uint8_t         bios_detect:1;
                uint8_t         non_vga:1;
                uint8_t         pipe_id:3;
                uint16_t        reserved:10;
                uint8_t         device_id_scheme:1;
        } __packed              fmt;
};

/* Format of output device status (appendix B.6.6, table B-4) */
union acpidisp_od_status {
        UINT32                  raw;
        struct {
                uint8_t         exists:1;
                uint8_t         activated:1;
                uint8_t         ready:1;
                uint8_t         not_defective:1;
                uint8_t         attached:1;
                uint32_t        reserved:27;
        } __packed              fmt;
};

/* Format of output device state (appendix B.6.8, table B-6) */
union acpidisp_od_state {
        UINT32                  raw;
        struct {
                uint8_t         active:1;
                uint32_t        reserved:29;
                uint8_t         b30:1;
                uint8_t         b31:1;
        } __packed              fmt;
};

/* Output device id of a value of type union acpidisp_od_attrs */
#define ACPI_DISP_OUTDEV_ID(oda)        ((oda).raw & 0xffff)

/*
 * acpidisp_outdev:
 *
 *      Description of an ACPI video output device.
 */
struct acpidisp_outdev {
        ACPI_HANDLE             od_handle;
                                /* our ACPI handle */
        union acpidisp_od_attrs od_attrs;
                                /* attributes (appendix B.4.2) */
        struct acpidisp_softc   *od_softc;
                                /* backpointer to display softc */
        char                    od_name[5];
                                /* Human-readable device name */
};

/*
 * acpidisp_softc:
 *
 *      Software state of the ACPI display adapter driver.
 */
struct acpidisp_softc {
        device_t                sc_dev;
                                /* base device info */
        ACPI_HANDLE             sc_handle;
                                /* our ACPI handle */
        struct acpidisp_outdev  *sc_outdev;
                                /* array of output devices */
        uint8_t                 sc_noutdev;
                                /* number of output devices */
};

static int      acpidisp_match_common(ACPI_HANDLE);
static int      acpidisp_match_acpi(device_t, struct cfdata *, void *);
static int      acpidisp_match_pci(device_t, struct cfdata *, void *);
static void     acpidisp_attach_common(device_t, device_t, ACPI_HANDLE);
static void     acpidisp_attach_acpi(device_t, device_t, void *);
static void     acpidisp_attach_pci(device_t, device_t, void *);

CFATTACH_DECL_NEW(acpidisp_acpi, sizeof(struct acpidisp_softc),
    acpidisp_match_acpi, acpidisp_attach_acpi, NULL, NULL);

CFATTACH_DECL_NEW(acpidisp_pci, sizeof(struct acpidisp_softc),
    acpidisp_match_pci, acpidisp_attach_pci, NULL, NULL);

static void     acpidisp_notify_handler(ACPI_HANDLE, UINT32, void *);
static void     acpidisp_outdev_notify_handler(ACPI_HANDLE, UINT32, void *);
static void     acpidisp_cycle_output_device(void *);
static void     acpidisp_outdev_increase_brightness(void *);
static void     acpidisp_outdev_decrease_brightness(void *);

static void     acpidisp_od_attrs_to_string(union acpidisp_od_attrs, char *,
                    size_t);

static ACPI_STATUS      acpidisp_enumerate_output_devices(struct
                            acpidisp_softc *);
static ACPI_STATUS      acpidisp_register_output_device(struct
                            acpidisp_softc *, uint8_t, ACPI_OBJECT *);
static ACPI_STATUS      acpidisp_acpi_eval_set_integer(ACPI_HANDLE,
                            const char *, ACPI_INTEGER);
static ACPI_STATUS      acpidisp_match_node_with_address(ACPI_HANDLE, UINT32,
                            void *, void **);
static ACPI_STATUS      acpidisp_find_pci_device(u_int, u_int, u_int,
                            ACPI_HANDLE *);
static ACPI_STATUS      acpidisp_match_pci_bus(ACPI_HANDLE, UINT32, void *,
                            void **);

static const char * const acpidisp_pci_bus_ids[] = {
        "PNP0A03",
        "PNP0A08",
        NULL
};


/*
 * acpidisp_match_common:
 *
 *      Autoconfiguration `match' common subroutine.
 */
static int
acpidisp_match_common(ACPI_HANDLE handle)
{
        ACPI_HANDLE hdl;
        ACPI_STATUS rv;
        ACPI_BUFFER buf;
        ACPI_DEVICE_INFO *info;

        buf.Pointer = NULL;
        buf.Length = ACPI_ALLOCATE_BUFFER;
        rv = AcpiGetObjectInfo(handle, &buf);
        if (ACPI_FAILURE(rv)) {
                aprint_error("acpidisp_match_common: "
                    "failed to get ACPI object info: %s\n",
                    AcpiFormatException(rv));
                return 0;
        }
        info = buf.Pointer;

        /*
         * 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 ((info->Type == ACPI_TYPE_DEVICE) &&
            (info->Valid & ACPI_VALID_ADR) &&
            ACPI_SUCCESS(AcpiGetHandle(handle, "_DOS", &hdl)) &&
            ACPI_SUCCESS(AcpiGetHandle(handle, "_DOD", &hdl))) {
                AcpiOsFree(buf.Pointer);
                return 1;
        }
        else {
                AcpiOsFree(buf.Pointer);
                return 0;
        }
}

/*
 * acpidisp_match_acpi:
 *
 *      Autoconfiguration `match' routine for the acpinodebus interface.
 */
static int
acpidisp_match_acpi(device_t parent, struct cfdata *match, void *aux)
{
        struct acpi_attach_args *aa = aux;
        return acpidisp_match_common(aa->aa_node->ad_handle);
}

/*
 * acpidisp_match_pci:
 *
 *      Autoconfiguration `match' routine for the pcidisplaybus interface.
 */
static int
acpidisp_match_pci(device_t parent, struct cfdata *match, void *aux)
{
#if NACPI > 0
        struct pci_attach_args *pa = aux;
        ACPI_HANDLE handle;
        ACPI_STATUS rv;

        if (acpi_active == 0)
                return 0;

        rv = acpidisp_find_pci_device(pa->pa_bus, pa->pa_device,
            pa->pa_function, &handle);
        if (ACPI_FAILURE(rv))
                return 0;

        KASSERT(handle != NULL);

        return acpidisp_match_common(handle);
#else
        return 0;
#endif
}

/*
 * acpidisp_attach_common:
 *
 *      Autoconfiguration `attach' common subroutine.
 */
static void
acpidisp_attach_common(device_t parent, device_t self, ACPI_HANDLE handle)
{
        struct acpidisp_softc *sc = device_private(self);
        ACPI_STATUS rv;
        char outdevinfo[256];
        uint8_t i;

        aprint_naive(": ACPI Display Adapter\n");
        aprint_normal(": ACPI Display Adapter\n");

        sc->sc_dev = self;
        sc->sc_handle = handle;
        sc->sc_noutdev = 0;

#ifdef ACPI_DISP_SWITCH_OVERRIDE
        /*
         * The following _DOS call disables output switching by the BIOS and
         * the BIOS shall instead generate display switch events.  Brightness
         * control of the BIOS remains unchanged.  See appendix B.4.1.
         */
        rv = acpidisp_acpi_eval_set_integer(handle, "_DOS", 0x0);
        if (ACPI_FAILURE(rv)) {
                aprint_error_dev(self, "failed to evaluate _DOS: %s\n",
                    AcpiFormatException(rv));
        }
#endif

        /* Enumerate and register output devices */
        rv = acpidisp_enumerate_output_devices(sc);
        if (ACPI_FAILURE(rv)) {
                aprint_error_dev(self,
                    "unable to enumerate (all) output devices: %s\n",
                    AcpiFormatException(rv));
        }

        /* Print information about output devices */
        aprint_verbose_dev(self, "%" PRIu8 " video output devices\n",
            sc->sc_noutdev);
        for (i = 0 ; i < sc->sc_noutdev ; i++) {
                acpidisp_od_attrs_to_string(sc->sc_outdev[i].od_attrs,
                    outdevinfo, sizeof(outdevinfo));
                aprint_verbose_dev(self,
                    "(%s, 0x%04" PRIx16 "): %s",
                    sc->sc_outdev[i].od_name,
                    (uint16_t) ACPI_DISP_OUTDEV_ID(sc->sc_outdev[i].od_attrs),
                    outdevinfo);
                aprint_debug(" (raw attributes: 0x%08" PRIx32 ")",
                    sc->sc_outdev[i].od_attrs.raw);
                aprint_verbose("\n");
        }

        /* Install notify handler for this display adapter device */
        rv = AcpiInstallNotifyHandler(handle,
            ACPI_DEVICE_NOTIFY, acpidisp_notify_handler, sc);
        if (ACPI_FAILURE(rv)) {
                aprint_error_dev(self,
                    "unable to register DEVICE NOTIFY handler: %s\n",
                    AcpiFormatException(rv));
        }

        /* Install notify handler for the child output devices */
        for (i = 0 ; i < sc->sc_noutdev ; i++) {
                rv = AcpiInstallNotifyHandler(sc->sc_outdev[i].od_handle,
                    ACPI_DEVICE_NOTIFY, acpidisp_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");
}

/*
 * acpidisp_attach_acpi:
 *
 *      Autoconfiguration `attach' routine for the acpinodebus interface.
 */
static void
acpidisp_attach_acpi(device_t parent, device_t self, void *aux)
{
        struct acpi_attach_args *aa = aux;
        acpidisp_attach_common(parent, self, aa->aa_node->ad_handle);
}

/*
 * acpidisp_attach_pci:
 *
 *      Autoconfiguration `attach' routine for the pcidisplaybus interface.
 */
static void
acpidisp_attach_pci(device_t parent, device_t self, void *aux)
{
#if NACPI > 0
        struct pci_attach_args *pa = aux;
        ACPI_HANDLE handle;
        ACPI_STATUS rv;

        KASSERT(acpi_active != 0);

        /* XXX Already done in match: try to cache the result somewhere */
        rv = acpidisp_find_pci_device(pa->pa_bus, pa->pa_device,
            pa->pa_function, &handle);

        KASSERT(ACPI_SUCCESS(rv));
        KASSERT(handle != NULL);

        acpidisp_attach_common(parent, self, handle);
#else
        return 0;
#endif
}

static void
acpidisp_od_attrs_to_string(union acpidisp_od_attrs oda, char *cp, size_t l)
{
        const char *type;
        char *ep;

        ep = cp + l;

        if (oda.fmt.device_id_scheme == 1) {
                switch (oda.fmt.type) {
                case ACPI_DISP_OUTDEV_ATTR_TYPE_OTHER:
                        type = "Other";                         break;
                case ACPI_DISP_OUTDEV_ATTR_TYPE_VGA:
                        type = "VGA/CRT/VESA Monitor";          break;
                case ACPI_DISP_OUTDEV_ATTR_TYPE_TV:
                        type = "TV/HDTV Monitor";               break;
                case ACPI_DISP_OUTDEV_ATTR_TYPE_EXTDIG:
                        type = "Ext. Digital Monitor";          break;
                case ACPI_DISP_OUTDEV_ATTR_TYPE_INTDFP:
                        type = "Int. Digital Flat Panel";       break;
                default:
                        type = "Unknown";                       break;
                }

                cp += snprintf(cp, ep - cp, "%s index %d port %d",
                    type,
                    oda.fmt.index,
                    oda.fmt.port);
        }
        else
                cp += snprintf(cp, ep - cp, "custom device id");

        if (oda.fmt.bios_detect)
                cp += snprintf(cp, ep - cp, ", bios detect");
        if (oda.fmt.non_vga)
                cp += snprintf(cp, ep - cp, ", non vga");

        cp += snprintf(cp, ep - cp, ", pipe %d", oda.fmt.pipe_id);
}

static ACPI_STATUS
acpidisp_enumerate_output_devices(struct acpidisp_softc *sc)
{
        ACPI_BUFFER buf;
        ACPI_STATUS rv;
        ACPI_OBJECT *pkg;
        uint8_t i, k;

        rv = acpi_eval_struct(sc->sc_handle, "_DOD", &buf);
        if (ACPI_FAILURE(rv)) {
                aprint_error_dev(sc->sc_dev, "failed to evaluate _DOD: %s\n",
                    AcpiFormatException(rv));
                return rv;
        }

        pkg = buf.Pointer;
        if (pkg == NULL || pkg->Type != ACPI_TYPE_PACKAGE) {
                aprint_error_dev(sc->sc_dev, "_DOD didn't return a package\n");
                return AE_BAD_DATA;
        }

        sc->sc_outdev = kmem_alloc(pkg->Package.Count *
            sizeof(struct acpidisp_outdev), KM_SLEEP);

        for (i = 0, k = 0 ; i < pkg->Package.Count ; i++) {
                rv = acpidisp_register_output_device(sc, k,
                    &pkg->Package.Elements[i]);
                if (ACPI_SUCCESS(rv))
                        k++;
        }
        sc->sc_noutdev = k;

        AcpiOsFree(buf.Pointer);
        return rv;
}

static ACPI_STATUS
acpidisp_register_output_device(struct acpidisp_softc *sc, uint8_t k,
    ACPI_OBJECT *obj)
{
        ACPI_HANDLE hdl;
        ACPI_STATUS rv;
        ACPI_INTEGER attr, addr;
        ACPI_BUFFER buf;

        if (obj == NULL || obj->Type != ACPI_TYPE_INTEGER) {
                aprint_error_dev(sc->sc_dev,
                    "unexpected non-integer ACPI object\n");
                return AE_BAD_PARAMETER;
        }

        attr = obj->Integer.Value;
        addr = attr & 0xffff;
        hdl = NULL;
        rv = AcpiWalkNamespace(ACPI_TYPE_DEVICE, sc->sc_handle, 100,
            acpidisp_match_node_with_address, &addr, &hdl);
        if (ACPI_FAILURE(rv)) {
                aprint_error_dev(sc->sc_dev,
                    "failure in ACPI namespace walk: %s\n",
                    AcpiFormatException(rv));
                return rv;
        }

        if (hdl == NULL) {
                aprint_error_dev(sc->sc_dev,
                    "failed to find output device : %" PRIx64 "\n", attr);
                return AE_NOT_FOUND;
        }

        sc->sc_outdev[k].od_handle = hdl;
        sc->sc_outdev[k].od_attrs.raw = attr;
        sc->sc_outdev[k].od_softc = sc;

        buf.Pointer = NULL;
        buf.Length = ACPI_ALLOCATE_BUFFER;
        rv = AcpiGetName(hdl, ACPI_SINGLE_NAME, &buf);
        if (ACPI_FAILURE(rv)) {
                aprint_error_dev(sc->sc_dev, "failed to get ACPI name: %s\n",
                    AcpiFormatException(rv));
                strlcpy(sc->sc_outdev[k].od_name, "????", 5);
        }
        else {
                strlcpy(sc->sc_outdev[k].od_name, buf.Pointer, 5);
                AcpiOsFree(buf.Pointer);
        }

        return AE_OK;
}

/*
 * acpidisp_notify_handler:
 *
 *      Callback from ACPI interrupt handler to notify us of an event on the
 *      display adapter device.
 */
static void
acpidisp_notify_handler(ACPI_HANDLE handle, UINT32 notify,
    void *context)
{
        struct acpidisp_softc *sc = context;
        ACPI_STATUS rv;

#ifdef ACPI_DISP_DEBUG
        aprint_debug_dev(sc->sc_dev,
            "received notify message: 0x%" PRIx32
            " (on display adapter device)\n", notify);
#endif

        switch (notify) {
        case ACPI_NOTIFY_CycleOutputDevice:
                rv = AcpiOsExecute(OSL_NOTIFY_HANDLER,
                    acpidisp_cycle_output_device, sc);
                if (ACPI_FAILURE(rv))
                        aprint_error_dev(sc->sc_dev,
                            "unable to queue cycle output device callback: "
                            "%s\n", AcpiFormatException(rv));
                return ;
        case ACPI_NOTIFY_OutputDeviceStatusChange:
        case ACPI_NOTIFY_CycleDisplayOutputHotkeyPressed:
        case ACPI_NOTIFY_NextDisplayOutputHotkeyPressed:
        case ACPI_NOTIFY_PreviousDisplayOutputHotkeyPressed:
                aprint_debug_dev(sc->sc_dev,
                    "received unhandled notify message: 0x%" PRIx32 "\n",
                    notify);
                return ;
        default:
                aprint_error_dev(sc->sc_dev,
                    "received unknown notify message: 0x%" PRIx32 "\n",
                    notify);
                return ;
        }
}

/*
 * acpidisp_outdev_notify_handler:
 *
 *      Callback from ACPI interrupt handler to notify us of an event on a
 *      display output device.
 */
static void
acpidisp_outdev_notify_handler(ACPI_HANDLE handle, UINT32 notify,
    void *context)
{
        struct acpidisp_outdev *od = context;
        ACPI_STATUS rv;
        ACPI_OSD_EXEC_CALLBACK callback = NULL;
        const char *callback_name = NULL;

#ifdef ACPI_DISP_DEBUG
        aprint_debug_dev(od->od_softc->sc_dev,
            "received notify message: 0x%" PRIx32 " on output device: %s\n",
            notify, od->od_name);
#endif

        switch (notify) {
        case ACPI_NOTIFY_IncreaseBrightness:
                callback = acpidisp_outdev_increase_brightness;
                callback_name = "increase brightness";
                break;
        case ACPI_NOTIFY_DecreaseBrightness:
                callback = acpidisp_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%" PRIx32 "\n",
                    notify);
                return ;
        default:
                aprint_error_dev(od->od_softc->sc_dev,
                    "received unknown notify message: 0x%" PRIx32 "\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
acpidisp_cycle_output_device(void *arg)
{
        struct acpidisp_softc *sc = arg;
        ACPI_HANDLE handle;
        ACPI_INTEGER val;
        union acpidisp_od_status dcs;
        union acpidisp_od_state dgs;
        ACPI_STATUS rv;
        uint8_t i;

        for (i = 0 ; i < sc->sc_noutdev ; i++) {
#ifdef ACPI_DISP_DEBUG
                aprint_debug_dev(sc->sc_dev, "%s: ", sc->sc_outdev[i].od_name);
#endif
                handle = sc->sc_outdev[i].od_handle;
                rv = acpi_eval_integer(handle, "_DGS", &val);
                if (ACPI_FAILURE(rv))
                        continue;
#ifdef ACPI_DISP_DEBUG
                aprint_debug("_DGS -> 0x%" PRIx64, val);
#endif
                dgs.raw = val & 0x1;

                rv = acpi_eval_integer(handle, "_DCS", &val);
                if (ACPI_FAILURE(rv))
                        continue;
#ifdef ACPI_DISP_DEBUG
                aprint_debug(", _DCS -> 0x%" PRIx64, val);
#endif
                dcs.raw = val & 0xffffffff;

                if (dcs.fmt.ready == 0)
                        dgs.raw = 0;
                else
                        dgs.fmt.b30 = 1;

                if (i == sc->sc_noutdev - 1)
                        dgs.fmt.b31 = 1;        /* commit */

                val = dgs.raw;
                rv = acpidisp_acpi_eval_set_integer(handle, "_DSS", val);
                if (ACPI_FAILURE(rv))
                        aprint_error_dev(sc->sc_dev, "failed to evaluate _DSS: "
                            "%s\n", AcpiFormatException(rv));
#ifdef ACPI_DISP_DEBUG
                aprint_debug(", _DSS(0x%" PRIx64 ")", val);
                aprint_debug(", %s\n", dgs.fmt.active ? "enabled" : "disabled");
#endif
        }
}

static void
acpidisp_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
acpidisp_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);
}


/******************************************************************************
 * ACPI Utility Routines.
 ******************************************************************************/

static ACPI_STATUS
acpidisp_acpi_eval_set_integer(ACPI_HANDLE handle, const char *path,
    ACPI_INTEGER arg)
{
        ACPI_STATUS rv;
        ACPI_OBJECT 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;

        rv = AcpiEvaluateObject(handle, path, &param_args, NULL);

        return rv;
}

/*
 * acpidisp_match_node_with_address:
 *
 *      Walker to find the ACPI node having a given address (_ADR).
 */
static ACPI_STATUS
acpidisp_match_node_with_address(ACPI_HANDLE handle, UINT32 level,
    void *context, void **retval)
{
        ACPI_INTEGER *addrp = 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 != *addrp)
                return AE_OK;   /* Continue walk */

        /* Match found */
        if (*hdlp != NULL)
                /* Already found a match */
                return AE_ALREADY_EXISTS;       /* Stop walk with error */

        *hdlp = handle;
        return AE_OK;           /* Continue walk to check unicity */
}

/*
 * acpidisp_find_pci_device:
 *
 *      Finds a PCI device in the ACPI name space.  The PCI device to look for
 *      is given as a triple (bus, device, function).  The argument handlep
 *      points to a location where the ACPI handle of the device will be written
 *      if the device is (uniquely) found.
 *
 *      There is no straightforward way to match PCI devices.  This function
 *      looks for an ACPI device that (1) is a descendant of the ACPI device
 *      that corresponds to the given PCI bus, and (2) has an ACPI address
 *      (_ADR) that is equal to: (device << 16) | function.  The return status
 *      is either:
 *      - AE_NOT_FOUND if no such device was found.
 *      - AE_OK if one and only one such device was found.
 *      - AE_ALREADY_EXISTS if multiple such devices were found.
 */
static ACPI_STATUS
acpidisp_find_pci_device(u_int bus, u_int device, u_int function,
    ACPI_HANDLE *handlep)
{
        ACPI_STATUS rv;
        ACPI_INTEGER addr;
        ACPI_HANDLE hdl_bus, hdl;

        hdl_bus = NULL;
        rv = AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 100,
            acpidisp_match_pci_bus, &bus, &hdl_bus);
        if (ACPI_FAILURE(rv)) {
                aprint_error("acpidisp_find_pci_device: "
                    "failure in ACPI namespace walk: %s\n",
                    AcpiFormatException(rv));
                return rv;
        }

        if (hdl_bus == NULL) {
                aprint_debug("acpidisp_find_pci_device: "
                    "couldn't find ACPI device for PCI bus: %u\n",
                    bus);
                return AE_NOT_FOUND;
        }

        addr = (device << 16) | function;

        /*
         * We walk the ACPI sub-tree of hdl_bus to find the device with address
         * equal to addr.  Since AcpiWalkNamespace does not walk the given ACPI
         * handle (only its children and their descendents), we first apply the
         * walker function to hdl_bus.
         */
        hdl = NULL;
        acpidisp_match_node_with_address(hdl_bus, 0, &addr, &hdl);
        rv = AcpiWalkNamespace(ACPI_TYPE_DEVICE, hdl_bus, 100,
            acpidisp_match_node_with_address, &addr, &hdl);
        if (ACPI_FAILURE(rv)) {
                aprint_error("acpidisp_find_pci_device: "
                    "failure in ACPI namespace walk: %s\n",
                    AcpiFormatException(rv));
                return rv;
        }

        if (hdl == NULL) {
                aprint_debug("acpidisp_find_pci_device: "
                    "couldn't find ACPI device for PCI: bus %u dev %u fun %u\n",
                    bus, device, function);
                return AE_NOT_FOUND;
        }

        *handlep = hdl;
        return AE_OK;
}

/*
 * acpidisp_match_pci_bus:
 *
 *      Walker to find the ACPI node corresponding to a given PCI bus number.
 */
static ACPI_STATUS
acpidisp_match_pci_bus(ACPI_HANDLE handle, UINT32 level,
    void *context, void **retval)
{
        u_int *busp = context;
        ACPI_HANDLE *hdlp = retval;
        ACPI_STATUS rv;
        ACPI_INTEGER bbn;
        ACPI_HANDLE hdl;
        ACPI_BUFFER buf;
        ACPI_DEVICE_INFO *info;
        bool id_match;

        /*
         * Check whether this is a PCI bus from the device's HID and CIDs.
         */
        id_match = false;
        /* Get device info */
        buf.Pointer = NULL;
        buf.Length = ACPI_ALLOCATE_BUFFER;
        rv = AcpiGetObjectInfo(handle, &buf);
        if (ACPI_SUCCESS(rv)) {
                info = buf.Pointer;
                id_match = acpi_match_hid(info, acpidisp_pci_bus_ids);
                AcpiOsFree(buf.Pointer);
        }

        if (! id_match)
                return AE_OK;           /* Continue walk */

        /*
         * We found a PCI bus, let us check whether it is the right number
         */
        bbn = 0;                        /* bus number is 0 by default */
        if (ACPI_SUCCESS(AcpiGetHandle(handle, "_BBN", &hdl))) {
                if (ACPI_FAILURE(acpi_eval_integer(handle, "_BBN", &bbn)))
                        return AE_OK;   /* Continue walk */
        }
        if (bbn != *busp)
                return AE_OK;           /* Continue walk */

        /* Match found */
        if (*hdlp != NULL)
                /* Already found a match */
                return AE_ALREADY_EXISTS;       /* Stop walk with error */

        *hdlp = handle;
        return AE_OK;                   /* Continue walk to check unicity */
}
Index: conf/files
===================================================================
RCS file: /cvsroot/src/sys/conf/files,v
retrieving revision 1.924
diff -u -r1.924 files
--- conf/files  15 Oct 2008 06:51:20 -0000      1.924
+++ conf/files  9 Dec 2008 18:10:38 -0000
@@ -1018,6 +1018,9 @@
 define drm {}
 include        dev/drm/files.drm
 
+# Attribute used by ACPI Display to attach to pci display devices (vga)
+define pcidisplaybus { }
+
 # Definitions for wscons
 # device attributes: display, display with emulator, keyboard, and mouse
 #
@@ -1045,7 +1048,7 @@
 defflag opt_vga.h              VGA_CONSOLE_ATI_BROKEN_FONTSEL
 defflag opt_vga.h              VGA_RASTERCONSOLE
 defflag opt_vga.h              VGA_RESET
-device vga: displaydev, wsemuldisplaydev, pcdisplayops, drm
+device vga: displaydev, wsemuldisplaydev, pcdisplayops, drm, pcidisplaybus
 file   dev/ic/vga.c            vga & !vga_rasterconsole        needs-flag
 file   dev/ic/vga_raster.c     vga_rasterconsole               needs-flag
 file   dev/ic/vga_subr.c       vga | vga_rasterconsole
Index: dev/acpi/files.acpi
===================================================================
RCS file: /cvsroot/src/sys/dev/acpi/files.acpi,v
retrieving revision 1.52
diff -u -r1.52 files.acpi
--- dev/acpi/files.acpi 18 May 2008 22:05:59 -0000      1.52
+++ dev/acpi/files.acpi 9 Dec 2008 18:10:38 -0000
@@ -52,9 +52,12 @@
 file   dev/acpi/acpi_bat.c             acpibat
 
 # ACPI Display Device
-device acpidisplay
-attach acpidisplay at acpinodebus
-file   dev/acpi/acpi_display.c         acpidisplay
+device acpidisp
+attach acpidisp at acpinodebus   with acpidisp_acpi
+# TODO: tell autoconf(9) that the following device depends on acpi.
+# This device will not match/attach if acpi is not built (NACPI = 0).
+attach acpidisp at pcidisplaybus with acpidisp_pci
+file   dev/acpi/acpi_display.c         acpidisp
 
 # ACPI Thermal Zone
 device acpitz: sysmon_envsys
@@ -141,3 +144,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
Index: dev/pci/vga_pci.c
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/vga_pci.c,v
retrieving revision 1.44
diff -u -r1.44 vga_pci.c
--- dev/pci/vga_pci.c   3 Aug 2008 02:12:22 -0000       1.44
+++ dev/pci/vga_pci.c   9 Dec 2008 18:10:38 -0000
@@ -251,6 +251,9 @@
        if (!pmf_device_register(self, NULL, vga_pci_resume))
                aprint_error_dev(self, "couldn't establish power handler\n");
        config_found_ia(self, "drm", aux, vga_drm_print);
+
+       /* Configure PCI display subdevices (e.g. ACPI Display) */
+       config_found_ia(self, "pcidisplaybus", aux, NULL);
 }
 
 static int
/* $Id: fujbl_acpi.c 1020 2008-12-11 15:29:13Z 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 1020 2008-12-11 15:29:13Z sutre $");

#include <sys/inttypes.h>
#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;
}


/******************************************************************************
 * ACPI Utility Routines.
 ******************************************************************************/

static ACPI_STATUS
fujbl_acpi_eval_set_integer(ACPI_HANDLE handle, const char *path,
    ACPI_INTEGER arg)
{
        ACPI_STATUS rv;
        ACPI_OBJECT 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;

        rv = AcpiEvaluateObject(handle, path, &param_args, NULL);

        return rv;
}
/* $Id: fujhk_acpi.c 1013 2008-12-06 18:07:34Z sutre $ */

/*
 * Hotkey Button Driver for Fujitsu Lifebooks.
 *
 * This driver currently only supports 4 hotkey buttons.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$Id: fujhk_acpi.c 1013 2008-12-06 18:07:34Z sutre $");

#include <sys/inttypes.h>
#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%" PRIx64 "\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%" PRIx32 "\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