diff options
Diffstat (limited to 'drivers/platform/x86/thinkpad_acpi.c')
| -rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 380 | 
1 files changed, 316 insertions, 64 deletions
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 03ca6c139f1..d82f196e3cf 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -23,7 +23,7 @@  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define TPACPI_VERSION "0.24" +#define TPACPI_VERSION "0.25"  #define TPACPI_SYSFS_VERSION 0x020700  /* @@ -61,7 +61,6 @@  #include <linux/freezer.h>  #include <linux/delay.h>  #include <linux/slab.h> -  #include <linux/nvram.h>  #include <linux/proc_fs.h>  #include <linux/seq_file.h> @@ -74,20 +73,16 @@  #include <linux/input.h>  #include <linux/leds.h>  #include <linux/rfkill.h> -#include <asm/uaccess.h> -  #include <linux/dmi.h>  #include <linux/jiffies.h>  #include <linux/workqueue.h> - +#include <linux/acpi.h> +#include <linux/pci_ids.h> +#include <linux/thinkpad_acpi.h>  #include <sound/core.h>  #include <sound/control.h>  #include <sound/initval.h> - -#include <acpi/acpi_drivers.h> - -#include <linux/pci_ids.h> - +#include <asm/uaccess.h>  /* ThinkPad CMOS commands */  #define TP_CMOS_VOLUME_DOWN	0 @@ -700,6 +695,14 @@ static void __init drv_acpi_handle_init(const char *name,  static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle,  			u32 level, void *context, void **return_value)  { +	struct acpi_device *dev; +	if (!strcmp(context, "video")) { +		if (acpi_bus_get_device(handle, &dev)) +			return AE_OK; +		if (strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev))) +			return AE_OK; +	} +  	*(acpi_handle *)return_value = handle;  	return AE_CTRL_TERMINATE; @@ -712,10 +715,10 @@ static void __init tpacpi_acpi_handle_locate(const char *name,  	acpi_status status;  	acpi_handle device_found; -	BUG_ON(!name || !hid || !handle); +	BUG_ON(!name || !handle);  	vdbg_printk(TPACPI_DBG_INIT,  			"trying to locate ACPI handle for %s, using HID %s\n", -			name, hid); +			name, hid ? hid : "NULL");  	memset(&device_found, 0, sizeof(device_found));  	status = acpi_get_devices(hid, tpacpi_acpi_handle_locate_callback, @@ -2318,53 +2321,55 @@ static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m)  	}  } -static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, -					   struct tp_nvram_state *newn, -					   const u32 event_mask) -{ -  #define TPACPI_COMPARE_KEY(__scancode, __member) \ -	do { \ -		if ((event_mask & (1 << __scancode)) && \ -		    oldn->__member != newn->__member) \ -			tpacpi_hotkey_send_key(__scancode); \ -	} while (0) +do { \ +	if ((event_mask & (1 << __scancode)) && \ +	    oldn->__member != newn->__member) \ +		tpacpi_hotkey_send_key(__scancode); \ +} while (0)  #define TPACPI_MAY_SEND_KEY(__scancode) \ -	do { \ -		if (event_mask & (1 << __scancode)) \ -			tpacpi_hotkey_send_key(__scancode); \ -	} while (0) +do { \ +	if (event_mask & (1 << __scancode)) \ +		tpacpi_hotkey_send_key(__scancode); \ +} while (0) -	void issue_volchange(const unsigned int oldvol, -			     const unsigned int newvol) -	{ -		unsigned int i = oldvol; +static void issue_volchange(const unsigned int oldvol, +			    const unsigned int newvol, +			    const u32 event_mask) +{ +	unsigned int i = oldvol; -		while (i > newvol) { -			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); -			i--; -		} -		while (i < newvol) { -			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); -			i++; -		} +	while (i > newvol) { +		TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); +		i--;  	} +	while (i < newvol) { +		TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); +		i++; +	} +} -	void issue_brightnesschange(const unsigned int oldbrt, -				    const unsigned int newbrt) -	{ -		unsigned int i = oldbrt; +static void issue_brightnesschange(const unsigned int oldbrt, +				   const unsigned int newbrt, +				   const u32 event_mask) +{ +	unsigned int i = oldbrt; -		while (i > newbrt) { -			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND); -			i--; -		} -		while (i < newbrt) { -			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME); -			i++; -		} +	while (i > newbrt) { +		TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND); +		i--; +	} +	while (i < newbrt) { +		TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME); +		i++;  	} +} + +static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, +					   struct tp_nvram_state *newn, +					   const u32 event_mask) +{  	TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle);  	TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle); @@ -2399,7 +2404,8 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,  		    oldn->volume_level != newn->volume_level) {  			/* recently muted, or repeated mute keypress, or  			 * multiple presses ending in mute */ -			issue_volchange(oldn->volume_level, newn->volume_level); +			issue_volchange(oldn->volume_level, newn->volume_level, +				event_mask);  			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);  		}  	} else { @@ -2409,7 +2415,8 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,  			TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);  		}  		if (oldn->volume_level != newn->volume_level) { -			issue_volchange(oldn->volume_level, newn->volume_level); +			issue_volchange(oldn->volume_level, newn->volume_level, +				event_mask);  		} else if (oldn->volume_toggle != newn->volume_toggle) {  			/* repeated vol up/down keypress at end of scale ? */  			if (newn->volume_level == 0) @@ -2422,7 +2429,7 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,  	/* handle brightness */  	if (oldn->brightness_level != newn->brightness_level) {  		issue_brightnesschange(oldn->brightness_level, -				       newn->brightness_level); +				       newn->brightness_level, event_mask);  	} else if (oldn->brightness_toggle != newn->brightness_toggle) {  		/* repeated key presses that didn't change state */  		if (newn->brightness_level == 0) @@ -3164,8 +3171,10 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)  		KEY_MICMUTE,	/* 0x1a: Mic mute (since ?400 or so) */  		/* (assignments unknown, please report if found) */ -		KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,  		KEY_UNKNOWN, + +		/* Extra keys in use since the X240 / T440 / T540 */ +		KEY_CONFIG, KEY_SEARCH, KEY_SCALE, KEY_COMPUTER,  		},  	}; @@ -3434,6 +3443,106 @@ err_exit:  	return (res < 0)? res : 1;  } +/* Thinkpad X1 Carbon support 5 modes including Home mode, Web browser + * mode, Web conference mode, Function mode and Lay-flat mode. + * We support Home mode and Function mode currently. + * + * Will consider support rest of modes in future. + * + */ +enum ADAPTIVE_KEY_MODE { +	HOME_MODE, +	WEB_BROWSER_MODE, +	WEB_CONFERENCE_MODE, +	FUNCTION_MODE, +	LAYFLAT_MODE +}; + +const int adaptive_keyboard_modes[] = { +	HOME_MODE, +/*	WEB_BROWSER_MODE = 2, +	WEB_CONFERENCE_MODE = 3, */ +	FUNCTION_MODE +}; + +#define DFR_CHANGE_ROW			0x101 +#define DFR_SHOW_QUICKVIEW_ROW		0x102 + +/* press Fn key a while second, it will switch to Function Mode. Then + * release Fn key, previous mode be restored. + */ +static bool adaptive_keyboard_mode_is_saved; +static int adaptive_keyboard_prev_mode; + +static int adaptive_keyboard_get_next_mode(int mode) +{ +	size_t i; +	size_t max_mode = ARRAY_SIZE(adaptive_keyboard_modes) - 1; + +	for (i = 0; i <= max_mode; i++) { +		if (adaptive_keyboard_modes[i] == mode) +			break; +	} + +	if (i >= max_mode) +		i = 0; +	else +		i++; + +	return adaptive_keyboard_modes[i]; +} + +static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) +{ +	u32 current_mode = 0; +	int new_mode = 0; + +	switch (scancode) { +	case DFR_CHANGE_ROW: +		if (adaptive_keyboard_mode_is_saved) { +			new_mode = adaptive_keyboard_prev_mode; +			adaptive_keyboard_mode_is_saved = false; +		} else { +			if (!acpi_evalf( +					hkey_handle, ¤t_mode, +					"GTRW", "dd", 0)) { +				pr_err("Cannot read adaptive keyboard mode\n"); +				return false; +			} else { +				new_mode = adaptive_keyboard_get_next_mode( +						current_mode); +			} +		} + +		if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", new_mode)) { +			pr_err("Cannot set adaptive keyboard mode\n"); +			return false; +		} + +		return true; + +	case DFR_SHOW_QUICKVIEW_ROW: +		if (!acpi_evalf(hkey_handle, +				&adaptive_keyboard_prev_mode, +				"GTRW", "dd", 0)) { +			pr_err("Cannot read adaptive keyboard mode\n"); +			return false; +		} else { +			adaptive_keyboard_mode_is_saved = true; + +			if (!acpi_evalf(hkey_handle, +					NULL, "STRW", "vd", FUNCTION_MODE)) { +				pr_err("Cannot set adaptive keyboard mode\n"); +				return false; +			} +		} +		return true; + +	default: +		return false; +	} +} +  static bool hotkey_notify_hotkey(const u32 hkey,  				 bool *send_acpi_ev,  				 bool *ignore_acpi_ev) @@ -3453,6 +3562,8 @@ static bool hotkey_notify_hotkey(const u32 hkey,  			*ignore_acpi_ev = true;  		}  		return true; +	} else { +		return adaptive_keyboard_hotkey_notify_hotkey(scancode);  	}  	return false;  } @@ -3725,13 +3836,28 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event)  static void hotkey_suspend(void)  { +	int hkeyv; +  	/* Do these on suspend, we get the events on early resume! */  	hotkey_wakeup_reason = TP_ACPI_WAKEUP_NONE;  	hotkey_autosleep_ack = 0; + +	/* save previous mode of adaptive keyboard of X1 Carbon */ +	if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { +		if ((hkeyv >> 8) == 2) { +			if (!acpi_evalf(hkey_handle, +						&adaptive_keyboard_prev_mode, +						"GTRW", "dd", 0)) { +				pr_err("Cannot read adaptive keyboard mode.\n"); +			} +		} +	}  }  static void hotkey_resume(void)  { +	int hkeyv; +  	tpacpi_disable_brightness_delay();  	if (hotkey_status_set(true) < 0 || @@ -3744,6 +3870,18 @@ static void hotkey_resume(void)  	hotkey_wakeup_reason_notify_change();  	hotkey_wakeup_hotunplug_complete_notify_change();  	hotkey_poll_setup_safe(false); + +	/* restore previous mode of adapive keyboard of X1 Carbon */ +	if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { +		if ((hkeyv >> 8) == 2) { +			if (!acpi_evalf(hkey_handle, +						NULL, +						"STRW", "vd", +						adaptive_keyboard_prev_mode)) { +				pr_err("Cannot set adaptive keyboard mode.\n"); +			} +		} +	}  }  /* procfs -------------------------------------------------------------- */ @@ -6090,19 +6228,28 @@ static int __init tpacpi_query_bcl_levels(acpi_handle handle)  {  	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };  	union acpi_object *obj; +	struct acpi_device *device, *child;  	int rc; -	if (ACPI_SUCCESS(acpi_evaluate_object(handle, "_BCL", NULL, &buffer))) { +	if (acpi_bus_get_device(handle, &device)) +		return 0; + +	rc = 0; +	list_for_each_entry(child, &device->children, node) { +		acpi_status status = acpi_evaluate_object(child->handle, "_BCL", +							  NULL, &buffer); +		if (ACPI_FAILURE(status)) +			continue; +  		obj = (union acpi_object *)buffer.pointer;  		if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {  			pr_err("Unknown _BCL data, please report this to %s\n", -			       TPACPI_MAIL); +				TPACPI_MAIL);  			rc = 0;  		} else {  			rc = obj->package.count;  		} -	} else { -		return 0; +		break;  	}  	kfree(buffer.pointer); @@ -6118,7 +6265,7 @@ static unsigned int __init tpacpi_check_std_acpi_brightness_support(void)  	acpi_handle video_device;  	int bcl_levels = 0; -	tpacpi_acpi_handle_locate("video", ACPI_VIDEO_HID, &video_device); +	tpacpi_acpi_handle_locate("video", NULL, &video_device);  	if (video_device)  		bcl_levels = tpacpi_query_bcl_levels(video_device); @@ -6420,7 +6567,12 @@ static struct ibm_struct brightness_driver_data = {  #define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control"  #define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME -static int alsa_index = ~((1 << (SNDRV_CARDS - 3)) - 1); /* last three slots */ +#if SNDRV_CARDS <= 32 +#define DEFAULT_ALSA_IDX		~((1 << (SNDRV_CARDS - 3)) - 1) +#else +#define DEFAULT_ALSA_IDX		~((1 << (32 - 3)) - 1) +#endif +static int alsa_index = DEFAULT_ALSA_IDX; /* last three slots */  static char *alsa_id = "ThinkPadEC";  static bool alsa_enable = SNDRV_DEFAULT_ENABLE1; @@ -6759,8 +6911,9 @@ static int __init volume_create_alsa_mixer(void)  	struct snd_kcontrol *ctl_mute;  	int rc; -	rc = snd_card_create(alsa_index, alsa_id, THIS_MODULE, -			    sizeof(struct tpacpi_alsa_data), &card); +	rc = snd_card_new(&tpacpi_pdev->dev, +			  alsa_index, alsa_id, THIS_MODULE, +			  sizeof(struct tpacpi_alsa_data), &card);  	if (rc < 0 || !card) {  		pr_err("Failed to create ALSA card structures: %d\n", rc);  		return 1; @@ -6811,7 +6964,6 @@ static int __init volume_create_alsa_mixer(void)  	}  	data->ctl_mute_id = &ctl_mute->id; -	snd_card_set_dev(card, &tpacpi_pdev->dev);  	rc = snd_card_register(card);  	if (rc < 0) {  		pr_err("Failed to register ALSA card: %d\n", rc); @@ -8350,6 +8502,103 @@ static struct ibm_struct fan_driver_data = {  	.resume = fan_resume,  }; +/************************************************************************* + * Mute LED subdriver + */ + + +struct tp_led_table { +	acpi_string name; +	int on_value; +	int off_value; +	int state; +}; + +static struct tp_led_table led_tables[] = { +	[TPACPI_LED_MUTE] = { +		.name = "SSMS", +		.on_value = 1, +		.off_value = 0, +	}, +	[TPACPI_LED_MICMUTE] = { +		.name = "MMTS", +		.on_value = 2, +		.off_value = 0, +	}, +}; + +static int mute_led_on_off(struct tp_led_table *t, bool state) +{ +	acpi_handle temp; +	int output; + +	if (!ACPI_SUCCESS(acpi_get_handle(hkey_handle, t->name, &temp))) { +		pr_warn("Thinkpad ACPI has no %s interface.\n", t->name); +		return -EIO; +	} + +	if (!acpi_evalf(hkey_handle, &output, t->name, "dd", +			state ? t->on_value : t->off_value)) +		return -EIO; + +	t->state = state; +	return state; +} + +int tpacpi_led_set(int whichled, bool on) +{ +	struct tp_led_table *t; + +	if (whichled < 0 || whichled >= TPACPI_LED_MAX) +		return -EINVAL; + +	t = &led_tables[whichled]; +	if (t->state < 0 || t->state == on) +		return t->state; +	return mute_led_on_off(t, on); +} +EXPORT_SYMBOL_GPL(tpacpi_led_set); + +static int mute_led_init(struct ibm_init_struct *iibm) +{ +	acpi_handle temp; +	int i; + +	for (i = 0; i < TPACPI_LED_MAX; i++) { +		struct tp_led_table *t = &led_tables[i]; +		if (ACPI_SUCCESS(acpi_get_handle(hkey_handle, t->name, &temp))) +			mute_led_on_off(t, false); +		else +			t->state = -ENODEV; +	} +	return 0; +} + +static void mute_led_exit(void) +{ +	int i; + +	for (i = 0; i < TPACPI_LED_MAX; i++) +		tpacpi_led_set(i, false); +} + +static void mute_led_resume(void) +{ +	int i; + +	for (i = 0; i < TPACPI_LED_MAX; i++) { +		struct tp_led_table *t = &led_tables[i]; +		if (t->state >= 0) +			mute_led_on_off(t, t->state); +	} +} + +static struct ibm_struct mute_led_driver_data = { +	.name = "mute_led", +	.exit = mute_led_exit, +	.resume = mute_led_resume, +}; +  /****************************************************************************   ****************************************************************************   * @@ -8768,6 +9017,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {  		.init = fan_init,  		.data = &fan_driver_data,  	}, +	{ +		.init = mute_led_init, +		.data = &mute_led_driver_data, +	},  };  static int __init set_ibm_param(const char *val, struct kernel_param *kp) @@ -9056,7 +9309,6 @@ static int __init thinkpad_acpi_module_init(void)  	mutex_init(&tpacpi_inputdev_send_mutex);  	tpacpi_inputdev = input_allocate_device();  	if (!tpacpi_inputdev) { -		pr_err("unable to allocate input device\n");  		thinkpad_acpi_module_exit();  		return -ENOMEM;  	} else {  | 
