Просмотр исходного кода

Merge branch 'for-upstream/platform-x86_tpacpi' of git://repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6 into x86-platform

Matthew Garrett 15 лет назад
Родитель
Сommit
420f5f0c5a
2 измененных файлов с 425 добавлено и 241 удалено
  1. 49 17
      Documentation/laptops/thinkpad-acpi.txt
  2. 376 224
      drivers/platform/x86/thinkpad_acpi.c

+ 49 - 17
Documentation/laptops/thinkpad-acpi.txt

@@ -292,13 +292,13 @@ sysfs notes:
 
 
 		Warning: when in NVRAM mode, the volume up/down/mute
 		Warning: when in NVRAM mode, the volume up/down/mute
 		keys are synthesized according to changes in the mixer,
 		keys are synthesized according to changes in the mixer,
-		so you have to use volume up or volume down to unmute,
-		as per the ThinkPad volume mixer user interface.  When
-		in ACPI event mode, volume up/down/mute are reported as
-		separate events, but this behaviour may be corrected in
-		future releases of this driver, in which case the
-		ThinkPad volume mixer user interface semantics will be
-		enforced.
+		which uses a single volume up or volume down hotkey
+		press to unmute, as per the ThinkPad volume mixer user
+		interface.  When in ACPI event mode, volume up/down/mute
+		events are reported by the firmware and can behave
+		differently (and that behaviour changes with firmware
+		version -- not just with firmware models -- as well as
+		OSI(Linux) state).
 
 
 	hotkey_poll_freq:
 	hotkey_poll_freq:
 		frequency in Hz for hot key polling. It must be between
 		frequency in Hz for hot key polling. It must be between
@@ -309,7 +309,7 @@ sysfs notes:
 		will cause hot key presses that require NVRAM polling
 		will cause hot key presses that require NVRAM polling
 		to never be reported.
 		to never be reported.
 
 
-		Setting hotkey_poll_freq too low will cause repeated
+		Setting hotkey_poll_freq too low may cause repeated
 		pressings of the same hot key to be misreported as a
 		pressings of the same hot key to be misreported as a
 		single key press, or to not even be detected at all.
 		single key press, or to not even be detected at all.
 		The recommended polling frequency is 10Hz.
 		The recommended polling frequency is 10Hz.
@@ -397,6 +397,7 @@ ACPI	Scan
 event	code	Key		Notes
 event	code	Key		Notes
 
 
 0x1001	0x00	FN+F1		-
 0x1001	0x00	FN+F1		-
+
 0x1002	0x01	FN+F2		IBM: battery (rare)
 0x1002	0x01	FN+F2		IBM: battery (rare)
 				Lenovo: Screen lock
 				Lenovo: Screen lock
 
 
@@ -404,7 +405,8 @@ event	code	Key		Notes
 				this hot key, even with hot keys
 				this hot key, even with hot keys
 				disabled or with Fn+F3 masked
 				disabled or with Fn+F3 masked
 				off
 				off
-				IBM: screen lock
+				IBM: screen lock, often turns
+				off the ThinkLight as side-effect
 				Lenovo: battery
 				Lenovo: battery
 
 
 0x1004	0x03	FN+F4		Sleep button (ACPI sleep button
 0x1004	0x03	FN+F4		Sleep button (ACPI sleep button
@@ -433,7 +435,8 @@ event	code	Key		Notes
 				Do you feel lucky today?
 				Do you feel lucky today?
 
 
 0x1008	0x07	FN+F8		IBM: toggle screen expand
 0x1008	0x07	FN+F8		IBM: toggle screen expand
-				Lenovo: configure UltraNav
+				Lenovo: configure UltraNav,
+				or toggle screen expand
 
 
 0x1009	0x08	FN+F9		-
 0x1009	0x08	FN+F9		-
 	..	..		..
 	..	..		..
@@ -444,7 +447,7 @@ event	code	Key		Notes
 				either through the ACPI event,
 				either through the ACPI event,
 				or through a hotkey event.
 				or through a hotkey event.
 				The firmware may refuse to
 				The firmware may refuse to
-				generate further FN+F4 key
+				generate further FN+F12 key
 				press events until a S3 or S4
 				press events until a S3 or S4
 				ACPI sleep cycle is performed,
 				ACPI sleep cycle is performed,
 				or some time passes.
 				or some time passes.
@@ -512,15 +515,19 @@ events for switches:
 SW_RFKILL_ALL	T60 and later hardware rfkill rocker switch
 SW_RFKILL_ALL	T60 and later hardware rfkill rocker switch
 SW_TABLET_MODE	Tablet ThinkPads HKEY events 0x5009 and 0x500A
 SW_TABLET_MODE	Tablet ThinkPads HKEY events 0x5009 and 0x500A
 
 
-Non hot-key ACPI HKEY event map:
+Non hotkey ACPI HKEY event map:
+-------------------------------
+
+Events that are not propagated by the driver, except for legacy
+compatibility purposes when hotkey_report_mode is set to 1:
+
 0x5001		Lid closed
 0x5001		Lid closed
 0x5002		Lid opened
 0x5002		Lid opened
 0x5009		Tablet swivel: switched to tablet mode
 0x5009		Tablet swivel: switched to tablet mode
 0x500A		Tablet swivel: switched to normal mode
 0x500A		Tablet swivel: switched to normal mode
 0x7000		Radio Switch may have changed state
 0x7000		Radio Switch may have changed state
 
 
-The above events are not propagated by the driver, except for legacy
-compatibility purposes when hotkey_report_mode is set to 1.
+Events that are never propagated by the driver:
 
 
 0x2304		System is waking up from suspend to undock
 0x2304		System is waking up from suspend to undock
 0x2305		System is waking up from suspend to eject bay
 0x2305		System is waking up from suspend to eject bay
@@ -528,14 +535,39 @@ compatibility purposes when hotkey_report_mode is set to 1.
 0x2405		System is waking up from hibernation to eject bay
 0x2405		System is waking up from hibernation to eject bay
 0x5010		Brightness level changed/control event
 0x5010		Brightness level changed/control event
 
 
-The above events are never propagated by the driver.
+Events that are propagated by the driver to userspace:
 
 
+0x2313		ALARM: System is waking up from suspend because
+		the battery is nearly empty
+0x2413		ALARM: System is waking up from hibernation because
+		the battery is nearly empty
 0x3003		Bay ejection (see 0x2x05) complete, can sleep again
 0x3003		Bay ejection (see 0x2x05) complete, can sleep again
+0x3006		Bay hotplug request (hint to power up SATA link when
+		the optical drive tray is ejected)
 0x4003		Undocked (see 0x2x04), can sleep again
 0x4003		Undocked (see 0x2x04), can sleep again
 0x500B		Tablet pen inserted into its storage bay
 0x500B		Tablet pen inserted into its storage bay
 0x500C		Tablet pen removed from its storage bay
 0x500C		Tablet pen removed from its storage bay
-
-The above events are propagated by the driver.
+0x6011		ALARM: battery is too hot
+0x6012		ALARM: battery is extremely hot
+0x6021		ALARM: a sensor is too hot
+0x6022		ALARM: a sensor is extremely hot
+0x6030		System thermal table changed
+
+Battery nearly empty alarms are a last resort attempt to get the
+operating system to hibernate or shutdown cleanly (0x2313), or shutdown
+cleanly (0x2413) before power is lost.  They must be acted upon, as the
+wake up caused by the firmware will have negated most safety nets...
+
+When any of the "too hot" alarms happen, according to Lenovo the user
+should suspend or hibernate the laptop (and in the case of battery
+alarms, unplug the AC adapter) to let it cool down.  These alarms do
+signal that something is wrong, they should never happen on normal
+operating conditions.
+
+The "extremely hot" alarms are emergencies.  According to Lenovo, the
+operating system is to force either an immediate suspend or hibernate
+cycle, or a system shutdown.  Obviously, something is very wrong if this
+happens.
 
 
 Compatibility notes:
 Compatibility notes:
 
 

+ 376 - 224
drivers/platform/x86/thinkpad_acpi.c

@@ -122,8 +122,14 @@ enum {
 	TP_NVRAM_POS_LEVEL_VOLUME	= 0,
 	TP_NVRAM_POS_LEVEL_VOLUME	= 0,
 };
 };
 
 
+/* Misc NVRAM-related */
+enum {
+	TP_NVRAM_LEVEL_VOLUME_MAX = 14,
+};
+
 /* ACPI HIDs */
 /* ACPI HIDs */
 #define TPACPI_ACPI_HKEY_HID		"IBM0068"
 #define TPACPI_ACPI_HKEY_HID		"IBM0068"
+#define TPACPI_ACPI_EC_HID		"PNP0C09"
 
 
 /* Input IDs */
 /* Input IDs */
 #define TPACPI_HKEY_INPUT_PRODUCT	0x5054 /* "TP" */
 #define TPACPI_HKEY_INPUT_PRODUCT	0x5054 /* "TP" */
@@ -299,8 +305,8 @@ static struct {
 	u32 hotkey_tablet:1;
 	u32 hotkey_tablet:1;
 	u32 light:1;
 	u32 light:1;
 	u32 light_status:1;
 	u32 light_status:1;
-	u32 bright_16levels:1;
 	u32 bright_acpimode:1;
 	u32 bright_acpimode:1;
+	u32 bright_unkfw:1;
 	u32 wan:1;
 	u32 wan:1;
 	u32 uwb:1;
 	u32 uwb:1;
 	u32 fan_ctrl_status_undef:1;
 	u32 fan_ctrl_status_undef:1;
@@ -363,6 +369,9 @@ struct tpacpi_led_classdev {
 	unsigned int led;
 	unsigned int led;
 };
 };
 
 
+/* brightness level capabilities */
+static unsigned int bright_maxlvl;	/* 0 = unknown */
+
 #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
 #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
 static int dbg_wlswemul;
 static int dbg_wlswemul;
 static int tpacpi_wlsw_emulstate;
 static int tpacpi_wlsw_emulstate;
@@ -480,6 +489,15 @@ static unsigned long __init tpacpi_check_quirks(
 	return 0;
 	return 0;
 }
 }
 
 
+static inline bool __pure __init tpacpi_is_lenovo(void)
+{
+	return thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO;
+}
+
+static inline bool __pure __init tpacpi_is_ibm(void)
+{
+	return thinkpad_id.vendor == PCI_VENDOR_ID_IBM;
+}
 
 
 /****************************************************************************
 /****************************************************************************
  ****************************************************************************
  ****************************************************************************
@@ -494,21 +512,13 @@ static unsigned long __init tpacpi_check_quirks(
  */
  */
 
 
 static acpi_handle root_handle;
 static acpi_handle root_handle;
+static acpi_handle ec_handle;
 
 
 #define TPACPI_HANDLE(object, parent, paths...)			\
 #define TPACPI_HANDLE(object, parent, paths...)			\
 	static acpi_handle  object##_handle;			\
 	static acpi_handle  object##_handle;			\
-	static acpi_handle *object##_parent = &parent##_handle;	\
-	static char        *object##_path;			\
-	static char        *object##_paths[] = { paths }
-
-TPACPI_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0",	/* 240, 240x */
-	   "\\_SB.PCI.ISA.EC",	/* 570 */
-	   "\\_SB.PCI0.ISA0.EC0",	/* 600e/x, 770e, 770x */
-	   "\\_SB.PCI0.ISA.EC",	/* A21e, A2xm/p, T20-22, X20-21 */
-	   "\\_SB.PCI0.AD4S.EC0",	/* i1400, R30 */
-	   "\\_SB.PCI0.ICH3.EC0",	/* R31 */
-	   "\\_SB.PCI0.LPC.EC",	/* all others */
-	   );
+	static const acpi_handle *object##_parent __initdata =	\
+						&parent##_handle; \
+	static char *object##_paths[] __initdata = { paths }
 
 
 TPACPI_HANDLE(ecrd, ec, "ECRD");	/* 570 */
 TPACPI_HANDLE(ecrd, ec, "ECRD");	/* 570 */
 TPACPI_HANDLE(ecwr, ec, "ECWR");	/* 570 */
 TPACPI_HANDLE(ecwr, ec, "ECWR");	/* 570 */
@@ -528,6 +538,7 @@ TPACPI_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA",	/* 570 */
 	   "\\_SB.PCI0.AGP0.VID0",	/* 600e/x, 770x */
 	   "\\_SB.PCI0.AGP0.VID0",	/* 600e/x, 770x */
 	   "\\_SB.PCI0.VID0",	/* 770e */
 	   "\\_SB.PCI0.VID0",	/* 770e */
 	   "\\_SB.PCI0.VID",	/* A21e, G4x, R50e, X30, X40 */
 	   "\\_SB.PCI0.VID",	/* A21e, G4x, R50e, X30, X40 */
+	   "\\_SB.PCI0.AGP.VGA",	/* X100e and a few others */
 	   "\\_SB.PCI0.AGP.VID",	/* all others */
 	   "\\_SB.PCI0.AGP.VID",	/* all others */
 	   );				/* R30, R31 */
 	   );				/* R30, R31 */
 
 
@@ -594,9 +605,10 @@ static int acpi_evalf(acpi_handle handle,
 
 
 	switch (res_type) {
 	switch (res_type) {
 	case 'd':		/* int */
 	case 'd':		/* int */
-		if (res)
+		success = (status == AE_OK &&
+			   out_obj.type == ACPI_TYPE_INTEGER);
+		if (success && res)
 			*(int *)res = out_obj.integer.value;
 			*(int *)res = out_obj.integer.value;
-		success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER;
 		break;
 		break;
 	case 'v':		/* void */
 	case 'v':		/* void */
 		success = status == AE_OK;
 		success = status == AE_OK;
@@ -609,8 +621,8 @@ static int acpi_evalf(acpi_handle handle,
 	}
 	}
 
 
 	if (!success && !quiet)
 	if (!success && !quiet)
-		printk(TPACPI_ERR "acpi_evalf(%s, %s, ...) failed: %d\n",
-		       method, fmt0, status);
+		printk(TPACPI_ERR "acpi_evalf(%s, %s, ...) failed: %s\n",
+		       method, fmt0, acpi_format_exception(status));
 
 
 	return success;
 	return success;
 }
 }
@@ -661,11 +673,11 @@ static int issue_thinkpad_cmos_command(int cmos_cmd)
 
 
 #define TPACPI_ACPIHANDLE_INIT(object) \
 #define TPACPI_ACPIHANDLE_INIT(object) \
 	drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \
 	drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \
-		object##_paths, ARRAY_SIZE(object##_paths), &object##_path)
+		object##_paths, ARRAY_SIZE(object##_paths))
 
 
-static void drv_acpi_handle_init(char *name,
-			   acpi_handle *handle, acpi_handle parent,
-			   char **paths, int num_paths, char **path)
+static void __init drv_acpi_handle_init(const char *name,
+			   acpi_handle *handle, const acpi_handle parent,
+			   char **paths, const int num_paths)
 {
 {
 	int i;
 	int i;
 	acpi_status status;
 	acpi_status status;
@@ -676,10 +688,9 @@ static void drv_acpi_handle_init(char *name,
 	for (i = 0; i < num_paths; i++) {
 	for (i = 0; i < num_paths; i++) {
 		status = acpi_get_handle(parent, paths[i], handle);
 		status = acpi_get_handle(parent, paths[i], handle);
 		if (ACPI_SUCCESS(status)) {
 		if (ACPI_SUCCESS(status)) {
-			*path = paths[i];
 			dbg_printk(TPACPI_DBG_INIT,
 			dbg_printk(TPACPI_DBG_INIT,
 				   "Found ACPI handle %s for %s\n",
 				   "Found ACPI handle %s for %s\n",
-				   *path, name);
+				   paths[i], name);
 			return;
 			return;
 		}
 		}
 	}
 	}
@@ -689,6 +700,43 @@ static void drv_acpi_handle_init(char *name,
 	*handle = NULL;
 	*handle = NULL;
 }
 }
 
 
+static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle,
+			u32 level, void *context, void **return_value)
+{
+	*(acpi_handle *)return_value = handle;
+
+	return AE_CTRL_TERMINATE;
+}
+
+static void __init tpacpi_acpi_handle_locate(const char *name,
+		const char *hid,
+		acpi_handle *handle)
+{
+	acpi_status status;
+	acpi_handle device_found;
+
+	BUG_ON(!name || !hid || !handle);
+	vdbg_printk(TPACPI_DBG_INIT,
+			"trying to locate ACPI handle for %s, using HID %s\n",
+			name, hid);
+
+	memset(&device_found, 0, sizeof(device_found));
+	status = acpi_get_devices(hid, tpacpi_acpi_handle_locate_callback,
+				  (void *)name, &device_found);
+
+	*handle = NULL;
+
+	if (ACPI_SUCCESS(status)) {
+		*handle = device_found;
+		dbg_printk(TPACPI_DBG_INIT,
+			   "Found ACPI handle for %s\n", name);
+	} else {
+		vdbg_printk(TPACPI_DBG_INIT,
+			    "Could not locate an ACPI handle for %s: %s\n",
+			    name, acpi_format_exception(status));
+	}
+}
+
 static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
 static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
 {
 {
 	struct ibm_struct *ibm = data;
 	struct ibm_struct *ibm = data;
@@ -736,8 +784,8 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm)
 			       "handling %s events\n", ibm->name);
 			       "handling %s events\n", ibm->name);
 		} else {
 		} else {
 			printk(TPACPI_ERR
 			printk(TPACPI_ERR
-			       "acpi_install_notify_handler(%s) failed: %d\n",
-			       ibm->name, status);
+			       "acpi_install_notify_handler(%s) failed: %s\n",
+			       ibm->name, acpi_format_exception(status));
 		}
 		}
 		return -ENODEV;
 		return -ENODEV;
 	}
 	}
@@ -1035,80 +1083,6 @@ static void tpacpi_disable_brightness_delay(void)
 			"ACPI backlight control delay disabled\n");
 			"ACPI backlight control delay disabled\n");
 }
 }
 
 
-static int __init tpacpi_query_bcl_levels(acpi_handle handle)
-{
-	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
-	union acpi_object *obj;
-	int rc;
-
-	if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) {
-		obj = (union acpi_object *)buffer.pointer;
-		if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
-			printk(TPACPI_ERR "Unknown _BCL data, "
-			       "please report this to %s\n", TPACPI_MAIL);
-			rc = 0;
-		} else {
-			rc = obj->package.count;
-		}
-	} else {
-		return 0;
-	}
-
-	kfree(buffer.pointer);
-	return rc;
-}
-
-static acpi_status __init tpacpi_acpi_walk_find_bcl(acpi_handle handle,
-					u32 lvl, void *context, void **rv)
-{
-	char name[ACPI_PATH_SEGMENT_LENGTH];
-	struct acpi_buffer buffer = { sizeof(name), &name };
-
-	if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) &&
-	    !strncmp("_BCL", name, sizeof(name) - 1)) {
-		BUG_ON(!rv || !*rv);
-		**(int **)rv = tpacpi_query_bcl_levels(handle);
-		return AE_CTRL_TERMINATE;
-	} else {
-		return AE_OK;
-	}
-}
-
-/*
- * Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map
- */
-static int __init tpacpi_check_std_acpi_brightness_support(void)
-{
-	int status;
-	int bcl_levels = 0;
-	void *bcl_ptr = &bcl_levels;
-
-	if (!vid_handle) {
-		TPACPI_ACPIHANDLE_INIT(vid);
-	}
-	if (!vid_handle)
-		return 0;
-
-	/*
-	 * Search for a _BCL method, and execute it.  This is safe on all
-	 * ThinkPads, and as a side-effect, _BCL will place a Lenovo Vista
-	 * BIOS in ACPI backlight control mode.  We do NOT have to care
-	 * about calling the _BCL method in an enabled video device, any
-	 * will do for our purposes.
-	 */
-
-	status = acpi_walk_namespace(ACPI_TYPE_METHOD, vid_handle, 3,
-				     tpacpi_acpi_walk_find_bcl, NULL, NULL,
-				     &bcl_ptr);
-
-	if (ACPI_SUCCESS(status) && bcl_levels > 2) {
-		tp_features.bright_acpimode = 1;
-		return (bcl_levels - 2);
-	}
-
-	return 0;
-}
-
 static void printk_deprecated_attribute(const char * const what,
 static void printk_deprecated_attribute(const char * const what,
 					const char * const details)
 					const char * const details)
 {
 {
@@ -1872,34 +1846,9 @@ static bool __init tpacpi_is_fw_known(void)
  ****************************************************************************/
  ****************************************************************************/
 
 
 /*************************************************************************
 /*************************************************************************
- * thinkpad-acpi init subdriver
+ * thinkpad-acpi metadata subdriver
  */
  */
 
 
-static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
-{
-	printk(TPACPI_INFO "%s v%s\n", TPACPI_DESC, TPACPI_VERSION);
-	printk(TPACPI_INFO "%s\n", TPACPI_URL);
-
-	printk(TPACPI_INFO "ThinkPad BIOS %s, EC %s\n",
-		(thinkpad_id.bios_version_str) ?
-			thinkpad_id.bios_version_str : "unknown",
-		(thinkpad_id.ec_version_str) ?
-			thinkpad_id.ec_version_str : "unknown");
-
-	if (thinkpad_id.vendor && thinkpad_id.model_str)
-		printk(TPACPI_INFO "%s %s, model %s\n",
-			(thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ?
-				"IBM" : ((thinkpad_id.vendor ==
-						PCI_VENDOR_ID_LENOVO) ?
-					"Lenovo" : "Unknown vendor"),
-			thinkpad_id.model_str,
-			(thinkpad_id.nummodel_str) ?
-				thinkpad_id.nummodel_str : "unknown");
-
-	tpacpi_check_outdated_fw();
-	return 0;
-}
-
 static int thinkpad_acpi_driver_read(struct seq_file *m)
 static int thinkpad_acpi_driver_read(struct seq_file *m)
 {
 {
 	seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC);
 	seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC);
@@ -2405,6 +2354,36 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
 			tpacpi_hotkey_send_key(__scancode); \
 			tpacpi_hotkey_send_key(__scancode); \
 	} while (0)
 	} while (0)
 
 
+	void issue_volchange(const unsigned int oldvol,
+			     const unsigned int newvol)
+	{
+		unsigned int i = oldvol;
+
+		while (i > newvol) {
+			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
+			i--;
+		}
+		while (i < newvol) {
+			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
+			i++;
+		}
+	}
+
+	void issue_brightnesschange(const unsigned int oldbrt,
+				    const unsigned int newbrt)
+	{
+		unsigned int i = oldbrt;
+
+		while (i > newbrt) {
+			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
+			i--;
+		}
+		while (i < newbrt) {
+			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
+			i++;
+		}
+	}
+
 	TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle);
 	TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle);
 	TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle);
 	TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle);
 	TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF7, display_toggle);
 	TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF7, display_toggle);
@@ -2414,41 +2393,61 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
 
 
 	TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF8, displayexp_toggle);
 	TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF8, displayexp_toggle);
 
 
-	/* handle volume */
-	if (oldn->volume_toggle != newn->volume_toggle) {
-		if (oldn->mute != newn->mute) {
+	/*
+	 * Handle volume
+	 *
+	 * This code is supposed to duplicate the IBM firmware behaviour:
+	 * - Pressing MUTE issues mute hotkey message, even when already mute
+	 * - Pressing Volume up/down issues volume up/down hotkey messages,
+	 *   even when already at maximum or minumum volume
+	 * - The act of unmuting issues volume up/down notification,
+	 *   depending which key was used to unmute
+	 *
+	 * We are constrained to what the NVRAM can tell us, which is not much
+	 * and certainly not enough if more than one volume hotkey was pressed
+	 * since the last poll cycle.
+	 *
+	 * Just to make our life interesting, some newer Lenovo ThinkPads have
+	 * bugs in the BIOS and may fail to update volume_toggle properly.
+	 */
+	if (newn->mute) {
+		/* muted */
+		if (!oldn->mute ||
+		    oldn->volume_toggle != newn->volume_toggle ||
+		    oldn->volume_level != newn->volume_level) {
+			/* recently muted, or repeated mute keypress, or
+			 * multiple presses ending in mute */
+			issue_volchange(oldn->volume_level, newn->volume_level);
 			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
 			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
 		}
 		}
-		if (oldn->volume_level > newn->volume_level) {
-			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
-		} else if (oldn->volume_level < newn->volume_level) {
+	} else {
+		/* unmute */
+		if (oldn->mute) {
+			/* recently unmuted, issue 'unmute' keypress */
 			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
 			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
-		} else if (oldn->mute == newn->mute) {
-			/* repeated key presses that didn't change state */
-			if (newn->mute) {
-				TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
-			} else if (newn->volume_level != 0) {
-				TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
-			} else {
+		}
+		if (oldn->volume_level != newn->volume_level) {
+			issue_volchange(oldn->volume_level, newn->volume_level);
+		} else if (oldn->volume_toggle != newn->volume_toggle) {
+			/* repeated vol up/down keypress at end of scale ? */
+			if (newn->volume_level == 0)
 				TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
 				TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
-			}
+			else if (newn->volume_level >= TP_NVRAM_LEVEL_VOLUME_MAX)
+				TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
 		}
 		}
 	}
 	}
 
 
 	/* handle brightness */
 	/* handle brightness */
-	if (oldn->brightness_toggle != newn->brightness_toggle) {
-		if (oldn->brightness_level < newn->brightness_level) {
-			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
-		} else if (oldn->brightness_level > newn->brightness_level) {
+	if (oldn->brightness_level != newn->brightness_level) {
+		issue_brightnesschange(oldn->brightness_level,
+				       newn->brightness_level);
+	} else if (oldn->brightness_toggle != newn->brightness_toggle) {
+		/* repeated key presses that didn't change state */
+		if (newn->brightness_level == 0)
 			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
 			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
-		} else {
-			/* repeated key presses that didn't change state */
-			if (newn->brightness_level != 0) {
-				TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
-			} else {
-				TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
-			}
-		}
+		else if (newn->brightness_level >= bright_maxlvl
+				&& !tp_features.bright_unkfw)
+			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
 	}
 	}
 
 
 #undef TPACPI_COMPARE_KEY
 #undef TPACPI_COMPARE_KEY
@@ -3353,7 +3352,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
 		goto err_exit;
 		goto err_exit;
 	}
 	}
 
 
-	if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO) {
+	if (tpacpi_is_lenovo()) {
 		dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
 		dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
 			   "using Lenovo default hot key map\n");
 			   "using Lenovo default hot key map\n");
 		memcpy(hotkey_keycode_map, &lenovo_keycode_map,
 		memcpy(hotkey_keycode_map, &lenovo_keycode_map,
@@ -3391,11 +3390,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
 	}
 	}
 
 
 	/* Do not issue duplicate brightness change events to
 	/* Do not issue duplicate brightness change events to
-	 * userspace */
-	if (!tp_features.bright_acpimode)
-		/* update bright_acpimode... */
-		tpacpi_check_std_acpi_brightness_support();
-
+	 * userspace. tpacpi_detect_brightness_capabilities() must have
+	 * been called before this point  */
 	if (tp_features.bright_acpimode && acpi_video_backlight_support()) {
 	if (tp_features.bright_acpimode && acpi_video_backlight_support()) {
 		printk(TPACPI_INFO
 		printk(TPACPI_INFO
 		       "This ThinkPad has standard ACPI backlight "
 		       "This ThinkPad has standard ACPI backlight "
@@ -4422,7 +4418,8 @@ static int __init video_init(struct ibm_init_struct *iibm)
 	vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n");
 	vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n");
 
 
 	TPACPI_ACPIHANDLE_INIT(vid);
 	TPACPI_ACPIHANDLE_INIT(vid);
-	TPACPI_ACPIHANDLE_INIT(vid2);
+	if (tpacpi_is_ibm())
+		TPACPI_ACPIHANDLE_INIT(vid2);
 
 
 	if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga)
 	if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga)
 		/* G41, assume IVGA doesn't change */
 		/* G41, assume IVGA doesn't change */
@@ -4431,10 +4428,12 @@ static int __init video_init(struct ibm_init_struct *iibm)
 	if (!vid_handle)
 	if (!vid_handle)
 		/* video switching not supported on R30, R31 */
 		/* video switching not supported on R30, R31 */
 		video_supported = TPACPI_VIDEO_NONE;
 		video_supported = TPACPI_VIDEO_NONE;
-	else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
+	else if (tpacpi_is_ibm() &&
+		 acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
 		/* 570 */
 		/* 570 */
 		video_supported = TPACPI_VIDEO_570;
 		video_supported = TPACPI_VIDEO_570;
-	else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
+	else if (tpacpi_is_ibm() &&
+		 acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
 		/* 600e/x, 770e, 770x */
 		/* 600e/x, 770e, 770x */
 		video_supported = TPACPI_VIDEO_770;
 		video_supported = TPACPI_VIDEO_770;
 	else
 	else
@@ -4811,8 +4810,10 @@ static int __init light_init(struct ibm_init_struct *iibm)
 
 
 	vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n");
 	vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n");
 
 
-	TPACPI_ACPIHANDLE_INIT(ledb);
-	TPACPI_ACPIHANDLE_INIT(lght);
+	if (tpacpi_is_ibm()) {
+		TPACPI_ACPIHANDLE_INIT(ledb);
+		TPACPI_ACPIHANDLE_INIT(lght);
+	}
 	TPACPI_ACPIHANDLE_INIT(cmos);
 	TPACPI_ACPIHANDLE_INIT(cmos);
 	INIT_WORK(&tpacpi_led_thinklight.work, light_set_status_worker);
 	INIT_WORK(&tpacpi_led_thinklight.work, light_set_status_worker);
 
 
@@ -5007,11 +5008,7 @@ enum {	/* For TPACPI_LED_OLD */
 
 
 static enum led_access_mode led_supported;
 static enum led_access_mode led_supported;
 
 
-TPACPI_HANDLE(led, ec, "SLED",	/* 570 */
-	   "SYSL",		/* 600e/x, 770e, 770x, A21e, A2xm/p, */
-				/* T20-22, X20-21 */
-	   "LED",		/* all others */
-	   );			/* R30, R31 */
+static acpi_handle led_handle;
 
 
 #define TPACPI_LED_NUMLEDS 16
 #define TPACPI_LED_NUMLEDS 16
 static struct tpacpi_led_classdev *tpacpi_leds;
 static struct tpacpi_led_classdev *tpacpi_leds;
@@ -5271,6 +5268,32 @@ static const struct tpacpi_quirk led_useful_qtable[] __initconst = {
 #undef TPACPI_LEDQ_IBM
 #undef TPACPI_LEDQ_IBM
 #undef TPACPI_LEDQ_LNV
 #undef TPACPI_LEDQ_LNV
 
 
+static enum led_access_mode __init led_init_detect_mode(void)
+{
+	acpi_status status;
+
+	if (tpacpi_is_ibm()) {
+		/* 570 */
+		status = acpi_get_handle(ec_handle, "SLED", &led_handle);
+		if (ACPI_SUCCESS(status))
+			return TPACPI_LED_570;
+
+		/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
+		status = acpi_get_handle(ec_handle, "SYSL", &led_handle);
+		if (ACPI_SUCCESS(status))
+			return TPACPI_LED_OLD;
+	}
+
+	/* most others */
+	status = acpi_get_handle(ec_handle, "LED", &led_handle);
+	if (ACPI_SUCCESS(status))
+		return TPACPI_LED_NEW;
+
+	/* R30, R31, and unknown firmwares */
+	led_handle = NULL;
+	return TPACPI_LED_NONE;
+}
+
 static int __init led_init(struct ibm_init_struct *iibm)
 static int __init led_init(struct ibm_init_struct *iibm)
 {
 {
 	unsigned int i;
 	unsigned int i;
@@ -5279,20 +5302,7 @@ static int __init led_init(struct ibm_init_struct *iibm)
 
 
 	vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
 	vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
 
 
-	TPACPI_ACPIHANDLE_INIT(led);
-
-	if (!led_handle)
-		/* led not supported on R30, R31 */
-		led_supported = TPACPI_LED_NONE;
-	else if (strlencmp(led_path, "SLED") == 0)
-		/* 570 */
-		led_supported = TPACPI_LED_570;
-	else if (strlencmp(led_path, "SYSL") == 0)
-		/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
-		led_supported = TPACPI_LED_OLD;
-	else
-		/* all others */
-		led_supported = TPACPI_LED_NEW;
+	led_supported = led_init_detect_mode();
 
 
 	vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
 	vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
 		str_supported(led_supported), led_supported);
 		str_supported(led_supported), led_supported);
@@ -5741,11 +5751,12 @@ static int __init thermal_init(struct ibm_init_struct *iibm)
 			    TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8;
 			    TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8;
 		}
 		}
 	} else if (acpi_tmp7) {
 	} else if (acpi_tmp7) {
-		if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
+		if (tpacpi_is_ibm() &&
+		    acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
 			/* 600e/x, 770e, 770x */
 			/* 600e/x, 770e, 770x */
 			thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT;
 			thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT;
 		} else {
 		} else {
-			/* Standard ACPI TMPx access, max 8 sensors */
+			/* IBM/LENOVO DSDT EC.TMPx access, max 8 sensors */
 			thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
 			thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
 		}
 		}
 	} else {
 	} else {
@@ -5954,7 +5965,7 @@ static unsigned int tpacpi_brightness_nvram_get(void)
 	lnvram = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
 	lnvram = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
 		  & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
 		  & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
 		  >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
 		  >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
-	lnvram &= (tp_features.bright_16levels) ? 0x0f : 0x07;
+	lnvram &= bright_maxlvl;
 
 
 	return lnvram;
 	return lnvram;
 }
 }
@@ -6063,8 +6074,7 @@ static int brightness_set(unsigned int value)
 {
 {
 	int res;
 	int res;
 
 
-	if (value > ((tp_features.bright_16levels)? 15 : 7) ||
-	    value < 0)
+	if (value > bright_maxlvl || value < 0)
 		return -EINVAL;
 		return -EINVAL;
 
 
 	vdbg_printk(TPACPI_DBG_BRGHT,
 	vdbg_printk(TPACPI_DBG_BRGHT,
@@ -6139,6 +6149,80 @@ static struct backlight_ops ibm_backlight_data = {
 
 
 /* --------------------------------------------------------------------- */
 /* --------------------------------------------------------------------- */
 
 
+static int __init tpacpi_query_bcl_levels(acpi_handle handle)
+{
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	int rc;
+
+	if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) {
+		obj = (union acpi_object *)buffer.pointer;
+		if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
+			printk(TPACPI_ERR "Unknown _BCL data, "
+			       "please report this to %s\n", TPACPI_MAIL);
+			rc = 0;
+		} else {
+			rc = obj->package.count;
+		}
+	} else {
+		return 0;
+	}
+
+	kfree(buffer.pointer);
+	return rc;
+}
+
+static acpi_status __init tpacpi_acpi_walk_find_bcl(acpi_handle handle,
+					u32 lvl, void *context, void **rv)
+{
+	char name[ACPI_PATH_SEGMENT_LENGTH];
+	struct acpi_buffer buffer = { sizeof(name), &name };
+
+	if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) &&
+	    !strncmp("_BCL", name, sizeof(name) - 1)) {
+		BUG_ON(!rv || !*rv);
+		**(int **)rv = tpacpi_query_bcl_levels(handle);
+		return AE_CTRL_TERMINATE;
+	} else {
+		return AE_OK;
+	}
+}
+
+/*
+ * Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map
+ */
+static unsigned int __init tpacpi_check_std_acpi_brightness_support(void)
+{
+	int status;
+	int bcl_levels = 0;
+	void *bcl_ptr = &bcl_levels;
+
+	if (!vid_handle)
+		TPACPI_ACPIHANDLE_INIT(vid);
+
+	if (!vid_handle)
+		return 0;
+
+	/*
+	 * Search for a _BCL method, and execute it.  This is safe on all
+	 * ThinkPads, and as a side-effect, _BCL will place a Lenovo Vista
+	 * BIOS in ACPI backlight control mode.  We do NOT have to care
+	 * about calling the _BCL method in an enabled video device, any
+	 * will do for our purposes.
+	 */
+
+	status = acpi_walk_namespace(ACPI_TYPE_METHOD, vid_handle, 3,
+				     tpacpi_acpi_walk_find_bcl, NULL, NULL,
+				     &bcl_ptr);
+
+	if (ACPI_SUCCESS(status) && bcl_levels > 2) {
+		tp_features.bright_acpimode = 1;
+		return bcl_levels - 2;
+	}
+
+	return 0;
+}
+
 /*
 /*
  * These are only useful for models that have only one possibility
  * These are only useful for models that have only one possibility
  * of GPU.  If the BIOS model handles both ATI and Intel, don't use
  * of GPU.  If the BIOS model handles both ATI and Intel, don't use
@@ -6169,6 +6253,47 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = {
 	TPACPI_Q_IBM('7', '5', TPACPI_BRGHT_Q_NOEC),	/* X41 Tablet */
 	TPACPI_Q_IBM('7', '5', TPACPI_BRGHT_Q_NOEC),	/* X41 Tablet */
 };
 };
 
 
+/*
+ * Returns < 0 for error, otherwise sets tp_features.bright_*
+ * and bright_maxlvl.
+ */
+static void __init tpacpi_detect_brightness_capabilities(void)
+{
+	unsigned int b;
+
+	vdbg_printk(TPACPI_DBG_INIT,
+		    "detecting firmware brightness interface capabilities\n");
+
+	/* we could run a quirks check here (same table used by
+	 * brightness_init) if needed */
+
+	/*
+	 * We always attempt to detect acpi support, so as to switch
+	 * Lenovo Vista BIOS to ACPI brightness mode even if we are not
+	 * going to publish a backlight interface
+	 */
+	b = tpacpi_check_std_acpi_brightness_support();
+	switch (b) {
+	case 16:
+		bright_maxlvl = 15;
+		printk(TPACPI_INFO
+		       "detected a 16-level brightness capable ThinkPad\n");
+		break;
+	case 8:
+	case 0:
+		bright_maxlvl = 7;
+		printk(TPACPI_INFO
+		       "detected a 8-level brightness capable ThinkPad\n");
+		break;
+	default:
+		printk(TPACPI_ERR
+		       "Unsupported brightness interface, "
+		       "please contact %s\n", TPACPI_MAIL);
+		tp_features.bright_unkfw = 1;
+		bright_maxlvl = b - 1;
+	}
+}
+
 static int __init brightness_init(struct ibm_init_struct *iibm)
 static int __init brightness_init(struct ibm_init_struct *iibm)
 {
 {
 	struct backlight_properties props;
 	struct backlight_properties props;
@@ -6182,14 +6307,13 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
 	quirks = tpacpi_check_quirks(brightness_quirk_table,
 	quirks = tpacpi_check_quirks(brightness_quirk_table,
 				ARRAY_SIZE(brightness_quirk_table));
 				ARRAY_SIZE(brightness_quirk_table));
 
 
-	/*
-	 * We always attempt to detect acpi support, so as to switch
-	 * Lenovo Vista BIOS to ACPI brightness mode even if we are not
-	 * going to publish a backlight interface
-	 */
-	b = tpacpi_check_std_acpi_brightness_support();
-	if (b > 0) {
+	/* tpacpi_detect_brightness_capabilities() must have run already */
+
+	/* if it is unknown, we don't handle it: it wouldn't be safe */
+	if (tp_features.bright_unkfw)
+		return 1;
 
 
+	if (tp_features.bright_acpimode) {
 		if (acpi_video_backlight_support()) {
 		if (acpi_video_backlight_support()) {
 			if (brightness_enable > 1) {
 			if (brightness_enable > 1) {
 				printk(TPACPI_NOTICE
 				printk(TPACPI_NOTICE
@@ -6218,15 +6342,6 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
 		return 1;
 		return 1;
 	}
 	}
 
 
-	if (b > 16) {
-		printk(TPACPI_ERR
-		       "Unsupported brightness interface, "
-		       "please contact %s\n", TPACPI_MAIL);
-		return 1;
-	}
-	if (b == 16)
-		tp_features.bright_16levels = 1;
-
 	/*
 	/*
 	 * Check for module parameter bogosity, note that we
 	 * Check for module parameter bogosity, note that we
 	 * init brightness_mode to TPACPI_BRGHT_MODE_MAX in order to be
 	 * init brightness_mode to TPACPI_BRGHT_MODE_MAX in order to be
@@ -6249,7 +6364,7 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
 	}
 	}
 
 
 	/* Safety */
 	/* Safety */
-	if (thinkpad_id.vendor != PCI_VENDOR_ID_IBM &&
+	if (!tpacpi_is_ibm() &&
 	    (brightness_mode == TPACPI_BRGHT_MODE_ECNVRAM ||
 	    (brightness_mode == TPACPI_BRGHT_MODE_ECNVRAM ||
 	     brightness_mode == TPACPI_BRGHT_MODE_EC))
 	     brightness_mode == TPACPI_BRGHT_MODE_EC))
 		return -EINVAL;
 		return -EINVAL;
@@ -6257,12 +6372,9 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
 	if (tpacpi_brightness_get_raw(&b) < 0)
 	if (tpacpi_brightness_get_raw(&b) < 0)
 		return 1;
 		return 1;
 
 
-	if (tp_features.bright_16levels)
-		printk(TPACPI_INFO
-		       "detected a 16-level brightness capable ThinkPad\n");
-
 	memset(&props, 0, sizeof(struct backlight_properties));
 	memset(&props, 0, sizeof(struct backlight_properties));
-	props.max_brightness = (tp_features.bright_16levels) ? 15 : 7;
+	props.max_brightness = bright_maxlvl;
+	props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
 	ibm_backlight_device = backlight_device_register(TPACPI_BACKLIGHT_DEV_NAME,
 	ibm_backlight_device = backlight_device_register(TPACPI_BACKLIGHT_DEV_NAME,
 							 NULL, NULL,
 							 NULL, NULL,
 							 &ibm_backlight_data,
 							 &ibm_backlight_data,
@@ -6285,7 +6397,10 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
 			"or not on your ThinkPad\n", TPACPI_MAIL);
 			"or not on your ThinkPad\n", TPACPI_MAIL);
 	}
 	}
 
 
-	ibm_backlight_device->props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
+	/* Added by mistake in early 2007.  Probably useless, but it could
+	 * be working around some unknown firmware problem where the value
+	 * read at startup doesn't match the real hardware state... so leave
+	 * it in place just in case */
 	backlight_update_status(ibm_backlight_device);
 	backlight_update_status(ibm_backlight_device);
 
 
 	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
 	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
@@ -6328,9 +6443,8 @@ static int brightness_read(struct seq_file *m)
 	} else {
 	} else {
 		seq_printf(m, "level:\t\t%d\n", level);
 		seq_printf(m, "level:\t\t%d\n", level);
 		seq_printf(m, "commands:\tup, down\n");
 		seq_printf(m, "commands:\tup, down\n");
-		seq_printf(m, "commands:\tlevel <level>"
-			       " (<level> is 0-%d)\n",
-			       (tp_features.bright_16levels) ? 15 : 7);
+		seq_printf(m, "commands:\tlevel <level> (<level> is 0-%d)\n",
+			       bright_maxlvl);
 	}
 	}
 
 
 	return 0;
 	return 0;
@@ -6341,7 +6455,6 @@ static int brightness_write(char *buf)
 	int level;
 	int level;
 	int rc;
 	int rc;
 	char *cmd;
 	char *cmd;
-	int max_level = (tp_features.bright_16levels) ? 15 : 7;
 
 
 	level = brightness_get(NULL);
 	level = brightness_get(NULL);
 	if (level < 0)
 	if (level < 0)
@@ -6349,13 +6462,13 @@ static int brightness_write(char *buf)
 
 
 	while ((cmd = next_cmd(&buf))) {
 	while ((cmd = next_cmd(&buf))) {
 		if (strlencmp(cmd, "up") == 0) {
 		if (strlencmp(cmd, "up") == 0) {
-			if (level < max_level)
+			if (level < bright_maxlvl)
 				level++;
 				level++;
 		} else if (strlencmp(cmd, "down") == 0) {
 		} else if (strlencmp(cmd, "down") == 0) {
 			if (level > 0)
 			if (level > 0)
 				level--;
 				level--;
 		} else if (sscanf(cmd, "level %d", &level) == 1 &&
 		} else if (sscanf(cmd, "level %d", &level) == 1 &&
-			   level >= 0 && level <= max_level) {
+			   level >= 0 && level <= bright_maxlvl) {
 			/* new level set */
 			/* new level set */
 		} else
 		} else
 			return -EINVAL;
 			return -EINVAL;
@@ -6669,6 +6782,8 @@ static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol,
 static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol,
 static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol,
 				struct snd_ctl_elem_value *ucontrol)
 				struct snd_ctl_elem_value *ucontrol)
 {
 {
+	tpacpi_disclose_usertask("ALSA", "set volume to %ld\n",
+				 ucontrol->value.integer.value[0]);
 	return volume_alsa_set_volume(ucontrol->value.integer.value[0]);
 	return volume_alsa_set_volume(ucontrol->value.integer.value[0]);
 }
 }
 
 
@@ -6692,6 +6807,9 @@ static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol,
 static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol,
 static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol,
 				struct snd_ctl_elem_value *ucontrol)
 				struct snd_ctl_elem_value *ucontrol)
 {
 {
+	tpacpi_disclose_usertask("ALSA", "%smute\n",
+				 ucontrol->value.integer.value[0] ?
+					"un" : "");
 	return volume_alsa_set_mute(!ucontrol->value.integer.value[0]);
 	return volume_alsa_set_mute(!ucontrol->value.integer.value[0]);
 }
 }
 
 
@@ -7968,9 +8086,11 @@ static int __init fan_init(struct ibm_init_struct *iibm)
 	tp_features.second_fan = 0;
 	tp_features.second_fan = 0;
 	fan_control_desired_level = 7;
 	fan_control_desired_level = 7;
 
 
-	TPACPI_ACPIHANDLE_INIT(fans);
-	TPACPI_ACPIHANDLE_INIT(gfan);
-	TPACPI_ACPIHANDLE_INIT(sfan);
+	if (tpacpi_is_ibm()) {
+		TPACPI_ACPIHANDLE_INIT(fans);
+		TPACPI_ACPIHANDLE_INIT(gfan);
+		TPACPI_ACPIHANDLE_INIT(sfan);
+	}
 
 
 	quirks = tpacpi_check_quirks(fan_quirk_table,
 	quirks = tpacpi_check_quirks(fan_quirk_table,
 				     ARRAY_SIZE(fan_quirk_table));
 				     ARRAY_SIZE(fan_quirk_table));
@@ -8662,6 +8782,10 @@ static int __init probe_for_thinkpad(void)
 	if (acpi_disabled)
 	if (acpi_disabled)
 		return -ENODEV;
 		return -ENODEV;
 
 
+	/* It would be dangerous to run the driver in this case */
+	if (!tpacpi_is_ibm() && !tpacpi_is_lenovo())
+		return -ENODEV;
+
 	/*
 	/*
 	 * Non-ancient models have better DMI tagging, but very old models
 	 * Non-ancient models have better DMI tagging, but very old models
 	 * don't.  tpacpi_is_fw_known() is a cheat to help in that case.
 	 * don't.  tpacpi_is_fw_known() is a cheat to help in that case.
@@ -8670,8 +8794,8 @@ static int __init probe_for_thinkpad(void)
 		      (thinkpad_id.ec_model != 0) ||
 		      (thinkpad_id.ec_model != 0) ||
 		      tpacpi_is_fw_known();
 		      tpacpi_is_fw_known();
 
 
-	/* ec is required because many other handles are relative to it */
-	TPACPI_ACPIHANDLE_INIT(ec);
+	/* The EC handler is required */
+	tpacpi_acpi_handle_locate("ec", TPACPI_ACPI_EC_HID, &ec_handle);
 	if (!ec_handle) {
 	if (!ec_handle) {
 		if (is_thinkpad)
 		if (is_thinkpad)
 			printk(TPACPI_ERR
 			printk(TPACPI_ERR
@@ -8685,12 +8809,34 @@ static int __init probe_for_thinkpad(void)
 	return 0;
 	return 0;
 }
 }
 
 
+static void __init thinkpad_acpi_init_banner(void)
+{
+	printk(TPACPI_INFO "%s v%s\n", TPACPI_DESC, TPACPI_VERSION);
+	printk(TPACPI_INFO "%s\n", TPACPI_URL);
+
+	printk(TPACPI_INFO "ThinkPad BIOS %s, EC %s\n",
+		(thinkpad_id.bios_version_str) ?
+			thinkpad_id.bios_version_str : "unknown",
+		(thinkpad_id.ec_version_str) ?
+			thinkpad_id.ec_version_str : "unknown");
+
+	BUG_ON(!thinkpad_id.vendor);
+
+	if (thinkpad_id.model_str)
+		printk(TPACPI_INFO "%s %s, model %s\n",
+			(thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ?
+				"IBM" : ((thinkpad_id.vendor ==
+						PCI_VENDOR_ID_LENOVO) ?
+					"Lenovo" : "Unknown vendor"),
+			thinkpad_id.model_str,
+			(thinkpad_id.nummodel_str) ?
+				thinkpad_id.nummodel_str : "unknown");
+}
 
 
 /* Module init, exit, parameters */
 /* Module init, exit, parameters */
 
 
 static struct ibm_init_struct ibms_init[] __initdata = {
 static struct ibm_init_struct ibms_init[] __initdata = {
 	{
 	{
-		.init = thinkpad_acpi_driver_init,
 		.data = &thinkpad_acpi_driver_data,
 		.data = &thinkpad_acpi_driver_data,
 	},
 	},
 	{
 	{
@@ -8960,6 +9106,9 @@ static int __init thinkpad_acpi_module_init(void)
 
 
 	/* Driver initialization */
 	/* Driver initialization */
 
 
+	thinkpad_acpi_init_banner();
+	tpacpi_check_outdated_fw();
+
 	TPACPI_ACPIHANDLE_INIT(ecrd);
 	TPACPI_ACPIHANDLE_INIT(ecrd);
 	TPACPI_ACPIHANDLE_INIT(ecwr);
 	TPACPI_ACPIHANDLE_INIT(ecwr);
 
 
@@ -9059,13 +9208,16 @@ static int __init thinkpad_acpi_module_init(void)
 		tpacpi_inputdev->name = "ThinkPad Extra Buttons";
 		tpacpi_inputdev->name = "ThinkPad Extra Buttons";
 		tpacpi_inputdev->phys = TPACPI_DRVR_NAME "/input0";
 		tpacpi_inputdev->phys = TPACPI_DRVR_NAME "/input0";
 		tpacpi_inputdev->id.bustype = BUS_HOST;
 		tpacpi_inputdev->id.bustype = BUS_HOST;
-		tpacpi_inputdev->id.vendor = (thinkpad_id.vendor) ?
-						thinkpad_id.vendor :
-						PCI_VENDOR_ID_IBM;
+		tpacpi_inputdev->id.vendor = thinkpad_id.vendor;
 		tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT;
 		tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT;
 		tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION;
 		tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION;
 		tpacpi_inputdev->dev.parent = &tpacpi_pdev->dev;
 		tpacpi_inputdev->dev.parent = &tpacpi_pdev->dev;
 	}
 	}
+
+	/* Init subdriver dependencies */
+	tpacpi_detect_brightness_capabilities();
+
+	/* Init subdrivers */
 	for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
 	for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
 		ret = ibm_init(&ibms_init[i]);
 		ret = ibm_init(&ibms_init[i]);
 		if (ret >= 0 && *ibms_init[i].param)
 		if (ret >= 0 && *ibms_init[i].param)