diff options
Diffstat (limited to 'drivers/platform')
| -rw-r--r-- | drivers/platform/x86/Kconfig | 31 | ||||
| -rw-r--r-- | drivers/platform/x86/Makefile | 1 | ||||
| -rw-r--r-- | drivers/platform/x86/acer-wmi.c | 52 | ||||
| -rw-r--r-- | drivers/platform/x86/acerhdf.c | 602 | ||||
| -rw-r--r-- | drivers/platform/x86/dell-laptop.c | 101 | ||||
| -rw-r--r-- | drivers/platform/x86/dell-wmi.c | 56 | ||||
| -rw-r--r-- | drivers/platform/x86/eeepc-laptop.c | 141 | ||||
| -rw-r--r-- | drivers/platform/x86/hp-wmi.c | 185 | ||||
| -rw-r--r-- | drivers/platform/x86/sony-laptop.c | 192 | ||||
| -rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 1283 | ||||
| -rw-r--r-- | drivers/platform/x86/toshiba_acpi.c | 160 | 
11 files changed, 1834 insertions, 970 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 284ebaca6e4..5613483db14 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -21,7 +21,7 @@ config ACER_WMI  	depends on NEW_LEDS  	depends on BACKLIGHT_CLASS_DEVICE  	depends on SERIO_I8042 -	depends on RFKILL +	depends on RFKILL || RFKILL = n  	select ACPI_WMI  	---help---  	  This is a driver for newer Acer (and Wistron) laptops. It adds @@ -34,6 +34,23 @@ config ACER_WMI  	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M  	  here. +config ACERHDF +	tristate "Acer Aspire One temperature and fan driver" +	depends on THERMAL && THERMAL_HWMON && ACPI +	---help--- +	  This is a driver for Acer Aspire One netbooks. It allows to access +	  the temperature sensor and to control the fan. + +	  After loading this driver the BIOS is still in control of the fan. +	  To let the kernel handle the fan, do: +	  echo -n enabled > /sys/class/thermal/thermal_zone0/mode + +	  For more information about this driver see +	  <http://piie.net/files/acerhdf_README.txt> + +	  If you have an Acer Aspire One netbook, say Y or M +	  here. +  config ASUS_LAPTOP  	tristate "Asus Laptop Extras (EXPERIMENTAL)"  	depends on ACPI @@ -60,7 +77,7 @@ config DELL_LAPTOP  	depends on DCDBAS  	depends on EXPERIMENTAL  	depends on BACKLIGHT_CLASS_DEVICE -	depends on RFKILL +	depends on RFKILL || RFKILL = n  	depends on POWER_SUPPLY  	default n  	---help--- @@ -117,7 +134,7 @@ config HP_WMI  	tristate "HP WMI extras"  	depends on ACPI_WMI  	depends on INPUT -	depends on RFKILL +	depends on RFKILL || RFKILL = n  	help  	 Say Y here if you want to support WMI-based hotkeys on HP laptops and  	 to read data from WMI such as docking or ambient light sensor state. @@ -196,14 +213,13 @@ config THINKPAD_ACPI  	tristate "ThinkPad ACPI Laptop Extras"  	depends on ACPI  	depends on INPUT +	depends on RFKILL || RFKILL = n  	select BACKLIGHT_LCD_SUPPORT  	select BACKLIGHT_CLASS_DEVICE  	select HWMON  	select NVRAM  	select NEW_LEDS  	select LEDS_CLASS -	select NET -	select RFKILL  	---help---  	  This is a driver for the IBM and Lenovo ThinkPad laptops. It adds  	  support for Fn-Fx key combinations, Bluetooth control, video @@ -338,9 +354,9 @@ config EEEPC_LAPTOP  	depends on ACPI  	depends on INPUT  	depends on EXPERIMENTAL +	depends on RFKILL || RFKILL = n  	select BACKLIGHT_CLASS_DEVICE  	select HWMON -	select RFKILL  	---help---  	  This driver supports the Fn-Fx keys on Eee PC laptops.  	  It also adds the ability to switch camera/wlan on/off. @@ -405,9 +421,8 @@ config ACPI_TOSHIBA  	tristate "Toshiba Laptop Extras"  	depends on ACPI  	depends on INPUT +	depends on RFKILL || RFKILL = n  	select INPUT_POLLDEV -	select NET -	select RFKILL  	select BACKLIGHT_CLASS_DEVICE  	---help---  	  This driver adds support for access to certain system settings diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index e40c7bd1b87..641b8bfa553 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP)	+= compal-laptop.o  obj-$(CONFIG_DELL_LAPTOP)	+= dell-laptop.o  obj-$(CONFIG_DELL_WMI)		+= dell-wmi.o  obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o +obj-$(CONFIG_ACERHDF)		+= acerhdf.o  obj-$(CONFIG_HP_WMI)		+= hp-wmi.o  obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o  obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 0f6e43bf4fc..be2fd6f9163 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -958,59 +958,47 @@ static void acer_rfkill_update(struct work_struct *ignored)  	status = get_u32(&state, ACER_CAP_WIRELESS);  	if (ACPI_SUCCESS(status)) -		rfkill_force_state(wireless_rfkill, state ? -			RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED); +		rfkill_set_sw_state(wireless_rfkill, !state);  	if (has_cap(ACER_CAP_BLUETOOTH)) {  		status = get_u32(&state, ACER_CAP_BLUETOOTH);  		if (ACPI_SUCCESS(status)) -			rfkill_force_state(bluetooth_rfkill, state ? -				RFKILL_STATE_UNBLOCKED : -				RFKILL_STATE_SOFT_BLOCKED); +			rfkill_set_sw_state(bluetooth_rfkill, !state);  	}  	schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ));  } -static int acer_rfkill_set(void *data, enum rfkill_state state) +static int acer_rfkill_set(void *data, bool blocked)  {  	acpi_status status; -	u32 *cap = data; -	status = set_u32((u32) (state == RFKILL_STATE_UNBLOCKED), *cap); +	u32 cap = (unsigned long)data; +	status = set_u32(!!blocked, cap);  	if (ACPI_FAILURE(status))  		return -ENODEV;  	return 0;  } -static struct rfkill * acer_rfkill_register(struct device *dev, -enum rfkill_type type, char *name, u32 cap) +static const struct rfkill_ops acer_rfkill_ops = { +	.set_block = acer_rfkill_set, +}; + +static struct rfkill *acer_rfkill_register(struct device *dev, +					   enum rfkill_type type, +					   char *name, u32 cap)  {  	int err; -	u32 state; -	u32 *data;  	struct rfkill *rfkill_dev; -	rfkill_dev = rfkill_allocate(dev, type); +	rfkill_dev = rfkill_alloc(name, dev, type, +				  &acer_rfkill_ops, +				  (void *)(unsigned long)cap);  	if (!rfkill_dev)  		return ERR_PTR(-ENOMEM); -	rfkill_dev->name = name; -	get_u32(&state, cap); -	rfkill_dev->state = state ? RFKILL_STATE_UNBLOCKED : -		RFKILL_STATE_SOFT_BLOCKED; -	data = kzalloc(sizeof(u32), GFP_KERNEL); -	if (!data) { -		rfkill_free(rfkill_dev); -		return ERR_PTR(-ENOMEM); -	} -	*data = cap; -	rfkill_dev->data = data; -	rfkill_dev->toggle_radio = acer_rfkill_set; -	rfkill_dev->user_claim_unsupported = 1;  	err = rfkill_register(rfkill_dev);  	if (err) { -		kfree(rfkill_dev->data); -		rfkill_free(rfkill_dev); +		rfkill_destroy(rfkill_dev);  		return ERR_PTR(err);  	}  	return rfkill_dev; @@ -1028,8 +1016,8 @@ static int acer_rfkill_init(struct device *dev)  			RFKILL_TYPE_BLUETOOTH, "acer-bluetooth",  			ACER_CAP_BLUETOOTH);  		if (IS_ERR(bluetooth_rfkill)) { -			kfree(wireless_rfkill->data);  			rfkill_unregister(wireless_rfkill); +			rfkill_destroy(wireless_rfkill);  			return PTR_ERR(bluetooth_rfkill);  		}  	} @@ -1042,11 +1030,13 @@ static int acer_rfkill_init(struct device *dev)  static void acer_rfkill_exit(void)  {  	cancel_delayed_work_sync(&acer_rfkill_work); -	kfree(wireless_rfkill->data); +  	rfkill_unregister(wireless_rfkill); +	rfkill_destroy(wireless_rfkill); +  	if (has_cap(ACER_CAP_BLUETOOTH)) { -		kfree(bluetooth_rfkill->data);  		rfkill_unregister(bluetooth_rfkill); +		rfkill_destroy(bluetooth_rfkill);  	}  	return;  } diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c new file mode 100644 index 00000000000..bdfee177eef --- /dev/null +++ b/drivers/platform/x86/acerhdf.c @@ -0,0 +1,602 @@ +/* + * acerhdf - A driver which monitors the temperature + *           of the aspire one netbook, turns on/off the fan + *           as soon as the upper/lower threshold is reached. + * + * (C) 2009 - Peter Feuerer     peter (a) piie.net + *                              http://piie.net + *     2009 Borislav Petkov <petkovbb@gmail.com> + * + * Inspired by and many thanks to: + *  o acerfand   - Rachel Greenham + *  o acer_ec.pl - Michael Kurz     michi.kurz (at) googlemail.com + *               - Petr Tomasek     tomasek (#) etf,cuni,cz + *               - Carlos Corbacho  cathectic (at) gmail.com + *  o lkml       - Matthew Garrett + *               - Borislav Petkov + *               - Andreas Mohr + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; either version 2 of the License, or + *  (at your option) any later version. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program; if not, write to the Free Software + *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + */ + +#define pr_fmt(fmt) "acerhdf: " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/dmi.h> +#include <acpi/acpi_drivers.h> +#include <linux/sched.h> +#include <linux/thermal.h> +#include <linux/platform_device.h> + +/* + * The driver is started with "kernel mode off" by default. That means, the BIOS + * is still in control of the fan. In this mode the driver allows to read the + * temperature of the cpu and a userspace tool may take over control of the fan. + * If the driver is switched to "kernel mode" (e.g. via module parameter) the + * driver is in full control of the fan. If you want the module to be started in + * kernel mode by default, define the following: + */ +#undef START_IN_KERNEL_MODE + +#define DRV_VER "0.5.13" + +/* + * According to the Atom N270 datasheet, + * (http://download.intel.com/design/processor/datashts/320032.pdf) the + * CPU's optimal operating limits denoted in junction temperature as + * measured by the on-die thermal monitor are within 0 <= Tj <= 90. So, + * assume 89°C is critical temperature. + */ +#define ACERHDF_TEMP_CRIT 89 +#define ACERHDF_FAN_OFF 0 +#define ACERHDF_FAN_AUTO 1 + +/* + * No matter what value the user puts into the fanon variable, turn on the fan + * at 80 degree Celsius to prevent hardware damage + */ +#define ACERHDF_MAX_FANON 80 + +/* + * Maximum interval between two temperature checks is 15 seconds, as the die + * can get hot really fast under heavy load (plus we shouldn't forget about + * possible impact of _external_ aggressive sources such as heaters, sun etc.) + */ +#define ACERHDF_MAX_INTERVAL 15 + +#ifdef START_IN_KERNEL_MODE +static int kernelmode = 1; +#else +static int kernelmode; +#endif + +static unsigned int interval = 10; +static unsigned int fanon = 63; +static unsigned int fanoff = 58; +static unsigned int verbose; +static unsigned int fanstate = ACERHDF_FAN_AUTO; +static char force_bios[16]; +static unsigned int prev_interval; +struct thermal_zone_device *thz_dev; +struct thermal_cooling_device *cl_dev; +struct platform_device *acerhdf_dev; + +module_param(kernelmode, uint, 0); +MODULE_PARM_DESC(kernelmode, "Kernel mode fan control on / off"); +module_param(interval, uint, 0600); +MODULE_PARM_DESC(interval, "Polling interval of temperature check"); +module_param(fanon, uint, 0600); +MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature"); +module_param(fanoff, uint, 0600); +MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature"); +module_param(verbose, uint, 0600); +MODULE_PARM_DESC(verbose, "Enable verbose dmesg output"); +module_param_string(force_bios, force_bios, 16, 0); +MODULE_PARM_DESC(force_bios, "Force BIOS version and omit BIOS check"); + +/* BIOS settings */ +struct bios_settings_t { +	const char *vendor; +	const char *version; +	unsigned char fanreg; +	unsigned char tempreg; +	unsigned char fancmd[2]; /* fan off and auto commands */ +}; + +/* Register addresses and values for different BIOS versions */ +static const struct bios_settings_t bios_tbl[] = { +	{"Acer", "v0.3109", 0x55, 0x58, {0x1f, 0x00} }, +	{"Acer", "v0.3114", 0x55, 0x58, {0x1f, 0x00} }, +	{"Acer", "v0.3301", 0x55, 0x58, {0xaf, 0x00} }, +	{"Acer", "v0.3304", 0x55, 0x58, {0xaf, 0x00} }, +	{"Acer", "v0.3305", 0x55, 0x58, {0xaf, 0x00} }, +	{"Acer", "v0.3308", 0x55, 0x58, {0x21, 0x00} }, +	{"Acer", "v0.3309", 0x55, 0x58, {0x21, 0x00} }, +	{"Acer", "v0.3310", 0x55, 0x58, {0x21, 0x00} }, +	{"Gateway", "v0.3103", 0x55, 0x58, {0x21, 0x00} }, +	{"Packard Bell", "v0.3105", 0x55, 0x58, {0x21, 0x00} }, +	{"", "", 0, 0, {0, 0} } +}; + +static const struct bios_settings_t *bios_cfg __read_mostly; + + +static int acerhdf_get_temp(int *temp) +{ +	u8 read_temp; + +	if (ec_read(bios_cfg->tempreg, &read_temp)) +		return -EINVAL; + +	*temp = read_temp; + +	return 0; +} + +static int acerhdf_get_fanstate(int *state) +{ +	u8 fan; +	bool tmp; + +	if (ec_read(bios_cfg->fanreg, &fan)) +		return -EINVAL; + +	tmp = (fan == bios_cfg->fancmd[ACERHDF_FAN_OFF]); +	*state = tmp ? ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO; + +	return 0; +} + +static void acerhdf_change_fanstate(int state) +{ +	unsigned char cmd; + +	if (verbose) +		pr_notice("fan %s\n", (state == ACERHDF_FAN_OFF) ? +				"OFF" : "ON"); + +	if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) { +		pr_err("invalid fan state %d requested, setting to auto!\n", +			state); +		state = ACERHDF_FAN_AUTO; +	} + +	cmd = bios_cfg->fancmd[state]; +	fanstate = state; + +	ec_write(bios_cfg->fanreg, cmd); +} + +static void acerhdf_check_param(struct thermal_zone_device *thermal) +{ +	if (fanon > ACERHDF_MAX_FANON) { +		pr_err("fanon temperature too high, set to %d\n", +				ACERHDF_MAX_FANON); +		fanon = ACERHDF_MAX_FANON; +	} + +	if (kernelmode && prev_interval != interval) { +		if (interval > ACERHDF_MAX_INTERVAL) { +			pr_err("interval too high, set to %d\n", +				ACERHDF_MAX_INTERVAL); +			interval = ACERHDF_MAX_INTERVAL; +		} +		if (verbose) +			pr_notice("interval changed to: %d\n", +					interval); +		thermal->polling_delay = interval*1000; +		prev_interval = interval; +	} +} + +/* + * This is the thermal zone callback which does the delayed polling of the fan + * state. We do check /sysfs-originating settings here in acerhdf_check_param() + * as late as the polling interval is since we can't do that in the respective + * accessors of the module parameters. + */ +static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal, +			       unsigned long *t) +{ +	int temp, err = 0; + +	acerhdf_check_param(thermal); + +	err = acerhdf_get_temp(&temp); +	if (err) +		return err; + +	if (verbose) +		pr_notice("temp %d\n", temp); + +	*t = temp; +	return 0; +} + +static int acerhdf_bind(struct thermal_zone_device *thermal, +			struct thermal_cooling_device *cdev) +{ +	/* if the cooling device is the one from acerhdf bind it */ +	if (cdev != cl_dev) +		return 0; + +	if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) { +		pr_err("error binding cooling dev\n"); +		return -EINVAL; +	} +	return 0; +} + +static int acerhdf_unbind(struct thermal_zone_device *thermal, +			  struct thermal_cooling_device *cdev) +{ +	if (cdev != cl_dev) +		return 0; + +	if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) { +		pr_err("error unbinding cooling dev\n"); +		return -EINVAL; +	} +	return 0; +} + +static inline void acerhdf_revert_to_bios_mode(void) +{ +	acerhdf_change_fanstate(ACERHDF_FAN_AUTO); +	kernelmode = 0; +	if (thz_dev) +		thz_dev->polling_delay = 0; +	pr_notice("kernel mode fan control OFF\n"); +} +static inline void acerhdf_enable_kernelmode(void) +{ +	kernelmode = 1; + +	thz_dev->polling_delay = interval*1000; +	thermal_zone_device_update(thz_dev); +	pr_notice("kernel mode fan control ON\n"); +} + +static int acerhdf_get_mode(struct thermal_zone_device *thermal, +			    enum thermal_device_mode *mode) +{ +	if (verbose) +		pr_notice("kernel mode fan control %d\n", kernelmode); + +	*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED +			     : THERMAL_DEVICE_DISABLED; + +	return 0; +} + +/* + * set operation mode; + * enabled: the thermal layer of the kernel takes care about + *          the temperature and the fan. + * disabled: the BIOS takes control of the fan. + */ +static int acerhdf_set_mode(struct thermal_zone_device *thermal, +			    enum thermal_device_mode mode) +{ +	if (mode == THERMAL_DEVICE_DISABLED && kernelmode) +		acerhdf_revert_to_bios_mode(); +	else if (mode == THERMAL_DEVICE_ENABLED && !kernelmode) +		acerhdf_enable_kernelmode(); + +	return 0; +} + +static int acerhdf_get_trip_type(struct thermal_zone_device *thermal, int trip, +				 enum thermal_trip_type *type) +{ +	if (trip == 0) +		*type = THERMAL_TRIP_ACTIVE; + +	return 0; +} + +static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal, int trip, +				 unsigned long *temp) +{ +	if (trip == 0) +		*temp = fanon; + +	return 0; +} + +static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal, +				 unsigned long *temperature) +{ +	*temperature = ACERHDF_TEMP_CRIT; +	return 0; +} + +/* bind callback functions to thermalzone */ +struct thermal_zone_device_ops acerhdf_dev_ops = { +	.bind = acerhdf_bind, +	.unbind = acerhdf_unbind, +	.get_temp = acerhdf_get_ec_temp, +	.get_mode = acerhdf_get_mode, +	.set_mode = acerhdf_set_mode, +	.get_trip_type = acerhdf_get_trip_type, +	.get_trip_temp = acerhdf_get_trip_temp, +	.get_crit_temp = acerhdf_get_crit_temp, +}; + + +/* + * cooling device callback functions + * get maximal fan cooling state + */ +static int acerhdf_get_max_state(struct thermal_cooling_device *cdev, +				 unsigned long *state) +{ +	*state = 1; + +	return 0; +} + +static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev, +				 unsigned long *state) +{ +	int err = 0, tmp; + +	err = acerhdf_get_fanstate(&tmp); +	if (err) +		return err; + +	*state = (tmp == ACERHDF_FAN_AUTO) ? 1 : 0; +	return 0; +} + +/* change current fan state - is overwritten when running in kernel mode */ +static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev, +				 unsigned long state) +{ +	int cur_temp, cur_state, err = 0; + +	if (!kernelmode) +		return 0; + +	err = acerhdf_get_temp(&cur_temp); +	if (err) { +		pr_err("error reading temperature, hand off control to BIOS\n"); +		goto err_out; +	} + +	err = acerhdf_get_fanstate(&cur_state); +	if (err) { +		pr_err("error reading fan state, hand off control to BIOS\n"); +		goto err_out; +	} + +	if (state == 0) { +		/* turn fan off only if below fanoff temperature */ +		if ((cur_state == ACERHDF_FAN_AUTO) && +		    (cur_temp < fanoff)) +			acerhdf_change_fanstate(ACERHDF_FAN_OFF); +	} else { +		if (cur_state == ACERHDF_FAN_OFF) +			acerhdf_change_fanstate(ACERHDF_FAN_AUTO); +	} +	return 0; + +err_out: +	acerhdf_revert_to_bios_mode(); +	return -EINVAL; +} + +/* bind fan callbacks to fan device */ +struct thermal_cooling_device_ops acerhdf_cooling_ops = { +	.get_max_state = acerhdf_get_max_state, +	.get_cur_state = acerhdf_get_cur_state, +	.set_cur_state = acerhdf_set_cur_state, +}; + +/* suspend / resume functionality */ +static int acerhdf_suspend(struct platform_device *dev, pm_message_t state) +{ +	if (kernelmode) +		acerhdf_change_fanstate(ACERHDF_FAN_AUTO); + +	if (verbose) +		pr_notice("going suspend\n"); + +	return 0; +} + +static int acerhdf_resume(struct platform_device *device) +{ +	if (verbose) +		pr_notice("resuming\n"); + +	return 0; +} + +static int __devinit acerhdf_probe(struct platform_device *device) +{ +	return 0; +} + +static int acerhdf_remove(struct platform_device *device) +{ +	return 0; +} + +struct platform_driver acerhdf_drv = { +	.driver = { +		.name = "acerhdf", +		.owner = THIS_MODULE, +	}, +	.probe = acerhdf_probe, +	.remove = acerhdf_remove, +	.suspend = acerhdf_suspend, +	.resume = acerhdf_resume, +}; + + +/* check hardware */ +static int acerhdf_check_hardware(void) +{ +	char const *vendor, *version, *product; +	int i; + +	/* get BIOS data */ +	vendor  = dmi_get_system_info(DMI_SYS_VENDOR); +	version = dmi_get_system_info(DMI_BIOS_VERSION); +	product = dmi_get_system_info(DMI_PRODUCT_NAME); + +	pr_info("Acer Aspire One Fan driver, v.%s\n", DRV_VER); + +	if (!force_bios[0]) { +		if (strncmp(product, "AO", 2)) { +			pr_err("no Aspire One hardware found\n"); +			return -EINVAL; +		} +	} else { +		pr_info("forcing BIOS version: %s\n", version); +		version = force_bios; +		kernelmode = 0; +	} + +	if (verbose) +		pr_info("BIOS info: %s %s, product: %s\n", +			vendor, version, product); + +	/* search BIOS version and vendor in BIOS settings table */ +	for (i = 0; bios_tbl[i].version[0]; i++) { +		if (!strcmp(bios_tbl[i].vendor, vendor) && +		    !strcmp(bios_tbl[i].version, version)) { +			bios_cfg = &bios_tbl[i]; +			break; +		} +	} + +	if (!bios_cfg) { +		pr_err("unknown (unsupported) BIOS version %s/%s, " +			"please report, aborting!\n", vendor, version); +		return -EINVAL; +	} + +	/* +	 * if started with kernel mode off, prevent the kernel from switching +	 * off the fan +	 */ +	if (!kernelmode) { +		pr_notice("Fan control off, to enable do:\n"); +		pr_notice("echo -n \"enabled\" > " +			"/sys/class/thermal/thermal_zone0/mode\n"); +	} + +	return 0; +} + +static int acerhdf_register_platform(void) +{ +	int err = 0; + +	err = platform_driver_register(&acerhdf_drv); +	if (err) +		return err; + +	acerhdf_dev = platform_device_alloc("acerhdf", -1); +	platform_device_add(acerhdf_dev); + +	return 0; +} + +static void acerhdf_unregister_platform(void) +{ +	if (!acerhdf_dev) +		return; + +	platform_device_del(acerhdf_dev); +	platform_driver_unregister(&acerhdf_drv); +} + +static int acerhdf_register_thermal(void) +{ +	cl_dev = thermal_cooling_device_register("acerhdf-fan", NULL, +						 &acerhdf_cooling_ops); + +	if (IS_ERR(cl_dev)) +		return -EINVAL; + +	thz_dev = thermal_zone_device_register("acerhdf", 1, NULL, +					      &acerhdf_dev_ops, 0, 0, 0, +					      (kernelmode) ? interval*1000 : 0); +	if (IS_ERR(thz_dev)) +		return -EINVAL; + +	return 0; +} + +static void acerhdf_unregister_thermal(void) +{ +	if (cl_dev) { +		thermal_cooling_device_unregister(cl_dev); +		cl_dev = NULL; +	} + +	if (thz_dev) { +		thermal_zone_device_unregister(thz_dev); +		thz_dev = NULL; +	} +} + +static int __init acerhdf_init(void) +{ +	int err = 0; + +	err = acerhdf_check_hardware(); +	if (err) +		goto out_err; + +	err = acerhdf_register_platform(); +	if (err) +		goto err_unreg; + +	err = acerhdf_register_thermal(); +	if (err) +		goto err_unreg; + +	return 0; + +err_unreg: +	acerhdf_unregister_thermal(); +	acerhdf_unregister_platform(); + +out_err: +	return -ENODEV; +} + +static void __exit acerhdf_exit(void) +{ +	acerhdf_change_fanstate(ACERHDF_FAN_AUTO); +	acerhdf_unregister_thermal(); +	acerhdf_unregister_platform(); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Peter Feuerer"); +MODULE_DESCRIPTION("Aspire One temperature and fan driver"); +MODULE_ALIAS("dmi:*:*Acer*:*:"); +MODULE_ALIAS("dmi:*:*Gateway*:*:"); +MODULE_ALIAS("dmi:*:*Packard Bell*:*:"); + +module_init(acerhdf_init); +module_exit(acerhdf_exit); diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index af9f4302117..74909c4aaee 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -174,10 +174,11 @@ dell_send_request(struct calling_interface_buffer *buffer, int class,     result[3]: NVRAM format version number  */ -static int dell_rfkill_set(int radio, enum rfkill_state state) +static int dell_rfkill_set(void *data, bool blocked)  {  	struct calling_interface_buffer buffer; -	int disable = (state == RFKILL_STATE_UNBLOCKED) ? 0 : 1; +	int disable = blocked ? 1 : 0; +	unsigned long radio = (unsigned long)data;  	memset(&buffer, 0, sizeof(struct calling_interface_buffer));  	buffer.input[0] = (1 | (radio<<8) | (disable << 16)); @@ -186,56 +187,24 @@ static int dell_rfkill_set(int radio, enum rfkill_state state)  	return 0;  } -static int dell_wifi_set(void *data, enum rfkill_state state) -{ -	return dell_rfkill_set(1, state); -} - -static int dell_bluetooth_set(void *data, enum rfkill_state state) -{ -	return dell_rfkill_set(2, state); -} - -static int dell_wwan_set(void *data, enum rfkill_state state) -{ -	return dell_rfkill_set(3, state); -} - -static int dell_rfkill_get(int bit, enum rfkill_state *state) +static void dell_rfkill_query(struct rfkill *rfkill, void *data)  {  	struct calling_interface_buffer buffer;  	int status; -	int new_state = RFKILL_STATE_HARD_BLOCKED; +	int bit = (unsigned long)data + 16;  	memset(&buffer, 0, sizeof(struct calling_interface_buffer));  	dell_send_request(&buffer, 17, 11);  	status = buffer.output[1]; -	if (status & (1<<16)) -		new_state = RFKILL_STATE_SOFT_BLOCKED; - -	if (status & (1<<bit)) -		*state = new_state; -	else -		*state = RFKILL_STATE_UNBLOCKED; - -	return 0; -} - -static int dell_wifi_get(void *data, enum rfkill_state *state) -{ -	return dell_rfkill_get(17, state); -} - -static int dell_bluetooth_get(void *data, enum rfkill_state *state) -{ -	return dell_rfkill_get(18, state); +	if (status & BIT(bit)) +		rfkill_set_hw_state(rfkill, !!(status & BIT(16)));  } -static int dell_wwan_get(void *data, enum rfkill_state *state) -{ -	return dell_rfkill_get(19, state); -} +static const struct rfkill_ops dell_rfkill_ops = { +	.set_block = dell_rfkill_set, +	.query = dell_rfkill_query, +};  static int dell_setup_rfkill(void)  { @@ -248,36 +217,37 @@ static int dell_setup_rfkill(void)  	status = buffer.output[1];  	if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) { -		wifi_rfkill = rfkill_allocate(NULL, RFKILL_TYPE_WLAN); -		if (!wifi_rfkill) +		wifi_rfkill = rfkill_alloc("dell-wifi", NULL, RFKILL_TYPE_WLAN, +					   &dell_rfkill_ops, (void *) 1); +		if (!wifi_rfkill) { +			ret = -ENOMEM;  			goto err_wifi; -		wifi_rfkill->name = "dell-wifi"; -		wifi_rfkill->toggle_radio = dell_wifi_set; -		wifi_rfkill->get_state = dell_wifi_get; +		}  		ret = rfkill_register(wifi_rfkill);  		if (ret)  			goto err_wifi;  	}  	if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) { -		bluetooth_rfkill = rfkill_allocate(NULL, RFKILL_TYPE_BLUETOOTH); -		if (!bluetooth_rfkill) +		bluetooth_rfkill = rfkill_alloc("dell-bluetooth", NULL, +						RFKILL_TYPE_BLUETOOTH, +						&dell_rfkill_ops, (void *) 2); +		if (!bluetooth_rfkill) { +			ret = -ENOMEM;  			goto err_bluetooth; -		bluetooth_rfkill->name = "dell-bluetooth"; -		bluetooth_rfkill->toggle_radio = dell_bluetooth_set; -		bluetooth_rfkill->get_state = dell_bluetooth_get; +		}  		ret = rfkill_register(bluetooth_rfkill);  		if (ret)  			goto err_bluetooth;  	}  	if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) { -		wwan_rfkill = rfkill_allocate(NULL, RFKILL_TYPE_WWAN); -		if (!wwan_rfkill) +		wwan_rfkill = rfkill_alloc("dell-wwan", NULL, RFKILL_TYPE_WWAN, +					   &dell_rfkill_ops, (void *) 3); +		if (!wwan_rfkill) { +			ret = -ENOMEM;  			goto err_wwan; -		wwan_rfkill->name = "dell-wwan"; -		wwan_rfkill->toggle_radio = dell_wwan_set; -		wwan_rfkill->get_state = dell_wwan_get; +		}  		ret = rfkill_register(wwan_rfkill);  		if (ret)  			goto err_wwan; @@ -285,22 +255,15 @@ static int dell_setup_rfkill(void)  	return 0;  err_wwan: -	if (wwan_rfkill) -		rfkill_free(wwan_rfkill); -	if (bluetooth_rfkill) { +	rfkill_destroy(wwan_rfkill); +	if (bluetooth_rfkill)  		rfkill_unregister(bluetooth_rfkill); -		bluetooth_rfkill = NULL; -	}  err_bluetooth: -	if (bluetooth_rfkill) -		rfkill_free(bluetooth_rfkill); -	if (wifi_rfkill) { +	rfkill_destroy(bluetooth_rfkill); +	if (wifi_rfkill)  		rfkill_unregister(wifi_rfkill); -		wifi_rfkill = NULL; -	}  err_wifi: -	if (wifi_rfkill) -		rfkill_free(wifi_rfkill); +	rfkill_destroy(wifi_rfkill);  	return ret;  } diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 2fab9416214..0f900cc9fa7 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -46,10 +46,53 @@ struct key_entry {  	u16 keycode;  }; -enum { KE_KEY, KE_SW, KE_END }; +enum { KE_KEY, KE_SW, KE_IGNORE, KE_END }; + +/* + * Certain keys are flagged as KE_IGNORE. All of these are either + * notifications (rather than requests for change) or are also sent + * via the keyboard controller so should not be sent again. + */  static struct key_entry dell_wmi_keymap[] = {  	{KE_KEY, 0xe045, KEY_PROG1}, +	{KE_KEY, 0xe009, KEY_EJECTCD}, + +	/* These also contain the brightness level at offset 6 */ +	{KE_KEY, 0xe006, KEY_BRIGHTNESSUP}, +	{KE_KEY, 0xe005, KEY_BRIGHTNESSDOWN}, + +	/* Battery health status button */ +	{KE_KEY, 0xe007, KEY_BATTERY}, + +	/* This is actually for all radios. Although physically a +	 * switch, the notification does not provide an indication of +	 * state and so it should be reported as a key */ +	{KE_KEY, 0xe008, KEY_WLAN}, + +	/* The next device is at offset 6, the active devices are at +	   offset 8 and the attached devices at offset 10 */ +	{KE_KEY, 0xe00b, KEY_DISPLAYTOGGLE}, + +	{KE_IGNORE, 0xe00c, KEY_KBDILLUMTOGGLE}, + +	/* BIOS error detected */ +	{KE_IGNORE, 0xe00d, KEY_RESERVED}, + +	/* Wifi Catcher */ +	{KE_KEY, 0xe011, KEY_PROG2}, + +	/* Ambient light sensor toggle */ +	{KE_IGNORE, 0xe013, KEY_RESERVED}, + +	{KE_IGNORE, 0xe020, KEY_MUTE}, +	{KE_IGNORE, 0xe02e, KEY_VOLUMEDOWN}, +	{KE_IGNORE, 0xe030, KEY_VOLUMEUP}, +	{KE_IGNORE, 0xe033, KEY_KBDILLUMUP}, +	{KE_IGNORE, 0xe034, KEY_KBDILLUMDOWN}, +	{KE_IGNORE, 0xe03a, KEY_CAPSLOCK}, +	{KE_IGNORE, 0xe045, KEY_NUMLOCK}, +	{KE_IGNORE, 0xe046, KEY_SCROLLLOCK},  	{KE_END, 0}  }; @@ -122,15 +165,20 @@ static void dell_wmi_notify(u32 value, void *context)  	if (obj && obj->type == ACPI_TYPE_BUFFER) {  		int *buffer = (int *)obj->buffer.pointer; -		key = dell_wmi_get_entry_by_scancode(buffer[1]); +		/* +		 *  The upper bytes of the event may contain +		 *  additional information, so mask them off for the +		 *  scancode lookup +		 */ +		key = dell_wmi_get_entry_by_scancode(buffer[1] & 0xFFFF);  		if (key) {  			input_report_key(dell_wmi_input_dev, key->keycode, 1);  			input_sync(dell_wmi_input_dev);  			input_report_key(dell_wmi_input_dev, key->keycode, 0);  			input_sync(dell_wmi_input_dev); -		} else +		} else if (buffer[1] & 0xFFFF)  			printk(KERN_INFO "dell-wmi: Unknown key %x pressed\n", -			       buffer[1]); +			       buffer[1] & 0xFFFF);  	}  } diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index 353a898c369..8153b3e5918 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -180,6 +180,7 @@ static struct key_entry eeepc_keymap[] = {   */  static int eeepc_hotk_add(struct acpi_device *device);  static int eeepc_hotk_remove(struct acpi_device *device, int type); +static int eeepc_hotk_resume(struct acpi_device *device);  static const struct acpi_device_id eeepc_device_ids[] = {  	{EEEPC_HOTK_HID, 0}, @@ -194,6 +195,7 @@ static struct acpi_driver eeepc_hotk_driver = {  	.ops = {  		.add = eeepc_hotk_add,  		.remove = eeepc_hotk_remove, +		.resume = eeepc_hotk_resume,  	},  }; @@ -299,39 +301,22 @@ static int update_bl_status(struct backlight_device *bd)   * Rfkill helpers   */ -static int eeepc_wlan_rfkill_set(void *data, enum rfkill_state state) -{ -	if (state == RFKILL_STATE_SOFT_BLOCKED) -		return set_acpi(CM_ASL_WLAN, 0); -	else -		return set_acpi(CM_ASL_WLAN, 1); -} - -static int eeepc_wlan_rfkill_state(void *data, enum rfkill_state *state) +static bool eeepc_wlan_rfkill_blocked(void)  {  	if (get_acpi(CM_ASL_WLAN) == 1) -		*state = RFKILL_STATE_UNBLOCKED; -	else -		*state = RFKILL_STATE_SOFT_BLOCKED; -	return 0; +		return false; +	return true;  } -static int eeepc_bluetooth_rfkill_set(void *data, enum rfkill_state state) +static int eeepc_rfkill_set(void *data, bool blocked)  { -	if (state == RFKILL_STATE_SOFT_BLOCKED) -		return set_acpi(CM_ASL_BLUETOOTH, 0); -	else -		return set_acpi(CM_ASL_BLUETOOTH, 1); +	unsigned long asl = (unsigned long)data; +	return set_acpi(asl, !blocked);  } -static int eeepc_bluetooth_rfkill_state(void *data, enum rfkill_state *state) -{ -	if (get_acpi(CM_ASL_BLUETOOTH) == 1) -		*state = RFKILL_STATE_UNBLOCKED; -	else -		*state = RFKILL_STATE_SOFT_BLOCKED; -	return 0; -} +static const struct rfkill_ops eeepc_rfkill_ops = { +	.set_block = eeepc_rfkill_set, +};  /*   * Sys helpers @@ -529,23 +514,19 @@ static int notify_brn(void)  	return -1;  } -static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data) +static void eeepc_rfkill_hotplug(void)  { -	enum rfkill_state state;  	struct pci_dev *dev;  	struct pci_bus *bus = pci_find_bus(0, 1); - -	if (event != ACPI_NOTIFY_BUS_CHECK) -		return; +	bool blocked;  	if (!bus) {  		printk(EEEPC_WARNING "Unable to find PCI bus 1?\n");  		return;  	} -	eeepc_wlan_rfkill_state(ehotk->eeepc_wlan_rfkill, &state); - -	if (state == RFKILL_STATE_UNBLOCKED) { +	blocked = eeepc_wlan_rfkill_blocked(); +	if (!blocked) {  		dev = pci_get_slot(bus, 0);  		if (dev) {  			/* Device already present */ @@ -566,7 +547,15 @@ static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data)  		}  	} -	rfkill_force_state(ehotk->eeepc_wlan_rfkill, state); +	rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill, blocked); +} + +static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data) +{ +	if (event != ACPI_NOTIFY_BUS_CHECK) +		return; + +	eeepc_rfkill_hotplug();  }  static void eeepc_hotk_notify(acpi_handle handle, u32 event, void *data) @@ -684,26 +673,17 @@ static int eeepc_hotk_add(struct acpi_device *device)  	eeepc_register_rfkill_notifier("\\_SB.PCI0.P0P7");  	if (get_acpi(CM_ASL_WLAN) != -1) { -		ehotk->eeepc_wlan_rfkill = rfkill_allocate(&device->dev, -							   RFKILL_TYPE_WLAN); +		ehotk->eeepc_wlan_rfkill = rfkill_alloc("eeepc-wlan", +							&device->dev, +							RFKILL_TYPE_WLAN, +							&eeepc_rfkill_ops, +							(void *)CM_ASL_WLAN);  		if (!ehotk->eeepc_wlan_rfkill)  			goto wlan_fail; -		ehotk->eeepc_wlan_rfkill->name = "eeepc-wlan"; -		ehotk->eeepc_wlan_rfkill->toggle_radio = eeepc_wlan_rfkill_set; -		ehotk->eeepc_wlan_rfkill->get_state = eeepc_wlan_rfkill_state; -		if (get_acpi(CM_ASL_WLAN) == 1) { -			ehotk->eeepc_wlan_rfkill->state = -				RFKILL_STATE_UNBLOCKED; -			rfkill_set_default(RFKILL_TYPE_WLAN, -					   RFKILL_STATE_UNBLOCKED); -		} else { -			ehotk->eeepc_wlan_rfkill->state = -				RFKILL_STATE_SOFT_BLOCKED; -			rfkill_set_default(RFKILL_TYPE_WLAN, -					   RFKILL_STATE_SOFT_BLOCKED); -		} +		rfkill_init_sw_state(ehotk->eeepc_wlan_rfkill, +				     get_acpi(CM_ASL_WLAN) != 1);  		result = rfkill_register(ehotk->eeepc_wlan_rfkill);  		if (result)  			goto wlan_fail; @@ -711,28 +691,17 @@ static int eeepc_hotk_add(struct acpi_device *device)  	if (get_acpi(CM_ASL_BLUETOOTH) != -1) {  		ehotk->eeepc_bluetooth_rfkill = -			rfkill_allocate(&device->dev, RFKILL_TYPE_BLUETOOTH); +			rfkill_alloc("eeepc-bluetooth", +				     &device->dev, +				     RFKILL_TYPE_BLUETOOTH, +				     &eeepc_rfkill_ops, +				     (void *)CM_ASL_BLUETOOTH);  		if (!ehotk->eeepc_bluetooth_rfkill)  			goto bluetooth_fail; -		ehotk->eeepc_bluetooth_rfkill->name = "eeepc-bluetooth"; -		ehotk->eeepc_bluetooth_rfkill->toggle_radio = -			eeepc_bluetooth_rfkill_set; -		ehotk->eeepc_bluetooth_rfkill->get_state = -			eeepc_bluetooth_rfkill_state; -		if (get_acpi(CM_ASL_BLUETOOTH) == 1) { -			ehotk->eeepc_bluetooth_rfkill->state = -				RFKILL_STATE_UNBLOCKED; -			rfkill_set_default(RFKILL_TYPE_BLUETOOTH, -					   RFKILL_STATE_UNBLOCKED); -		} else { -			ehotk->eeepc_bluetooth_rfkill->state = -				RFKILL_STATE_SOFT_BLOCKED; -			rfkill_set_default(RFKILL_TYPE_BLUETOOTH, -					   RFKILL_STATE_SOFT_BLOCKED); -		} - +		rfkill_init_sw_state(ehotk->eeepc_bluetooth_rfkill, +				     get_acpi(CM_ASL_BLUETOOTH) != 1);  		result = rfkill_register(ehotk->eeepc_bluetooth_rfkill);  		if (result)  			goto bluetooth_fail; @@ -741,13 +710,10 @@ static int eeepc_hotk_add(struct acpi_device *device)  	return 0;   bluetooth_fail: -	if (ehotk->eeepc_bluetooth_rfkill) -		rfkill_free(ehotk->eeepc_bluetooth_rfkill); +	rfkill_destroy(ehotk->eeepc_bluetooth_rfkill);  	rfkill_unregister(ehotk->eeepc_wlan_rfkill); -	ehotk->eeepc_wlan_rfkill = NULL;   wlan_fail: -	if (ehotk->eeepc_wlan_rfkill) -		rfkill_free(ehotk->eeepc_wlan_rfkill); +	rfkill_destroy(ehotk->eeepc_wlan_rfkill);  	eeepc_unregister_rfkill_notifier("\\_SB.PCI0.P0P6");  	eeepc_unregister_rfkill_notifier("\\_SB.PCI0.P0P7");   ehotk_fail: @@ -775,6 +741,33 @@ static int eeepc_hotk_remove(struct acpi_device *device, int type)  	return 0;  } +static int eeepc_hotk_resume(struct acpi_device *device) +{ +	if (ehotk->eeepc_wlan_rfkill) { +		bool wlan; + +		/* Workaround - it seems that _PTS disables the wireless +		   without notification or changing the value read by WLAN. +		   Normally this is fine because the correct value is restored +		   from the non-volatile storage on resume, but we need to do +		   it ourself if case suspend is aborted, or we lose wireless. +		 */ +		wlan = get_acpi(CM_ASL_WLAN); +		set_acpi(CM_ASL_WLAN, wlan); + +		rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill, +				    wlan != 1); + +		eeepc_rfkill_hotplug(); +	} + +	if (ehotk->eeepc_bluetooth_rfkill) +		rfkill_set_sw_state(ehotk->eeepc_bluetooth_rfkill, +				    get_acpi(CM_ASL_BLUETOOTH) != 1); + +	return 0; +} +  /*   * Hwmon   */ diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 50d9019de2b..4ac2311c00a 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -47,7 +47,7 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4");  #define HPWMI_DISPLAY_QUERY 0x1  #define HPWMI_HDDTEMP_QUERY 0x2  #define HPWMI_ALS_QUERY 0x3 -#define HPWMI_DOCK_QUERY 0x4 +#define HPWMI_HARDWARE_QUERY 0x4  #define HPWMI_WIRELESS_QUERY 0x5  #define HPWMI_HOTKEY_QUERY 0xc @@ -75,10 +75,9 @@ struct key_entry {  	u16 keycode;  }; -enum { KE_KEY, KE_SW, KE_END }; +enum { KE_KEY, KE_END };  static struct key_entry hp_wmi_keymap[] = { -	{KE_SW, 0x01, SW_DOCK},  	{KE_KEY, 0x02, KEY_BRIGHTNESSUP},  	{KE_KEY, 0x03, KEY_BRIGHTNESSDOWN},  	{KE_KEY, 0x20e6, KEY_PROG1}, @@ -151,61 +150,64 @@ static int hp_wmi_als_state(void)  static int hp_wmi_dock_state(void)  { -	return hp_wmi_perform_query(HPWMI_DOCK_QUERY, 0, 0); -} +	int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, 0); -static int hp_wmi_wifi_set(void *data, enum rfkill_state state) -{ -	if (state) -		return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x101); -	else -		return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x100); +	if (ret < 0) +		return ret; + +	return ret & 0x1;  } -static int hp_wmi_bluetooth_set(void *data, enum rfkill_state state) +static int hp_wmi_tablet_state(void)  { -	if (state) -		return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x202); -	else -		return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x200); +	int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, 0); + +	if (ret < 0) +		return ret; + +	return (ret & 0x4) ? 1 : 0;  } -static int hp_wmi_wwan_set(void *data, enum rfkill_state state) +static int hp_wmi_set_block(void *data, bool blocked)  { -	if (state) -		return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x404); -	else -		return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x400); +	unsigned long b = (unsigned long) data; +	int query = BIT(b + 8) | ((!!blocked) << b); + +	return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, query);  } -static int hp_wmi_wifi_state(void) +static const struct rfkill_ops hp_wmi_rfkill_ops = { +	.set_block = hp_wmi_set_block, +}; + +static bool hp_wmi_wifi_state(void)  {  	int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0);  	if (wireless & 0x100) -		return RFKILL_STATE_UNBLOCKED; +		return false;  	else -		return RFKILL_STATE_SOFT_BLOCKED; +		return true;  } -static int hp_wmi_bluetooth_state(void) +static bool hp_wmi_bluetooth_state(void)  {  	int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0);  	if (wireless & 0x10000) -		return RFKILL_STATE_UNBLOCKED; +		return false;  	else -		return RFKILL_STATE_SOFT_BLOCKED; +		return true;  } -static int hp_wmi_wwan_state(void) +static bool hp_wmi_wwan_state(void)  {  	int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0);  	if (wireless & 0x1000000) -		return RFKILL_STATE_UNBLOCKED; +		return false;  	else -		return RFKILL_STATE_SOFT_BLOCKED; +		return true;  }  static ssize_t show_display(struct device *dev, struct device_attribute *attr, @@ -244,6 +246,15 @@ static ssize_t show_dock(struct device *dev, struct device_attribute *attr,  	return sprintf(buf, "%d\n", value);  } +static ssize_t show_tablet(struct device *dev, struct device_attribute *attr, +			 char *buf) +{ +	int value = hp_wmi_tablet_state(); +	if (value < 0) +		return -EINVAL; +	return sprintf(buf, "%d\n", value); +} +  static ssize_t set_als(struct device *dev, struct device_attribute *attr,  		       const char *buf, size_t count)  { @@ -256,6 +267,7 @@ static DEVICE_ATTR(display, S_IRUGO, show_display, NULL);  static DEVICE_ATTR(hddtemp, S_IRUGO, show_hddtemp, NULL);  static DEVICE_ATTR(als, S_IRUGO | S_IWUSR, show_als, set_als);  static DEVICE_ATTR(dock, S_IRUGO, show_dock, NULL); +static DEVICE_ATTR(tablet, S_IRUGO, show_tablet, NULL);  static struct key_entry *hp_wmi_get_entry_by_scancode(int code)  { @@ -338,23 +350,23 @@ static void hp_wmi_notify(u32 value, void *context)  						 key->keycode, 0);  				input_sync(hp_wmi_input_dev);  				break; -			case KE_SW: -				input_report_switch(hp_wmi_input_dev, -						    key->keycode, -						    hp_wmi_dock_state()); -				input_sync(hp_wmi_input_dev); -				break;  			} +		} else if (eventcode == 0x1) { +			input_report_switch(hp_wmi_input_dev, SW_DOCK, +					    hp_wmi_dock_state()); +			input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, +					    hp_wmi_tablet_state()); +			input_sync(hp_wmi_input_dev);  		} else if (eventcode == 0x5) {  			if (wifi_rfkill) -				rfkill_force_state(wifi_rfkill, -						   hp_wmi_wifi_state()); +				rfkill_set_sw_state(wifi_rfkill, +						    hp_wmi_wifi_state());  			if (bluetooth_rfkill) -				rfkill_force_state(bluetooth_rfkill, -						   hp_wmi_bluetooth_state()); +				rfkill_set_sw_state(bluetooth_rfkill, +						    hp_wmi_bluetooth_state());  			if (wwan_rfkill) -				rfkill_force_state(wwan_rfkill, -						   hp_wmi_wwan_state()); +				rfkill_set_sw_state(wwan_rfkill, +						    hp_wmi_wwan_state());  		} else  			printk(KERN_INFO "HP WMI: Unknown key pressed - %x\n",  			       eventcode); @@ -381,18 +393,19 @@ static int __init hp_wmi_input_setup(void)  			set_bit(EV_KEY, hp_wmi_input_dev->evbit);  			set_bit(key->keycode, hp_wmi_input_dev->keybit);  			break; -		case KE_SW: -			set_bit(EV_SW, hp_wmi_input_dev->evbit); -			set_bit(key->keycode, hp_wmi_input_dev->swbit); - -			/* Set initial dock state */ -			input_report_switch(hp_wmi_input_dev, key->keycode, -					    hp_wmi_dock_state()); -			input_sync(hp_wmi_input_dev); -			break;  		}  	} +	set_bit(EV_SW, hp_wmi_input_dev->evbit); +	set_bit(SW_DOCK, hp_wmi_input_dev->swbit); +	set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); + +	/* Set initial hardware state */ +	input_report_switch(hp_wmi_input_dev, SW_DOCK, hp_wmi_dock_state()); +	input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, +			    hp_wmi_tablet_state()); +	input_sync(hp_wmi_input_dev); +  	err = input_register_device(hp_wmi_input_dev);  	if (err) { @@ -409,6 +422,7 @@ static void cleanup_sysfs(struct platform_device *device)  	device_remove_file(&device->dev, &dev_attr_hddtemp);  	device_remove_file(&device->dev, &dev_attr_als);  	device_remove_file(&device->dev, &dev_attr_dock); +	device_remove_file(&device->dev, &dev_attr_tablet);  }  static int __init hp_wmi_bios_setup(struct platform_device *device) @@ -428,36 +442,35 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)  	err = device_create_file(&device->dev, &dev_attr_dock);  	if (err)  		goto add_sysfs_error; +	err = device_create_file(&device->dev, &dev_attr_tablet); +	if (err) +		goto add_sysfs_error;  	if (wireless & 0x1) { -		wifi_rfkill = rfkill_allocate(&device->dev, RFKILL_TYPE_WLAN); -		wifi_rfkill->name = "hp-wifi"; -		wifi_rfkill->state = hp_wmi_wifi_state(); -		wifi_rfkill->toggle_radio = hp_wmi_wifi_set; -		wifi_rfkill->user_claim_unsupported = 1; +		wifi_rfkill = rfkill_alloc("hp-wifi", &device->dev, +					   RFKILL_TYPE_WLAN, +					   &hp_wmi_rfkill_ops, +					   (void *) 0);  		err = rfkill_register(wifi_rfkill);  		if (err) -			goto add_sysfs_error; +			goto register_wifi_error;  	}  	if (wireless & 0x2) { -		bluetooth_rfkill = rfkill_allocate(&device->dev, -						   RFKILL_TYPE_BLUETOOTH); -		bluetooth_rfkill->name = "hp-bluetooth"; -		bluetooth_rfkill->state = hp_wmi_bluetooth_state(); -		bluetooth_rfkill->toggle_radio = hp_wmi_bluetooth_set; -		bluetooth_rfkill->user_claim_unsupported = 1; +		bluetooth_rfkill = rfkill_alloc("hp-bluetooth", &device->dev, +						RFKILL_TYPE_BLUETOOTH, +						&hp_wmi_rfkill_ops, +						(void *) 1);  		err = rfkill_register(bluetooth_rfkill);  		if (err)  			goto register_bluetooth_error;  	}  	if (wireless & 0x4) { -		wwan_rfkill = rfkill_allocate(&device->dev, RFKILL_TYPE_WWAN); -		wwan_rfkill->name = "hp-wwan"; -		wwan_rfkill->state = hp_wmi_wwan_state(); -		wwan_rfkill->toggle_radio = hp_wmi_wwan_set; -		wwan_rfkill->user_claim_unsupported = 1; +		wwan_rfkill = rfkill_alloc("hp-wwan", &device->dev, +					   RFKILL_TYPE_WWAN, +					   &hp_wmi_rfkill_ops, +					   (void *) 2);  		err = rfkill_register(wwan_rfkill);  		if (err)  			goto register_wwan_err; @@ -465,11 +478,15 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)  	return 0;  register_wwan_err: +	rfkill_destroy(wwan_rfkill);  	if (bluetooth_rfkill)  		rfkill_unregister(bluetooth_rfkill);  register_bluetooth_error: +	rfkill_destroy(bluetooth_rfkill);  	if (wifi_rfkill)  		rfkill_unregister(wifi_rfkill); +register_wifi_error: +	rfkill_destroy(wifi_rfkill);  add_sysfs_error:  	cleanup_sysfs(device);  	return err; @@ -479,35 +496,35 @@ static int __exit hp_wmi_bios_remove(struct platform_device *device)  {  	cleanup_sysfs(device); -	if (wifi_rfkill) +	if (wifi_rfkill) {  		rfkill_unregister(wifi_rfkill); -	if (bluetooth_rfkill) +		rfkill_destroy(wifi_rfkill); +	} +	if (bluetooth_rfkill) {  		rfkill_unregister(bluetooth_rfkill); -	if (wwan_rfkill) +		rfkill_destroy(wifi_rfkill); +	} +	if (wwan_rfkill) {  		rfkill_unregister(wwan_rfkill); +		rfkill_destroy(wwan_rfkill); +	}  	return 0;  }  static int hp_wmi_resume_handler(struct platform_device *device)  { -	struct key_entry *key; -  	/* -	 * Docking state may have changed while suspended, so trigger -	 * an input event for the current state. As this is a switch, +	 * Hardware state may have changed while suspended, so trigger +	 * input events for the current state. As this is a switch,  	 * the input layer will only actually pass it on if the state  	 * changed.  	 */ -	for (key = hp_wmi_keymap; key->type != KE_END; key++) { -		switch (key->type) { -		case KE_SW: -			input_report_switch(hp_wmi_input_dev, key->keycode, -					    hp_wmi_dock_state()); -			input_sync(hp_wmi_input_dev); -			break; -		} -	} + +	input_report_switch(hp_wmi_input_dev, SW_DOCK, hp_wmi_dock_state()); +	input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, +			    hp_wmi_tablet_state()); +	input_sync(hp_wmi_input_dev);  	return 0;  } diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index 552958545f9..dafaa4a92df 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -128,11 +128,11 @@ enum sony_nc_rfkill {  	SONY_BLUETOOTH,  	SONY_WWAN,  	SONY_WIMAX, -	SONY_RFKILL_MAX, +	N_SONY_RFKILL,  }; -static struct rfkill *sony_rfkill_devices[SONY_RFKILL_MAX]; -static int sony_rfkill_address[SONY_RFKILL_MAX] = {0x300, 0x500, 0x700, 0x900}; +static struct rfkill *sony_rfkill_devices[N_SONY_RFKILL]; +static int sony_rfkill_address[N_SONY_RFKILL] = {0x300, 0x500, 0x700, 0x900};  static void sony_nc_rfkill_update(void);  /*********** Input Devices ***********/ @@ -1051,151 +1051,97 @@ static void sony_nc_rfkill_cleanup(void)  {  	int i; -	for (i = 0; i < SONY_RFKILL_MAX; i++) { -		if (sony_rfkill_devices[i]) +	for (i = 0; i < N_SONY_RFKILL; i++) { +		if (sony_rfkill_devices[i]) {  			rfkill_unregister(sony_rfkill_devices[i]); +			rfkill_destroy(sony_rfkill_devices[i]); +		}  	}  } -static int sony_nc_rfkill_get(void *data, enum rfkill_state *state) -{ -	int result; -	int argument = sony_rfkill_address[(long) data]; - -	sony_call_snc_handle(0x124, 0x200, &result); -	if (result & 0x1) { -		sony_call_snc_handle(0x124, argument, &result); -		if (result & 0xf) -			*state = RFKILL_STATE_UNBLOCKED; -		else -			*state = RFKILL_STATE_SOFT_BLOCKED; -	} else { -		*state = RFKILL_STATE_HARD_BLOCKED; -	} - -	return 0; -} - -static int sony_nc_rfkill_set(void *data, enum rfkill_state state) +static int sony_nc_rfkill_set(void *data, bool blocked)  {  	int result;  	int argument = sony_rfkill_address[(long) data] + 0x100; -	if (state == RFKILL_STATE_UNBLOCKED) +	if (!blocked)  		argument |= 0xff0000;  	return sony_call_snc_handle(0x124, argument, &result);  } -static int sony_nc_setup_wifi_rfkill(struct acpi_device *device) -{ -	int err = 0; -	struct rfkill *sony_wifi_rfkill; - -	sony_wifi_rfkill = rfkill_allocate(&device->dev, RFKILL_TYPE_WLAN); -	if (!sony_wifi_rfkill) -		return -1; -	sony_wifi_rfkill->name = "sony-wifi"; -	sony_wifi_rfkill->toggle_radio = sony_nc_rfkill_set; -	sony_wifi_rfkill->get_state = sony_nc_rfkill_get; -	sony_wifi_rfkill->user_claim_unsupported = 1; -	sony_wifi_rfkill->data = (void *)SONY_WIFI; -	err = rfkill_register(sony_wifi_rfkill); -	if (err) -		rfkill_free(sony_wifi_rfkill); -	else { -		sony_rfkill_devices[SONY_WIFI] = sony_wifi_rfkill; -		sony_nc_rfkill_set(sony_wifi_rfkill->data, -				RFKILL_STATE_UNBLOCKED); -	} -	return err; -} +static const struct rfkill_ops sony_rfkill_ops = { +	.set_block = sony_nc_rfkill_set, +}; -static int sony_nc_setup_bluetooth_rfkill(struct acpi_device *device) +static int sony_nc_setup_rfkill(struct acpi_device *device, +				enum sony_nc_rfkill nc_type)  {  	int err = 0; -	struct rfkill *sony_bluetooth_rfkill; +	struct rfkill *rfk; +	enum rfkill_type type; +	const char *name; -	sony_bluetooth_rfkill = rfkill_allocate(&device->dev, -						RFKILL_TYPE_BLUETOOTH); -	if (!sony_bluetooth_rfkill) -		return -1; -	sony_bluetooth_rfkill->name = "sony-bluetooth"; -	sony_bluetooth_rfkill->toggle_radio = sony_nc_rfkill_set; -	sony_bluetooth_rfkill->get_state = sony_nc_rfkill_get; -	sony_bluetooth_rfkill->user_claim_unsupported = 1; -	sony_bluetooth_rfkill->data = (void *)SONY_BLUETOOTH; -	err = rfkill_register(sony_bluetooth_rfkill); -	if (err) -		rfkill_free(sony_bluetooth_rfkill); -	else { -		sony_rfkill_devices[SONY_BLUETOOTH] = sony_bluetooth_rfkill; -		sony_nc_rfkill_set(sony_bluetooth_rfkill->data, -				RFKILL_STATE_UNBLOCKED); +	switch (nc_type) { +	case SONY_WIFI: +		type = RFKILL_TYPE_WLAN; +		name = "sony-wifi"; +		break; +	case SONY_BLUETOOTH: +		type = RFKILL_TYPE_BLUETOOTH; +		name = "sony-bluetooth"; +		break; +	case SONY_WWAN: +		type = RFKILL_TYPE_WWAN; +		name = "sony-wwan"; +		break; +	case SONY_WIMAX: +		type = RFKILL_TYPE_WIMAX; +		name = "sony-wimax"; +		break; +	default: +		return -EINVAL;  	} -	return err; -} -static int sony_nc_setup_wwan_rfkill(struct acpi_device *device) -{ -	int err = 0; -	struct rfkill *sony_wwan_rfkill; +	rfk = rfkill_alloc(name, &device->dev, type, +			   &sony_rfkill_ops, (void *)nc_type); +	if (!rfk) +		return -ENOMEM; -	sony_wwan_rfkill = rfkill_allocate(&device->dev, RFKILL_TYPE_WWAN); -	if (!sony_wwan_rfkill) -		return -1; -	sony_wwan_rfkill->name = "sony-wwan"; -	sony_wwan_rfkill->toggle_radio = sony_nc_rfkill_set; -	sony_wwan_rfkill->get_state = sony_nc_rfkill_get; -	sony_wwan_rfkill->user_claim_unsupported = 1; -	sony_wwan_rfkill->data = (void *)SONY_WWAN; -	err = rfkill_register(sony_wwan_rfkill); -	if (err) -		rfkill_free(sony_wwan_rfkill); -	else { -		sony_rfkill_devices[SONY_WWAN] = sony_wwan_rfkill; -		sony_nc_rfkill_set(sony_wwan_rfkill->data, -				RFKILL_STATE_UNBLOCKED); +	err = rfkill_register(rfk); +	if (err) { +		rfkill_destroy(rfk); +		return err;  	} +	sony_rfkill_devices[nc_type] = rfk;  	return err;  } -static int sony_nc_setup_wimax_rfkill(struct acpi_device *device) +static void sony_nc_rfkill_update()  { -	int err = 0; -	struct rfkill *sony_wimax_rfkill; +	enum sony_nc_rfkill i; +	int result; +	bool hwblock; -	sony_wimax_rfkill = rfkill_allocate(&device->dev, RFKILL_TYPE_WIMAX); -	if (!sony_wimax_rfkill) -		return -1; -	sony_wimax_rfkill->name = "sony-wimax"; -	sony_wimax_rfkill->toggle_radio = sony_nc_rfkill_set; -	sony_wimax_rfkill->get_state = sony_nc_rfkill_get; -	sony_wimax_rfkill->user_claim_unsupported = 1; -	sony_wimax_rfkill->data = (void *)SONY_WIMAX; -	err = rfkill_register(sony_wimax_rfkill); -	if (err) -		rfkill_free(sony_wimax_rfkill); -	else { -		sony_rfkill_devices[SONY_WIMAX] = sony_wimax_rfkill; -		sony_nc_rfkill_set(sony_wimax_rfkill->data, -				RFKILL_STATE_UNBLOCKED); -	} -	return err; -} +	sony_call_snc_handle(0x124, 0x200, &result); +	hwblock = !(result & 0x1); -static void sony_nc_rfkill_update() -{ -	int i; -	enum rfkill_state state; +	for (i = 0; i < N_SONY_RFKILL; i++) { +		int argument = sony_rfkill_address[i]; -	for (i = 0; i < SONY_RFKILL_MAX; i++) { -		if (sony_rfkill_devices[i]) { -			sony_rfkill_devices[i]-> -				get_state(sony_rfkill_devices[i]->data, -					  &state); -			rfkill_force_state(sony_rfkill_devices[i], state); +		if (!sony_rfkill_devices[i]) +			continue; + +		if (hwblock) { +			if (rfkill_set_hw_state(sony_rfkill_devices[i], true)) { +				/* we already know we're blocked */ +			} +			continue;  		} + +		sony_call_snc_handle(0x124, argument, &result); +		rfkill_set_states(sony_rfkill_devices[i], +				  !(result & 0xf), false);  	}  } @@ -1214,13 +1160,13 @@ static int sony_nc_rfkill_setup(struct acpi_device *device)  	}  	if (result & 0x1) -		sony_nc_setup_wifi_rfkill(device); +		sony_nc_setup_rfkill(device, SONY_WIFI);  	if (result & 0x2) -		sony_nc_setup_bluetooth_rfkill(device); +		sony_nc_setup_rfkill(device, SONY_BLUETOOTH);  	if (result & 0x1c) -		sony_nc_setup_wwan_rfkill(device); +		sony_nc_setup_rfkill(device, SONY_WWAN);  	if (result & 0x20) -		sony_nc_setup_wimax_rfkill(device); +		sony_nc_setup_rfkill(device, SONY_WIMAX);  	return 0;  } diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 912be65b626..a463fd72c49 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -22,7 +22,7 @@   */  #define TPACPI_VERSION "0.23" -#define TPACPI_SYSFS_VERSION 0x020300 +#define TPACPI_SYSFS_VERSION 0x020400  /*   *  Changelog: @@ -166,13 +166,6 @@ enum {  #define TPACPI_MAX_ACPI_ARGS 3 -/* rfkill switches */ -enum { -	TPACPI_RFK_BLUETOOTH_SW_ID = 0, -	TPACPI_RFK_WWAN_SW_ID, -	TPACPI_RFK_UWB_SW_ID, -}; -  /* printk headers */  #define TPACPI_LOG TPACPI_FILE ": "  #define TPACPI_EMERG	KERN_EMERG	TPACPI_LOG @@ -264,6 +257,8 @@ static struct {  	u32 wan:1;  	u32 uwb:1;  	u32 fan_ctrl_status_undef:1; +	u32 second_fan:1; +	u32 beep_needs_two_args:1;  	u32 input_device_registered:1;  	u32 platform_drv_registered:1;  	u32 platform_drv_attrs_registered:1; @@ -284,8 +279,10 @@ struct thinkpad_id_data {  	char *bios_version_str;	/* Something like 1ZET51WW (1.03z) */  	char *ec_version_str;	/* Something like 1ZHT51WW-1.04a */ -	u16 bios_model;		/* Big Endian, TP-1Y = 0x5931, 0 = unknown */ +	u16 bios_model;		/* 1Y = 0x5931, 0 = unknown */  	u16 ec_model; +	u16 bios_release;	/* 1ZETK1WW = 0x314b, 0 = unknown */ +	u16 ec_release;  	char *model_str;	/* ThinkPad T43 */  	char *nummodel_str;	/* 9384A9C for a 9384-A9C model */ @@ -362,6 +359,73 @@ static void tpacpi_log_usertask(const char * const what)  		} \  	} while (0) +/* + * Quirk handling helpers + * + * ThinkPad IDs and versions seen in the field so far + * are two-characters from the set [0-9A-Z], i.e. base 36. + * + * We use values well outside that range as specials. + */ + +#define TPACPI_MATCH_ANY		0xffffU +#define TPACPI_MATCH_UNKNOWN		0U + +/* TPID('1', 'Y') == 0x5931 */ +#define TPID(__c1, __c2) (((__c2) << 8) | (__c1)) + +#define TPACPI_Q_IBM(__id1, __id2, __quirk)	\ +	{ .vendor = PCI_VENDOR_ID_IBM,		\ +	  .bios = TPID(__id1, __id2),		\ +	  .ec = TPACPI_MATCH_ANY,		\ +	  .quirks = (__quirk) } + +#define TPACPI_Q_LNV(__id1, __id2, __quirk)	\ +	{ .vendor = PCI_VENDOR_ID_LENOVO,	\ +	  .bios = TPID(__id1, __id2),		\ +	  .ec = TPACPI_MATCH_ANY,		\ +	  .quirks = (__quirk) } + +struct tpacpi_quirk { +	unsigned int vendor; +	u16 bios; +	u16 ec; +	unsigned long quirks; +}; + +/** + * tpacpi_check_quirks() - search BIOS/EC version on a list + * @qlist:		array of &struct tpacpi_quirk + * @qlist_size:		number of elements in @qlist + * + * Iterates over a quirks list until one is found that matches the + * ThinkPad's vendor, BIOS and EC model. + * + * Returns 0 if nothing matches, otherwise returns the quirks field of + * the matching &struct tpacpi_quirk entry. + * + * The match criteria is: vendor, ec and bios much match. + */ +static unsigned long __init tpacpi_check_quirks( +			const struct tpacpi_quirk *qlist, +			unsigned int qlist_size) +{ +	while (qlist_size) { +		if ((qlist->vendor == thinkpad_id.vendor || +				qlist->vendor == TPACPI_MATCH_ANY) && +		    (qlist->bios == thinkpad_id.bios_model || +				qlist->bios == TPACPI_MATCH_ANY) && +		    (qlist->ec == thinkpad_id.ec_model || +				qlist->ec == TPACPI_MATCH_ANY)) +			return qlist->quirks; + +		qlist_size--; +		qlist++; +	} +	return 0; +} + +  /****************************************************************************   ****************************************************************************   * @@ -1005,67 +1069,234 @@ static int __init tpacpi_check_std_acpi_brightness_support(void)  	return 0;  } -static int __init tpacpi_new_rfkill(const unsigned int id, -			struct rfkill **rfk, +static void printk_deprecated_attribute(const char * const what, +					const char * const details) +{ +	tpacpi_log_usertask("deprecated sysfs attribute"); +	printk(TPACPI_WARN "WARNING: sysfs attribute %s is deprecated and " +		"will be removed. %s\n", +		what, details); +} + +/************************************************************************* + * rfkill and radio control support helpers + */ + +/* + * ThinkPad-ACPI firmware handling model: + * + * WLSW (master wireless switch) is event-driven, and is common to all + * firmware-controlled radios.  It cannot be controlled, just monitored, + * as expected.  It overrides all radio state in firmware + * + * The kernel, a masked-off hotkey, and WLSW can change the radio state + * (TODO: verify how WLSW interacts with the returned radio state). + * + * The only time there are shadow radio state changes, is when + * masked-off hotkeys are used. + */ + +/* + * Internal driver API for radio state: + * + * int: < 0 = error, otherwise enum tpacpi_rfkill_state + * bool: true means radio blocked (off) + */ +enum tpacpi_rfkill_state { +	TPACPI_RFK_RADIO_OFF = 0, +	TPACPI_RFK_RADIO_ON +}; + +/* rfkill switches */ +enum tpacpi_rfk_id { +	TPACPI_RFK_BLUETOOTH_SW_ID = 0, +	TPACPI_RFK_WWAN_SW_ID, +	TPACPI_RFK_UWB_SW_ID, +	TPACPI_RFK_SW_MAX +}; + +static const char *tpacpi_rfkill_names[] = { +	[TPACPI_RFK_BLUETOOTH_SW_ID] = "bluetooth", +	[TPACPI_RFK_WWAN_SW_ID] = "wwan", +	[TPACPI_RFK_UWB_SW_ID] = "uwb", +	[TPACPI_RFK_SW_MAX] = NULL +}; + +/* ThinkPad-ACPI rfkill subdriver */ +struct tpacpi_rfk { +	struct rfkill *rfkill; +	enum tpacpi_rfk_id id; +	const struct tpacpi_rfk_ops *ops; +}; + +struct tpacpi_rfk_ops { +	/* firmware interface */ +	int (*get_status)(void); +	int (*set_status)(const enum tpacpi_rfkill_state); +}; + +static struct tpacpi_rfk *tpacpi_rfkill_switches[TPACPI_RFK_SW_MAX]; + +/* Query FW and update rfkill sw state for a given rfkill switch */ +static int tpacpi_rfk_update_swstate(const struct tpacpi_rfk *tp_rfk) +{ +	int status; + +	if (!tp_rfk) +		return -ENODEV; + +	status = (tp_rfk->ops->get_status)(); +	if (status < 0) +		return status; + +	rfkill_set_sw_state(tp_rfk->rfkill, +			    (status == TPACPI_RFK_RADIO_OFF)); + +	return status; +} + +/* Query FW and update rfkill sw state for all rfkill switches */ +static void tpacpi_rfk_update_swstate_all(void) +{ +	unsigned int i; + +	for (i = 0; i < TPACPI_RFK_SW_MAX; i++) +		tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[i]); +} + +/* + * Sync the HW-blocking state of all rfkill switches, + * do notice it causes the rfkill core to schedule uevents + */ +static void tpacpi_rfk_update_hwblock_state(bool blocked) +{ +	unsigned int i; +	struct tpacpi_rfk *tp_rfk; + +	for (i = 0; i < TPACPI_RFK_SW_MAX; i++) { +		tp_rfk = tpacpi_rfkill_switches[i]; +		if (tp_rfk) { +			if (rfkill_set_hw_state(tp_rfk->rfkill, +						blocked)) { +				/* ignore -- we track sw block */ +			} +		} +	} +} + +/* Call to get the WLSW state from the firmware */ +static int hotkey_get_wlsw(void); + +/* Call to query WLSW state and update all rfkill switches */ +static bool tpacpi_rfk_check_hwblock_state(void) +{ +	int res = hotkey_get_wlsw(); +	int hw_blocked; + +	/* When unknown or unsupported, we have to assume it is unblocked */ +	if (res < 0) +		return false; + +	hw_blocked = (res == TPACPI_RFK_RADIO_OFF); +	tpacpi_rfk_update_hwblock_state(hw_blocked); + +	return hw_blocked; +} + +static int tpacpi_rfk_hook_set_block(void *data, bool blocked) +{ +	struct tpacpi_rfk *tp_rfk = data; +	int res; + +	dbg_printk(TPACPI_DBG_RFKILL, +		   "request to change radio state to %s\n", +		   blocked ? "blocked" : "unblocked"); + +	/* try to set radio state */ +	res = (tp_rfk->ops->set_status)(blocked ? +				TPACPI_RFK_RADIO_OFF : TPACPI_RFK_RADIO_ON); + +	/* and update the rfkill core with whatever the FW really did */ +	tpacpi_rfk_update_swstate(tp_rfk); + +	return (res < 0) ? res : 0; +} + +static const struct rfkill_ops tpacpi_rfk_rfkill_ops = { +	.set_block = tpacpi_rfk_hook_set_block, +}; + +static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, +			const struct tpacpi_rfk_ops *tp_rfkops,  			const enum rfkill_type rfktype,  			const char *name, -			const bool set_default, -			int (*toggle_radio)(void *, enum rfkill_state), -			int (*get_state)(void *, enum rfkill_state *)) +			const bool set_default)  { +	struct tpacpi_rfk *atp_rfk;  	int res; -	enum rfkill_state initial_state = RFKILL_STATE_SOFT_BLOCKED; +	bool sw_state = false; +	int sw_status; -	res = get_state(NULL, &initial_state); -	if (res < 0) { -		printk(TPACPI_ERR -			"failed to read initial state for %s, error %d; " -			"will turn radio off\n", name, res); -	} else if (set_default) { -		/* try to set the initial state as the default for the rfkill -		 * type, since we ask the firmware to preserve it across S5 in -		 * NVRAM */ -		if (rfkill_set_default(rfktype, -				(initial_state == RFKILL_STATE_UNBLOCKED) ? -					RFKILL_STATE_UNBLOCKED : -					RFKILL_STATE_SOFT_BLOCKED) == -EPERM) -			vdbg_printk(TPACPI_DBG_RFKILL, -				    "Default state for %s cannot be changed\n", -				    name); -	} +	BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]); -	*rfk = rfkill_allocate(&tpacpi_pdev->dev, rfktype); -	if (!*rfk) { +	atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL); +	if (atp_rfk) +		atp_rfk->rfkill = rfkill_alloc(name, +						&tpacpi_pdev->dev, +						rfktype, +						&tpacpi_rfk_rfkill_ops, +						atp_rfk); +	if (!atp_rfk || !atp_rfk->rfkill) {  		printk(TPACPI_ERR  			"failed to allocate memory for rfkill class\n"); +		kfree(atp_rfk);  		return -ENOMEM;  	} -	(*rfk)->name = name; -	(*rfk)->get_state = get_state; -	(*rfk)->toggle_radio = toggle_radio; -	(*rfk)->state = initial_state; +	atp_rfk->id = id; +	atp_rfk->ops = tp_rfkops; + +	sw_status = (tp_rfkops->get_status)(); +	if (sw_status < 0) { +		printk(TPACPI_ERR +			"failed to read initial state for %s, error %d\n", +			name, sw_status); +	} else { +		sw_state = (sw_status == TPACPI_RFK_RADIO_OFF); +		if (set_default) { +			/* try to keep the initial state, since we ask the +			 * firmware to preserve it across S5 in NVRAM */ +			rfkill_init_sw_state(atp_rfk->rfkill, sw_state); +		} +	} +	rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state()); -	res = rfkill_register(*rfk); +	res = rfkill_register(atp_rfk->rfkill);  	if (res < 0) {  		printk(TPACPI_ERR  			"failed to register %s rfkill switch: %d\n",  			name, res); -		rfkill_free(*rfk); -		*rfk = NULL; +		rfkill_destroy(atp_rfk->rfkill); +		kfree(atp_rfk);  		return res;  	} +	tpacpi_rfkill_switches[id] = atp_rfk;  	return 0;  } -static void printk_deprecated_attribute(const char * const what, -					const char * const details) +static void tpacpi_destroy_rfkill(const enum tpacpi_rfk_id id)  { -	tpacpi_log_usertask("deprecated sysfs attribute"); -	printk(TPACPI_WARN "WARNING: sysfs attribute %s is deprecated and " -		"will be removed. %s\n", -		what, details); +	struct tpacpi_rfk *tp_rfk; + +	BUG_ON(id >= TPACPI_RFK_SW_MAX); + +	tp_rfk = tpacpi_rfkill_switches[id]; +	if (tp_rfk) { +		rfkill_unregister(tp_rfk->rfkill); +		tpacpi_rfkill_switches[id] = NULL; +		kfree(tp_rfk); +	}  }  static void printk_deprecated_rfkill_attribute(const char * const what) @@ -1074,6 +1305,112 @@ static void printk_deprecated_rfkill_attribute(const char * const what)  			"Please switch to generic rfkill before year 2010");  } +/* sysfs <radio> enable ------------------------------------------------ */ +static ssize_t tpacpi_rfk_sysfs_enable_show(const enum tpacpi_rfk_id id, +					    struct device_attribute *attr, +					    char *buf) +{ +	int status; + +	printk_deprecated_rfkill_attribute(attr->attr.name); + +	/* This is in the ABI... */ +	if (tpacpi_rfk_check_hwblock_state()) { +		status = TPACPI_RFK_RADIO_OFF; +	} else { +		status = tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]); +		if (status < 0) +			return status; +	} + +	return snprintf(buf, PAGE_SIZE, "%d\n", +			(status == TPACPI_RFK_RADIO_ON) ? 1 : 0); +} + +static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id, +			    struct device_attribute *attr, +			    const char *buf, size_t count) +{ +	unsigned long t; +	int res; + +	printk_deprecated_rfkill_attribute(attr->attr.name); + +	if (parse_strtoul(buf, 1, &t)) +		return -EINVAL; + +	tpacpi_disclose_usertask(attr->attr.name, "set to %ld\n", t); + +	/* This is in the ABI... */ +	if (tpacpi_rfk_check_hwblock_state() && !!t) +		return -EPERM; + +	res = tpacpi_rfkill_switches[id]->ops->set_status((!!t) ? +				TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF); +	tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]); + +	return (res < 0) ? res : count; +} + +/* procfs -------------------------------------------------------------- */ +static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, char *p) +{ +	int len = 0; + +	if (id >= TPACPI_RFK_SW_MAX) +		len += sprintf(p + len, "status:\t\tnot supported\n"); +	else { +		int status; + +		/* This is in the ABI... */ +		if (tpacpi_rfk_check_hwblock_state()) { +			status = TPACPI_RFK_RADIO_OFF; +		} else { +			status = tpacpi_rfk_update_swstate( +						tpacpi_rfkill_switches[id]); +			if (status < 0) +				return status; +		} + +		len += sprintf(p + len, "status:\t\t%s\n", +				(status == TPACPI_RFK_RADIO_ON) ? +					"enabled" : "disabled"); +		len += sprintf(p + len, "commands:\tenable, disable\n"); +	} + +	return len; +} + +static int tpacpi_rfk_procfs_write(const enum tpacpi_rfk_id id, char *buf) +{ +	char *cmd; +	int status = -1; +	int res = 0; + +	if (id >= TPACPI_RFK_SW_MAX) +		return -ENODEV; + +	while ((cmd = next_cmd(&buf))) { +		if (strlencmp(cmd, "enable") == 0) +			status = TPACPI_RFK_RADIO_ON; +		else if (strlencmp(cmd, "disable") == 0) +			status = TPACPI_RFK_RADIO_OFF; +		else +			return -EINVAL; +	} + +	if (status != -1) { +		tpacpi_disclose_usertask("procfs", "attempt to %s %s\n", +				(status == TPACPI_RFK_RADIO_ON) ? +						"enable" : "disable", +				tpacpi_rfkill_names[id]); +		res = (tpacpi_rfkill_switches[id]->ops->set_status)(status); +		tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]); +	} + +	return res; +} +  /*************************************************************************   * thinkpad-acpi driver attributes   */ @@ -1127,8 +1464,6 @@ static DRIVER_ATTR(version, S_IRUGO,  #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES -static void tpacpi_send_radiosw_update(void); -  /* wlsw_emulstate ------------------------------------------------------ */  static ssize_t tpacpi_driver_wlsw_emulstate_show(struct device_driver *drv,  						char *buf) @@ -1144,11 +1479,10 @@ static ssize_t tpacpi_driver_wlsw_emulstate_store(struct device_driver *drv,  	if (parse_strtoul(buf, 1, &t))  		return -EINVAL; -	if (tpacpi_wlsw_emulstate != t) { -		tpacpi_wlsw_emulstate = !!t; -		tpacpi_send_radiosw_update(); -	} else +	if (tpacpi_wlsw_emulstate != !!t) {  		tpacpi_wlsw_emulstate = !!t; +		tpacpi_rfk_update_hwblock_state(!t);	/* negative logic */ +	}  	return count;  } @@ -1463,17 +1797,23 @@ static struct attribute_set *hotkey_dev_attributes;  /* HKEY.MHKG() return bits */  #define TP_HOTKEY_TABLET_MASK (1 << 3) -static int hotkey_get_wlsw(int *status) +static int hotkey_get_wlsw(void)  { +	int status; + +	if (!tp_features.hotkey_wlsw) +		return -ENODEV; +  #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES -	if (dbg_wlswemul) { -		*status = !!tpacpi_wlsw_emulstate; -		return 0; -	} +	if (dbg_wlswemul) +		return (tpacpi_wlsw_emulstate) ? +				TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;  #endif -	if (!acpi_evalf(hkey_handle, status, "WLSW", "d")) + +	if (!acpi_evalf(hkey_handle, &status, "WLSW", "d"))  		return -EIO; -	return 0; + +	return (status) ? TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;  }  static int hotkey_get_tablet_mode(int *status) @@ -2107,12 +2447,16 @@ static ssize_t hotkey_radio_sw_show(struct device *dev,  			   struct device_attribute *attr,  			   char *buf)  { -	int res, s; -	res = hotkey_get_wlsw(&s); +	int res; +	res = hotkey_get_wlsw();  	if (res < 0)  		return res; -	return snprintf(buf, PAGE_SIZE, "%d\n", !!s); +	/* Opportunistic update */ +	tpacpi_rfk_update_hwblock_state((res == TPACPI_RFK_RADIO_OFF)); + +	return snprintf(buf, PAGE_SIZE, "%d\n", +			(res == TPACPI_RFK_RADIO_OFF) ? 0 : 1);  }  static struct device_attribute dev_attr_hotkey_radio_sw = @@ -2223,30 +2567,52 @@ static struct attribute *hotkey_mask_attributes[] __initdata = {  	&dev_attr_hotkey_wakeup_hotunplug_complete.attr,  }; -static void bluetooth_update_rfk(void); -static void wan_update_rfk(void); -static void uwb_update_rfk(void); +/* + * Sync both the hw and sw blocking state of all switches + */  static void tpacpi_send_radiosw_update(void)  {  	int wlsw; -	/* Sync these BEFORE sending any rfkill events */ -	if (tp_features.bluetooth) -		bluetooth_update_rfk(); -	if (tp_features.wan) -		wan_update_rfk(); -	if (tp_features.uwb) -		uwb_update_rfk(); +	/* +	 * We must sync all rfkill controllers *before* issuing any +	 * rfkill input events, or we will race the rfkill core input +	 * handler. +	 * +	 * tpacpi_inputdev_send_mutex works as a syncronization point +	 * for the above. +	 * +	 * We optimize to avoid numerous calls to hotkey_get_wlsw. +	 */ + +	wlsw = hotkey_get_wlsw(); + +	/* Sync hw blocking state first if it is hw-blocked */ +	if (wlsw == TPACPI_RFK_RADIO_OFF) +		tpacpi_rfk_update_hwblock_state(true); + +	/* Sync sw blocking state */ +	tpacpi_rfk_update_swstate_all(); -	if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) { +	/* Sync hw blocking state last if it is hw-unblocked */ +	if (wlsw == TPACPI_RFK_RADIO_ON) +		tpacpi_rfk_update_hwblock_state(false); + +	/* Issue rfkill input event for WLSW switch */ +	if (!(wlsw < 0)) {  		mutex_lock(&tpacpi_inputdev_send_mutex);  		input_report_switch(tpacpi_inputdev, -				    SW_RFKILL_ALL, !!wlsw); +				    SW_RFKILL_ALL, (wlsw > 0));  		input_sync(tpacpi_inputdev);  		mutex_unlock(&tpacpi_inputdev_send_mutex);  	} + +	/* +	 * this can be unconditional, as we will poll state again +	 * if userspace uses the notify to read data +	 */  	hotkey_radio_sw_notify_change();  } @@ -2585,7 +2951,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)  		/* update bright_acpimode... */  		tpacpi_check_std_acpi_brightness_support(); -	if (tp_features.bright_acpimode) { +	if (tp_features.bright_acpimode && acpi_video_backlight_support()) {  		printk(TPACPI_INFO  		       "This ThinkPad has standard ACPI backlight "  		       "brightness control, supported by the ACPI " @@ -3056,8 +3422,6 @@ enum {  #define TPACPI_RFK_BLUETOOTH_SW_NAME	"tpacpi_bluetooth_sw" -static struct rfkill *tpacpi_bluetooth_rfkill; -  static void bluetooth_suspend(pm_message_t state)  {  	/* Try to make sure radio will resume powered off */ @@ -3067,83 +3431,47 @@ static void bluetooth_suspend(pm_message_t state)  			"bluetooth power down on resume request failed\n");  } -static int bluetooth_get_radiosw(void) +static int bluetooth_get_status(void)  {  	int status; -	if (!tp_features.bluetooth) -		return -ENODEV; - -	/* WLSW overrides bluetooth in firmware/hardware, reflect that */ -	if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status) -		return RFKILL_STATE_HARD_BLOCKED; -  #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES  	if (dbg_bluetoothemul)  		return (tpacpi_bluetooth_emulstate) ? -			RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; +		       TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;  #endif  	if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))  		return -EIO;  	return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0) ? -		RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; +			TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;  } -static void bluetooth_update_rfk(void) +static int bluetooth_set_status(enum tpacpi_rfkill_state state)  {  	int status; -	if (!tpacpi_bluetooth_rfkill) -		return; - -	status = bluetooth_get_radiosw(); -	if (status < 0) -		return; -	rfkill_force_state(tpacpi_bluetooth_rfkill, status); - -	vdbg_printk(TPACPI_DBG_RFKILL, -		"forced rfkill state to %d\n", -		status); -} - -static int bluetooth_set_radiosw(int radio_on, int update_rfk) -{ -	int status; - -	if (!tp_features.bluetooth) -		return -ENODEV; - -	/* WLSW overrides bluetooth in firmware/hardware, but there is no -	 * reason to risk weird behaviour. */ -	if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status -	    && radio_on) -		return -EPERM; -  	vdbg_printk(TPACPI_DBG_RFKILL, -		"will %s bluetooth\n", radio_on ? "enable" : "disable"); +		"will attempt to %s bluetooth\n", +		(state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable");  #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES  	if (dbg_bluetoothemul) { -		tpacpi_bluetooth_emulstate = !!radio_on; -		if (update_rfk) -			bluetooth_update_rfk(); +		tpacpi_bluetooth_emulstate = (state == TPACPI_RFK_RADIO_ON);  		return 0;  	}  #endif  	/* We make sure to keep TP_ACPI_BLUETOOTH_RESUMECTRL off */ -	if (radio_on) +	if (state == TPACPI_RFK_RADIO_ON)  		status = TP_ACPI_BLUETOOTH_RADIOSSW;  	else  		status = 0; +  	if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))  		return -EIO; -	if (update_rfk) -		bluetooth_update_rfk(); -  	return 0;  } @@ -3152,35 +3480,16 @@ static ssize_t bluetooth_enable_show(struct device *dev,  			   struct device_attribute *attr,  			   char *buf)  { -	int status; - -	printk_deprecated_rfkill_attribute("bluetooth_enable"); - -	status = bluetooth_get_radiosw(); -	if (status < 0) -		return status; - -	return snprintf(buf, PAGE_SIZE, "%d\n", -			(status == RFKILL_STATE_UNBLOCKED) ? 1 : 0); +	return tpacpi_rfk_sysfs_enable_show(TPACPI_RFK_BLUETOOTH_SW_ID, +			attr, buf);  }  static ssize_t bluetooth_enable_store(struct device *dev,  			    struct device_attribute *attr,  			    const char *buf, size_t count)  { -	unsigned long t; -	int res; - -	printk_deprecated_rfkill_attribute("bluetooth_enable"); - -	if (parse_strtoul(buf, 1, &t)) -		return -EINVAL; - -	tpacpi_disclose_usertask("bluetooth_enable", "set to %ld\n", t); - -	res = bluetooth_set_radiosw(t, 1); - -	return (res) ? res : count; +	return tpacpi_rfk_sysfs_enable_store(TPACPI_RFK_BLUETOOTH_SW_ID, +				attr, buf, count);  }  static struct device_attribute dev_attr_bluetooth_enable = @@ -3198,23 +3507,10 @@ static const struct attribute_group bluetooth_attr_group = {  	.attrs = bluetooth_attributes,  }; -static int tpacpi_bluetooth_rfk_get(void *data, enum rfkill_state *state) -{ -	int bts = bluetooth_get_radiosw(); - -	if (bts < 0) -		return bts; - -	*state = bts; -	return 0; -} - -static int tpacpi_bluetooth_rfk_set(void *data, enum rfkill_state state) -{ -	dbg_printk(TPACPI_DBG_RFKILL, -		   "request to change radio state to %d\n", state); -	return bluetooth_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0); -} +static const struct tpacpi_rfk_ops bluetooth_tprfk_ops = { +	.get_status = bluetooth_get_status, +	.set_status = bluetooth_set_status, +};  static void bluetooth_shutdown(void)  { @@ -3230,13 +3526,12 @@ static void bluetooth_shutdown(void)  static void bluetooth_exit(void)  { -	bluetooth_shutdown(); - -	if (tpacpi_bluetooth_rfkill) -		rfkill_unregister(tpacpi_bluetooth_rfkill); -  	sysfs_remove_group(&tpacpi_pdev->dev.kobj,  			&bluetooth_attr_group); + +	tpacpi_destroy_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID); + +	bluetooth_shutdown();  }  static int __init bluetooth_init(struct ibm_init_struct *iibm) @@ -3277,20 +3572,18 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm)  	if (!tp_features.bluetooth)  		return 1; -	res = sysfs_create_group(&tpacpi_pdev->dev.kobj, -				&bluetooth_attr_group); -	if (res) -		return res; -  	res = tpacpi_new_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID, -				&tpacpi_bluetooth_rfkill, +				&bluetooth_tprfk_ops,  				RFKILL_TYPE_BLUETOOTH,  				TPACPI_RFK_BLUETOOTH_SW_NAME, -				true, -				tpacpi_bluetooth_rfk_set, -				tpacpi_bluetooth_rfk_get); +				true); +	if (res) +		return res; + +	res = sysfs_create_group(&tpacpi_pdev->dev.kobj, +				&bluetooth_attr_group);  	if (res) { -		bluetooth_exit(); +		tpacpi_destroy_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID);  		return res;  	} @@ -3300,46 +3593,12 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm)  /* procfs -------------------------------------------------------------- */  static int bluetooth_read(char *p)  { -	int len = 0; -	int status = bluetooth_get_radiosw(); - -	if (!tp_features.bluetooth) -		len += sprintf(p + len, "status:\t\tnot supported\n"); -	else { -		len += sprintf(p + len, "status:\t\t%s\n", -				(status == RFKILL_STATE_UNBLOCKED) ? -					"enabled" : "disabled"); -		len += sprintf(p + len, "commands:\tenable, disable\n"); -	} - -	return len; +	return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, p);  }  static int bluetooth_write(char *buf)  { -	char *cmd; -	int state = -1; - -	if (!tp_features.bluetooth) -		return -ENODEV; - -	while ((cmd = next_cmd(&buf))) { -		if (strlencmp(cmd, "enable") == 0) { -			state = 1; -		} else if (strlencmp(cmd, "disable") == 0) { -			state = 0; -		} else -			return -EINVAL; -	} - -	if (state != -1) { -		tpacpi_disclose_usertask("procfs bluetooth", -			"attempt to %s\n", -			state ? "enable" : "disable"); -		bluetooth_set_radiosw(state, 1); -	} - -	return 0; +	return tpacpi_rfk_procfs_write(TPACPI_RFK_BLUETOOTH_SW_ID, buf);  }  static struct ibm_struct bluetooth_driver_data = { @@ -3365,8 +3624,6 @@ enum {  #define TPACPI_RFK_WWAN_SW_NAME		"tpacpi_wwan_sw" -static struct rfkill *tpacpi_wan_rfkill; -  static void wan_suspend(pm_message_t state)  {  	/* Try to make sure radio will resume powered off */ @@ -3376,83 +3633,47 @@ static void wan_suspend(pm_message_t state)  			"WWAN power down on resume request failed\n");  } -static int wan_get_radiosw(void) +static int wan_get_status(void)  {  	int status; -	if (!tp_features.wan) -		return -ENODEV; - -	/* WLSW overrides WWAN in firmware/hardware, reflect that */ -	if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status) -		return RFKILL_STATE_HARD_BLOCKED; -  #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES  	if (dbg_wwanemul)  		return (tpacpi_wwan_emulstate) ? -			RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; +		       TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;  #endif  	if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))  		return -EIO;  	return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0) ? -		RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; +			TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;  } -static void wan_update_rfk(void) +static int wan_set_status(enum tpacpi_rfkill_state state)  {  	int status; -	if (!tpacpi_wan_rfkill) -		return; - -	status = wan_get_radiosw(); -	if (status < 0) -		return; -	rfkill_force_state(tpacpi_wan_rfkill, status); -  	vdbg_printk(TPACPI_DBG_RFKILL, -		"forced rfkill state to %d\n", -		status); -} - -static int wan_set_radiosw(int radio_on, int update_rfk) -{ -	int status; - -	if (!tp_features.wan) -		return -ENODEV; - -	/* WLSW overrides bluetooth in firmware/hardware, but there is no -	 * reason to risk weird behaviour. */ -	if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status -	    && radio_on) -		return -EPERM; - -	vdbg_printk(TPACPI_DBG_RFKILL, -		"will %s WWAN\n", radio_on ? "enable" : "disable"); +		"will attempt to %s wwan\n", +		(state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable");  #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES  	if (dbg_wwanemul) { -		tpacpi_wwan_emulstate = !!radio_on; -		if (update_rfk) -			wan_update_rfk(); +		tpacpi_wwan_emulstate = (state == TPACPI_RFK_RADIO_ON);  		return 0;  	}  #endif  	/* We make sure to keep TP_ACPI_WANCARD_RESUMECTRL off */ -	if (radio_on) +	if (state == TPACPI_RFK_RADIO_ON)  		status = TP_ACPI_WANCARD_RADIOSSW;  	else  		status = 0; +  	if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))  		return -EIO; -	if (update_rfk) -		wan_update_rfk(); -  	return 0;  } @@ -3461,35 +3682,16 @@ static ssize_t wan_enable_show(struct device *dev,  			   struct device_attribute *attr,  			   char *buf)  { -	int status; - -	printk_deprecated_rfkill_attribute("wwan_enable"); - -	status = wan_get_radiosw(); -	if (status < 0) -		return status; - -	return snprintf(buf, PAGE_SIZE, "%d\n", -			(status == RFKILL_STATE_UNBLOCKED) ? 1 : 0); +	return tpacpi_rfk_sysfs_enable_show(TPACPI_RFK_WWAN_SW_ID, +			attr, buf);  }  static ssize_t wan_enable_store(struct device *dev,  			    struct device_attribute *attr,  			    const char *buf, size_t count)  { -	unsigned long t; -	int res; - -	printk_deprecated_rfkill_attribute("wwan_enable"); - -	if (parse_strtoul(buf, 1, &t)) -		return -EINVAL; - -	tpacpi_disclose_usertask("wwan_enable", "set to %ld\n", t); - -	res = wan_set_radiosw(t, 1); - -	return (res) ? res : count; +	return tpacpi_rfk_sysfs_enable_store(TPACPI_RFK_WWAN_SW_ID, +			attr, buf, count);  }  static struct device_attribute dev_attr_wan_enable = @@ -3507,23 +3709,10 @@ static const struct attribute_group wan_attr_group = {  	.attrs = wan_attributes,  }; -static int tpacpi_wan_rfk_get(void *data, enum rfkill_state *state) -{ -	int wans = wan_get_radiosw(); - -	if (wans < 0) -		return wans; - -	*state = wans; -	return 0; -} - -static int tpacpi_wan_rfk_set(void *data, enum rfkill_state state) -{ -	dbg_printk(TPACPI_DBG_RFKILL, -		   "request to change radio state to %d\n", state); -	return wan_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0); -} +static const struct tpacpi_rfk_ops wan_tprfk_ops = { +	.get_status = wan_get_status, +	.set_status = wan_set_status, +};  static void wan_shutdown(void)  { @@ -3539,13 +3728,12 @@ static void wan_shutdown(void)  static void wan_exit(void)  { -	wan_shutdown(); - -	if (tpacpi_wan_rfkill) -		rfkill_unregister(tpacpi_wan_rfkill); -  	sysfs_remove_group(&tpacpi_pdev->dev.kobj,  		&wan_attr_group); + +	tpacpi_destroy_rfkill(TPACPI_RFK_WWAN_SW_ID); + +	wan_shutdown();  }  static int __init wan_init(struct ibm_init_struct *iibm) @@ -3584,20 +3772,19 @@ static int __init wan_init(struct ibm_init_struct *iibm)  	if (!tp_features.wan)  		return 1; -	res = sysfs_create_group(&tpacpi_pdev->dev.kobj, -				&wan_attr_group); -	if (res) -		return res; -  	res = tpacpi_new_rfkill(TPACPI_RFK_WWAN_SW_ID, -				&tpacpi_wan_rfkill, +				&wan_tprfk_ops,  				RFKILL_TYPE_WWAN,  				TPACPI_RFK_WWAN_SW_NAME, -				true, -				tpacpi_wan_rfk_set, -				tpacpi_wan_rfk_get); +				true); +	if (res) +		return res; + +	res = sysfs_create_group(&tpacpi_pdev->dev.kobj, +				&wan_attr_group); +  	if (res) { -		wan_exit(); +		tpacpi_destroy_rfkill(TPACPI_RFK_WWAN_SW_ID);  		return res;  	} @@ -3607,48 +3794,12 @@ static int __init wan_init(struct ibm_init_struct *iibm)  /* procfs -------------------------------------------------------------- */  static int wan_read(char *p)  { -	int len = 0; -	int status = wan_get_radiosw(); - -	tpacpi_disclose_usertask("procfs wan", "read"); - -	if (!tp_features.wan) -		len += sprintf(p + len, "status:\t\tnot supported\n"); -	else { -		len += sprintf(p + len, "status:\t\t%s\n", -				(status == RFKILL_STATE_UNBLOCKED) ? -					"enabled" : "disabled"); -		len += sprintf(p + len, "commands:\tenable, disable\n"); -	} - -	return len; +	return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, p);  }  static int wan_write(char *buf)  { -	char *cmd; -	int state = -1; - -	if (!tp_features.wan) -		return -ENODEV; - -	while ((cmd = next_cmd(&buf))) { -		if (strlencmp(cmd, "enable") == 0) { -			state = 1; -		} else if (strlencmp(cmd, "disable") == 0) { -			state = 0; -		} else -			return -EINVAL; -	} - -	if (state != -1) { -		tpacpi_disclose_usertask("procfs wan", -			"attempt to %s\n", -			state ? "enable" : "disable"); -		wan_set_radiosw(state, 1); -	} - -	return 0; +	return tpacpi_rfk_procfs_write(TPACPI_RFK_WWAN_SW_ID, buf);  }  static struct ibm_struct wan_driver_data = { @@ -3672,108 +3823,59 @@ enum {  #define TPACPI_RFK_UWB_SW_NAME	"tpacpi_uwb_sw" -static struct rfkill *tpacpi_uwb_rfkill; - -static int uwb_get_radiosw(void) +static int uwb_get_status(void)  {  	int status; -	if (!tp_features.uwb) -		return -ENODEV; - -	/* WLSW overrides UWB in firmware/hardware, reflect that */ -	if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status) -		return RFKILL_STATE_HARD_BLOCKED; -  #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES  	if (dbg_uwbemul)  		return (tpacpi_uwb_emulstate) ? -			RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; +		       TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;  #endif  	if (!acpi_evalf(hkey_handle, &status, "GUWB", "d"))  		return -EIO;  	return ((status & TP_ACPI_UWB_RADIOSSW) != 0) ? -		RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; +			TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;  } -static void uwb_update_rfk(void) +static int uwb_set_status(enum tpacpi_rfkill_state state)  {  	int status; -	if (!tpacpi_uwb_rfkill) -		return; - -	status = uwb_get_radiosw(); -	if (status < 0) -		return; -	rfkill_force_state(tpacpi_uwb_rfkill, status); -  	vdbg_printk(TPACPI_DBG_RFKILL, -		"forced rfkill state to %d\n", -		status); -} - -static int uwb_set_radiosw(int radio_on, int update_rfk) -{ -	int status; - -	if (!tp_features.uwb) -		return -ENODEV; - -	/* WLSW overrides UWB in firmware/hardware, but there is no -	 * reason to risk weird behaviour. */ -	if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status -	    && radio_on) -		return -EPERM; - -	vdbg_printk(TPACPI_DBG_RFKILL, -			"will %s UWB\n", radio_on ? "enable" : "disable"); +		"will attempt to %s UWB\n", +		(state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable");  #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES  	if (dbg_uwbemul) { -		tpacpi_uwb_emulstate = !!radio_on; -		if (update_rfk) -			uwb_update_rfk(); +		tpacpi_uwb_emulstate = (state == TPACPI_RFK_RADIO_ON);  		return 0;  	}  #endif -	status = (radio_on) ? TP_ACPI_UWB_RADIOSSW : 0; +	if (state == TPACPI_RFK_RADIO_ON) +		status = TP_ACPI_UWB_RADIOSSW; +	else +		status = 0; +  	if (!acpi_evalf(hkey_handle, NULL, "SUWB", "vd", status))  		return -EIO; -	if (update_rfk) -		uwb_update_rfk(); -  	return 0;  }  /* --------------------------------------------------------------------- */ -static int tpacpi_uwb_rfk_get(void *data, enum rfkill_state *state) -{ -	int uwbs = uwb_get_radiosw(); - -	if (uwbs < 0) -		return uwbs; - -	*state = uwbs; -	return 0; -} - -static int tpacpi_uwb_rfk_set(void *data, enum rfkill_state state) -{ -	dbg_printk(TPACPI_DBG_RFKILL, -		   "request to change radio state to %d\n", state); -	return uwb_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0); -} +static const struct tpacpi_rfk_ops uwb_tprfk_ops = { +	.get_status = uwb_get_status, +	.set_status = uwb_set_status, +};  static void uwb_exit(void)  { -	if (tpacpi_uwb_rfkill) -		rfkill_unregister(tpacpi_uwb_rfkill); +	tpacpi_destroy_rfkill(TPACPI_RFK_UWB_SW_ID);  }  static int __init uwb_init(struct ibm_init_struct *iibm) @@ -3813,13 +3915,10 @@ static int __init uwb_init(struct ibm_init_struct *iibm)  		return 1;  	res = tpacpi_new_rfkill(TPACPI_RFK_UWB_SW_ID, -				&tpacpi_uwb_rfkill, +				&uwb_tprfk_ops,  				RFKILL_TYPE_UWB,  				TPACPI_RFK_UWB_SW_NAME, -				false, -				tpacpi_uwb_rfk_set, -				tpacpi_uwb_rfk_get); - +				false);  	return res;  } @@ -4745,7 +4844,7 @@ TPACPI_HANDLE(led, ec, "SLED",	/* 570 */  	   "LED",		/* all others */  	   );			/* R30, R31 */ -#define TPACPI_LED_NUMLEDS 8 +#define TPACPI_LED_NUMLEDS 16  static struct tpacpi_led_classdev *tpacpi_leds;  static enum led_status_t tpacpi_led_state_cache[TPACPI_LED_NUMLEDS];  static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = { @@ -4758,15 +4857,20 @@ static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = {  	"tpacpi::dock_batt",  	"tpacpi::unknown_led",  	"tpacpi::standby", +	"tpacpi::dock_status1", +	"tpacpi::dock_status2", +	"tpacpi::unknown_led2", +	"tpacpi::unknown_led3", +	"tpacpi::thinkvantage",  }; -#define TPACPI_SAFE_LEDS	0x0081U +#define TPACPI_SAFE_LEDS	0x1081U  static inline bool tpacpi_is_led_restricted(const unsigned int led)  {  #ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS  	return false;  #else -	return (TPACPI_SAFE_LEDS & (1 << led)) == 0; +	return (1U & (TPACPI_SAFE_LEDS >> led)) == 0;  #endif  } @@ -4928,6 +5032,10 @@ static int __init tpacpi_init_led(unsigned int led)  	tpacpi_leds[led].led = led; +	/* LEDs with no name don't get registered */ +	if (!tpacpi_led_names[led]) +		return 0; +  	tpacpi_leds[led].led_classdev.brightness_set = &led_sysfs_set;  	tpacpi_leds[led].led_classdev.blink_set = &led_sysfs_blink_set;  	if (led_supported == TPACPI_LED_570) @@ -4946,10 +5054,59 @@ static int __init tpacpi_init_led(unsigned int led)  	return rc;  } +static const struct tpacpi_quirk led_useful_qtable[] __initconst = { +	TPACPI_Q_IBM('1', 'E', 0x009f), /* A30 */ +	TPACPI_Q_IBM('1', 'N', 0x009f), /* A31 */ +	TPACPI_Q_IBM('1', 'G', 0x009f), /* A31 */ + +	TPACPI_Q_IBM('1', 'I', 0x0097), /* T30 */ +	TPACPI_Q_IBM('1', 'R', 0x0097), /* T40, T41, T42, R50, R51 */ +	TPACPI_Q_IBM('7', '0', 0x0097), /* T43, R52 */ +	TPACPI_Q_IBM('1', 'Y', 0x0097), /* T43 */ +	TPACPI_Q_IBM('1', 'W', 0x0097), /* R50e */ +	TPACPI_Q_IBM('1', 'V', 0x0097), /* R51 */ +	TPACPI_Q_IBM('7', '8', 0x0097), /* R51e */ +	TPACPI_Q_IBM('7', '6', 0x0097), /* R52 */ + +	TPACPI_Q_IBM('1', 'K', 0x00bf), /* X30 */ +	TPACPI_Q_IBM('1', 'Q', 0x00bf), /* X31, X32 */ +	TPACPI_Q_IBM('1', 'U', 0x00bf), /* X40 */ +	TPACPI_Q_IBM('7', '4', 0x00bf), /* X41 */ +	TPACPI_Q_IBM('7', '5', 0x00bf), /* X41t */ + +	TPACPI_Q_IBM('7', '9', 0x1f97), /* T60 (1) */ +	TPACPI_Q_IBM('7', '7', 0x1f97), /* Z60* (1) */ +	TPACPI_Q_IBM('7', 'F', 0x1f97), /* Z61* (1) */ +	TPACPI_Q_IBM('7', 'B', 0x1fb7), /* X60 (1) */ + +	/* (1) - may have excess leds enabled on MSB */ + +	/* Defaults (order matters, keep last, don't reorder!) */ +	{ /* Lenovo */ +	  .vendor = PCI_VENDOR_ID_LENOVO, +	  .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY, +	  .quirks = 0x1fffU, +	}, +	{ /* IBM ThinkPads with no EC version string */ +	  .vendor = PCI_VENDOR_ID_IBM, +	  .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_UNKNOWN, +	  .quirks = 0x00ffU, +	}, +	{ /* IBM ThinkPads with EC version string */ +	  .vendor = PCI_VENDOR_ID_IBM, +	  .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY, +	  .quirks = 0x00bfU, +	}, +}; + +#undef TPACPI_LEDQ_IBM +#undef TPACPI_LEDQ_LNV +  static int __init led_init(struct ibm_init_struct *iibm)  {  	unsigned int i;  	int rc; +	unsigned long useful_leds;  	vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n"); @@ -4971,6 +5128,9 @@ static int __init led_init(struct ibm_init_struct *iibm)  	vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",  		str_supported(led_supported), led_supported); +	if (led_supported == TPACPI_LED_NONE) +		return 1; +  	tpacpi_leds = kzalloc(sizeof(*tpacpi_leds) * TPACPI_LED_NUMLEDS,  			      GFP_KERNEL);  	if (!tpacpi_leds) { @@ -4978,8 +5138,12 @@ static int __init led_init(struct ibm_init_struct *iibm)  		return -ENOMEM;  	} +	useful_leds = tpacpi_check_quirks(led_useful_qtable, +					  ARRAY_SIZE(led_useful_qtable)); +  	for (i = 0; i < TPACPI_LED_NUMLEDS; i++) { -		if (!tpacpi_is_led_restricted(i)) { +		if (!tpacpi_is_led_restricted(i) && +		    test_bit(i, &useful_leds)) {  			rc = tpacpi_init_led(i);  			if (rc < 0) {  				led_exit(); @@ -4989,12 +5153,11 @@ static int __init led_init(struct ibm_init_struct *iibm)  	}  #ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS -	if (led_supported != TPACPI_LED_NONE) -		printk(TPACPI_NOTICE -			"warning: userspace override of important " -			"firmware LEDs is enabled\n"); +	printk(TPACPI_NOTICE +		"warning: userspace override of important " +		"firmware LEDs is enabled\n");  #endif -	return (led_supported != TPACPI_LED_NONE)? 0 : 1; +	return 0;  }  #define str_led_status(s) \ @@ -5024,7 +5187,7 @@ static int led_read(char *p)  	}  	len += sprintf(p + len, "commands:\t" -		       "<led> on, <led> off, <led> blink (<led> is 0-7)\n"); +		       "<led> on, <led> off, <led> blink (<led> is 0-15)\n");  	return len;  } @@ -5039,7 +5202,7 @@ static int led_write(char *buf)  		return -ENODEV;  	while ((cmd = next_cmd(&buf))) { -		if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7) +		if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 15)  			return -EINVAL;  		if (strstr(cmd, "off")) { @@ -5073,8 +5236,17 @@ static struct ibm_struct led_driver_data = {  TPACPI_HANDLE(beep, ec, "BEEP");	/* all except R30, R31 */ +#define TPACPI_BEEP_Q1 0x0001 + +static const struct tpacpi_quirk beep_quirk_table[] __initconst = { +	TPACPI_Q_IBM('I', 'M', TPACPI_BEEP_Q1), /* 570 */ +	TPACPI_Q_IBM('I', 'U', TPACPI_BEEP_Q1), /* 570E - unverified */ +}; +  static int __init beep_init(struct ibm_init_struct *iibm)  { +	unsigned long quirks; +  	vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n");  	TPACPI_ACPIHANDLE_INIT(beep); @@ -5082,6 +5254,11 @@ static int __init beep_init(struct ibm_init_struct *iibm)  	vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n",  		str_supported(beep_handle != NULL)); +	quirks = tpacpi_check_quirks(beep_quirk_table, +				     ARRAY_SIZE(beep_quirk_table)); + +	tp_features.beep_needs_two_args = !!(quirks & TPACPI_BEEP_Q1); +  	return (beep_handle)? 0 : 1;  } @@ -5113,8 +5290,15 @@ static int beep_write(char *buf)  			/* beep_cmd set */  		} else  			return -EINVAL; -		if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0)) -			return -EIO; +		if (tp_features.beep_needs_two_args) { +			if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", +					beep_cmd, 0)) +				return -EIO; +		} else { +			if (!acpi_evalf(beep_handle, NULL, NULL, "vd", +					beep_cmd)) +				return -EIO; +		}  	}  	return 0; @@ -5541,6 +5725,10 @@ static struct ibm_struct ecdump_driver_data = {   *   Bit 3-0: backlight brightness level   *   * brightness_get_raw returns status data in the HBRV layout + * + * WARNING: The X61 has been verified to use HBRV for something else, so + * this should be used _only_ on IBM ThinkPads, and maybe with some careful + * testing on the very early *60 Lenovo models...   */  enum { @@ -5841,6 +6029,12 @@ static int __init brightness_init(struct ibm_init_struct *iibm)  			   brightness_mode);  	} +	/* Safety */ +	if (thinkpad_id.vendor != PCI_VENDOR_ID_IBM && +	    (brightness_mode == TPACPI_BRGHT_MODE_ECNVRAM || +	     brightness_mode == TPACPI_BRGHT_MODE_EC)) +		return -EINVAL; +  	if (tpacpi_brightness_get_raw(&b) < 0)  		return 1; @@ -6133,6 +6327,21 @@ static struct ibm_struct volume_driver_data = {   *	For firmware bugs, refer to:   *	http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues   * + *	---- + * + *	ThinkPad EC register 0x31 bit 0 (only on select models) + * + *	When bit 0 of EC register 0x31 is zero, the tachometer registers + *	show the speed of the main fan.  When bit 0 of EC register 0x31 + *	is one, the tachometer registers show the speed of the auxiliary + *	fan. + * + *	Fan control seems to affect both fans, regardless of the state + *	of this bit. + * + *	So far, only the firmware for the X60/X61 non-tablet versions + *	seem to support this (firmware TP-7M). + *   * TPACPI_FAN_WR_ACPI_FANS:   *	ThinkPad X31, X40, X41.  Not available in the X60.   * @@ -6159,6 +6368,8 @@ enum {					/* Fan control constants */  	fan_status_offset = 0x2f,	/* EC register 0x2f */  	fan_rpm_offset = 0x84,		/* EC register 0x84: LSB, 0x85 MSB (RPM)  					 * 0x84 must be read before 0x85 */ +	fan_select_offset = 0x31,	/* EC register 0x31 (Firmware 7M) +					   bit 0 selects which fan is active */  	TP_EC_FAN_FULLSPEED = 0x40,	/* EC fan mode: full speed */  	TP_EC_FAN_AUTO	    = 0x80,	/* EC fan mode: auto fan control */ @@ -6221,30 +6432,18 @@ TPACPI_HANDLE(sfan, ec, "SFAN",	/* 570 */   * We assume 0x07 really means auto mode while this quirk is active,   * as this is far more likely than the ThinkPad being in level 7,   * which is only used by the firmware during thermal emergencies. + * + * Enable for TP-1Y (T43), TP-78 (R51e), TP-76 (R52), + * TP-70 (T43, R52), which are known to be buggy.   */ -static void fan_quirk1_detect(void) +static void fan_quirk1_setup(void)  { -	/* In some ThinkPads, neither the EC nor the ACPI -	 * DSDT initialize the HFSP register, and it ends up -	 * being initially set to 0x07 when it *could* be -	 * either 0x07 or 0x80. -	 * -	 * Enable for TP-1Y (T43), TP-78 (R51e), -	 * TP-76 (R52), TP-70 (T43, R52), which are known -	 * to be buggy. */  	if (fan_control_initial_status == 0x07) { -		switch (thinkpad_id.ec_model) { -		case 0x5931: /* TP-1Y */ -		case 0x3837: /* TP-78 */ -		case 0x3637: /* TP-76 */ -		case 0x3037: /* TP-70 */ -			printk(TPACPI_NOTICE -			       "fan_init: initial fan status is unknown, " -			       "assuming it is in auto mode\n"); -			tp_features.fan_ctrl_status_undef = 1; -			;; -		} +		printk(TPACPI_NOTICE +		       "fan_init: initial fan status is unknown, " +		       "assuming it is in auto mode\n"); +		tp_features.fan_ctrl_status_undef = 1;  	}  } @@ -6264,6 +6463,38 @@ static void fan_quirk1_handle(u8 *fan_status)  	}  } +/* Select main fan on X60/X61, NOOP on others */ +static bool fan_select_fan1(void) +{ +	if (tp_features.second_fan) { +		u8 val; + +		if (ec_read(fan_select_offset, &val) < 0) +			return false; +		val &= 0xFEU; +		if (ec_write(fan_select_offset, val) < 0) +			return false; +	} +	return true; +} + +/* Select secondary fan on X60/X61 */ +static bool fan_select_fan2(void) +{ +	u8 val; + +	if (!tp_features.second_fan) +		return false; + +	if (ec_read(fan_select_offset, &val) < 0) +		return false; +	val |= 0x01U; +	if (ec_write(fan_select_offset, val) < 0) +		return false; + +	return true; +} +  /*   * Call with fan_mutex held   */ @@ -6341,6 +6572,8 @@ static int fan_get_speed(unsigned int *speed)  	switch (fan_status_access_mode) {  	case TPACPI_FAN_RD_TPEC:  		/* all except 570, 600e/x, 770e, 770x */ +		if (unlikely(!fan_select_fan1())) +			return -EIO;  		if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||  			     !acpi_ec_read(fan_rpm_offset + 1, &hi)))  			return -EIO; @@ -6357,6 +6590,34 @@ static int fan_get_speed(unsigned int *speed)  	return 0;  } +static int fan2_get_speed(unsigned int *speed) +{ +	u8 hi, lo; +	bool rc; + +	switch (fan_status_access_mode) { +	case TPACPI_FAN_RD_TPEC: +		/* all except 570, 600e/x, 770e, 770x */ +		if (unlikely(!fan_select_fan2())) +			return -EIO; +		rc = !acpi_ec_read(fan_rpm_offset, &lo) || +			     !acpi_ec_read(fan_rpm_offset + 1, &hi); +		fan_select_fan1(); /* play it safe */ +		if (rc) +			return -EIO; + +		if (likely(speed)) +			*speed = (hi << 8) | lo; + +		break; + +	default: +		return -ENXIO; +	} + +	return 0; +} +  static int fan_set_level(int level)  {  	if (!fan_control_allowed) @@ -6762,6 +7023,25 @@ static struct device_attribute dev_attr_fan_fan1_input =  	__ATTR(fan1_input, S_IRUGO,  		fan_fan1_input_show, NULL); +/* sysfs fan fan2_input ------------------------------------------------ */ +static ssize_t fan_fan2_input_show(struct device *dev, +			   struct device_attribute *attr, +			   char *buf) +{ +	int res; +	unsigned int speed; + +	res = fan2_get_speed(&speed); +	if (res < 0) +		return res; + +	return snprintf(buf, PAGE_SIZE, "%u\n", speed); +} + +static struct device_attribute dev_attr_fan_fan2_input = +	__ATTR(fan2_input, S_IRUGO, +		fan_fan2_input_show, NULL); +  /* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */  static ssize_t fan_fan_watchdog_show(struct device_driver *drv,  				     char *buf) @@ -6795,6 +7075,7 @@ static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO,  static struct attribute *fan_attributes[] = {  	&dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr,  	&dev_attr_fan_fan1_input.attr, +	NULL, /* for fan2_input */  	NULL  }; @@ -6802,9 +7083,36 @@ static const struct attribute_group fan_attr_group = {  	.attrs = fan_attributes,  }; +#define	TPACPI_FAN_Q1	0x0001		/* Unitialized HFSP */ +#define TPACPI_FAN_2FAN	0x0002		/* EC 0x31 bit 0 selects fan2 */ + +#define TPACPI_FAN_QI(__id1, __id2, __quirks)	\ +	{ .vendor = PCI_VENDOR_ID_IBM,		\ +	  .bios = TPACPI_MATCH_ANY,		\ +	  .ec = TPID(__id1, __id2),		\ +	  .quirks = __quirks } + +#define TPACPI_FAN_QL(__id1, __id2, __quirks)	\ +	{ .vendor = PCI_VENDOR_ID_LENOVO,	\ +	  .bios = TPACPI_MATCH_ANY,		\ +	  .ec = TPID(__id1, __id2),		\ +	  .quirks = __quirks } + +static const struct tpacpi_quirk fan_quirk_table[] __initconst = { +	TPACPI_FAN_QI('1', 'Y', TPACPI_FAN_Q1), +	TPACPI_FAN_QI('7', '8', TPACPI_FAN_Q1), +	TPACPI_FAN_QI('7', '6', TPACPI_FAN_Q1), +	TPACPI_FAN_QI('7', '0', TPACPI_FAN_Q1), +	TPACPI_FAN_QL('7', 'M', TPACPI_FAN_2FAN), +}; + +#undef TPACPI_FAN_QL +#undef TPACPI_FAN_QI +  static int __init fan_init(struct ibm_init_struct *iibm)  {  	int rc; +	unsigned long quirks;  	vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,  			"initializing fan subdriver\n"); @@ -6815,12 +7123,16 @@ static int __init fan_init(struct ibm_init_struct *iibm)  	fan_control_commands = 0;  	fan_watchdog_maxinterval = 0;  	tp_features.fan_ctrl_status_undef = 0; +	tp_features.second_fan = 0;  	fan_control_desired_level = 7;  	TPACPI_ACPIHANDLE_INIT(fans);  	TPACPI_ACPIHANDLE_INIT(gfan);  	TPACPI_ACPIHANDLE_INIT(sfan); +	quirks = tpacpi_check_quirks(fan_quirk_table, +				     ARRAY_SIZE(fan_quirk_table)); +  	if (gfan_handle) {  		/* 570, 600e/x, 770e, 770x */  		fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN; @@ -6830,7 +7142,13 @@ static int __init fan_init(struct ibm_init_struct *iibm)  		if (likely(acpi_ec_read(fan_status_offset,  					&fan_control_initial_status))) {  			fan_status_access_mode = TPACPI_FAN_RD_TPEC; -			fan_quirk1_detect(); +			if (quirks & TPACPI_FAN_Q1) +				fan_quirk1_setup(); +			if (quirks & TPACPI_FAN_2FAN) { +				tp_features.second_fan = 1; +				dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN, +					"secondary fan support enabled\n"); +			}  		} else {  			printk(TPACPI_ERR  			       "ThinkPad ACPI EC access misbehaving, " @@ -6886,6 +7204,11 @@ static int __init fan_init(struct ibm_init_struct *iibm)  	if (fan_status_access_mode != TPACPI_FAN_NONE ||  	    fan_control_access_mode != TPACPI_FAN_WR_NONE) { +		if (tp_features.second_fan) { +			/* attach second fan tachometer */ +			fan_attributes[ARRAY_SIZE(fan_attributes)-2] = +					&dev_attr_fan_fan2_input.attr; +		}  		rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,  					 &fan_attr_group);  		if (rc < 0) @@ -7357,6 +7680,24 @@ err_out:  /* Probing */ +static bool __pure __init tpacpi_is_fw_digit(const char c) +{ +	return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z'); +} + +/* Most models: xxyTkkWW (#.##c); Ancient 570/600 and -SL lacks (#.##c) */ +static bool __pure __init tpacpi_is_valid_fw_id(const char* const s, +						const char t) +{ +	return s && strlen(s) >= 8 && +		tpacpi_is_fw_digit(s[0]) && +		tpacpi_is_fw_digit(s[1]) && +		s[2] == t && s[3] == 'T' && +		tpacpi_is_fw_digit(s[4]) && +		tpacpi_is_fw_digit(s[5]) && +		s[6] == 'W' && s[7] == 'W'; +} +  /* returns 0 - probe ok, or < 0 - probe error.   * Probe ok doesn't mean thinkpad found.   * On error, kfree() cleanup on tp->* is not performed, caller must do it */ @@ -7383,10 +7724,15 @@ static int __must_check __init get_thinkpad_model_data(  	tp->bios_version_str = kstrdup(s, GFP_KERNEL);  	if (s && !tp->bios_version_str)  		return -ENOMEM; -	if (!tp->bios_version_str) + +	/* Really ancient ThinkPad 240X will fail this, which is fine */ +	if (!tpacpi_is_valid_fw_id(tp->bios_version_str, 'E'))  		return 0; +  	tp->bios_model = tp->bios_version_str[0]  			 | (tp->bios_version_str[1] << 8); +	tp->bios_release = (tp->bios_version_str[4] << 8) +			 | tp->bios_version_str[5];  	/*  	 * ThinkPad T23 or newer, A31 or newer, R50e or newer, @@ -7405,8 +7751,21 @@ static int __must_check __init get_thinkpad_model_data(  			tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL);  			if (!tp->ec_version_str)  				return -ENOMEM; -			tp->ec_model = ec_fw_string[0] -					| (ec_fw_string[1] << 8); + +			if (tpacpi_is_valid_fw_id(ec_fw_string, 'H')) { +				tp->ec_model = ec_fw_string[0] +						| (ec_fw_string[1] << 8); +				tp->ec_release = (ec_fw_string[4] << 8) +						| ec_fw_string[5]; +			} else { +				printk(TPACPI_NOTICE +					"ThinkPad firmware release %s " +					"doesn't match the known patterns\n", +					ec_fw_string); +				printk(TPACPI_NOTICE +					"please report this to %s\n", +					TPACPI_MAIL); +			}  			break;  		}  	} diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 9f187265db8..81d31ea507d 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -45,7 +45,6 @@  #include <linux/backlight.h>  #include <linux/platform_device.h>  #include <linux/rfkill.h> -#include <linux/input-polldev.h>  #include <asm/uaccess.h> @@ -250,21 +249,15 @@ static acpi_status hci_read2(u32 reg, u32 *out1, u32 *out2, u32 *result)  struct toshiba_acpi_dev {  	struct platform_device *p_dev; -	struct rfkill *rfk_dev; -	struct input_polled_dev *poll_dev; +	struct rfkill *bt_rfk;  	const char *bt_name; -	const char *rfk_name; - -	bool last_rfk_state;  	struct mutex mutex;  };  static struct toshiba_acpi_dev toshiba_acpi = {  	.bt_name = "Toshiba Bluetooth", -	.rfk_name = "Toshiba RFKill Switch", -	.last_rfk_state = false,  };  /* Bluetooth rfkill handlers */ @@ -283,21 +276,6 @@ static u32 hci_get_bt_present(bool *present)  	return hci_result;  } -static u32 hci_get_bt_on(bool *on) -{ -	u32 hci_result; -	u32 value, value2; - -	value = 0; -	value2 = 0x0001; -	hci_read2(HCI_WIRELESS, &value, &value2, &hci_result); -	if (hci_result == HCI_SUCCESS) -		*on = (value & HCI_WIRELESS_BT_POWER) && -		      (value & HCI_WIRELESS_BT_ATTACH); - -	return hci_result; -} -  static u32 hci_get_radio_state(bool *radio_state)  {  	u32 hci_result; @@ -311,70 +289,67 @@ static u32 hci_get_radio_state(bool *radio_state)  	return hci_result;  } -static int bt_rfkill_toggle_radio(void *data, enum rfkill_state state) +static int bt_rfkill_set_block(void *data, bool blocked)  { +	struct toshiba_acpi_dev *dev = data;  	u32 result1, result2;  	u32 value; +	int err;  	bool radio_state; -	struct toshiba_acpi_dev *dev = data; -	value = (state == RFKILL_STATE_UNBLOCKED); +	value = (blocked == false); -	if (hci_get_radio_state(&radio_state) != HCI_SUCCESS) -		return -EFAULT; +	mutex_lock(&dev->mutex); +	if (hci_get_radio_state(&radio_state) != HCI_SUCCESS) { +		err = -EBUSY; +		goto out; +	} -	switch (state) { -	case RFKILL_STATE_UNBLOCKED: -		if (!radio_state) -			return -EPERM; -		break; -	case RFKILL_STATE_SOFT_BLOCKED: -		break; -	default: -		return -EINVAL; +	if (!radio_state) { +		err = 0; +		goto out;  	} -	mutex_lock(&dev->mutex);  	hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER, &result1);  	hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH, &result2); -	mutex_unlock(&dev->mutex);  	if (result1 != HCI_SUCCESS || result2 != HCI_SUCCESS) -		return -EFAULT; - -	return 0; +		err = -EBUSY; +	else +		err = 0; + out: +	mutex_unlock(&dev->mutex); +	return err;  } -static void bt_poll_rfkill(struct input_polled_dev *poll_dev) +static void bt_rfkill_poll(struct rfkill *rfkill, void *data)  { -	bool state_changed;  	bool new_rfk_state;  	bool value;  	u32 hci_result; -	struct toshiba_acpi_dev *dev = poll_dev->private; +	struct toshiba_acpi_dev *dev = data; + +	mutex_lock(&dev->mutex);  	hci_result = hci_get_radio_state(&value); -	if (hci_result != HCI_SUCCESS) -		return; /* Can't do anything useful */ +	if (hci_result != HCI_SUCCESS) { +		/* Can't do anything useful */ +		mutex_unlock(&dev->mutex); +	}  	new_rfk_state = value; -	mutex_lock(&dev->mutex); -	state_changed = new_rfk_state != dev->last_rfk_state; -	dev->last_rfk_state = new_rfk_state;  	mutex_unlock(&dev->mutex); -	if (unlikely(state_changed)) { -		rfkill_force_state(dev->rfk_dev, -				   new_rfk_state ? -				   RFKILL_STATE_SOFT_BLOCKED : -				   RFKILL_STATE_HARD_BLOCKED); -		input_report_switch(poll_dev->input, SW_RFKILL_ALL, -				    new_rfk_state); -		input_sync(poll_dev->input); -	} +	if (rfkill_set_hw_state(rfkill, !new_rfk_state)) +		bt_rfkill_set_block(data, true);  } +static const struct rfkill_ops toshiba_rfk_ops = { +	.set_block = bt_rfkill_set_block, +	.poll = bt_rfkill_poll, +}; +  static struct proc_dir_entry *toshiba_proc_dir /*= 0*/ ;  static struct backlight_device *toshiba_backlight_device;  static int force_fan; @@ -702,14 +677,11 @@ static struct backlight_ops toshiba_backlight_data = {  static void toshiba_acpi_exit(void)  { -	if (toshiba_acpi.poll_dev) { -		input_unregister_polled_device(toshiba_acpi.poll_dev); -		input_free_polled_device(toshiba_acpi.poll_dev); +	if (toshiba_acpi.bt_rfk) { +		rfkill_unregister(toshiba_acpi.bt_rfk); +		rfkill_destroy(toshiba_acpi.bt_rfk);  	} -	if (toshiba_acpi.rfk_dev) -		rfkill_unregister(toshiba_acpi.rfk_dev); -  	if (toshiba_backlight_device)  		backlight_device_unregister(toshiba_backlight_device); @@ -728,8 +700,6 @@ static int __init toshiba_acpi_init(void)  	acpi_status status = AE_OK;  	u32 hci_result;  	bool bt_present; -	bool bt_on; -	bool radio_on;  	int ret = 0;  	if (acpi_disabled) @@ -793,61 +763,21 @@ static int __init toshiba_acpi_init(void)  	/* Register rfkill switch for Bluetooth */  	if (hci_get_bt_present(&bt_present) == HCI_SUCCESS && bt_present) { -		toshiba_acpi.rfk_dev = rfkill_allocate(&toshiba_acpi.p_dev->dev, -							RFKILL_TYPE_BLUETOOTH); -		if (!toshiba_acpi.rfk_dev) { +		toshiba_acpi.bt_rfk = rfkill_alloc(toshiba_acpi.bt_name, +						   &toshiba_acpi.p_dev->dev, +						   RFKILL_TYPE_BLUETOOTH, +						   &toshiba_rfk_ops, +						   &toshiba_acpi); +		if (!toshiba_acpi.bt_rfk) {  			printk(MY_ERR "unable to allocate rfkill device\n");  			toshiba_acpi_exit();  			return -ENOMEM;  		} -		toshiba_acpi.rfk_dev->name = toshiba_acpi.bt_name; -		toshiba_acpi.rfk_dev->toggle_radio = bt_rfkill_toggle_radio; -		toshiba_acpi.rfk_dev->user_claim_unsupported = 1; -		toshiba_acpi.rfk_dev->data = &toshiba_acpi; - -		if (hci_get_bt_on(&bt_on) == HCI_SUCCESS && bt_on) { -			toshiba_acpi.rfk_dev->state = RFKILL_STATE_UNBLOCKED; -		} else if (hci_get_radio_state(&radio_on) == HCI_SUCCESS && -			   radio_on) { -			toshiba_acpi.rfk_dev->state = RFKILL_STATE_SOFT_BLOCKED; -		} else { -			toshiba_acpi.rfk_dev->state = RFKILL_STATE_HARD_BLOCKED; -		} - -		ret = rfkill_register(toshiba_acpi.rfk_dev); +		ret = rfkill_register(toshiba_acpi.bt_rfk);  		if (ret) {  			printk(MY_ERR "unable to register rfkill device\n"); -			toshiba_acpi_exit(); -			return -ENOMEM; -		} - -		/* Register input device for kill switch */ -		toshiba_acpi.poll_dev = input_allocate_polled_device(); -		if (!toshiba_acpi.poll_dev) { -			printk(MY_ERR -			       "unable to allocate kill-switch input device\n"); -			toshiba_acpi_exit(); -			return -ENOMEM; -		} -		toshiba_acpi.poll_dev->private = &toshiba_acpi; -		toshiba_acpi.poll_dev->poll = bt_poll_rfkill; -		toshiba_acpi.poll_dev->poll_interval = 1000; /* msecs */ - -		toshiba_acpi.poll_dev->input->name = toshiba_acpi.rfk_name; -		toshiba_acpi.poll_dev->input->id.bustype = BUS_HOST; -		/* Toshiba USB ID */ -		toshiba_acpi.poll_dev->input->id.vendor = 0x0930; -		set_bit(EV_SW, toshiba_acpi.poll_dev->input->evbit); -		set_bit(SW_RFKILL_ALL, toshiba_acpi.poll_dev->input->swbit); -		input_report_switch(toshiba_acpi.poll_dev->input, -				    SW_RFKILL_ALL, TRUE); -		input_sync(toshiba_acpi.poll_dev->input); - -		ret = input_register_polled_device(toshiba_acpi.poll_dev); -		if (ret) { -			printk(MY_ERR -			       "unable to register kill-switch input device\n"); +			rfkill_destroy(toshiba_acpi.bt_rfk);  			toshiba_acpi_exit();  			return ret;  		}  |