19caeb532SHerton Ronaldo Krzesinski /*
29caeb532SHerton Ronaldo Krzesinski  * ACPI driver for Topstar notebooks (hotkeys support only)
39caeb532SHerton Ronaldo Krzesinski  *
49caeb532SHerton Ronaldo Krzesinski  * Copyright (c) 2009 Herton Ronaldo Krzesinski <herton@mandriva.com.br>
59caeb532SHerton Ronaldo Krzesinski  *
69caeb532SHerton Ronaldo Krzesinski  * Implementation inspired by existing x86 platform drivers, in special
79caeb532SHerton Ronaldo Krzesinski  * asus/eepc/fujitsu-laptop, thanks to their authors
89caeb532SHerton Ronaldo Krzesinski  *
99caeb532SHerton Ronaldo Krzesinski  * This program is free software; you can redistribute it and/or modify
109caeb532SHerton Ronaldo Krzesinski  * it under the terms of the GNU General Public License version 2 as
119caeb532SHerton Ronaldo Krzesinski  * published by the Free Software Foundation.
129caeb532SHerton Ronaldo Krzesinski  */
139caeb532SHerton Ronaldo Krzesinski 
149caeb532SHerton Ronaldo Krzesinski #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
159caeb532SHerton Ronaldo Krzesinski 
169caeb532SHerton Ronaldo Krzesinski #include <linux/kernel.h>
179caeb532SHerton Ronaldo Krzesinski #include <linux/module.h>
189caeb532SHerton Ronaldo Krzesinski #include <linux/init.h>
195a0e3ad6STejun Heo #include <linux/slab.h>
209caeb532SHerton Ronaldo Krzesinski #include <linux/acpi.h>
219caeb532SHerton Ronaldo Krzesinski #include <linux/input.h>
2297490f1cSDmitry Torokhov #include <linux/input/sparse-keymap.h>
239caeb532SHerton Ronaldo Krzesinski 
249caeb532SHerton Ronaldo Krzesinski #define ACPI_TOPSTAR_CLASS "topstar"
259caeb532SHerton Ronaldo Krzesinski 
269caeb532SHerton Ronaldo Krzesinski struct topstar_hkey {
279caeb532SHerton Ronaldo Krzesinski 	struct input_dev *inputdev;
289caeb532SHerton Ronaldo Krzesinski };
299caeb532SHerton Ronaldo Krzesinski 
3097490f1cSDmitry Torokhov static const struct key_entry topstar_keymap[] = {
3197490f1cSDmitry Torokhov 	{ KE_KEY, 0x80, { KEY_BRIGHTNESSUP } },
3297490f1cSDmitry Torokhov 	{ KE_KEY, 0x81, { KEY_BRIGHTNESSDOWN } },
3397490f1cSDmitry Torokhov 	{ KE_KEY, 0x83, { KEY_VOLUMEUP } },
3497490f1cSDmitry Torokhov 	{ KE_KEY, 0x84, { KEY_VOLUMEDOWN } },
3597490f1cSDmitry Torokhov 	{ KE_KEY, 0x85, { KEY_MUTE } },
3697490f1cSDmitry Torokhov 	{ KE_KEY, 0x86, { KEY_SWITCHVIDEOMODE } },
3797490f1cSDmitry Torokhov 	{ KE_KEY, 0x87, { KEY_F13 } }, /* touchpad enable/disable key */
3897490f1cSDmitry Torokhov 	{ KE_KEY, 0x88, { KEY_WLAN } },
3997490f1cSDmitry Torokhov 	{ KE_KEY, 0x8a, { KEY_WWW } },
4097490f1cSDmitry Torokhov 	{ KE_KEY, 0x8b, { KEY_MAIL } },
4197490f1cSDmitry Torokhov 	{ KE_KEY, 0x8c, { KEY_MEDIA } },
4297490f1cSDmitry Torokhov 
4397490f1cSDmitry Torokhov 	/* Known non hotkey events don't handled or that we don't care yet */
44bd038080SManuel Lauss 	{ KE_IGNORE, 0x82, }, /* backlight event */
4597490f1cSDmitry Torokhov 	{ KE_IGNORE, 0x8e, },
4697490f1cSDmitry Torokhov 	{ KE_IGNORE, 0x8f, },
4797490f1cSDmitry Torokhov 	{ KE_IGNORE, 0x90, },
4897490f1cSDmitry Torokhov 
4997490f1cSDmitry Torokhov 	/*
5097490f1cSDmitry Torokhov 	 * 'G key' generate two event codes, convert to only
5197490f1cSDmitry Torokhov 	 * one event/key code for now, consider replacing by
5297490f1cSDmitry Torokhov 	 * a switch (3G switch - SW_3G?)
5397490f1cSDmitry Torokhov 	 */
5497490f1cSDmitry Torokhov 	{ KE_KEY, 0x96, { KEY_F14 } },
5597490f1cSDmitry Torokhov 	{ KE_KEY, 0x97, { KEY_F14 } },
5697490f1cSDmitry Torokhov 
5797490f1cSDmitry Torokhov 	{ KE_END, 0 }
589caeb532SHerton Ronaldo Krzesinski };
599caeb532SHerton Ronaldo Krzesinski 
609caeb532SHerton Ronaldo Krzesinski static void acpi_topstar_notify(struct acpi_device *device, u32 event)
619caeb532SHerton Ronaldo Krzesinski {
629caeb532SHerton Ronaldo Krzesinski 	static bool dup_evnt[2];
639caeb532SHerton Ronaldo Krzesinski 	bool *dup;
649caeb532SHerton Ronaldo Krzesinski 	struct topstar_hkey *hkey = acpi_driver_data(device);
659caeb532SHerton Ronaldo Krzesinski 
669caeb532SHerton Ronaldo Krzesinski 	/* 0x83 and 0x84 key events comes duplicated... */
679caeb532SHerton Ronaldo Krzesinski 	if (event == 0x83 || event == 0x84) {
689caeb532SHerton Ronaldo Krzesinski 		dup = &dup_evnt[event - 0x83];
699caeb532SHerton Ronaldo Krzesinski 		if (*dup) {
709caeb532SHerton Ronaldo Krzesinski 			*dup = false;
719caeb532SHerton Ronaldo Krzesinski 			return;
729caeb532SHerton Ronaldo Krzesinski 		}
739caeb532SHerton Ronaldo Krzesinski 		*dup = true;
749caeb532SHerton Ronaldo Krzesinski 	}
759caeb532SHerton Ronaldo Krzesinski 
7697490f1cSDmitry Torokhov 	if (!sparse_keymap_report_event(hkey->inputdev, event, 1, true))
779caeb532SHerton Ronaldo Krzesinski 		pr_info("unknown event = 0x%02x\n", event);
789caeb532SHerton Ronaldo Krzesinski }
799caeb532SHerton Ronaldo Krzesinski 
809caeb532SHerton Ronaldo Krzesinski static int acpi_topstar_fncx_switch(struct acpi_device *device, bool state)
819caeb532SHerton Ronaldo Krzesinski {
829caeb532SHerton Ronaldo Krzesinski 	acpi_status status;
839caeb532SHerton Ronaldo Krzesinski 
84a7f6451fSZhang Rui 	status = acpi_execute_simple_method(device->handle, "FNCX",
85a7f6451fSZhang Rui 						state ? 0x86 : 0x87);
869caeb532SHerton Ronaldo Krzesinski 	if (ACPI_FAILURE(status)) {
879caeb532SHerton Ronaldo Krzesinski 		pr_err("Unable to switch FNCX notifications\n");
889caeb532SHerton Ronaldo Krzesinski 		return -ENODEV;
899caeb532SHerton Ronaldo Krzesinski 	}
909caeb532SHerton Ronaldo Krzesinski 
919caeb532SHerton Ronaldo Krzesinski 	return 0;
929caeb532SHerton Ronaldo Krzesinski }
939caeb532SHerton Ronaldo Krzesinski 
949caeb532SHerton Ronaldo Krzesinski static int acpi_topstar_init_hkey(struct topstar_hkey *hkey)
959caeb532SHerton Ronaldo Krzesinski {
9697490f1cSDmitry Torokhov 	struct input_dev *input;
9797490f1cSDmitry Torokhov 	int error;
989caeb532SHerton Ronaldo Krzesinski 
9997490f1cSDmitry Torokhov 	input = input_allocate_device();
100b222cca6SJoe Perches 	if (!input)
10197490f1cSDmitry Torokhov 		return -ENOMEM;
1029caeb532SHerton Ronaldo Krzesinski 
10397490f1cSDmitry Torokhov 	input->name = "Topstar Laptop extra buttons";
10497490f1cSDmitry Torokhov 	input->phys = "topstar/input0";
10597490f1cSDmitry Torokhov 	input->id.bustype = BUS_HOST;
10697490f1cSDmitry Torokhov 
10797490f1cSDmitry Torokhov 	error = sparse_keymap_setup(input, topstar_keymap, NULL);
10897490f1cSDmitry Torokhov 	if (error) {
10997490f1cSDmitry Torokhov 		pr_err("Unable to setup input device keymap\n");
11097490f1cSDmitry Torokhov 		goto err_free_dev;
11197490f1cSDmitry Torokhov 	}
11297490f1cSDmitry Torokhov 
11397490f1cSDmitry Torokhov 	error = input_register_device(input);
11497490f1cSDmitry Torokhov 	if (error) {
11597490f1cSDmitry Torokhov 		pr_err("Unable to register input device\n");
1163f2e1a32SMichał Kępień 		goto err_free_dev;
11797490f1cSDmitry Torokhov 	}
11897490f1cSDmitry Torokhov 
11997490f1cSDmitry Torokhov 	hkey->inputdev = input;
1209caeb532SHerton Ronaldo Krzesinski 	return 0;
12197490f1cSDmitry Torokhov 
12297490f1cSDmitry Torokhov  err_free_dev:
12397490f1cSDmitry Torokhov 	input_free_device(input);
12497490f1cSDmitry Torokhov 	return error;
1259caeb532SHerton Ronaldo Krzesinski }
1269caeb532SHerton Ronaldo Krzesinski 
1279caeb532SHerton Ronaldo Krzesinski static int acpi_topstar_add(struct acpi_device *device)
1289caeb532SHerton Ronaldo Krzesinski {
1299caeb532SHerton Ronaldo Krzesinski 	struct topstar_hkey *tps_hkey;
1309caeb532SHerton Ronaldo Krzesinski 
1319caeb532SHerton Ronaldo Krzesinski 	tps_hkey = kzalloc(sizeof(struct topstar_hkey), GFP_KERNEL);
1329caeb532SHerton Ronaldo Krzesinski 	if (!tps_hkey)
1339caeb532SHerton Ronaldo Krzesinski 		return -ENOMEM;
1349caeb532SHerton Ronaldo Krzesinski 
1359caeb532SHerton Ronaldo Krzesinski 	strcpy(acpi_device_name(device), "Topstar TPSACPI");
1369caeb532SHerton Ronaldo Krzesinski 	strcpy(acpi_device_class(device), ACPI_TOPSTAR_CLASS);
1379caeb532SHerton Ronaldo Krzesinski 
1389caeb532SHerton Ronaldo Krzesinski 	if (acpi_topstar_fncx_switch(device, true))
1399caeb532SHerton Ronaldo Krzesinski 		goto add_err;
1409caeb532SHerton Ronaldo Krzesinski 
1419caeb532SHerton Ronaldo Krzesinski 	if (acpi_topstar_init_hkey(tps_hkey))
1429caeb532SHerton Ronaldo Krzesinski 		goto add_err;
1439caeb532SHerton Ronaldo Krzesinski 
1449caeb532SHerton Ronaldo Krzesinski 	device->driver_data = tps_hkey;
1459caeb532SHerton Ronaldo Krzesinski 	return 0;
1469caeb532SHerton Ronaldo Krzesinski 
1479caeb532SHerton Ronaldo Krzesinski add_err:
1489caeb532SHerton Ronaldo Krzesinski 	kfree(tps_hkey);
1499caeb532SHerton Ronaldo Krzesinski 	return -ENODEV;
1509caeb532SHerton Ronaldo Krzesinski }
1519caeb532SHerton Ronaldo Krzesinski 
15251fac838SRafael J. Wysocki static int acpi_topstar_remove(struct acpi_device *device)
1539caeb532SHerton Ronaldo Krzesinski {
1549caeb532SHerton Ronaldo Krzesinski 	struct topstar_hkey *tps_hkey = acpi_driver_data(device);
1559caeb532SHerton Ronaldo Krzesinski 
1569caeb532SHerton Ronaldo Krzesinski 	acpi_topstar_fncx_switch(device, false);
1579caeb532SHerton Ronaldo Krzesinski 
1589caeb532SHerton Ronaldo Krzesinski 	input_unregister_device(tps_hkey->inputdev);
1599caeb532SHerton Ronaldo Krzesinski 	kfree(tps_hkey);
1609caeb532SHerton Ronaldo Krzesinski 
1619caeb532SHerton Ronaldo Krzesinski 	return 0;
1629caeb532SHerton Ronaldo Krzesinski }
1639caeb532SHerton Ronaldo Krzesinski 
1649caeb532SHerton Ronaldo Krzesinski static const struct acpi_device_id topstar_device_ids[] = {
16523ccd036SGuillaume Douézan-Grard 	{ "TPS0001", 0 },
1669caeb532SHerton Ronaldo Krzesinski 	{ "TPSACPI01", 0 },
1679caeb532SHerton Ronaldo Krzesinski 	{ "", 0 },
1689caeb532SHerton Ronaldo Krzesinski };
1699caeb532SHerton Ronaldo Krzesinski MODULE_DEVICE_TABLE(acpi, topstar_device_ids);
1709caeb532SHerton Ronaldo Krzesinski 
1719caeb532SHerton Ronaldo Krzesinski static struct acpi_driver acpi_topstar_driver = {
1729caeb532SHerton Ronaldo Krzesinski 	.name = "Topstar laptop ACPI driver",
1739caeb532SHerton Ronaldo Krzesinski 	.class = ACPI_TOPSTAR_CLASS,
1749caeb532SHerton Ronaldo Krzesinski 	.ids = topstar_device_ids,
1759caeb532SHerton Ronaldo Krzesinski 	.ops = {
1769caeb532SHerton Ronaldo Krzesinski 		.add = acpi_topstar_add,
1779caeb532SHerton Ronaldo Krzesinski 		.remove = acpi_topstar_remove,
1789caeb532SHerton Ronaldo Krzesinski 		.notify = acpi_topstar_notify,
1799caeb532SHerton Ronaldo Krzesinski 	},
1809caeb532SHerton Ronaldo Krzesinski };
18182acad40SGuillaume Douézan-Grard 
18282acad40SGuillaume Douézan-Grard static int __init topstar_laptop_init(void)
18382acad40SGuillaume Douézan-Grard {
18482acad40SGuillaume Douézan-Grard 	int ret;
18582acad40SGuillaume Douézan-Grard 
18682acad40SGuillaume Douézan-Grard 	ret = acpi_bus_register_driver(&acpi_topstar_driver);
18782acad40SGuillaume Douézan-Grard 	if (ret < 0)
18882acad40SGuillaume Douézan-Grard 		return ret;
18982acad40SGuillaume Douézan-Grard 
19082acad40SGuillaume Douézan-Grard 	pr_info("ACPI extras driver loaded\n");
19182acad40SGuillaume Douézan-Grard 
19282acad40SGuillaume Douézan-Grard 	return 0;
19382acad40SGuillaume Douézan-Grard }
19482acad40SGuillaume Douézan-Grard 
19582acad40SGuillaume Douézan-Grard static void __exit topstar_laptop_exit(void)
19682acad40SGuillaume Douézan-Grard {
19782acad40SGuillaume Douézan-Grard 	acpi_bus_unregister_driver(&acpi_topstar_driver);
19882acad40SGuillaume Douézan-Grard }
19982acad40SGuillaume Douézan-Grard 
20082acad40SGuillaume Douézan-Grard module_init(topstar_laptop_init);
20182acad40SGuillaume Douézan-Grard module_exit(topstar_laptop_exit);
2029caeb532SHerton Ronaldo Krzesinski 
2039caeb532SHerton Ronaldo Krzesinski MODULE_AUTHOR("Herton Ronaldo Krzesinski");
2049caeb532SHerton Ronaldo Krzesinski MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver");
2059caeb532SHerton Ronaldo Krzesinski MODULE_LICENSE("GPL");
206