diff options
Diffstat (limited to 'drivers/platform/x86/acer-wmi.c')
| -rw-r--r-- | drivers/platform/x86/acer-wmi.c | 429 | 
1 files changed, 421 insertions, 8 deletions
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index c8c65375bfe..ee40d681edd 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -37,6 +37,9 @@  #include <linux/workqueue.h>  #include <linux/debugfs.h>  #include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/dmi.h>  #include <acpi/acpi_drivers.h> @@ -48,6 +51,7 @@ MODULE_LICENSE("GPL");  #define ACER_ERR KERN_ERR ACER_LOGPREFIX  #define ACER_NOTICE KERN_NOTICE ACER_LOGPREFIX  #define ACER_INFO KERN_INFO ACER_LOGPREFIX +#define ACER_WARNING KERN_WARNING ACER_LOGPREFIX  /*   * Magic Number @@ -82,9 +86,82 @@ MODULE_LICENSE("GPL");  #define AMW0_GUID2		"431F16ED-0C2B-444C-B267-27DEB140CF9C"  #define WMID_GUID1		"6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3"  #define WMID_GUID2		"95764E09-FB56-4e83-B31A-37761F60994A" +#define WMID_GUID3		"61EF69EA-865C-4BC3-A502-A0DEBA0CB531" + +/* + * Acer ACPI event GUIDs + */ +#define ACERWMID_EVENT_GUID "676AA15E-6A47-4D9F-A2CC-1E6D18D14026"  MODULE_ALIAS("wmi:67C3371D-95A3-4C37-BB61-DD47B491DAAB");  MODULE_ALIAS("wmi:6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3"); +MODULE_ALIAS("wmi:676AA15E-6A47-4D9F-A2CC-1E6D18D14026"); + +enum acer_wmi_event_ids { +	WMID_HOTKEY_EVENT = 0x1, +}; + +static const struct key_entry acer_wmi_keymap[] = { +	{KE_KEY, 0x01, {KEY_WLAN} },     /* WiFi */ +	{KE_KEY, 0x12, {KEY_BLUETOOTH} },	/* BT */ +	{KE_KEY, 0x21, {KEY_PROG1} },    /* Backup */ +	{KE_KEY, 0x22, {KEY_PROG2} },    /* Arcade */ +	{KE_KEY, 0x23, {KEY_PROG3} },    /* P_Key */ +	{KE_KEY, 0x24, {KEY_PROG4} },    /* Social networking_Key */ +	{KE_KEY, 0x64, {KEY_SWITCHVIDEOMODE} },	/* Display Switch */ +	{KE_KEY, 0x82, {KEY_F22} },      /* Touch Pad On/Off */ +	{KE_END, 0} +}; + +static struct input_dev *acer_wmi_input_dev; + +struct event_return_value { +	u8 function; +	u8 key_num; +	u16 device_state; +	u32 reserved; +} __attribute__((packed)); + +/* + * GUID3 Get Device Status device flags + */ +#define ACER_WMID3_GDS_WIRELESS		(1<<0)	/* WiFi */ +#define ACER_WMID3_GDS_THREEG		(1<<6)	/* 3G */ +#define ACER_WMID3_GDS_BLUETOOTH	(1<<11)	/* BT */ + +struct lm_input_params { +	u8 function_num;        /* Function Number */ +	u16 commun_devices;     /* Communication type devices default status */ +	u16 devices;            /* Other type devices default status */ +	u8 lm_status;           /* Launch Manager Status */ +	u16 reserved; +} __attribute__((packed)); + +struct lm_return_value { +	u8 error_code;          /* Error Code */ +	u8 ec_return_value;     /* EC Return Value */ +	u16 reserved; +} __attribute__((packed)); + +struct wmid3_gds_input_param {	/* Get Device Status input parameter */ +	u8 function_num;	/* Function Number */ +	u8 hotkey_number;	/* Hotkey Number */ +	u16 devices;		/* Get Device */ +} __attribute__((packed)); + +struct wmid3_gds_return_value {	/* Get Device Status return value*/ +	u8 error_code;		/* Error Code */ +	u8 ec_return_value;	/* EC Return Value */ +	u16 devices;		/* Current Device Status */ +	u32 reserved; +} __attribute__((packed)); + +struct hotkey_function_type_aa { +	u8 type; +	u8 length; +	u16 handle; +	u16 commun_func_bitmap; +} __attribute__((packed));  /*   * Interface capability flags @@ -116,15 +193,19 @@ static int mailled = -1;  static int brightness = -1;  static int threeg = -1;  static int force_series; +static bool ec_raw_mode; +static bool has_type_aa;  module_param(mailled, int, 0444);  module_param(brightness, int, 0444);  module_param(threeg, int, 0444);  module_param(force_series, int, 0444); +module_param(ec_raw_mode, bool, 0444);  MODULE_PARM_DESC(mailled, "Set initial state of Mail LED");  MODULE_PARM_DESC(brightness, "Set initial LCD backlight brightness");  MODULE_PARM_DESC(threeg, "Set initial state of 3G hardware");  MODULE_PARM_DESC(force_series, "Force a different laptop series"); +MODULE_PARM_DESC(ec_raw_mode, "Enable EC raw mode");  struct acer_data {  	int mailled; @@ -140,6 +221,7 @@ struct acer_debug {  static struct rfkill *wireless_rfkill;  static struct rfkill *bluetooth_rfkill; +static struct rfkill *threeg_rfkill;  /* Each low-level interface must define at least some of the following */  struct wmi_interface { @@ -753,6 +835,28 @@ static acpi_status WMID_set_u32(u32 value, u32 cap, struct wmi_interface *iface)  	return WMI_execute_u32(method_id, (u32)value, NULL);  } +static void type_aa_dmi_decode(const struct dmi_header *header, void *dummy) +{ +	struct hotkey_function_type_aa *type_aa; + +	/* We are looking for OEM-specific Type AAh */ +	if (header->type != 0xAA) +		return; + +	has_type_aa = true; +	type_aa = (struct hotkey_function_type_aa *) header; + +	printk(ACER_INFO "Function bitmap for Communication Button: 0x%x\n", +		type_aa->commun_func_bitmap); + +	if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_WIRELESS) +		interface->capability |= ACER_CAP_WIRELESS; +	if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_THREEG) +		interface->capability |= ACER_CAP_THREEG; +	if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_BLUETOOTH) +		interface->capability |= ACER_CAP_BLUETOOTH; +} +  static acpi_status WMID_set_capabilities(void)  {  	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; @@ -773,16 +877,17 @@ static acpi_status WMID_set_capabilities(void)  		return AE_ERROR;  	} -	/* Not sure on the meaning of the relevant bits yet to detect these */ -	interface->capability |= ACER_CAP_WIRELESS; -	interface->capability |= ACER_CAP_THREEG; +	dmi_walk(type_aa_dmi_decode, NULL); +	if (!has_type_aa) { +		interface->capability |= ACER_CAP_WIRELESS; +		interface->capability |= ACER_CAP_THREEG; +		if (devices & 0x10) +			interface->capability |= ACER_CAP_BLUETOOTH; +	}  	/* WMID always provides brightness methods */  	interface->capability |= ACER_CAP_BRIGHTNESS; -	if (devices & 0x10) -		interface->capability |= ACER_CAP_BLUETOOTH; -  	if (!(devices & 0x20))  		max_brightness = 0x9; @@ -861,7 +966,8 @@ static void __init acer_commandline_init(void)  	 * capability isn't available on the given interface  	 */  	set_u32(mailled, ACER_CAP_MAILLED); -	set_u32(threeg, ACER_CAP_THREEG); +	if (!has_type_aa) +		set_u32(threeg, ACER_CAP_THREEG);  	set_u32(brightness, ACER_CAP_BRIGHTNESS);  } @@ -948,6 +1054,79 @@ static void acer_backlight_exit(void)  	backlight_device_unregister(acer_backlight_device);  } +static acpi_status wmid3_get_device_status(u32 *value, u16 device) +{ +	struct wmid3_gds_return_value return_value; +	acpi_status status; +	union acpi_object *obj; +	struct wmid3_gds_input_param params = { +		.function_num = 0x1, +		.hotkey_number = 0x01, +		.devices = device, +	}; +	struct acpi_buffer input = { +		sizeof(struct wmid3_gds_input_param), +		¶ms +	}; +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + +	status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &input, &output); +	if (ACPI_FAILURE(status)) +		return status; + +	obj = output.pointer; + +	if (!obj) +		return AE_ERROR; +	else if (obj->type != ACPI_TYPE_BUFFER) { +		kfree(obj); +		return AE_ERROR; +	} +	if (obj->buffer.length != 8) { +		printk(ACER_WARNING "Unknown buffer length %d\n", +			obj->buffer.length); +		kfree(obj); +		return AE_ERROR; +	} + +	return_value = *((struct wmid3_gds_return_value *)obj->buffer.pointer); +	kfree(obj); + +	if (return_value.error_code || return_value.ec_return_value) +		printk(ACER_WARNING "Get Device Status failed: " +			"0x%x - 0x%x\n", return_value.error_code, +			return_value.ec_return_value); +	else +		*value = !!(return_value.devices & device); + +	return status; +} + +static acpi_status get_device_status(u32 *value, u32 cap) +{ +	if (wmi_has_guid(WMID_GUID3)) { +		u16 device; + +		switch (cap) { +		case ACER_CAP_WIRELESS: +			device = ACER_WMID3_GDS_WIRELESS; +			break; +		case ACER_CAP_BLUETOOTH: +			device = ACER_WMID3_GDS_BLUETOOTH; +			break; +		case ACER_CAP_THREEG: +			device = ACER_WMID3_GDS_THREEG; +			break; +		default: +			return AE_ERROR; +		} +		return wmid3_get_device_status(value, device); + +	} else { +		return get_u32(value, cap); +	} +} +  /*   * Rfkill devices   */ @@ -968,6 +1147,13 @@ static void acer_rfkill_update(struct work_struct *ignored)  			rfkill_set_sw_state(bluetooth_rfkill, !state);  	} +	if (has_cap(ACER_CAP_THREEG) && wmi_has_guid(WMID_GUID3)) { +		status = wmid3_get_device_status(&state, +				ACER_WMID3_GDS_THREEG); +		if (ACPI_SUCCESS(status)) +			rfkill_set_sw_state(threeg_rfkill, !state); +	} +  	schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ));  } @@ -991,6 +1177,8 @@ static struct rfkill *acer_rfkill_register(struct device *dev,  {  	int err;  	struct rfkill *rfkill_dev; +	u32 state; +	acpi_status status;  	rfkill_dev = rfkill_alloc(name, dev, type,  				  &acer_rfkill_ops, @@ -998,6 +1186,10 @@ static struct rfkill *acer_rfkill_register(struct device *dev,  	if (!rfkill_dev)  		return ERR_PTR(-ENOMEM); +	status = get_device_status(&state, cap); +	if (ACPI_SUCCESS(status)) +		rfkill_init_sw_state(rfkill_dev, !state); +  	err = rfkill_register(rfkill_dev);  	if (err) {  		rfkill_destroy(rfkill_dev); @@ -1024,6 +1216,19 @@ static int acer_rfkill_init(struct device *dev)  		}  	} +	if (has_cap(ACER_CAP_THREEG)) { +		threeg_rfkill = acer_rfkill_register(dev, +			RFKILL_TYPE_WWAN, "acer-threeg", +			ACER_CAP_THREEG); +		if (IS_ERR(threeg_rfkill)) { +			rfkill_unregister(wireless_rfkill); +			rfkill_destroy(wireless_rfkill); +			rfkill_unregister(bluetooth_rfkill); +			rfkill_destroy(bluetooth_rfkill); +			return PTR_ERR(threeg_rfkill); +		} +	} +  	schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ));  	return 0; @@ -1040,6 +1245,11 @@ static void acer_rfkill_exit(void)  		rfkill_unregister(bluetooth_rfkill);  		rfkill_destroy(bluetooth_rfkill);  	} + +	if (has_cap(ACER_CAP_THREEG)) { +		rfkill_unregister(threeg_rfkill); +		rfkill_destroy(threeg_rfkill); +	}  	return;  } @@ -1050,7 +1260,12 @@ static ssize_t show_bool_threeg(struct device *dev,  	struct device_attribute *attr, char *buf)  {  	u32 result; \ -	acpi_status status = get_u32(&result, ACER_CAP_THREEG); +	acpi_status status; +	if (wmi_has_guid(WMID_GUID3)) +		status = wmid3_get_device_status(&result, +				ACER_WMID3_GDS_THREEG); +	else +		status = get_u32(&result, ACER_CAP_THREEG);  	if (ACPI_SUCCESS(status))  		return sprintf(buf, "%u\n", result);  	return sprintf(buf, "Read error\n"); @@ -1085,6 +1300,178 @@ static ssize_t show_interface(struct device *dev, struct device_attribute *attr,  static DEVICE_ATTR(interface, S_IRUGO, show_interface, NULL); +static void acer_wmi_notify(u32 value, void *context) +{ +	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; +	union acpi_object *obj; +	struct event_return_value return_value; +	acpi_status status; + +	status = wmi_get_event_data(value, &response); +	if (status != AE_OK) { +		printk(ACER_WARNING "bad event status 0x%x\n", status); +		return; +	} + +	obj = (union acpi_object *)response.pointer; + +	if (!obj) +		return; +	if (obj->type != ACPI_TYPE_BUFFER) { +		printk(ACER_WARNING "Unknown response received %d\n", +			obj->type); +		kfree(obj); +		return; +	} +	if (obj->buffer.length != 8) { +		printk(ACER_WARNING "Unknown buffer length %d\n", +			obj->buffer.length); +		kfree(obj); +		return; +	} + +	return_value = *((struct event_return_value *)obj->buffer.pointer); +	kfree(obj); + +	switch (return_value.function) { +	case WMID_HOTKEY_EVENT: +		if (!sparse_keymap_report_event(acer_wmi_input_dev, +				return_value.key_num, 1, true)) +			printk(ACER_WARNING "Unknown key number - 0x%x\n", +				return_value.key_num); +		break; +	default: +		printk(ACER_WARNING "Unknown function number - %d - %d\n", +			return_value.function, return_value.key_num); +		break; +	} +} + +static acpi_status +wmid3_set_lm_mode(struct lm_input_params *params, +		  struct lm_return_value *return_value) +{ +	acpi_status status; +	union acpi_object *obj; + +	struct acpi_buffer input = { sizeof(struct lm_input_params), params }; +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + +	status = wmi_evaluate_method(WMID_GUID3, 0, 0x1, &input, &output); +	if (ACPI_FAILURE(status)) +		return status; + +	obj = output.pointer; + +	if (!obj) +		return AE_ERROR; +	else if (obj->type != ACPI_TYPE_BUFFER) { +		kfree(obj); +		return AE_ERROR; +	} +	if (obj->buffer.length != 4) { +		printk(ACER_WARNING "Unknown buffer length %d\n", +		       obj->buffer.length); +		kfree(obj); +		return AE_ERROR; +	} + +	*return_value = *((struct lm_return_value *)obj->buffer.pointer); +	kfree(obj); + +	return status; +} + +static int acer_wmi_enable_ec_raw(void) +{ +	struct lm_return_value return_value; +	acpi_status status; +	struct lm_input_params params = { +		.function_num = 0x1, +		.commun_devices = 0xFFFF, +		.devices = 0xFFFF, +		.lm_status = 0x00,            /* Launch Manager Deactive */ +	}; + +	status = wmid3_set_lm_mode(¶ms, &return_value); + +	if (return_value.error_code || return_value.ec_return_value) +		printk(ACER_WARNING "Enabling EC raw mode failed: " +		       "0x%x - 0x%x\n", return_value.error_code, +		       return_value.ec_return_value); +	else +		printk(ACER_INFO "Enabled EC raw mode"); + +	return status; +} + +static int acer_wmi_enable_lm(void) +{ +	struct lm_return_value return_value; +	acpi_status status; +	struct lm_input_params params = { +		.function_num = 0x1, +		.commun_devices = 0xFFFF, +		.devices = 0xFFFF, +		.lm_status = 0x01,            /* Launch Manager Active */ +	}; + +	status = wmid3_set_lm_mode(¶ms, &return_value); + +	if (return_value.error_code || return_value.ec_return_value) +		printk(ACER_WARNING "Enabling Launch Manager failed: " +		       "0x%x - 0x%x\n", return_value.error_code, +		       return_value.ec_return_value); + +	return status; +} + +static int __init acer_wmi_input_setup(void) +{ +	acpi_status status; +	int err; + +	acer_wmi_input_dev = input_allocate_device(); +	if (!acer_wmi_input_dev) +		return -ENOMEM; + +	acer_wmi_input_dev->name = "Acer WMI hotkeys"; +	acer_wmi_input_dev->phys = "wmi/input0"; +	acer_wmi_input_dev->id.bustype = BUS_HOST; + +	err = sparse_keymap_setup(acer_wmi_input_dev, acer_wmi_keymap, NULL); +	if (err) +		goto err_free_dev; + +	status = wmi_install_notify_handler(ACERWMID_EVENT_GUID, +						acer_wmi_notify, NULL); +	if (ACPI_FAILURE(status)) { +		err = -EIO; +		goto err_free_keymap; +	} + +	err = input_register_device(acer_wmi_input_dev); +	if (err) +		goto err_uninstall_notifier; + +	return 0; + +err_uninstall_notifier: +	wmi_remove_notify_handler(ACERWMID_EVENT_GUID); +err_free_keymap: +	sparse_keymap_free(acer_wmi_input_dev); +err_free_dev: +	input_free_device(acer_wmi_input_dev); +	return err; +} + +static void acer_wmi_input_destroy(void) +{ +	wmi_remove_notify_handler(ACERWMID_EVENT_GUID); +	sparse_keymap_free(acer_wmi_input_dev); +	input_unregister_device(acer_wmi_input_dev); +} +  /*   * debugfs functions   */ @@ -1327,6 +1714,26 @@ static int __init acer_wmi_init(void)  		       "generic video driver\n");  	} +	if (wmi_has_guid(WMID_GUID3)) { +		if (ec_raw_mode) { +			if (ACPI_FAILURE(acer_wmi_enable_ec_raw())) { +				printk(ACER_ERR "Cannot enable EC raw mode\n"); +				return -ENODEV; +			} +		} else if (ACPI_FAILURE(acer_wmi_enable_lm())) { +			printk(ACER_ERR "Cannot enable Launch Manager mode\n"); +			return -ENODEV; +		} +	} else if (ec_raw_mode) { +		printk(ACER_INFO "No WMID EC raw mode enable method\n"); +	} + +	if (wmi_has_guid(ACERWMID_EVENT_GUID)) { +		err = acer_wmi_input_setup(); +		if (err) +			return err; +	} +  	err = platform_driver_register(&acer_platform_driver);  	if (err) {  		printk(ACER_ERR "Unable to register platform driver.\n"); @@ -1368,11 +1775,17 @@ error_device_add:  error_device_alloc:  	platform_driver_unregister(&acer_platform_driver);  error_platform_register: +	if (wmi_has_guid(ACERWMID_EVENT_GUID)) +		acer_wmi_input_destroy(); +  	return err;  }  static void __exit acer_wmi_exit(void)  { +	if (wmi_has_guid(ACERWMID_EVENT_GUID)) +		acer_wmi_input_destroy(); +  	remove_sysfs(acer_platform_device);  	remove_debugfs();  	platform_device_unregister(acer_platform_device);  | 
