diff options
Diffstat (limited to 'drivers/platform/x86/msi-wmi.c')
| -rw-r--r-- | drivers/platform/x86/msi-wmi.c | 252 | 
1 files changed, 160 insertions, 92 deletions
diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 42a5469a245..70222f265f6 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -20,6 +20,7 @@   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA   */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt  #include <linux/kernel.h>  #include <linux/input.h> @@ -27,35 +28,71 @@  #include <linux/acpi.h>  #include <linux/backlight.h>  #include <linux/slab.h> +#include <linux/module.h>  MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>");  MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver");  MODULE_LICENSE("GPL"); -MODULE_ALIAS("wmi:551A1F84-FBDD-4125-91DB-3EA8F44F1D45"); -MODULE_ALIAS("wmi:B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2"); -  #define DRV_NAME "msi-wmi" -#define DRV_PFX DRV_NAME ": "  #define MSIWMI_BIOS_GUID "551A1F84-FBDD-4125-91DB-3EA8F44F1D45" -#define MSIWMI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2" +#define MSIWMI_MSI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2" +#define MSIWMI_WIND_EVENT_GUID "5B3CC38A-40D9-7245-8AE6-1145B751BE3F" + +MODULE_ALIAS("wmi:" MSIWMI_BIOS_GUID); +MODULE_ALIAS("wmi:" MSIWMI_MSI_EVENT_GUID); +MODULE_ALIAS("wmi:" MSIWMI_WIND_EVENT_GUID); + +enum msi_scancodes { +	/* Generic MSI keys (not present on MSI Wind) */ +	MSI_KEY_BRIGHTNESSUP	= 0xD0, +	MSI_KEY_BRIGHTNESSDOWN, +	MSI_KEY_VOLUMEUP, +	MSI_KEY_VOLUMEDOWN, +	MSI_KEY_MUTE, +	/* MSI Wind keys */ +	WIND_KEY_TOUCHPAD	= 0x08,	/* Fn+F3 touchpad toggle */ +	WIND_KEY_BLUETOOTH	= 0x56,	/* Fn+F11 Bluetooth toggle */ +	WIND_KEY_CAMERA,		/* Fn+F6 webcam toggle */ +	WIND_KEY_WLAN		= 0x5f,	/* Fn+F11 Wi-Fi toggle */ +	WIND_KEY_TURBO,			/* Fn+F10 turbo mode toggle */ +	WIND_KEY_ECO		= 0x69,	/* Fn+F10 ECO mode toggle */ +}; +static struct key_entry msi_wmi_keymap[] = { +	{ KE_KEY, MSI_KEY_BRIGHTNESSUP,		{KEY_BRIGHTNESSUP} }, +	{ KE_KEY, MSI_KEY_BRIGHTNESSDOWN,	{KEY_BRIGHTNESSDOWN} }, +	{ KE_KEY, MSI_KEY_VOLUMEUP,		{KEY_VOLUMEUP} }, +	{ KE_KEY, MSI_KEY_VOLUMEDOWN,		{KEY_VOLUMEDOWN} }, +	{ KE_KEY, MSI_KEY_MUTE,			{KEY_MUTE} }, + +	/* These keys work without WMI. Ignore them to avoid double keycodes */ +	{ KE_IGNORE, WIND_KEY_TOUCHPAD,		{KEY_TOUCHPAD_TOGGLE} }, +	{ KE_IGNORE, WIND_KEY_BLUETOOTH,	{KEY_BLUETOOTH} }, +	{ KE_IGNORE, WIND_KEY_CAMERA,		{KEY_CAMERA} }, +	{ KE_IGNORE, WIND_KEY_WLAN,		{KEY_WLAN} }, + +	/* These are unknown WMI events found on MSI Wind */ +	{ KE_IGNORE, 0x00 }, +	{ KE_IGNORE, 0x62 }, +	{ KE_IGNORE, 0x63 }, + +	/* These are MSI Wind keys that should be handled via WMI */ +	{ KE_KEY, WIND_KEY_TURBO,		{KEY_PROG1} }, +	{ KE_KEY, WIND_KEY_ECO,			{KEY_PROG2} }, + +	{ KE_END, 0 } +}; -#define dprintk(msg...) pr_debug(DRV_PFX msg) +static ktime_t last_pressed; -#define KEYCODE_BASE 0xD0 -#define MSI_WMI_BRIGHTNESSUP   KEYCODE_BASE -#define MSI_WMI_BRIGHTNESSDOWN (KEYCODE_BASE + 1) -#define MSI_WMI_VOLUMEUP       (KEYCODE_BASE + 2) -#define MSI_WMI_VOLUMEDOWN     (KEYCODE_BASE + 3) -static struct key_entry msi_wmi_keymap[] = { -	{ KE_KEY, MSI_WMI_BRIGHTNESSUP,   {KEY_BRIGHTNESSUP} }, -	{ KE_KEY, MSI_WMI_BRIGHTNESSDOWN, {KEY_BRIGHTNESSDOWN} }, -	{ KE_KEY, MSI_WMI_VOLUMEUP,       {KEY_VOLUMEUP} }, -	{ KE_KEY, MSI_WMI_VOLUMEDOWN,     {KEY_VOLUMEDOWN} }, -	{ KE_END, 0} +static const struct { +	const char *guid; +	bool quirk_last_pressed; +} *event_wmi, event_wmis[] = { +	{ MSIWMI_MSI_EVENT_GUID, true }, +	{ MSIWMI_WIND_EVENT_GUID, false },  }; -static ktime_t last_pressed[ARRAY_SIZE(msi_wmi_keymap) - 1];  static struct backlight_device *backlight; @@ -76,7 +113,7 @@ static int msi_wmi_query_block(int instance, int *ret)  	if (!obj || obj->type != ACPI_TYPE_INTEGER) {  		if (obj) { -			printk(KERN_ERR DRV_PFX "query block returned object " +			pr_err("query block returned object "  			       "type: %d - buffer length:%d\n", obj->type,  			       obj->type == ACPI_TYPE_BUFFER ?  			       obj->buffer.length : 0); @@ -95,8 +132,8 @@ static int msi_wmi_set_block(int instance, int value)  	struct acpi_buffer input = { sizeof(int), &value }; -	dprintk("Going to set block of instance: %d - value: %d\n", -		instance, value); +	pr_debug("Going to set block of instance: %d - value: %d\n", +		 instance, value);  	status = wmi_set_block(MSIWMI_BIOS_GUID, instance, &input); @@ -110,20 +147,19 @@ static int bl_get(struct backlight_device *bd)  	/* Instance 1 is "get backlight", cmp with DSDT */  	err = msi_wmi_query_block(1, &ret);  	if (err) { -		printk(KERN_ERR DRV_PFX "Could not query backlight: %d\n", err); +		pr_err("Could not query backlight: %d\n", err);  		return -EINVAL;  	} -	dprintk("Get: Query block returned: %d\n", ret); +	pr_debug("Get: Query block returned: %d\n", ret);  	for (level = 0; level < ARRAY_SIZE(backlight_map); level++) {  		if (backlight_map[level] == ret) { -			dprintk("Current backlight level: 0x%X - index: %d\n", -				backlight_map[level], level); +			pr_debug("Current backlight level: 0x%X - index: %d\n", +				 backlight_map[level], level);  			break;  		}  	}  	if (level == ARRAY_SIZE(backlight_map)) { -		printk(KERN_ERR DRV_PFX "get: Invalid brightness value: 0x%X\n", -		       ret); +		pr_err("get: Invalid brightness value: 0x%X\n", ret);  		return -EINVAL;  	}  	return level; @@ -149,12 +185,11 @@ static void msi_wmi_notify(u32 value, void *context)  	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };  	static struct key_entry *key;  	union acpi_object *obj; -	ktime_t cur;  	acpi_status status;  	status = wmi_get_event_data(value, &response);  	if (status != AE_OK) { -		printk(KERN_INFO DRV_PFX "bad event status 0x%x\n", status); +		pr_info("bad event status 0x%x\n", status);  		return;  	} @@ -162,43 +197,70 @@ static void msi_wmi_notify(u32 value, void *context)  	if (obj && obj->type == ACPI_TYPE_INTEGER) {  		int eventcode = obj->integer.value; -		dprintk("Eventcode: 0x%x\n", eventcode); +		pr_debug("Eventcode: 0x%x\n", eventcode);  		key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev,  				eventcode); -		if (key) { -			ktime_t diff; -			cur = ktime_get_real(); -			diff = ktime_sub(cur, last_pressed[key->code - -					KEYCODE_BASE]); -			/* Ignore event if the same event happened in a 50 ms +		if (!key) { +			pr_info("Unknown key pressed - %x\n", eventcode); +			goto msi_wmi_notify_exit; +		} + +		if (event_wmi->quirk_last_pressed) { +			ktime_t cur = ktime_get_real(); +			ktime_t diff = ktime_sub(cur, last_pressed); +			/* Ignore event if any event happened in a 50 ms  			   timeframe -> Key press may result in 10-20 GPEs */  			if (ktime_to_us(diff) < 1000 * 50) { -				dprintk("Suppressed key event 0x%X - " -					"Last press was %lld us ago\n", +				pr_debug("Suppressed key event 0x%X - " +					 "Last press was %lld us ago\n",  					 key->code, ktime_to_us(diff)); -				return; -			} -			last_pressed[key->code - KEYCODE_BASE] = cur; - -			if (key->type == KE_KEY && -			/* Brightness is served via acpi video driver */ -			(!acpi_video_backlight_support() || -			(key->code != MSI_WMI_BRIGHTNESSUP && -			key->code != MSI_WMI_BRIGHTNESSDOWN))) { -				dprintk("Send key: 0x%X - " -					"Input layer keycode: %d\n", key->code, -					 key->keycode); -				sparse_keymap_report_entry(msi_wmi_input_dev, -						key, 1, true); +				goto msi_wmi_notify_exit;  			} -		} else -			printk(KERN_INFO "Unknown key pressed - %x\n", -			       eventcode); +			last_pressed = cur; +		} + +		if (key->type == KE_KEY && +		/* Brightness is served via acpi video driver */ +		(backlight || +		(key->code != MSI_KEY_BRIGHTNESSUP && +		key->code != MSI_KEY_BRIGHTNESSDOWN))) { +			pr_debug("Send key: 0x%X - Input layer keycode: %d\n", +				 key->code, key->keycode); +			sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, +						   true); +		}  	} else -		printk(KERN_INFO DRV_PFX "Unknown event received\n"); +		pr_info("Unknown event received\n"); + +msi_wmi_notify_exit:  	kfree(response.pointer);  } +static int __init msi_wmi_backlight_setup(void) +{ +	int err; +	struct backlight_properties props; + +	memset(&props, 0, sizeof(struct backlight_properties)); +	props.type = BACKLIGHT_PLATFORM; +	props.max_brightness = ARRAY_SIZE(backlight_map) - 1; +	backlight = backlight_device_register(DRV_NAME, NULL, NULL, +					      &msi_backlight_ops, +					      &props); +	if (IS_ERR(backlight)) +		return PTR_ERR(backlight); + +	err = bl_get(NULL); +	if (err < 0) { +		backlight_device_unregister(backlight); +		return err; +	} + +	backlight->props.brightness = err; + +	return 0; +} +  static int __init msi_wmi_input_setup(void)  {  	int err; @@ -220,7 +282,7 @@ static int __init msi_wmi_input_setup(void)  	if (err)  		goto err_free_keymap; -	memset(last_pressed, 0, sizeof(last_pressed)); +	last_pressed = ktime_set(0, 0);  	return 0; @@ -234,60 +296,66 @@ err_free_dev:  static int __init msi_wmi_init(void)  {  	int err; +	int i; -	if (!wmi_has_guid(MSIWMI_EVENT_GUID)) { -		printk(KERN_ERR -		       "This machine doesn't have MSI-hotkeys through WMI\n"); -		return -ENODEV; -	} -	err = wmi_install_notify_handler(MSIWMI_EVENT_GUID, -			msi_wmi_notify, NULL); -	if (ACPI_FAILURE(err)) -		return -EINVAL; +	for (i = 0; i < ARRAY_SIZE(event_wmis); i++) { +		if (!wmi_has_guid(event_wmis[i].guid)) +			continue; -	err = msi_wmi_input_setup(); -	if (err) -		goto err_uninstall_notifier; - -	if (!acpi_video_backlight_support()) { -		struct backlight_properties props; -		memset(&props, 0, sizeof(struct backlight_properties)); -		props.max_brightness = ARRAY_SIZE(backlight_map) - 1; -		backlight = backlight_device_register(DRV_NAME, NULL, NULL, -						      &msi_backlight_ops, -						      &props); -		if (IS_ERR(backlight)) { -			err = PTR_ERR(backlight); +		err = msi_wmi_input_setup(); +		if (err) { +			pr_err("Unable to setup input device\n"); +			return err; +		} + +		err = wmi_install_notify_handler(event_wmis[i].guid, +			msi_wmi_notify, NULL); +		if (ACPI_FAILURE(err)) { +			pr_err("Unable to setup WMI notify handler\n");  			goto err_free_input;  		} -		err = bl_get(NULL); -		if (err < 0) -			goto err_free_backlight; +		pr_debug("Event handler installed\n"); +		event_wmi = &event_wmis[i]; +		break; +	} -		backlight->props.brightness = err; +	if (wmi_has_guid(MSIWMI_BIOS_GUID) && !acpi_video_backlight_support()) { +		err = msi_wmi_backlight_setup(); +		if (err) { +			pr_err("Unable to setup backlight device\n"); +			goto err_uninstall_handler; +		} +		pr_debug("Backlight device created\n"); +	} + +	if (!event_wmi && !backlight) { +		pr_err("This machine doesn't have neither MSI-hotkeys nor backlight through WMI\n"); +		return -ENODEV;  	} -	dprintk("Event handler installed\n");  	return 0; -err_free_backlight: -	backlight_device_unregister(backlight); +err_uninstall_handler: +	if (event_wmi) +		wmi_remove_notify_handler(event_wmi->guid);  err_free_input: -	input_unregister_device(msi_wmi_input_dev); -err_uninstall_notifier: -	wmi_remove_notify_handler(MSIWMI_EVENT_GUID); +	if (event_wmi) { +		sparse_keymap_free(msi_wmi_input_dev); +		input_unregister_device(msi_wmi_input_dev); +	}  	return err;  }  static void __exit msi_wmi_exit(void)  { -	if (wmi_has_guid(MSIWMI_EVENT_GUID)) { -		wmi_remove_notify_handler(MSIWMI_EVENT_GUID); +	if (event_wmi) { +		wmi_remove_notify_handler(event_wmi->guid);  		sparse_keymap_free(msi_wmi_input_dev);  		input_unregister_device(msi_wmi_input_dev); -		backlight_device_unregister(backlight);  	} +	if (backlight) +		backlight_device_unregister(backlight);  }  module_init(msi_wmi_init);  | 
