114991fc7SAzael Avalos /*
214991fc7SAzael Avalos  * toshiba_wmi.c - Toshiba WMI Hotkey Driver
314991fc7SAzael Avalos  *
414991fc7SAzael Avalos  * Copyright (C) 2015 Azael Avalos <coproscefalo@gmail.com>
514991fc7SAzael Avalos  *
614991fc7SAzael Avalos  * This program is free software; you can redistribute it and/or modify
714991fc7SAzael Avalos  * it under the terms of the GNU General Public License as published by
814991fc7SAzael Avalos  * the Free Software Foundation; either version 2 of the License, or
914991fc7SAzael Avalos  * (at your option) any later version.
1014991fc7SAzael Avalos  *
1114991fc7SAzael Avalos  * This program is distributed in the hope that it will be useful,
1214991fc7SAzael Avalos  * but WITHOUT ANY WARRANTY; without even the implied warranty of
1314991fc7SAzael Avalos  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1414991fc7SAzael Avalos  * GNU General Public License for more details.
1514991fc7SAzael Avalos  *
1614991fc7SAzael Avalos  */
1714991fc7SAzael Avalos 
1814991fc7SAzael Avalos #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1914991fc7SAzael Avalos 
2014991fc7SAzael Avalos #include <linux/kernel.h>
2114991fc7SAzael Avalos #include <linux/module.h>
2214991fc7SAzael Avalos #include <linux/init.h>
2314991fc7SAzael Avalos #include <linux/types.h>
2414991fc7SAzael Avalos #include <linux/acpi.h>
2514991fc7SAzael Avalos #include <linux/input.h>
2614991fc7SAzael Avalos #include <linux/input/sparse-keymap.h>
271c80e960SAzael Avalos #include <linux/dmi.h>
2814991fc7SAzael Avalos 
2914991fc7SAzael Avalos MODULE_AUTHOR("Azael Avalos");
3014991fc7SAzael Avalos MODULE_DESCRIPTION("Toshiba WMI Hotkey Driver");
3114991fc7SAzael Avalos MODULE_LICENSE("GPL");
3214991fc7SAzael Avalos 
331c80e960SAzael Avalos #define WMI_EVENT_GUID	"59142400-C6A3-40FA-BADB-8A2652834100"
3414991fc7SAzael Avalos 
351c80e960SAzael Avalos MODULE_ALIAS("wmi:"WMI_EVENT_GUID);
3614991fc7SAzael Avalos 
3714991fc7SAzael Avalos static struct input_dev *toshiba_wmi_input_dev;
3814991fc7SAzael Avalos 
3914991fc7SAzael Avalos static const struct key_entry toshiba_wmi_keymap[] __initconst = {
4014991fc7SAzael Avalos 	/* TODO: Add keymap values once found... */
4114991fc7SAzael Avalos 	/*{ KE_KEY, 0x00, { KEY_ } },*/
4214991fc7SAzael Avalos 	{ KE_END, 0 }
4314991fc7SAzael Avalos };
4414991fc7SAzael Avalos 
4514991fc7SAzael Avalos static void toshiba_wmi_notify(u32 value, void *context)
4614991fc7SAzael Avalos {
4714991fc7SAzael Avalos 	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
4814991fc7SAzael Avalos 	union acpi_object *obj;
4914991fc7SAzael Avalos 	acpi_status status;
5014991fc7SAzael Avalos 
5114991fc7SAzael Avalos 	status = wmi_get_event_data(value, &response);
5214991fc7SAzael Avalos 	if (ACPI_FAILURE(status)) {
5314991fc7SAzael Avalos 		pr_err("Bad event status 0x%x\n", status);
5414991fc7SAzael Avalos 		return;
5514991fc7SAzael Avalos 	}
5614991fc7SAzael Avalos 
5714991fc7SAzael Avalos 	obj = (union acpi_object *)response.pointer;
5814991fc7SAzael Avalos 	if (!obj)
5914991fc7SAzael Avalos 		return;
6014991fc7SAzael Avalos 
6114991fc7SAzael Avalos 	/* TODO: Add proper checks once we have data */
6214991fc7SAzael Avalos 	pr_debug("Unknown event received, obj type %x\n", obj->type);
6314991fc7SAzael Avalos 
6414991fc7SAzael Avalos 	kfree(response.pointer);
6514991fc7SAzael Avalos }
6614991fc7SAzael Avalos 
671c80e960SAzael Avalos static struct dmi_system_id toshiba_wmi_dmi_table[] __initdata = {
681c80e960SAzael Avalos 	{
691c80e960SAzael Avalos 		.ident = "Toshiba laptop",
701c80e960SAzael Avalos 		.matches = {
711c80e960SAzael Avalos 			DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
721c80e960SAzael Avalos 		},
731c80e960SAzael Avalos 	},
741c80e960SAzael Avalos 	{}
751c80e960SAzael Avalos };
761c80e960SAzael Avalos 
7714991fc7SAzael Avalos static int __init toshiba_wmi_input_setup(void)
7814991fc7SAzael Avalos {
7914991fc7SAzael Avalos 	acpi_status status;
8014991fc7SAzael Avalos 	int err;
8114991fc7SAzael Avalos 
8214991fc7SAzael Avalos 	toshiba_wmi_input_dev = input_allocate_device();
8314991fc7SAzael Avalos 	if (!toshiba_wmi_input_dev)
8414991fc7SAzael Avalos 		return -ENOMEM;
8514991fc7SAzael Avalos 
8614991fc7SAzael Avalos 	toshiba_wmi_input_dev->name = "Toshiba WMI hotkeys";
8714991fc7SAzael Avalos 	toshiba_wmi_input_dev->phys = "wmi/input0";
8814991fc7SAzael Avalos 	toshiba_wmi_input_dev->id.bustype = BUS_HOST;
8914991fc7SAzael Avalos 
9014991fc7SAzael Avalos 	err = sparse_keymap_setup(toshiba_wmi_input_dev,
9114991fc7SAzael Avalos 				  toshiba_wmi_keymap, NULL);
9214991fc7SAzael Avalos 	if (err)
9314991fc7SAzael Avalos 		goto err_free_dev;
9414991fc7SAzael Avalos 
951c80e960SAzael Avalos 	status = wmi_install_notify_handler(WMI_EVENT_GUID,
9614991fc7SAzael Avalos 					    toshiba_wmi_notify, NULL);
9714991fc7SAzael Avalos 	if (ACPI_FAILURE(status)) {
9814991fc7SAzael Avalos 		err = -EIO;
9914991fc7SAzael Avalos 		goto err_free_keymap;
10014991fc7SAzael Avalos 	}
10114991fc7SAzael Avalos 
10214991fc7SAzael Avalos 	err = input_register_device(toshiba_wmi_input_dev);
10314991fc7SAzael Avalos 	if (err)
10414991fc7SAzael Avalos 		goto err_remove_notifier;
10514991fc7SAzael Avalos 
10614991fc7SAzael Avalos 	return 0;
10714991fc7SAzael Avalos 
10814991fc7SAzael Avalos  err_remove_notifier:
1091c80e960SAzael Avalos 	wmi_remove_notify_handler(WMI_EVENT_GUID);
11014991fc7SAzael Avalos  err_free_keymap:
11114991fc7SAzael Avalos 	sparse_keymap_free(toshiba_wmi_input_dev);
11214991fc7SAzael Avalos  err_free_dev:
11314991fc7SAzael Avalos 	input_free_device(toshiba_wmi_input_dev);
11414991fc7SAzael Avalos 	return err;
11514991fc7SAzael Avalos }
11614991fc7SAzael Avalos 
11714991fc7SAzael Avalos static void toshiba_wmi_input_destroy(void)
11814991fc7SAzael Avalos {
1191c80e960SAzael Avalos 	wmi_remove_notify_handler(WMI_EVENT_GUID);
12014991fc7SAzael Avalos 	sparse_keymap_free(toshiba_wmi_input_dev);
12114991fc7SAzael Avalos 	input_unregister_device(toshiba_wmi_input_dev);
12214991fc7SAzael Avalos }
12314991fc7SAzael Avalos 
12414991fc7SAzael Avalos static int __init toshiba_wmi_init(void)
12514991fc7SAzael Avalos {
12614991fc7SAzael Avalos 	int ret;
12714991fc7SAzael Avalos 
1281c80e960SAzael Avalos 	if (!wmi_has_guid(WMI_EVENT_GUID) ||
1291c80e960SAzael Avalos 	    !dmi_check_system(toshiba_wmi_dmi_table))
13014991fc7SAzael Avalos 		return -ENODEV;
13114991fc7SAzael Avalos 
13214991fc7SAzael Avalos 	ret = toshiba_wmi_input_setup();
13314991fc7SAzael Avalos 	if (ret) {
13414991fc7SAzael Avalos 		pr_err("Failed to setup input device\n");
13514991fc7SAzael Avalos 		return ret;
13614991fc7SAzael Avalos 	}
13714991fc7SAzael Avalos 
13814991fc7SAzael Avalos 	pr_info("Toshiba WMI Hotkey Driver\n");
13914991fc7SAzael Avalos 
14014991fc7SAzael Avalos 	return 0;
14114991fc7SAzael Avalos }
14214991fc7SAzael Avalos 
14314991fc7SAzael Avalos static void __exit toshiba_wmi_exit(void)
14414991fc7SAzael Avalos {
1451c80e960SAzael Avalos 	if (wmi_has_guid(WMI_EVENT_GUID))
14614991fc7SAzael Avalos 		toshiba_wmi_input_destroy();
14714991fc7SAzael Avalos }
14814991fc7SAzael Avalos 
14914991fc7SAzael Avalos module_init(toshiba_wmi_init);
15014991fc7SAzael Avalos module_exit(toshiba_wmi_exit);
151