xref: /linux/drivers/platform/x86/hp/tc1100-wmi.c (revision 60675d4ca1ef0857e44eba5849b74a3a998d0c0f)
16e9b8992SJorge Lopez // SPDX-License-Identifier: GPL-2.0-or-later
26e9b8992SJorge Lopez /*
36e9b8992SJorge Lopez  *  HP Compaq TC1100 Tablet WMI Extras Driver
46e9b8992SJorge Lopez  *
56e9b8992SJorge Lopez  *  Copyright (C) 2007 Carlos Corbacho <carlos@strangeworlds.co.uk>
66e9b8992SJorge Lopez  *  Copyright (C) 2004 Jamey Hicks <jamey.hicks@hp.com>
76e9b8992SJorge Lopez  *  Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
86e9b8992SJorge Lopez  *  Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
96e9b8992SJorge Lopez  */
106e9b8992SJorge Lopez 
116e9b8992SJorge Lopez #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
126e9b8992SJorge Lopez 
136e9b8992SJorge Lopez #include <linux/kernel.h>
146e9b8992SJorge Lopez #include <linux/module.h>
156e9b8992SJorge Lopez #include <linux/slab.h>
166e9b8992SJorge Lopez #include <linux/init.h>
176e9b8992SJorge Lopez #include <linux/types.h>
186e9b8992SJorge Lopez #include <linux/acpi.h>
196e9b8992SJorge Lopez #include <linux/platform_device.h>
206e9b8992SJorge Lopez 
216e9b8992SJorge Lopez #define GUID "C364AC71-36DB-495A-8494-B439D472A505"
226e9b8992SJorge Lopez 
236e9b8992SJorge Lopez #define TC1100_INSTANCE_WIRELESS		1
246e9b8992SJorge Lopez #define TC1100_INSTANCE_JOGDIAL		2
256e9b8992SJorge Lopez 
266e9b8992SJorge Lopez MODULE_AUTHOR("Jamey Hicks, Carlos Corbacho");
276e9b8992SJorge Lopez MODULE_DESCRIPTION("HP Compaq TC1100 Tablet WMI Extras");
286e9b8992SJorge Lopez MODULE_LICENSE("GPL");
296e9b8992SJorge Lopez MODULE_ALIAS("wmi:C364AC71-36DB-495A-8494-B439D472A505");
306e9b8992SJorge Lopez 
316e9b8992SJorge Lopez static struct platform_device *tc1100_device;
326e9b8992SJorge Lopez 
336e9b8992SJorge Lopez struct tc1100_data {
346e9b8992SJorge Lopez 	u32 wireless;
356e9b8992SJorge Lopez 	u32 jogdial;
366e9b8992SJorge Lopez };
376e9b8992SJorge Lopez 
386e9b8992SJorge Lopez #ifdef CONFIG_PM
396e9b8992SJorge Lopez static struct tc1100_data suspend_data;
406e9b8992SJorge Lopez #endif
416e9b8992SJorge Lopez 
426e9b8992SJorge Lopez /* --------------------------------------------------------------------------
436e9b8992SJorge Lopez 				Device Management
446e9b8992SJorge Lopez    -------------------------------------------------------------------------- */
456e9b8992SJorge Lopez 
466e9b8992SJorge Lopez static int get_state(u32 *out, u8 instance)
476e9b8992SJorge Lopez {
486e9b8992SJorge Lopez 	u32 tmp;
496e9b8992SJorge Lopez 	acpi_status status;
506e9b8992SJorge Lopez 	struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };
516e9b8992SJorge Lopez 	union acpi_object *obj;
526e9b8992SJorge Lopez 
536e9b8992SJorge Lopez 	if (!out)
546e9b8992SJorge Lopez 		return -EINVAL;
556e9b8992SJorge Lopez 
566e9b8992SJorge Lopez 	if (instance > 2)
576e9b8992SJorge Lopez 		return -ENODEV;
586e9b8992SJorge Lopez 
596e9b8992SJorge Lopez 	status = wmi_query_block(GUID, instance, &result);
606e9b8992SJorge Lopez 	if (ACPI_FAILURE(status))
616e9b8992SJorge Lopez 		return -ENODEV;
626e9b8992SJorge Lopez 
636e9b8992SJorge Lopez 	obj = (union acpi_object *) result.pointer;
646e9b8992SJorge Lopez 	if (obj && obj->type == ACPI_TYPE_INTEGER) {
656e9b8992SJorge Lopez 		tmp = obj->integer.value;
666e9b8992SJorge Lopez 	} else {
676e9b8992SJorge Lopez 		tmp = 0;
686e9b8992SJorge Lopez 	}
696e9b8992SJorge Lopez 
706e9b8992SJorge Lopez 	if (result.length > 0)
716e9b8992SJorge Lopez 		kfree(result.pointer);
726e9b8992SJorge Lopez 
736e9b8992SJorge Lopez 	switch (instance) {
746e9b8992SJorge Lopez 	case TC1100_INSTANCE_WIRELESS:
756e9b8992SJorge Lopez 		*out = (tmp == 3) ? 1 : 0;
766e9b8992SJorge Lopez 		return 0;
776e9b8992SJorge Lopez 	case TC1100_INSTANCE_JOGDIAL:
786e9b8992SJorge Lopez 		*out = (tmp == 1) ? 0 : 1;
796e9b8992SJorge Lopez 		return 0;
806e9b8992SJorge Lopez 	default:
816e9b8992SJorge Lopez 		return -ENODEV;
826e9b8992SJorge Lopez 	}
836e9b8992SJorge Lopez }
846e9b8992SJorge Lopez 
856e9b8992SJorge Lopez static int set_state(u32 *in, u8 instance)
866e9b8992SJorge Lopez {
876e9b8992SJorge Lopez 	u32 value;
886e9b8992SJorge Lopez 	acpi_status status;
896e9b8992SJorge Lopez 	struct acpi_buffer input;
906e9b8992SJorge Lopez 
916e9b8992SJorge Lopez 	if (!in)
926e9b8992SJorge Lopez 		return -EINVAL;
936e9b8992SJorge Lopez 
946e9b8992SJorge Lopez 	if (instance > 2)
956e9b8992SJorge Lopez 		return -ENODEV;
966e9b8992SJorge Lopez 
976e9b8992SJorge Lopez 	switch (instance) {
986e9b8992SJorge Lopez 	case TC1100_INSTANCE_WIRELESS:
996e9b8992SJorge Lopez 		value = (*in) ? 1 : 2;
1006e9b8992SJorge Lopez 		break;
1016e9b8992SJorge Lopez 	case TC1100_INSTANCE_JOGDIAL:
1026e9b8992SJorge Lopez 		value = (*in) ? 0 : 1;
1036e9b8992SJorge Lopez 		break;
1046e9b8992SJorge Lopez 	default:
1056e9b8992SJorge Lopez 		return -ENODEV;
1066e9b8992SJorge Lopez 	}
1076e9b8992SJorge Lopez 
1086e9b8992SJorge Lopez 	input.length = sizeof(u32);
1096e9b8992SJorge Lopez 	input.pointer = &value;
1106e9b8992SJorge Lopez 
1116e9b8992SJorge Lopez 	status = wmi_set_block(GUID, instance, &input);
1126e9b8992SJorge Lopez 	if (ACPI_FAILURE(status))
1136e9b8992SJorge Lopez 		return -ENODEV;
1146e9b8992SJorge Lopez 
1156e9b8992SJorge Lopez 	return 0;
1166e9b8992SJorge Lopez }
1176e9b8992SJorge Lopez 
1186e9b8992SJorge Lopez /* --------------------------------------------------------------------------
1196e9b8992SJorge Lopez 				FS Interface (/sys)
1206e9b8992SJorge Lopez    -------------------------------------------------------------------------- */
1216e9b8992SJorge Lopez 
1226e9b8992SJorge Lopez /*
1236e9b8992SJorge Lopez  * Read/ write bool sysfs macro
1246e9b8992SJorge Lopez  */
1256e9b8992SJorge Lopez #define show_set_bool(value, instance) \
1266e9b8992SJorge Lopez static ssize_t \
1276e9b8992SJorge Lopez show_bool_##value(struct device *dev, struct device_attribute *attr, \
1286e9b8992SJorge Lopez 	char *buf) \
1296e9b8992SJorge Lopez { \
1306e9b8992SJorge Lopez 	u32 result; \
1316e9b8992SJorge Lopez 	acpi_status status = get_state(&result, instance); \
1326e9b8992SJorge Lopez 	if (ACPI_SUCCESS(status)) \
1336e9b8992SJorge Lopez 		return sprintf(buf, "%d\n", result); \
1346e9b8992SJorge Lopez 	return sprintf(buf, "Read error\n"); \
1356e9b8992SJorge Lopez } \
1366e9b8992SJorge Lopez \
1376e9b8992SJorge Lopez static ssize_t \
1386e9b8992SJorge Lopez set_bool_##value(struct device *dev, struct device_attribute *attr, \
1396e9b8992SJorge Lopez 	const char *buf, size_t count) \
1406e9b8992SJorge Lopez { \
1416e9b8992SJorge Lopez 	u32 tmp = simple_strtoul(buf, NULL, 10); \
1426e9b8992SJorge Lopez 	acpi_status status = set_state(&tmp, instance); \
1436e9b8992SJorge Lopez 		if (ACPI_FAILURE(status)) \
1446e9b8992SJorge Lopez 			return -EINVAL; \
1456e9b8992SJorge Lopez 	return count; \
1466e9b8992SJorge Lopez } \
1476e9b8992SJorge Lopez static DEVICE_ATTR(value, S_IRUGO | S_IWUSR, \
1486e9b8992SJorge Lopez 	show_bool_##value, set_bool_##value);
1496e9b8992SJorge Lopez 
1506e9b8992SJorge Lopez show_set_bool(wireless, TC1100_INSTANCE_WIRELESS);
1516e9b8992SJorge Lopez show_set_bool(jogdial, TC1100_INSTANCE_JOGDIAL);
1526e9b8992SJorge Lopez 
1536e9b8992SJorge Lopez static struct attribute *tc1100_attributes[] = {
1546e9b8992SJorge Lopez 	&dev_attr_wireless.attr,
1556e9b8992SJorge Lopez 	&dev_attr_jogdial.attr,
1566e9b8992SJorge Lopez 	NULL
1576e9b8992SJorge Lopez };
1586e9b8992SJorge Lopez 
1596e9b8992SJorge Lopez static const struct attribute_group tc1100_attribute_group = {
1606e9b8992SJorge Lopez 	.attrs	= tc1100_attributes,
1616e9b8992SJorge Lopez };
1626e9b8992SJorge Lopez 
1636e9b8992SJorge Lopez /* --------------------------------------------------------------------------
1646e9b8992SJorge Lopez 				Driver Model
1656e9b8992SJorge Lopez    -------------------------------------------------------------------------- */
1666e9b8992SJorge Lopez 
1676e9b8992SJorge Lopez static int __init tc1100_probe(struct platform_device *device)
1686e9b8992SJorge Lopez {
1696e9b8992SJorge Lopez 	return sysfs_create_group(&device->dev.kobj, &tc1100_attribute_group);
1706e9b8992SJorge Lopez }
1716e9b8992SJorge Lopez 
1726e9b8992SJorge Lopez 
173fbc0e1e6SUwe Kleine-König static void tc1100_remove(struct platform_device *device)
1746e9b8992SJorge Lopez {
1756e9b8992SJorge Lopez 	sysfs_remove_group(&device->dev.kobj, &tc1100_attribute_group);
1766e9b8992SJorge Lopez }
1776e9b8992SJorge Lopez 
1786e9b8992SJorge Lopez #ifdef CONFIG_PM
1796e9b8992SJorge Lopez static int tc1100_suspend(struct device *dev)
1806e9b8992SJorge Lopez {
1816e9b8992SJorge Lopez 	int ret;
1826e9b8992SJorge Lopez 
1836e9b8992SJorge Lopez 	ret = get_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS);
1846e9b8992SJorge Lopez 	if (ret)
1856e9b8992SJorge Lopez 		return ret;
1866e9b8992SJorge Lopez 
1876e9b8992SJorge Lopez 	ret = get_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL);
1886e9b8992SJorge Lopez 	if (ret)
1896e9b8992SJorge Lopez 		return ret;
1906e9b8992SJorge Lopez 
1916e9b8992SJorge Lopez 	return 0;
1926e9b8992SJorge Lopez }
1936e9b8992SJorge Lopez 
1946e9b8992SJorge Lopez static int tc1100_resume(struct device *dev)
1956e9b8992SJorge Lopez {
1966e9b8992SJorge Lopez 	int ret;
1976e9b8992SJorge Lopez 
1986e9b8992SJorge Lopez 	ret = set_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS);
1996e9b8992SJorge Lopez 	if (ret)
2006e9b8992SJorge Lopez 		return ret;
2016e9b8992SJorge Lopez 
2026e9b8992SJorge Lopez 	ret = set_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL);
2036e9b8992SJorge Lopez 	if (ret)
2046e9b8992SJorge Lopez 		return ret;
2056e9b8992SJorge Lopez 
2066e9b8992SJorge Lopez 	return 0;
2076e9b8992SJorge Lopez }
2086e9b8992SJorge Lopez 
2096e9b8992SJorge Lopez static const struct dev_pm_ops tc1100_pm_ops = {
2106e9b8992SJorge Lopez 	.suspend	= tc1100_suspend,
2116e9b8992SJorge Lopez 	.resume		= tc1100_resume,
2126e9b8992SJorge Lopez 	.freeze		= tc1100_suspend,
2136e9b8992SJorge Lopez 	.restore	= tc1100_resume,
2146e9b8992SJorge Lopez };
2156e9b8992SJorge Lopez #endif
2166e9b8992SJorge Lopez 
2176e9b8992SJorge Lopez static struct platform_driver tc1100_driver = {
2186e9b8992SJorge Lopez 	.driver = {
2196e9b8992SJorge Lopez 		.name = "tc1100-wmi",
2206e9b8992SJorge Lopez #ifdef CONFIG_PM
2216e9b8992SJorge Lopez 		.pm = &tc1100_pm_ops,
2226e9b8992SJorge Lopez #endif
2236e9b8992SJorge Lopez 	},
224*3ea5eb68SUwe Kleine-König 	.remove = tc1100_remove,
2256e9b8992SJorge Lopez };
2266e9b8992SJorge Lopez 
2276e9b8992SJorge Lopez static int __init tc1100_init(void)
2286e9b8992SJorge Lopez {
2296e9b8992SJorge Lopez 	int error;
2306e9b8992SJorge Lopez 
2316e9b8992SJorge Lopez 	if (!wmi_has_guid(GUID))
2326e9b8992SJorge Lopez 		return -ENODEV;
2336e9b8992SJorge Lopez 
2346e9b8992SJorge Lopez 	tc1100_device = platform_device_alloc("tc1100-wmi", PLATFORM_DEVID_NONE);
2356e9b8992SJorge Lopez 	if (!tc1100_device)
2366e9b8992SJorge Lopez 		return -ENOMEM;
2376e9b8992SJorge Lopez 
2386e9b8992SJorge Lopez 	error = platform_device_add(tc1100_device);
2396e9b8992SJorge Lopez 	if (error)
2406e9b8992SJorge Lopez 		goto err_device_put;
2416e9b8992SJorge Lopez 
2426e9b8992SJorge Lopez 	error = platform_driver_probe(&tc1100_driver, tc1100_probe);
2436e9b8992SJorge Lopez 	if (error)
2446e9b8992SJorge Lopez 		goto err_device_del;
2456e9b8992SJorge Lopez 
2466e9b8992SJorge Lopez 	pr_info("HP Compaq TC1100 Tablet WMI Extras loaded\n");
2476e9b8992SJorge Lopez 	return 0;
2486e9b8992SJorge Lopez 
2496e9b8992SJorge Lopez  err_device_del:
2506e9b8992SJorge Lopez 	platform_device_del(tc1100_device);
2516e9b8992SJorge Lopez  err_device_put:
2526e9b8992SJorge Lopez 	platform_device_put(tc1100_device);
2536e9b8992SJorge Lopez 	return error;
2546e9b8992SJorge Lopez }
2556e9b8992SJorge Lopez 
2566e9b8992SJorge Lopez static void __exit tc1100_exit(void)
2576e9b8992SJorge Lopez {
2586e9b8992SJorge Lopez 	platform_device_unregister(tc1100_device);
2596e9b8992SJorge Lopez 	platform_driver_unregister(&tc1100_driver);
2606e9b8992SJorge Lopez }
2616e9b8992SJorge Lopez 
2626e9b8992SJorge Lopez module_init(tc1100_init);
2636e9b8992SJorge Lopez module_exit(tc1100_exit);
264