xref: /openbmc/linux/drivers/platform/x86/asus-wireless.c (revision 7ae9fb1b7ecbb5d85d07857943f677fd1a559b18)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2f6a6bbaeSJoão Paulo Rechi Vita /*
3f6a6bbaeSJoão Paulo Rechi Vita  * Asus Wireless Radio Control Driver
4f6a6bbaeSJoão Paulo Rechi Vita  *
5f6a6bbaeSJoão Paulo Rechi Vita  * Copyright (C) 2015-2016 Endless Mobile, Inc.
6f6a6bbaeSJoão Paulo Rechi Vita  */
7f6a6bbaeSJoão Paulo Rechi Vita 
8f6a6bbaeSJoão Paulo Rechi Vita #include <linux/kernel.h>
9f6a6bbaeSJoão Paulo Rechi Vita #include <linux/module.h>
10f6a6bbaeSJoão Paulo Rechi Vita #include <linux/init.h>
11f6a6bbaeSJoão Paulo Rechi Vita #include <linux/types.h>
12f6a6bbaeSJoão Paulo Rechi Vita #include <linux/acpi.h>
13f6a6bbaeSJoão Paulo Rechi Vita #include <linux/input.h>
14f6a6bbaeSJoão Paulo Rechi Vita #include <linux/pci_ids.h>
152c1a49c9SJoão Paulo Rechi Vita #include <linux/leds.h>
162c1a49c9SJoão Paulo Rechi Vita 
174b7fb9fcSJoão Paulo Rechi Vita struct hswc_params {
184b7fb9fcSJoão Paulo Rechi Vita 	u8 on;
194b7fb9fcSJoão Paulo Rechi Vita 	u8 off;
204b7fb9fcSJoão Paulo Rechi Vita 	u8 status;
214b7fb9fcSJoão Paulo Rechi Vita };
22f6a6bbaeSJoão Paulo Rechi Vita 
23f6a6bbaeSJoão Paulo Rechi Vita struct asus_wireless_data {
24f6a6bbaeSJoão Paulo Rechi Vita 	struct input_dev *idev;
252c1a49c9SJoão Paulo Rechi Vita 	struct acpi_device *adev;
264b7fb9fcSJoão Paulo Rechi Vita 	const struct hswc_params *hswc_params;
272c1a49c9SJoão Paulo Rechi Vita 	struct workqueue_struct *wq;
282c1a49c9SJoão Paulo Rechi Vita 	struct work_struct led_work;
292c1a49c9SJoão Paulo Rechi Vita 	struct led_classdev led;
302c1a49c9SJoão Paulo Rechi Vita 	int led_state;
31f6a6bbaeSJoão Paulo Rechi Vita };
32f6a6bbaeSJoão Paulo Rechi Vita 
334b7fb9fcSJoão Paulo Rechi Vita static const struct hswc_params atk4001_id_params = {
344b7fb9fcSJoão Paulo Rechi Vita 	.on = 0x0,
354b7fb9fcSJoão Paulo Rechi Vita 	.off = 0x1,
364b7fb9fcSJoão Paulo Rechi Vita 	.status = 0x2,
374b7fb9fcSJoão Paulo Rechi Vita };
384b7fb9fcSJoão Paulo Rechi Vita 
394b7fb9fcSJoão Paulo Rechi Vita static const struct hswc_params atk4002_id_params = {
404b7fb9fcSJoão Paulo Rechi Vita 	.on = 0x5,
414b7fb9fcSJoão Paulo Rechi Vita 	.off = 0x4,
424b7fb9fcSJoão Paulo Rechi Vita 	.status = 0x2,
434b7fb9fcSJoão Paulo Rechi Vita };
444b7fb9fcSJoão Paulo Rechi Vita 
454b7fb9fcSJoão Paulo Rechi Vita static const struct acpi_device_id device_ids[] = {
464b7fb9fcSJoão Paulo Rechi Vita 	{"ATK4001", (kernel_ulong_t)&atk4001_id_params},
474b7fb9fcSJoão Paulo Rechi Vita 	{"ATK4002", (kernel_ulong_t)&atk4002_id_params},
484b7fb9fcSJoão Paulo Rechi Vita 	{"", 0},
494b7fb9fcSJoão Paulo Rechi Vita };
504b7fb9fcSJoão Paulo Rechi Vita MODULE_DEVICE_TABLE(acpi, device_ids);
514b7fb9fcSJoão Paulo Rechi Vita 
asus_wireless_method(acpi_handle handle,const char * method,int param,u64 * ret)52eca4c4e4SJoão Paulo Rechi Vita static acpi_status asus_wireless_method(acpi_handle handle, const char *method,
53eca4c4e4SJoão Paulo Rechi Vita 					int param, u64 *ret)
542c1a49c9SJoão Paulo Rechi Vita {
552c1a49c9SJoão Paulo Rechi Vita 	struct acpi_object_list p;
562c1a49c9SJoão Paulo Rechi Vita 	union acpi_object obj;
572c1a49c9SJoão Paulo Rechi Vita 	acpi_status s;
582c1a49c9SJoão Paulo Rechi Vita 
592c1a49c9SJoão Paulo Rechi Vita 	acpi_handle_debug(handle, "Evaluating method %s, parameter %#x\n",
602c1a49c9SJoão Paulo Rechi Vita 			  method, param);
612c1a49c9SJoão Paulo Rechi Vita 	obj.type = ACPI_TYPE_INTEGER;
622c1a49c9SJoão Paulo Rechi Vita 	obj.integer.value = param;
632c1a49c9SJoão Paulo Rechi Vita 	p.count = 1;
642c1a49c9SJoão Paulo Rechi Vita 	p.pointer = &obj;
652c1a49c9SJoão Paulo Rechi Vita 
66eca4c4e4SJoão Paulo Rechi Vita 	s = acpi_evaluate_integer(handle, (acpi_string) method, &p, ret);
672c1a49c9SJoão Paulo Rechi Vita 	if (ACPI_FAILURE(s))
682c1a49c9SJoão Paulo Rechi Vita 		acpi_handle_err(handle,
692c1a49c9SJoão Paulo Rechi Vita 				"Failed to eval method %s, param %#x (%d)\n",
702c1a49c9SJoão Paulo Rechi Vita 				method, param, s);
71eca4c4e4SJoão Paulo Rechi Vita 	else
72eca4c4e4SJoão Paulo Rechi Vita 		acpi_handle_debug(handle, "%s returned %#llx\n", method, *ret);
73eca4c4e4SJoão Paulo Rechi Vita 
74eca4c4e4SJoão Paulo Rechi Vita 	return s;
752c1a49c9SJoão Paulo Rechi Vita }
762c1a49c9SJoão Paulo Rechi Vita 
led_state_get(struct led_classdev * led)772c1a49c9SJoão Paulo Rechi Vita static enum led_brightness led_state_get(struct led_classdev *led)
782c1a49c9SJoão Paulo Rechi Vita {
792c1a49c9SJoão Paulo Rechi Vita 	struct asus_wireless_data *data;
80eca4c4e4SJoão Paulo Rechi Vita 	acpi_status s;
81eca4c4e4SJoão Paulo Rechi Vita 	u64 ret;
822c1a49c9SJoão Paulo Rechi Vita 
832c1a49c9SJoão Paulo Rechi Vita 	data = container_of(led, struct asus_wireless_data, led);
842c1a49c9SJoão Paulo Rechi Vita 	s = asus_wireless_method(acpi_device_handle(data->adev), "HSWC",
85eca4c4e4SJoão Paulo Rechi Vita 				 data->hswc_params->status, &ret);
86eca4c4e4SJoão Paulo Rechi Vita 	if (ACPI_SUCCESS(s) && ret == data->hswc_params->on)
872c1a49c9SJoão Paulo Rechi Vita 		return LED_FULL;
882c1a49c9SJoão Paulo Rechi Vita 	return LED_OFF;
892c1a49c9SJoão Paulo Rechi Vita }
902c1a49c9SJoão Paulo Rechi Vita 
led_state_update(struct work_struct * work)912c1a49c9SJoão Paulo Rechi Vita static void led_state_update(struct work_struct *work)
922c1a49c9SJoão Paulo Rechi Vita {
932c1a49c9SJoão Paulo Rechi Vita 	struct asus_wireless_data *data;
94eca4c4e4SJoão Paulo Rechi Vita 	u64 ret;
952c1a49c9SJoão Paulo Rechi Vita 
962c1a49c9SJoão Paulo Rechi Vita 	data = container_of(work, struct asus_wireless_data, led_work);
972c1a49c9SJoão Paulo Rechi Vita 	asus_wireless_method(acpi_device_handle(data->adev), "HSWC",
98eca4c4e4SJoão Paulo Rechi Vita 			     data->led_state, &ret);
992c1a49c9SJoão Paulo Rechi Vita }
1002c1a49c9SJoão Paulo Rechi Vita 
led_state_set(struct led_classdev * led,enum led_brightness value)1014ac20e62SJoão Paulo Rechi Vita static void led_state_set(struct led_classdev *led, enum led_brightness value)
1022c1a49c9SJoão Paulo Rechi Vita {
1032c1a49c9SJoão Paulo Rechi Vita 	struct asus_wireless_data *data;
1042c1a49c9SJoão Paulo Rechi Vita 
1052c1a49c9SJoão Paulo Rechi Vita 	data = container_of(led, struct asus_wireless_data, led);
1064b7fb9fcSJoão Paulo Rechi Vita 	data->led_state = value == LED_OFF ? data->hswc_params->off :
1074b7fb9fcSJoão Paulo Rechi Vita 					     data->hswc_params->on;
1082c1a49c9SJoão Paulo Rechi Vita 	queue_work(data->wq, &data->led_work);
1092c1a49c9SJoão Paulo Rechi Vita }
1102c1a49c9SJoão Paulo Rechi Vita 
asus_wireless_notify(struct acpi_device * adev,u32 event)111f6a6bbaeSJoão Paulo Rechi Vita static void asus_wireless_notify(struct acpi_device *adev, u32 event)
112f6a6bbaeSJoão Paulo Rechi Vita {
113f6a6bbaeSJoão Paulo Rechi Vita 	struct asus_wireless_data *data = acpi_driver_data(adev);
114f6a6bbaeSJoão Paulo Rechi Vita 
115f6a6bbaeSJoão Paulo Rechi Vita 	dev_dbg(&adev->dev, "event=%#x\n", event);
116f6a6bbaeSJoão Paulo Rechi Vita 	if (event != 0x88) {
117f6a6bbaeSJoão Paulo Rechi Vita 		dev_notice(&adev->dev, "Unknown ASHS event: %#x\n", event);
118f6a6bbaeSJoão Paulo Rechi Vita 		return;
119f6a6bbaeSJoão Paulo Rechi Vita 	}
120f6a6bbaeSJoão Paulo Rechi Vita 	input_report_key(data->idev, KEY_RFKILL, 1);
121bff5bf9dSPeter Hutterer 	input_sync(data->idev);
122f6a6bbaeSJoão Paulo Rechi Vita 	input_report_key(data->idev, KEY_RFKILL, 0);
123f6a6bbaeSJoão Paulo Rechi Vita 	input_sync(data->idev);
124f6a6bbaeSJoão Paulo Rechi Vita }
125f6a6bbaeSJoão Paulo Rechi Vita 
asus_wireless_add(struct acpi_device * adev)126f6a6bbaeSJoão Paulo Rechi Vita static int asus_wireless_add(struct acpi_device *adev)
127f6a6bbaeSJoão Paulo Rechi Vita {
128f6a6bbaeSJoão Paulo Rechi Vita 	struct asus_wireless_data *data;
1294b7fb9fcSJoão Paulo Rechi Vita 	const struct acpi_device_id *id;
1302c1a49c9SJoão Paulo Rechi Vita 	int err;
131f6a6bbaeSJoão Paulo Rechi Vita 
132f6a6bbaeSJoão Paulo Rechi Vita 	data = devm_kzalloc(&adev->dev, sizeof(*data), GFP_KERNEL);
133f6a6bbaeSJoão Paulo Rechi Vita 	if (!data)
134f6a6bbaeSJoão Paulo Rechi Vita 		return -ENOMEM;
135f6a6bbaeSJoão Paulo Rechi Vita 	adev->driver_data = data;
1364b7fb9fcSJoão Paulo Rechi Vita 	data->adev = adev;
137f6a6bbaeSJoão Paulo Rechi Vita 
138f6a6bbaeSJoão Paulo Rechi Vita 	data->idev = devm_input_allocate_device(&adev->dev);
139f6a6bbaeSJoão Paulo Rechi Vita 	if (!data->idev)
140f6a6bbaeSJoão Paulo Rechi Vita 		return -ENOMEM;
141f6a6bbaeSJoão Paulo Rechi Vita 	data->idev->name = "Asus Wireless Radio Control";
142f6a6bbaeSJoão Paulo Rechi Vita 	data->idev->phys = "asus-wireless/input0";
143f6a6bbaeSJoão Paulo Rechi Vita 	data->idev->id.bustype = BUS_HOST;
144f6a6bbaeSJoão Paulo Rechi Vita 	data->idev->id.vendor = PCI_VENDOR_ID_ASUSTEK;
145f6a6bbaeSJoão Paulo Rechi Vita 	set_bit(EV_KEY, data->idev->evbit);
146f6a6bbaeSJoão Paulo Rechi Vita 	set_bit(KEY_RFKILL, data->idev->keybit);
1472c1a49c9SJoão Paulo Rechi Vita 	err = input_register_device(data->idev);
1482c1a49c9SJoão Paulo Rechi Vita 	if (err)
1492c1a49c9SJoão Paulo Rechi Vita 		return err;
1502c1a49c9SJoão Paulo Rechi Vita 
1514b7fb9fcSJoão Paulo Rechi Vita 	for (id = device_ids; id->id[0]; id++) {
1524b7fb9fcSJoão Paulo Rechi Vita 		if (!strcmp((char *) id->id, acpi_device_hid(adev))) {
1534b7fb9fcSJoão Paulo Rechi Vita 			data->hswc_params =
1544b7fb9fcSJoão Paulo Rechi Vita 				(const struct hswc_params *)id->driver_data;
1554b7fb9fcSJoão Paulo Rechi Vita 			break;
1564b7fb9fcSJoão Paulo Rechi Vita 		}
1574b7fb9fcSJoão Paulo Rechi Vita 	}
1584b7fb9fcSJoão Paulo Rechi Vita 	if (!data->hswc_params)
1594b7fb9fcSJoão Paulo Rechi Vita 		return 0;
1604b7fb9fcSJoão Paulo Rechi Vita 
1612c1a49c9SJoão Paulo Rechi Vita 	data->wq = create_singlethread_workqueue("asus_wireless_workqueue");
1622c1a49c9SJoão Paulo Rechi Vita 	if (!data->wq)
1632c1a49c9SJoão Paulo Rechi Vita 		return -ENOMEM;
1642c1a49c9SJoão Paulo Rechi Vita 	INIT_WORK(&data->led_work, led_state_update);
1652c1a49c9SJoão Paulo Rechi Vita 	data->led.name = "asus-wireless::airplane";
1662c1a49c9SJoão Paulo Rechi Vita 	data->led.brightness_set = led_state_set;
1672c1a49c9SJoão Paulo Rechi Vita 	data->led.brightness_get = led_state_get;
1682c1a49c9SJoão Paulo Rechi Vita 	data->led.flags = LED_CORE_SUSPENDRESUME;
1692c1a49c9SJoão Paulo Rechi Vita 	data->led.max_brightness = 1;
1706bb6ec28SJoão Paulo Rechi Vita 	data->led.default_trigger = "rfkill-none";
1712c1a49c9SJoão Paulo Rechi Vita 	err = devm_led_classdev_register(&adev->dev, &data->led);
1722c1a49c9SJoão Paulo Rechi Vita 	if (err)
1732c1a49c9SJoão Paulo Rechi Vita 		destroy_workqueue(data->wq);
1744b7fb9fcSJoão Paulo Rechi Vita 
1752c1a49c9SJoão Paulo Rechi Vita 	return err;
176f6a6bbaeSJoão Paulo Rechi Vita }
177f6a6bbaeSJoão Paulo Rechi Vita 
asus_wireless_remove(struct acpi_device * adev)178*6c0eb5baSDawei Li static void asus_wireless_remove(struct acpi_device *adev)
179f6a6bbaeSJoão Paulo Rechi Vita {
1802c1a49c9SJoão Paulo Rechi Vita 	struct asus_wireless_data *data = acpi_driver_data(adev);
1812c1a49c9SJoão Paulo Rechi Vita 
18206b8b00bSJoão Paulo Rechi Vita 	if (data->wq) {
18306b8b00bSJoão Paulo Rechi Vita 		devm_led_classdev_unregister(&adev->dev, &data->led);
1842c1a49c9SJoão Paulo Rechi Vita 		destroy_workqueue(data->wq);
18506b8b00bSJoão Paulo Rechi Vita 	}
186f6a6bbaeSJoão Paulo Rechi Vita }
187f6a6bbaeSJoão Paulo Rechi Vita 
188f6a6bbaeSJoão Paulo Rechi Vita static struct acpi_driver asus_wireless_driver = {
189f6a6bbaeSJoão Paulo Rechi Vita 	.name = "Asus Wireless Radio Control Driver",
190f6a6bbaeSJoão Paulo Rechi Vita 	.class = "hotkey",
191f6a6bbaeSJoão Paulo Rechi Vita 	.ids = device_ids,
192f6a6bbaeSJoão Paulo Rechi Vita 	.ops = {
193f6a6bbaeSJoão Paulo Rechi Vita 		.add = asus_wireless_add,
194f6a6bbaeSJoão Paulo Rechi Vita 		.remove = asus_wireless_remove,
195f6a6bbaeSJoão Paulo Rechi Vita 		.notify = asus_wireless_notify,
196f6a6bbaeSJoão Paulo Rechi Vita 	},
197f6a6bbaeSJoão Paulo Rechi Vita };
198f6a6bbaeSJoão Paulo Rechi Vita module_acpi_driver(asus_wireless_driver);
199f6a6bbaeSJoão Paulo Rechi Vita 
200f6a6bbaeSJoão Paulo Rechi Vita MODULE_DESCRIPTION("Asus Wireless Radio Control Driver");
201f6a6bbaeSJoão Paulo Rechi Vita MODULE_AUTHOR("João Paulo Rechi Vita <jprvita@gmail.com>");
202f6a6bbaeSJoão Paulo Rechi Vita MODULE_LICENSE("GPL");
203