Some questions regarding ACPI

Hasso Tepper hasso at estpak.ee
Thu Mar 20 00:14:05 PDT 2008


YONETANI Tomokazu wrote:
> On Wed, Mar 19, 2008 at 09:15:35PM +0200, Hasso Tepper wrote:
> > acpi_ibm is almost ready to commit (renamed to acpi_thinkpad though).
>
> Ah, that's a good news!  My only concern is that ISTR someone mentioned
> me that his colleague was thinking about porting ACPI driver itself,
> so if you're not the person you may want to collaborate with him/her
> so as not to waste the effort or time...

Current state in my repo is attached. It should work on 1.12 as well as it 
doesn't depend yet on moving stuff around.

add-acpi-if-m.patch adds some new stuff to the ACPI code to access 
embedded controllers and match ACPI devices by ID. Not sure how, where 
and whether at all these should be documented.

add-acpi-thinkpad.patch is the driver itself. Note that some stuff doesn't 
work yet on newer thinkpads, I plan to work on it later.

There is still some things to do though. I have to decide what to do with 
led(4) part and with locking. There is also one fatal issue - laptop 
locks up hardly (no panic) while unloading module. Any idea regarding 
this is welcome. And of course test results :).


-- 
Hasso
# HG changeset patch
# User Hasso Tepper <hasso at estpak.ee>
# Date 1205919290 -7200
# Branch HEAD
# Node ID 5a6e2705cc95a340abf973ae5df73ef34e070b79
# Parent  cd529e2e7f849034d5d5a0973cb98af50db0e797
imported patch adding-acpi-if-m.patch

diff --git a/sys/conf/files b/sys/conf/files
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1387,6 +1387,7 @@ emulation/dragonfly12/dfbsd12_stat.c	non
 ${OSACPI_MI_DIR}/acpi_cmbat.c		optional acpi
 ${OSACPI_MI_DIR}/acpi_cpu.c		optional acpi
 ${OSACPI_MI_DIR}/acpi_ec.c		optional acpi
+${OSACPI_MI_DIR}/acpi_if.m		optional acpi
 ${OSACPI_MI_DIR}/acpi_isab.c		optional acpi isa
 ${OSACPI_MI_DIR}/acpi_lid.c		optional acpi
 ${OSACPI_MI_DIR}/acpi_package.c		optional acpi
diff --git a/sys/conf/kmod.mk b/sys/conf/kmod.mk
--- a/sys/conf/kmod.mk
+++ b/sys/conf/kmod.mk
@@ -275,7 +275,7 @@ MFILES?= kern/bus_if.m kern/device_if.m 
     bus/pccard/card_if.m bus/pccard/power_if.m bus/pci/pci_if.m \
     bus/pci/pcib_if.m \
     bus/ppbus/ppbus_if.m bus/smbus/smbus_if.m bus/usb/usb_if.m \
-    dev/disk/nata/ata_if.m \
+    dev/acpica5/acpi_if.m dev/disk/nata/ata_if.m \
     dev/sound/pcm/ac97_if.m dev/sound/pcm/channel_if.m \
     dev/sound/pcm/feeder_if.m dev/sound/pcm/mixer_if.m \
     libiconv/iconv_converter_if.m dev/agp/agp_if.m opencrypto/crypto_if.m
diff --git a/sys/dev/acpica5/Makefile b/sys/dev/acpica5/Makefile
--- a/sys/dev/acpica5/Makefile
+++ b/sys/dev/acpica5/Makefile
@@ -69,12 +69,14 @@ SRCS+=  acpi_package.c
 # SRCS+=	acpi_pci.c acpi_pcib.c acpi_pcib_acpi.c acpi_pcib_pci.c
 # SRCS+=	acpi_pci_link.c
 SRCS+=  acpi_powerres.c acpi_resource.c acpi_thermal.c
-SRCS+=  acpi_timer.c
+SRCS+=  acpi_timer.c acpi_if.c
 SRCS+=  OsdDebug.c
 SRCS+=  OsdHardware.c OsdInterface.c OsdInterrupt.c OsdMemory.c OsdSchedule.c
 SRCS+=  OsdStream.c OsdSynch.c OsdTable.c OsdEnvironment.c
 SRCS+=  opt_acpi.h opt_bus.h opt_ddb.h
-SRCS+=  device_if.h bus_if.h pci_if.h pcib_if.h isa_if.h
+SRCS+=  device_if.h bus_if.h pci_if.h pcib_if.h isa_if.h acpi_if.h
+MFILES = kern/device_if.m kern/bus_if.m bus/pci/pci_if.m bus/pci/pcib_if.m
+MFILES+= bus/isa/isa_if.m dev/acpica5/acpi_if.m
 
 # Debugging support
 DBSRC=	dbcmds.c dbdisply.c dbexec.c dbfileio.c dbhistry.c
diff --git a/sys/dev/acpica5/acpi.c b/sys/dev/acpica5/acpi.c
--- a/sys/dev/acpica5/acpi.c
+++ b/sys/dev/acpica5/acpi.c
@@ -135,6 +135,7 @@ static int	acpi_release_resource(device_
 			int rid, struct resource *r);
 static uint32_t	acpi_isa_get_logicalid(device_t dev);
 static int	acpi_isa_get_compatid(device_t dev, uint32_t *cids, int count);
+static char	*acpi_device_id_probe(device_t bus, device_t dev, char **ids);
 static int	acpi_isa_pnp_probe(device_t bus, device_t child,
 			struct isa_pnp_id *ids);
 static void	acpi_probe_children(device_t bus);
@@ -189,6 +190,9 @@ static device_method_t acpi_methods[] = 
     DEVMETHOD(bus_deactivate_resource,	bus_generic_deactivate_resource),
     DEVMETHOD(bus_setup_intr,		bus_generic_setup_intr),
     DEVMETHOD(bus_teardown_intr,	bus_generic_teardown_intr),
+
+    /* ACPI bus */
+    DEVMETHOD(acpi_id_probe,		acpi_device_id_probe),
 
     /* ISA emulation */
     DEVMETHOD(isa_pnp_probe,		acpi_isa_pnp_probe),
@@ -1121,6 +1125,24 @@ out:
 	AcpiOsFree(buf.Pointer);
     ACPI_UNLOCK;
     return_VALUE (valid);
+}
+
+static char *
+acpi_device_id_probe(device_t bus, device_t dev, char **ids) 
+{
+    ACPI_HANDLE h;
+    int i;
+
+    h = acpi_get_handle(dev);
+    if (ids == NULL || h == NULL || acpi_get_type(dev) != ACPI_TYPE_DEVICE)
+	return (NULL);
+
+    /* Try to match one of the array of IDs with a HID or CID. */
+    for (i = 0; ids[i] != NULL; i++) {
+	if (acpi_MatchHid(h, ids[i]))
+	    return (ids[i]);
+    }
+    return (NULL);
 }
 
 static int
diff --git a/sys/dev/acpica5/acpi_ec.c b/sys/dev/acpica5/acpi_ec.c
--- a/sys/dev/acpica5/acpi_ec.c
+++ b/sys/dev/acpica5/acpi_ec.c
@@ -327,11 +327,19 @@ static ACPI_STATUS	EcWrite(struct acpi_e
 				UINT8 *Data);
 static int		acpi_ec_probe(device_t dev);
 static int		acpi_ec_attach(device_t dev);
+static int		acpi_ec_read_method(device_t dev, u_int addr,
+				ACPI_INTEGER *val, int width);
+static int		acpi_ec_write_method(device_t dev, u_int addr,
+				ACPI_INTEGER val, int width);
 
 static device_method_t acpi_ec_methods[] = {
     /* Device interface */
     DEVMETHOD(device_probe,	acpi_ec_probe),
     DEVMETHOD(device_attach,	acpi_ec_attach),
+
+    /* Embedded controller interface */
+    DEVMETHOD(acpi_ec_read,     acpi_ec_read_method),
+    DEVMETHOD(acpi_ec_write,    acpi_ec_write_method),
 
     {0, 0}
 };
@@ -620,6 +628,33 @@ error:
 			     sc->ec_data_res);
     /* mtx_destroy(&sc->ec_mtx); */
     return (ENXIO);
+}
+
+/* Methods to allow other devices (e.g., smbat) to read/write EC space. */
+static int
+acpi_ec_read_method(device_t dev, u_int addr, ACPI_INTEGER *val, int width)
+{
+    struct acpi_ec_softc *sc;
+    ACPI_STATUS status;
+
+    sc = device_get_softc(dev);
+    status = EcSpaceHandler(ACPI_READ, addr, width * 8, val, sc, NULL);
+    if (ACPI_FAILURE(status))
+        return (ENXIO);
+    return (0);
+}
+
+static int
+acpi_ec_write_method(device_t dev, u_int addr, ACPI_INTEGER val, int width)
+{
+    struct acpi_ec_softc *sc;
+    ACPI_STATUS status;
+
+    sc = device_get_softc(dev);
+    status = EcSpaceHandler(ACPI_WRITE, addr, width * 8, &val, sc, NULL);
+    if (ACPI_FAILURE(status))
+        return (ENXIO);
+    return (0);
 }
 
 static void
diff --git a/sys/dev/acpica5/acpi_if.m b/sys/dev/acpica5/acpi_if.m
new file mode 100644
--- /dev/null
+++ b/sys/dev/acpica5/acpi_if.m
@@ -0,0 +1,92 @@
+#-
+# Copyright (c) 2004 Nate Lawson
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $DragonFly$
+#
+
+#include <sys/bus.h>
+#include <sys/types.h>
+#include "acpi.h"
+
+INTERFACE acpi;
+
+#
+# Default implementation for acpi_id_probe().
+#
+CODE {
+	static char *
+	acpi_generic_id_probe(device_t bus, device_t dev, char **ids)
+	{
+		return (NULL);
+	}
+};
+
+#
+# Check a device for a match in a list of ID strings.  The strings can be
+# EISA PNP IDs or ACPI _HID/_CID values.
+#
+# device_t bus:  parent bus for the device
+#
+# device_t dev:  device being considered
+#
+# char **ids:  array of ID strings to consider
+#
+# Returns:  ID string matched or NULL if no match
+#
+METHOD char * id_probe {
+	device_t	bus;
+	device_t	dev;
+	char		**ids;
+} DEFAULT acpi_generic_id_probe;
+
+#
+# Read embedded controller (EC) address space
+#
+# device_t dev:  EC device
+# u_int addr:  Address to read from in EC space
+# ACPI_INTEGER *val:  Location to store read value
+# int width:  Size of area to read in bytes
+#
+METHOD int ec_read {
+	device_t	dev;
+	u_int		addr;
+	ACPI_INTEGER	*val;
+	int		width;
+};
+
+#
+# Write embedded controller (EC) address space
+#
+# device_t dev:  EC device
+# u_int addr:  Address to write to in EC space
+# ACPI_INTEGER val:  Value to write
+# int width:  Size of value to write in bytes
+#
+METHOD int ec_write {
+	device_t	dev;
+	u_int		addr;
+	ACPI_INTEGER	val;
+	int		width;
+};
diff --git a/sys/dev/acpica5/acpivar.h b/sys/dev/acpica5/acpivar.h
--- a/sys/dev/acpica5/acpivar.h
+++ b/sys/dev/acpica5/acpivar.h
@@ -29,6 +29,7 @@
  * $DragonFly: src/sys/dev/acpica5/acpivar.h,v 1.12 2007/10/23 03:04:48 y0netan1 Exp $
  */
 
+#include "acpi_if.h"
 #include "bus_if.h"
 #include <sys/eventhandler.h>
 #include <sys/sysctl.h>
# HG changeset patch
# User Hasso Tepper <hasso at estpak.ee>
# Date 1205995474 -7200
# Branch HEAD
# Node ID c5db8d4a7c6b25a8576a8d42c5029aaf0e23bfb6
# Parent  5a6e2705cc95a340abf973ae5df73ef34e070b79
[mq]: adding-acpi-thinkpad.patch

diff --git a/sys/dev/acpica5/Makefile b/sys/dev/acpica5/Makefile
--- a/sys/dev/acpica5/Makefile
+++ b/sys/dev/acpica5/Makefile
@@ -117,7 +117,7 @@ acpi_wakecode.h: acpi_wakecode.S
 	${MAKE} -f ${SYSDIR}/${OSACPI_MD_DIR}/Makefile \
 		MAKESRCPATH=${SYSDIR}/${OSACPI_MD_DIR}
 
-SUBDIR=	acpi_toshiba
+SUBDIR=	acpi_toshiba acpi_thinkpad
 all: ${PROG} ${SUBDIR}
 
 # *.o file for each patched *.c file is created under the same directory,
diff --git a/sys/dev/acpica5/acpi_thinkpad/Makefile b/sys/dev/acpica5/acpi_thinkpad/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/dev/acpica5/acpi_thinkpad/Makefile
@@ -0,0 +1,9 @@
+# $DragonFly$
+
+SYSDIR?=${.CURDIR}/../../..
+
+KMOD=		acpi_thinkpad
+CFLAGS+=	-I${.OBJDIR}/.. -I${.CURDIR}/..
+SRCS=		acpi_thinkpad.c opt_acpi.h device_if.h bus_if.h
+
+.include <bsd.kmod.mk>
diff --git a/sys/dev/acpica5/acpi_thinkpad/acpi_thinkpad.c b/sys/dev/acpica5/acpi_thinkpad/acpi_thinkpad.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/acpica5/acpi_thinkpad/acpi_thinkpad.c
@@ -0,0 +1,1046 @@
+/*
+ * Copyright (c) 2004 Takanori Watanabe
+ * Copyright (c) 2005 Markus Brueffer <markus at FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Driver for extra ACPI-controlled gadgets found on IBM and Lenovo ThinkPad
+ * laptops. Inspired by the ibm-acpi and tpb projects which implement these
+ * features on Linux.
+ *
+ *   acpi-ibm: <http://ibm-acpi.sourceforge.net/>
+ *        tpb: <http://www.nongnu.org/tpb/>
+ */
+
+#include "opt_acpi.h"
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <machine/cpufunc.h>
+#include <sys/module.h>
+/* #include <dev/led/led.h> */
+#include <sys/sensors.h>
+#include <sys/sysctl.h>
+#include <machine/clock.h>
+
+#include "acpi.h"
+#include "acpivar.h"
+#include "acpi_if.h"
+
+#define _COMPONENT	ACPI_OEM
+ACPI_MODULE_NAME("THINKPAD")
+
+/* Internal methods */
+#define ACPI_THINKPAD_METHOD_EVENTS		1
+#define ACPI_THINKPAD_METHOD_EVENTMASK		2
+#define ACPI_THINKPAD_METHOD_HOTKEY		3
+#define ACPI_THINKPAD_METHOD_BRIGHTNESS		4
+#define ACPI_THINKPAD_METHOD_VOLUME		5
+#define ACPI_THINKPAD_METHOD_MUTE		6
+#define ACPI_THINKPAD_METHOD_THINKLIGHT		7
+#define ACPI_THINKPAD_METHOD_BLUETOOTH		8
+#define ACPI_THINKPAD_METHOD_WLAN		9
+#define ACPI_THINKPAD_METHOD_FANSPEED		10
+#define ACPI_THINKPAD_METHOD_FANLEVEL		11
+#define ACPI_THINKPAD_METHOD_FANSTATUS		12
+#define ACPI_THINKPAD_METHOD_THERMAL		13
+
+/* Hotkeys/Buttons */
+#define THINKPAD_RTC_HOTKEY1			0x64
+#define   THINKPAD_RTC_MASK_HOME		(1 << 0)
+#define   THINKPAD_RTC_MASK_SEARCH		(1 << 1)
+#define   THINKPAD_RTC_MASK_MAIL		(1 << 2)
+#define   THINKPAD_RTC_MASK_WLAN		(1 << 5)
+#define THINKPAD_RTC_HOTKEY2			0x65
+#define   THINKPAD_RTC_MASK_THINKPAD		(1 << 3)
+#define   THINKPAD_RTC_MASK_ZOOM		(1 << 5)
+#define   THINKPAD_RTC_MASK_VIDEO		(1 << 6)
+#define   THINKPAD_RTC_MASK_HIBERNATE		(1 << 7)
+#define THINKPAD_RTC_THINKLIGHT			0x66
+#define   THINKPAD_RTC_MASK_THINKLIGHT		(1 << 4)
+#define THINKPAD_RTC_SCREENEXPAND		0x67
+#define   THINKPAD_RTC_MASK_SCREENEXPAND	(1 << 5)
+#define THINKPAD_RTC_BRIGHTNESS			0x6c
+#define   THINKPAD_RTC_MASK_BRIGHTNESS		(1 << 5)
+#define THINKPAD_RTC_VOLUME			0x6e
+#define   THINKPAD_RTC_MASK_VOLUME		(1 << 7)
+
+/* Embedded Controller registers */
+#define THINKPAD_EC_BRIGHTNESS			0x31
+#define   THINKPAD_EC_MASK_BRI			0x7
+#define THINKPAD_EC_VOLUME			0x30
+#define   THINKPAD_EC_MASK_VOL			0xf
+#define   THINKPAD_EC_MASK_MUTE			(1 << 6)
+#define THINKPAD_EC_FANSTATUS			0x2F
+#define   THINKPAD_EC_MASK_FANLEVEL		0x3f
+#define   THINKPAD_EC_MASK_FANDISENGAGED	(1 << 6)
+#define   THINKPAD_EC_MASK_FANSTATUS		(1 << 7)
+#define THINKPAD_EC_FANSPEED			0x84
+
+/* CMOS Commands */
+#define THINKPAD_CMOS_VOLUME_DOWN		0
+#define THINKPAD_CMOS_VOLUME_UP			1
+#define THINKPAD_CMOS_VOLUME_MUTE		2
+#define THINKPAD_CMOS_BRIGHTNESS_UP		4
+#define THINKPAD_CMOS_BRIGHTNESS_DOWN		5
+
+/* ACPI methods */
+#define THINKPAD_NAME_KEYLIGHT			"KBLT"
+#define THINKPAD_NAME_WLAN_BT_GET		"GBDC"
+#define THINKPAD_NAME_WLAN_BT_SET		"SBDC"
+#define   THINKPAD_NAME_MASK_BT			(1 << 1)
+#define   THINKPAD_NAME_MASK_WLAN		(1 << 2)
+#define THINKPAD_NAME_THERMAL_GET		"TMP7"
+#define THINKPAD_NAME_THERMAL_UPDT		"UPDT"
+
+#define THINKPAD_NAME_EVENTS_STATUS_GET		"DHKC"
+#define THINKPAD_NAME_EVENTS_MASK_GET		"DHKN"
+#define THINKPAD_NAME_EVENTS_STATUS_SET		"MHKC"
+#define THINKPAD_NAME_EVENTS_MASK_SET		"MHKM"
+#define THINKPAD_NAME_EVENTS_GET		"MHKP"
+#define THINKPAD_NAME_EVENTS_AVAILMASK		"MHKA"
+
+#define ABS(x) (((x) < 0)? -(x) : (x))
+
+struct acpi_thinkpad_softc {
+	device_t	dev;
+	ACPI_HANDLE	handle;
+
+	/* Embedded controller */
+	device_t	ec_dev;
+	ACPI_HANDLE	ec_handle;
+
+	/* CMOS */
+	ACPI_HANDLE	cmos_handle;
+
+	/* Fan status */
+	ACPI_HANDLE	fan_handle;
+	int		fan_levels;
+
+	/* Keylight commands and states */
+	ACPI_HANDLE	light_handle;
+	int		light_cmd_on;
+	int		light_cmd_off;
+	int		light_val;
+	int		light_get_supported;
+	int		light_set_supported;
+
+	/* led(4) interface */
+	struct cdev	*led_dev;
+	int		led_busy;
+	int		led_state;
+
+	int		wlan_bt_flags;
+	int		thermal_updt_supported;
+
+	unsigned int	events_availmask;
+	unsigned int	events_initialmask;
+	int		events_mask_supported;
+	int		events_enable;
+
+	/* sensors(9) related */
+	struct ksensordev sensordev;
+	struct ksensor sensors[9];	/* XXX: 8 thermal + fan */
+
+	struct sysctl_ctx_list	*sysctl_ctx;
+	struct sysctl_oid	*sysctl_tree;
+};
+
+static struct {
+	char	*name;
+	int	method;
+	char	*description;
+	int	access;
+} acpi_thinkpad_sysctls[] = {
+	{
+		.name		= "events",
+		.method		= ACPI_THINKPAD_METHOD_EVENTS,
+		.description	= "ACPI events enable",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "eventmask",
+		.method		= ACPI_THINKPAD_METHOD_EVENTMASK,
+		.description	= "ACPI eventmask",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "hotkey",
+		.method		= ACPI_THINKPAD_METHOD_HOTKEY,
+		.description	= "Key Status",
+		.access		= CTLTYPE_INT | CTLFLAG_RD
+	},
+	{
+		.name		= "lcd_brightness",
+		.method		= ACPI_THINKPAD_METHOD_BRIGHTNESS,
+		.description	= "LCD Brightness",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "volume",
+		.method		= ACPI_THINKPAD_METHOD_VOLUME,
+		.description	= "Volume",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "mute",
+		.method		= ACPI_THINKPAD_METHOD_MUTE,
+		.description	= "Mute",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "thinklight",
+		.method		= ACPI_THINKPAD_METHOD_THINKLIGHT,
+		.description	= "Thinklight enable",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "bluetooth",
+		.method		= ACPI_THINKPAD_METHOD_BLUETOOTH,
+		.description	= "Bluetooth enable",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "wlan",
+		.method		= ACPI_THINKPAD_METHOD_WLAN,
+		.description	= "WLAN enable",
+		.access		= CTLTYPE_INT | CTLFLAG_RD
+	},
+	{
+		.name		= "fan_level",
+		.method		= ACPI_THINKPAD_METHOD_FANLEVEL,
+		.description	= "Fan level",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "fan",
+		.method		= ACPI_THINKPAD_METHOD_FANSTATUS,
+		.description	= "Fan enable",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+
+	{ NULL, 0, NULL, 0 }
+};
+
+/* ACPI_SERIAL_DECL(thinkpad, "ACPI THINKPAD extras"); */
+static int	acpi_thinkpad_probe(device_t dev);
+static int	acpi_thinkpad_attach(device_t dev);
+static int	acpi_thinkpad_detach(device_t dev);
+
+#if 0 /* not yet */
+static void	thinkpad_led(void *softc, int onoff);
+static void	thinkpad_led_task(struct acpi_thinkpad_softc *sc,
+		int pending __unused);
+#endif
+
+static int	acpi_thinkpad_sysctl(SYSCTL_HANDLER_ARGS);
+static int	acpi_thinkpad_sysctl_init(struct acpi_thinkpad_softc *sc,
+		int method);
+static int	acpi_thinkpad_sysctl_get(struct acpi_thinkpad_softc *sc,
+		int method);
+static int	acpi_thinkpad_sysctl_set(struct acpi_thinkpad_softc *sc,
+		int method, int val);
+
+static int	acpi_thinkpad_eventmask_set(struct acpi_thinkpad_softc *sc,
+		int val);
+static void	acpi_thinkpad_notify(ACPI_HANDLE h, UINT32 notify,
+		void *context);
+static void	acpi_thinkpad_refresh(void *);
+
+static device_method_t acpi_thinkpad_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe, acpi_thinkpad_probe),
+	DEVMETHOD(device_attach, acpi_thinkpad_attach),
+	DEVMETHOD(device_detach, acpi_thinkpad_detach),
+	{0, 0}
+};
+
+static driver_t	acpi_thinkpad_driver = {
+	"acpi_thinkpad",
+	acpi_thinkpad_methods,
+	sizeof(struct acpi_thinkpad_softc),
+};
+
+static devclass_t acpi_thinkpad_devclass;
+
+DRIVER_MODULE(acpi_thinkpad, acpi, acpi_thinkpad_driver,
+    acpi_thinkpad_devclass, 0, 0);
+MODULE_DEPEND(acpi_thinkpad, acpi, 1, 1, 1);
+static char    *thinkpad_ids[] = {"IBM0068", NULL};
+
+#if 0 /* not yet */
+static void
+thinkpad_led(void *softc, int onoff)
+{
+	struct acpi_thinkpad_softc* sc = (struct acpi_thinkpad_softc*) softc;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	if (sc->led_busy)
+		return;
+
+	sc->led_busy = 1;
+	sc->led_state = onoff;
+
+	AcpiOsExecute(OSL_NOTIFY_HANDLER, (void *)thinkpad_led_task, sc);
+}
+
+static void
+thinkpad_led_task(struct acpi_thinkpad_softc *sc, int pending __unused)
+{
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	/* ACPI_SERIAL_BEGIN(thinkpad); */
+	acpi_thinkpad_sysctl_set(sc, ACPI_THINKPAD_METHOD_THINKLIGHT, sc->led_state);
+	/* ACPI_SERIAL_END(thinkpad); */
+
+	sc->led_busy = 0;
+}
+#endif
+
+static int
+acpi_thinkpad_probe(device_t dev)
+{
+	if (acpi_disabled("thinkpad") ||
+	    ACPI_ID_PROBE(device_get_parent(dev), dev, thinkpad_ids) == NULL ||
+	    device_get_unit(dev) != 0) 
+		return (ENXIO);
+
+	device_set_desc(dev, "IBM/Lenovo ThinkPad ACPI Extras");
+	return (0);
+}
+
+static int
+acpi_thinkpad_attach(device_t dev)
+{
+	struct acpi_thinkpad_softc	*sc;
+	struct acpi_softc	*acpi_sc;
+	devclass_t		ec_devclass;
+	int			i;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
+
+	sc = device_get_softc(dev);
+	sc->dev = dev;
+	sc->handle = acpi_get_handle(dev);
+
+	acpi_sc = acpi_device_get_parent_softc(dev);
+
+	/* Look for the first embedded controller */
+        if (!(ec_devclass = devclass_find ("acpi_ec"))) {
+		if (bootverbose)
+			device_printf(dev, "Couldn't find acpi_ec devclass\n");
+		return (EINVAL);
+	}
+        if (!(sc->ec_dev = devclass_get_device(ec_devclass, 0))) {
+		if (bootverbose)
+			device_printf(dev, "Couldn't find acpi_ec device\n");
+		return (EINVAL);
+	}
+	sc->ec_handle = acpi_get_handle(sc->ec_dev);
+
+	/* ACPI_SERIAL_BEGIN(thinkpad); */
+
+#if 0
+	/* Get the sysctl tree */
+	sc->sysctl_ctx = device_get_sysctl_ctx(dev);
+	sc->sysctl_tree = device_get_sysctl_tree(dev);
+#endif
+	sysctl_ctx_init(sc->sysctl_ctx);
+	sc->sysctl_tree = SYSCTL_ADD_NODE(sc->sysctl_ctx,
+	    SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO,
+	    "thinkpad", CTLFLAG_RD, 0, "");
+
+	/* Look for event mask and hook up the nodes */
+	sc->events_mask_supported = ACPI_SUCCESS(acpi_GetInteger(sc->handle,
+	    THINKPAD_NAME_EVENTS_MASK_GET, &sc->events_initialmask));
+
+	if (sc->events_mask_supported) {
+		SYSCTL_ADD_INT(sc->sysctl_ctx,
+		    SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
+		    "initialmask", CTLFLAG_RD,
+		    &sc->events_initialmask, 0, "Initial eventmask");
+
+		/* The availmask is the bitmask of supported events */
+		if (ACPI_FAILURE(acpi_GetInteger(sc->handle,
+		    THINKPAD_NAME_EVENTS_AVAILMASK, &sc->events_availmask)))
+			sc->events_availmask = 0xffffffff;
+
+		SYSCTL_ADD_INT(sc->sysctl_ctx,
+		    SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
+		    "availmask", CTLFLAG_RD,
+		    &sc->events_availmask, 0, "Mask of supported events");
+	}
+
+	/* Hook up proc nodes */
+	for (i = 0; acpi_thinkpad_sysctls[i].name != NULL; i++) {
+		if (!acpi_thinkpad_sysctl_init(sc,
+		    acpi_thinkpad_sysctls[i].method))
+			continue;
+
+		SYSCTL_ADD_PROC(sc->sysctl_ctx,
+		    SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
+		    acpi_thinkpad_sysctls[i].name,
+		    acpi_thinkpad_sysctls[i].access,
+		    sc, i, acpi_thinkpad_sysctl, "I",
+		    acpi_thinkpad_sysctls[i].description);
+	}
+
+	/* ACPI_SERIAL_END(thinkpad); */
+
+	/* Handle notifies */
+	AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
+	    acpi_thinkpad_notify, dev);
+
+#if 0
+	/* Hook up light to led(4) */
+	if (sc->light_set_supported)
+		sc->led_dev = led_create_state(thinkpad_led, sc, "thinklight",
+		    sc->light_val);
+#endif
+	/* Attach sensors(9). */
+	if (sensor_task_register(sc, acpi_thinkpad_refresh, 5)) {
+		device_printf(sc->dev, "unable to register update task\n");
+		return 1;
+	}
+
+	strlcpy(sc->sensordev.xname, device_get_nameunit(sc->dev),
+	    sizeof(sc->sensordev.xname));
+
+	/* XXX: Temp sensors */
+	for (i = 0; i < 8; ++i) {
+		sc->sensors[i].type = SENSOR_TEMP;
+		sensor_attach(&sc->sensordev, &sc->sensors[i]);
+	}
+	
+	/* Fan RPM sensor */
+	sc->sensors[8].type = SENSOR_FANRPM;
+	sensor_attach(&sc->sensordev, &sc->sensors[8]);
+
+	sensordev_install(&sc->sensordev);
+
+	return (0);
+}
+
+static int
+acpi_thinkpad_detach(device_t dev)
+{
+	int i;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
+
+	struct acpi_thinkpad_softc *sc = device_get_softc(dev);
+
+	/* Disable events and restore eventmask */
+	/* ACPI_SERIAL_BEGIN(thinkpad); */
+	acpi_thinkpad_sysctl_set(sc, ACPI_THINKPAD_METHOD_EVENTS, 0);
+	acpi_thinkpad_sysctl_set(sc, ACPI_THINKPAD_METHOD_EVENTMASK,
+	    sc->events_initialmask);
+	/* ACPI_SERIAL_END(thinkpad); */
+
+	AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
+	    acpi_thinkpad_notify);
+
+	sysctl_ctx_free(sc->sysctl_ctx);
+
+#if 0
+	if (sc->led_dev != NULL)
+		led_destroy(sc->led_dev);
+#endif
+	sensordev_deinstall(&sc->sensordev);
+	for (i = 0; i < 9; i++)
+		sensor_detach(&sc->sensordev, &sc->sensors[i]);
+	sensor_task_unregister(sc);
+
+	return (0);
+}
+
+static int
+acpi_thinkpad_eventmask_set(struct acpi_thinkpad_softc *sc, int val)
+{
+	int i;
+	ACPI_OBJECT		arg[2];
+	ACPI_OBJECT_LIST	args;
+	ACPI_STATUS		status;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+	/* ACPI_SERIAL_ASSERT(thinkpad); */
+
+	args.Count = 2;
+	args.Pointer = arg;
+	arg[0].Type = ACPI_TYPE_INTEGER;
+	arg[1].Type = ACPI_TYPE_INTEGER;
+
+	for (i = 0; i < 32; ++i) {
+		arg[0].Integer.Value = i+1;
+		arg[1].Integer.Value = (((1 << i) & val) != 0);
+		status = AcpiEvaluateObject(sc->handle,
+		    THINKPAD_NAME_EVENTS_MASK_SET, &args, NULL);
+
+		if (ACPI_FAILURE(status))
+			return (status);
+	}
+
+	return (0);
+}
+
+static int
+acpi_thinkpad_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	struct acpi_thinkpad_softc	*sc;
+	int			arg;
+	int			error = 0;
+	int			function;
+	int			method;
+	
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	sc = (struct acpi_thinkpad_softc *)oidp->oid_arg1;
+	function = oidp->oid_arg2;
+	method = acpi_thinkpad_sysctls[function].method;
+
+	/* ACPI_SERIAL_BEGIN(thinkpad); */
+	arg = acpi_thinkpad_sysctl_get(sc, method);
+	error = sysctl_handle_int(oidp, &arg, 0, req);
+
+	/* Sanity check */
+	if (error != 0 || req->newptr == NULL)
+		goto out;
+
+	/* Update */
+	error = acpi_thinkpad_sysctl_set(sc, method, arg);
+
+out:
+	/* ACPI_SERIAL_END(thinkpad); */
+	return (error);
+}
+
+static int
+acpi_thinkpad_sysctl_get(struct acpi_thinkpad_softc *sc, int method)
+{
+	ACPI_INTEGER	val_ec;
+	int 		val = 0, key;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+	/* ACPI_SERIAL_ASSERT(thinkpad); */
+
+	switch (method) {
+	case ACPI_THINKPAD_METHOD_EVENTS:
+		acpi_GetInteger(sc->handle, THINKPAD_NAME_EVENTS_STATUS_GET,
+		    &val);
+		break;
+
+	case ACPI_THINKPAD_METHOD_EVENTMASK:
+		if (sc->events_mask_supported)
+			acpi_GetInteger(sc->handle,
+			    THINKPAD_NAME_EVENTS_MASK_GET, &val);
+		break;
+
+	case ACPI_THINKPAD_METHOD_HOTKEY:
+		/*
+		 * Construct the hotkey as a bitmask as illustrated below.
+		 * Note that whenever a key was pressed, the respecting bit
+		 * toggles and nothing else changes.
+		 * +--+--+-+-+-+-+-+-+-+-+-+-+
+		 * |11|10|9|8|7|6|5|4|3|2|1|0|
+		 * +--+--+-+-+-+-+-+-+-+-+-+-+
+		 *   |  | | | | | | | | | | |
+		 *   |  | | | | | | | | | | +- Home Button
+		 *   |  | | | | | | | | | +--- Search Button
+		 *   |  | | | | | | | | +----- Mail Button
+		 *   |  | | | | | | | +------- Thinkpad Button
+		 *   |  | | | | | | +--------- Zoom (Fn + Space)
+		 *   |  | | | | | +----------- WLAN Button
+		 *   |  | | | | +------------- Video Button
+		 *   |  | | | +--------------- Hibernate Button
+		 *   |  | | +----------------- Thinklight Button
+		 *   |  | +------------------- Screen expand (Fn + F8)
+		 *   |  +--------------------- Brightness
+		 *   +------------------------ Volume/Mute
+		 */
+		key = rtcin(THINKPAD_RTC_HOTKEY1);
+		val = (THINKPAD_RTC_MASK_HOME | THINKPAD_RTC_MASK_SEARCH |
+		    THINKPAD_RTC_MASK_MAIL | THINKPAD_RTC_MASK_WLAN) & key;
+		key = rtcin(THINKPAD_RTC_HOTKEY2);
+		val |= (THINKPAD_RTC_MASK_THINKPAD | THINKPAD_RTC_MASK_VIDEO |
+		    THINKPAD_RTC_MASK_HIBERNATE) & key;
+		val |= (THINKPAD_RTC_MASK_ZOOM & key) >> 1;
+		key = rtcin(THINKPAD_RTC_THINKLIGHT);
+		val |= (THINKPAD_RTC_MASK_THINKLIGHT & key) << 4;
+		key = rtcin(THINKPAD_RTC_SCREENEXPAND);
+		val |= (THINKPAD_RTC_MASK_THINKLIGHT & key) << 4;
+		key = rtcin(THINKPAD_RTC_BRIGHTNESS);
+		val |= (THINKPAD_RTC_MASK_BRIGHTNESS & key) << 5;
+		key = rtcin(THINKPAD_RTC_VOLUME);
+		val |= (THINKPAD_RTC_MASK_VOLUME & key) << 4;
+		break;
+
+	case ACPI_THINKPAD_METHOD_BRIGHTNESS:
+		ACPI_EC_READ(sc->ec_dev, THINKPAD_EC_BRIGHTNESS, &val_ec, 1);
+		val = val_ec & THINKPAD_EC_MASK_BRI;
+		break;
+
+	case ACPI_THINKPAD_METHOD_VOLUME:
+		ACPI_EC_READ(sc->ec_dev, THINKPAD_EC_VOLUME, &val_ec, 1);
+		val = val_ec & THINKPAD_EC_MASK_VOL;
+		break;
+
+	case ACPI_THINKPAD_METHOD_MUTE:
+		val = ((val_ec & THINKPAD_EC_MASK_MUTE) ==
+		    THINKPAD_EC_MASK_MUTE);
+		break;
+
+	case ACPI_THINKPAD_METHOD_THINKLIGHT:
+		if (sc->light_get_supported)
+			acpi_GetInteger(sc->ec_handle, THINKPAD_NAME_KEYLIGHT,
+			    &val);
+		else
+			val = sc->light_val;
+		break;
+
+	case ACPI_THINKPAD_METHOD_BLUETOOTH:
+		acpi_GetInteger(sc->handle, THINKPAD_NAME_WLAN_BT_GET, &val);
+		sc->wlan_bt_flags = val;
+		val = ((val & THINKPAD_NAME_MASK_BT) != 0);
+		break;
+
+	case ACPI_THINKPAD_METHOD_WLAN:
+		acpi_GetInteger(sc->handle, THINKPAD_NAME_WLAN_BT_GET, &val);
+		sc->wlan_bt_flags = val;
+		val = ((val & THINKPAD_NAME_MASK_WLAN) != 0);
+		break;
+
+	case ACPI_THINKPAD_METHOD_FANSPEED:
+		if (sc->fan_handle) {
+			if (ACPI_FAILURE(acpi_GetInteger(sc->fan_handle,
+			    NULL, &val)))
+				val = -1;
+		}
+		else {
+			ACPI_EC_READ(sc->ec_dev, THINKPAD_EC_FANSPEED,
+			    &val_ec, 2);
+			val = val_ec;
+		}
+		break;
+
+	case ACPI_THINKPAD_METHOD_FANLEVEL:
+		/*
+		 * The THINKPAD_EC_FANSTATUS register works as follows:
+		 * Bit 0-5 indicate the level at which the fan operates. Only
+		 *       values between 0 and 7 have an effect. Everything
+		 *       above 7 is treated the same as level 7
+		 * Bit 6 overrides the fan speed limit if set to 1
+		 * Bit 7 indicates at which mode the fan operates:
+		 *       manual (0) or automatic (1)
+		 */
+		if (!sc->fan_handle) {
+			ACPI_EC_READ(sc->ec_dev, THINKPAD_EC_FANSTATUS,
+			    &val_ec, 1);
+			val = val_ec & THINKPAD_EC_MASK_FANLEVEL;
+		}
+		break;
+
+	case ACPI_THINKPAD_METHOD_FANSTATUS:
+		if (!sc->fan_handle) {
+			ACPI_EC_READ(sc->ec_dev, THINKPAD_EC_FANSTATUS,
+			    &val_ec, 1);
+			val = (val_ec & THINKPAD_EC_MASK_FANSTATUS) ==
+			    THINKPAD_EC_MASK_FANSTATUS;
+		}
+		else
+			val = -1;
+		break;
+	}
+
+	return (val);
+}
+
+static int
+acpi_thinkpad_sysctl_set(struct acpi_thinkpad_softc *sc, int method, int arg)
+{
+	int			val, step, i;
+	ACPI_INTEGER		val_ec;
+	ACPI_OBJECT		Arg;
+	ACPI_OBJECT_LIST	Args;
+	ACPI_STATUS		status;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+	/* ACPI_SERIAL_ASSERT(thinkpad); */
+
+	switch (method) {
+	case ACPI_THINKPAD_METHOD_EVENTS:
+		if (arg < 0 || arg > 1)
+			return (EINVAL);
+
+		status = acpi_SetInteger(sc->handle,
+		    THINKPAD_NAME_EVENTS_STATUS_SET, arg);
+		if (ACPI_FAILURE(status))
+			return (status);
+		if (sc->events_mask_supported)
+			return acpi_thinkpad_eventmask_set(sc,
+			    sc->events_availmask);
+		break;
+
+	case ACPI_THINKPAD_METHOD_EVENTMASK:
+		if (sc->events_mask_supported)
+			return acpi_thinkpad_eventmask_set(sc, arg);
+		break;
+
+	case ACPI_THINKPAD_METHOD_BRIGHTNESS:
+		if (arg < 0 || arg > 7)
+			return (EINVAL);
+
+		if (sc->cmos_handle) {
+			/* Read the current brightness */
+			status = ACPI_EC_READ(sc->ec_dev,
+			    THINKPAD_EC_BRIGHTNESS, &val_ec, 1);
+			if (ACPI_FAILURE(status))
+				return (status);
+			val = val_ec & THINKPAD_EC_MASK_BRI;
+
+			Args.Count = 1;
+			Args.Pointer = &Arg;
+			Arg.Type = ACPI_TYPE_INTEGER;
+			Arg.Integer.Value = (arg > val) ?
+			    THINKPAD_CMOS_BRIGHTNESS_UP :
+			    THINKPAD_CMOS_BRIGHTNESS_DOWN;
+
+			step = (arg > val) ? 1 : -1;
+			for (i = val; i != arg; i += step) {
+				status = AcpiEvaluateObject(sc->cmos_handle,
+				    NULL, &Args, NULL);
+				if (ACPI_FAILURE(status))
+					break;
+			}
+		}
+		return ACPI_EC_WRITE(sc->ec_dev, THINKPAD_EC_BRIGHTNESS,
+		    arg, 1);
+		break;
+
+	case ACPI_THINKPAD_METHOD_VOLUME:
+		if (arg < 0 || arg > 14)
+			return (EINVAL);
+
+		status = ACPI_EC_READ(sc->ec_dev, THINKPAD_EC_VOLUME,
+		    &val_ec, 1);
+		if (ACPI_FAILURE(status))
+			return (status);
+
+		if (sc->cmos_handle) {
+			val = val_ec & THINKPAD_EC_MASK_VOL;
+
+			Args.Count = 1;
+			Args.Pointer = &Arg;
+			Arg.Type = ACPI_TYPE_INTEGER;
+			Arg.Integer.Value = (arg > val) ?
+			    THINKPAD_CMOS_VOLUME_UP : THINKPAD_CMOS_VOLUME_DOWN;
+
+			step = (arg > val) ? 1 : -1;
+			for (i = val; i != arg; i += step) {
+				status = AcpiEvaluateObject(sc->cmos_handle,
+				    NULL, &Args, NULL);
+				if (ACPI_FAILURE(status))
+					break;
+			}
+		}
+		return ACPI_EC_WRITE(sc->ec_dev, THINKPAD_EC_VOLUME, arg +
+		    (val_ec & (~THINKPAD_EC_MASK_VOL)), 1);
+		break;
+
+	case ACPI_THINKPAD_METHOD_MUTE:
+		if (arg < 0 || arg > 1)
+			return (EINVAL);
+
+		status = ACPI_EC_READ(sc->ec_dev, THINKPAD_EC_VOLUME,
+		    &val_ec, 1);
+		if (ACPI_FAILURE(status))
+			return (status);
+
+		if (sc->cmos_handle) {
+			val = val_ec & THINKPAD_EC_MASK_VOL;
+
+			Args.Count = 1;
+			Args.Pointer = &Arg;
+			Arg.Type = ACPI_TYPE_INTEGER;
+			Arg.Integer.Value = THINKPAD_CMOS_VOLUME_MUTE;
+
+			status = AcpiEvaluateObject(sc->cmos_handle, NULL,
+			    &Args, NULL);
+			if (ACPI_FAILURE(status))
+				break;
+		}
+		return ACPI_EC_WRITE(sc->ec_dev, THINKPAD_EC_VOLUME, (arg==1) ?
+		   val_ec | THINKPAD_EC_MASK_MUTE :
+		   val_ec & (~THINKPAD_EC_MASK_MUTE), 1);
+		break;
+
+	case ACPI_THINKPAD_METHOD_THINKLIGHT:
+		if (arg < 0 || arg > 1)
+			return (EINVAL);
+
+		if (sc->light_set_supported) {
+			Args.Count = 1;
+			Args.Pointer = &Arg;
+			Arg.Type = ACPI_TYPE_INTEGER;
+			Arg.Integer.Value = arg ?
+			    sc->light_cmd_on : sc->light_cmd_off;
+
+			status = AcpiEvaluateObject(sc->light_handle, NULL,
+			    &Args, NULL);
+			if (ACPI_SUCCESS(status))
+				sc->light_val = arg;
+			return (status);
+		}
+		break;
+
+	case ACPI_THINKPAD_METHOD_BLUETOOTH:
+		if (arg < 0 || arg > 1)
+			return (EINVAL);
+
+		val = (arg == 1) ? sc->wlan_bt_flags |
+		    THINKPAD_NAME_MASK_BT :
+		    sc->wlan_bt_flags & (~THINKPAD_NAME_MASK_BT);
+		return acpi_SetInteger(sc->handle, THINKPAD_NAME_WLAN_BT_SET,
+		    val);
+		break;
+
+	case ACPI_THINKPAD_METHOD_FANLEVEL:
+		if (arg < 0 || arg > 7)
+			return (EINVAL);
+
+		if (!sc->fan_handle) {
+			/* Read the current fanstatus */
+			ACPI_EC_READ(sc->ec_dev, THINKPAD_EC_FANSTATUS,
+			    &val_ec, 1);
+			val = val_ec & (~THINKPAD_EC_MASK_FANLEVEL);
+
+			return ACPI_EC_WRITE(sc->ec_dev, THINKPAD_EC_FANSTATUS,
+			    val | arg, 1);
+		}
+		break;
+
+	case ACPI_THINKPAD_METHOD_FANSTATUS:
+		if (arg < 0 || arg > 1)
+			return (EINVAL);
+
+		if (!sc->fan_handle) {
+			/* Read the current fanstatus */
+			ACPI_EC_READ(sc->ec_dev, THINKPAD_EC_FANSTATUS,
+			    &val_ec, 1);
+
+			return ACPI_EC_WRITE(sc->ec_dev, THINKPAD_EC_FANSTATUS,
+			    (arg == 1) ? (val_ec | THINKPAD_EC_MASK_FANSTATUS) :
+			    (val_ec & (~THINKPAD_EC_MASK_FANSTATUS)), 1);
+		}
+		break;
+	}
+
+	return (0);
+}
+
+static int
+acpi_thinkpad_sysctl_init(struct acpi_thinkpad_softc *sc, int method)
+{
+	int 			dummy;
+	ACPI_OBJECT_TYPE 	cmos_t;
+	ACPI_HANDLE		ledb_handle;
+
+	switch (method) {
+	case ACPI_THINKPAD_METHOD_EVENTS:
+		/* Events are disabled by default */
+		return (TRUE);
+
+	case ACPI_THINKPAD_METHOD_EVENTMASK:
+		return (sc->events_mask_supported);
+
+	case ACPI_THINKPAD_METHOD_HOTKEY:
+	case ACPI_THINKPAD_METHOD_BRIGHTNESS:
+	case ACPI_THINKPAD_METHOD_VOLUME:
+	case ACPI_THINKPAD_METHOD_MUTE:
+		/* EC is required here, which was already checked before */
+		return (TRUE);
+
+	case ACPI_THINKPAD_METHOD_THINKLIGHT:
+		sc->cmos_handle = NULL;
+		sc->light_get_supported = ACPI_SUCCESS(acpi_GetInteger(
+		    sc->ec_handle, THINKPAD_NAME_KEYLIGHT, &sc->light_val));
+
+		if ((ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\UCMS",
+		    &sc->light_handle)) ||
+		    ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\CMOS",
+		    &sc->light_handle)) ||
+		    ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\CMS",
+		    &sc->light_handle))) &&
+		    ACPI_SUCCESS(AcpiGetType(sc->light_handle, &cmos_t)) &&
+		    cmos_t == ACPI_TYPE_METHOD) {
+			sc->light_cmd_on = 0x0c;
+			sc->light_cmd_off = 0x0d;
+			sc->cmos_handle = sc->light_handle;
+		}
+		else if (ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\LGHT",
+		    &sc->light_handle))) {
+			sc->light_cmd_on = 1;
+			sc->light_cmd_off = 0;
+		}
+		else
+			sc->light_handle = NULL;
+
+		sc->light_set_supported = (sc->light_handle &&
+		    ACPI_FAILURE(AcpiGetHandle(sc->ec_handle, "LEDB",
+		    &ledb_handle)));
+
+		if (sc->light_get_supported)
+			return (TRUE);
+
+		if (sc->light_set_supported) {
+			sc->light_val = 0;
+			return (TRUE);
+		}
+
+		return (FALSE);
+
+	case ACPI_THINKPAD_METHOD_BLUETOOTH:
+	case ACPI_THINKPAD_METHOD_WLAN:
+		if (ACPI_SUCCESS(acpi_GetInteger(sc->handle,
+		    THINKPAD_NAME_WLAN_BT_GET, &dummy)))
+			return (TRUE);
+		return (FALSE);
+
+	case ACPI_THINKPAD_METHOD_FANSPEED:
+		/* 
+		 * Some models report the fan speed in levels from 0-7
+		 * Newer models report it contiguously
+		 */
+		sc->fan_levels = (ACPI_SUCCESS(AcpiGetHandle(sc->handle, "GFAN",
+		    &sc->fan_handle)) ||
+		    ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\FSPD",
+		    &sc->fan_handle)));
+		return (TRUE);
+
+	case ACPI_THINKPAD_METHOD_FANLEVEL:
+	case ACPI_THINKPAD_METHOD_FANSTATUS:
+		/* 
+		 * Fan status is only supported on those models,
+		 * which report fan RPM contiguously, not in levels
+		 */
+		if (sc->fan_levels)
+			return (FALSE);
+		return (TRUE);
+
+	case ACPI_THINKPAD_METHOD_THERMAL:
+		if (ACPI_SUCCESS(acpi_GetInteger(sc->ec_handle,
+		    THINKPAD_NAME_THERMAL_GET, &dummy))) {
+			sc->thermal_updt_supported =
+			    ACPI_SUCCESS(acpi_GetInteger(sc->ec_handle,
+			    THINKPAD_NAME_THERMAL_UPDT, &dummy));
+			return (TRUE);
+		}
+		return (FALSE);
+	}
+	return (FALSE);
+}
+
+static void
+acpi_thinkpad_notify(ACPI_HANDLE h, UINT32 notify, void *context)
+{
+	int		event, arg, type;
+	device_t	dev = context;
+	struct acpi_thinkpad_softc *sc = device_get_softc(dev);
+
+	ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify);
+
+	if (notify != 0x80)
+		device_printf(dev, "Unknown notify\n");
+
+	for (;;) {
+		acpi_GetInteger(acpi_get_handle(dev), THINKPAD_NAME_EVENTS_GET,
+		    &event);
+
+		if (event == 0)
+			break;
+
+		type = (event >> 12) & 0xf;
+		arg = event & 0xfff;
+		switch (type) {
+		case 1:
+			if (!(sc->events_availmask & (1 << (arg - 1)))) {
+				device_printf(dev, "Unknown key %d\n", arg);
+				break;
+			}
+
+			/* Notify devd(8) */
+			device_printf(dev, "THINKPAD event: %u\n",
+			    (arg & 0xff));
+			acpi_UserNotify("THINKPAD", h, (arg & 0xff));
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+static void
+acpi_thinkpad_refresh(void *arg)
+{
+	struct acpi_thinkpad_softc *sc = (struct acpi_thinkpad_softc *)arg;
+	char temp_cmd[] = "TMP0";
+	int data, i;
+	ACPI_INTEGER speed;
+
+	for (i = 0; i < 8; i++) {
+		temp_cmd[3] = '0' + i;
+		
+                /*
+                 * The TMPx methods seem to return +/- 128 or 0
+                 * when the respecting sensor is not available
+                 */
+		sc->sensors[i].flags &= ~SENSOR_FINVALID;
+                if (ACPI_FAILURE(acpi_GetInteger(sc->ec_handle, temp_cmd,
+                    &data)) || ABS(data) == 128 || data == 0) {
+			sc->sensors[i].flags |= SENSOR_FINVALID;
+                        data = 0;
+		}
+                else if (sc->thermal_updt_supported) {
+                        /* XXX: Temperature is reported in tenth of Kelvin */
+                        data = (data - 2732 + 5) / 10;
+		}
+		sc->sensors[i].value = data * 1000000 + 273150000;
+        }
+	
+	sc->sensors[8].flags &= ~SENSOR_FINVALID;
+	if (sc->fan_handle) {
+		if (ACPI_FAILURE(acpi_GetInteger(sc->fan_handle,
+		    NULL, &data)))
+			sc->sensors[8].flags |= SENSOR_FINVALID;
+                        data = -1;
+        }
+        else {
+                ACPI_EC_READ(sc->ec_dev, THINKPAD_EC_FANSPEED, &speed, 2);
+                data = speed;
+        }
+
+	sc->sensors[8].value = data;
+}
diff --git a/sys/platform/pc32/conf/files b/sys/platform/pc32/conf/files
--- a/sys/platform/pc32/conf/files
+++ b/sys/platform/pc32/conf/files
@@ -72,6 +72,7 @@ dev/serial/dgb/dgm.c			optional	dgm
 ${OSACPI_MD_DIR}/acpi_machdep.c		optional        acpi
 ${OSACPI_MD_DIR}/acpi_wakeup.c		optional        acpi
 ${OSACPI_MD_DIR}/acpi_toshiba.c		optional        acpi_toshiba acpi
+${ACPICA_DIR}/acpi_thinkpad/acpi_thinkpad.c optional	acpi_thinkpad acpi
 acpi.h					optional	acpi		\
 	dependency	"$S/${ACPICA_DIR}/include/acpi.h"		\
 	compile-with	"cp $S/${ACPICA_DIR}/include/acpi.h ${.OBJDIR}/"	\




More information about the Kernel mailing list