xref: /linux/drivers/platform/x86/asus-wireless.c (revision 5ea5880764cbb164afb17a62e76ca75dc371409d)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Asus Wireless Radio Control Driver
4  *
5  * Copyright (C) 2015-2016 Endless Mobile, Inc.
6  */
7 
8 #include <linux/kernel.h>
9 #include <linux/module.h>
10 #include <linux/init.h>
11 #include <linux/types.h>
12 #include <linux/acpi.h>
13 #include <linux/input.h>
14 #include <linux/pci_ids.h>
15 #include <linux/platform_device.h>
16 #include <linux/leds.h>
17 
18 struct hswc_params {
19 	u8 on;
20 	u8 off;
21 	u8 status;
22 };
23 
24 struct asus_wireless_data {
25 	struct input_dev *idev;
26 	struct acpi_device *adev;
27 	const struct hswc_params *hswc_params;
28 	struct workqueue_struct *wq;
29 	struct work_struct led_work;
30 	struct led_classdev led;
31 	int led_state;
32 };
33 
34 static const struct hswc_params atk4001_id_params = {
35 	.on = 0x0,
36 	.off = 0x1,
37 	.status = 0x2,
38 };
39 
40 static const struct hswc_params atk4002_id_params = {
41 	.on = 0x5,
42 	.off = 0x4,
43 	.status = 0x2,
44 };
45 
46 static const struct acpi_device_id device_ids[] = {
47 	{"ATK4001", (kernel_ulong_t)&atk4001_id_params},
48 	{"ATK4002", (kernel_ulong_t)&atk4002_id_params},
49 	{"", 0},
50 };
51 MODULE_DEVICE_TABLE(acpi, device_ids);
52 
53 static acpi_status asus_wireless_method(acpi_handle handle, const char *method,
54 					int param, u64 *ret)
55 {
56 	struct acpi_object_list p;
57 	union acpi_object obj;
58 	acpi_status s;
59 
60 	acpi_handle_debug(handle, "Evaluating method %s, parameter %#x\n",
61 			  method, param);
62 	obj.type = ACPI_TYPE_INTEGER;
63 	obj.integer.value = param;
64 	p.count = 1;
65 	p.pointer = &obj;
66 
67 	s = acpi_evaluate_integer(handle, (acpi_string) method, &p, ret);
68 	if (ACPI_FAILURE(s))
69 		acpi_handle_err(handle,
70 				"Failed to eval method %s, param %#x (%d)\n",
71 				method, param, s);
72 	else
73 		acpi_handle_debug(handle, "%s returned %#llx\n", method, *ret);
74 
75 	return s;
76 }
77 
78 static enum led_brightness led_state_get(struct led_classdev *led)
79 {
80 	struct asus_wireless_data *data;
81 	acpi_status s;
82 	u64 ret;
83 
84 	data = container_of(led, struct asus_wireless_data, led);
85 	s = asus_wireless_method(acpi_device_handle(data->adev), "HSWC",
86 				 data->hswc_params->status, &ret);
87 	if (ACPI_SUCCESS(s) && ret == data->hswc_params->on)
88 		return LED_FULL;
89 	return LED_OFF;
90 }
91 
92 static void led_state_update(struct work_struct *work)
93 {
94 	struct asus_wireless_data *data;
95 	u64 ret;
96 
97 	data = container_of(work, struct asus_wireless_data, led_work);
98 	asus_wireless_method(acpi_device_handle(data->adev), "HSWC",
99 			     data->led_state, &ret);
100 }
101 
102 static void led_state_set(struct led_classdev *led, enum led_brightness value)
103 {
104 	struct asus_wireless_data *data;
105 
106 	data = container_of(led, struct asus_wireless_data, led);
107 	data->led_state = value == LED_OFF ? data->hswc_params->off :
108 					     data->hswc_params->on;
109 	queue_work(data->wq, &data->led_work);
110 }
111 
112 static void asus_wireless_notify(acpi_handle handle, u32 event, void *context)
113 {
114 	struct asus_wireless_data *data = context;
115 	struct acpi_device *adev = data->adev;
116 
117 	dev_dbg(&adev->dev, "event=%#x\n", event);
118 	if (event != 0x88) {
119 		dev_notice(&adev->dev, "Unknown ASHS event: %#x\n", event);
120 		return;
121 	}
122 	input_report_key(data->idev, KEY_RFKILL, 1);
123 	input_sync(data->idev);
124 	input_report_key(data->idev, KEY_RFKILL, 0);
125 	input_sync(data->idev);
126 }
127 
128 static int asus_wireless_probe(struct platform_device *pdev)
129 {
130 	struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
131 	struct asus_wireless_data *data;
132 	const struct acpi_device_id *id;
133 	int err;
134 
135 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
136 	if (!data)
137 		return -ENOMEM;
138 
139 	platform_set_drvdata(pdev, data);
140 
141 	data->adev = adev;
142 
143 	data->idev = devm_input_allocate_device(&pdev->dev);
144 	if (!data->idev)
145 		return -ENOMEM;
146 	data->idev->name = "Asus Wireless Radio Control";
147 	data->idev->phys = "asus-wireless/input0";
148 	data->idev->id.bustype = BUS_HOST;
149 	data->idev->id.vendor = PCI_VENDOR_ID_ASUSTEK;
150 	set_bit(EV_KEY, data->idev->evbit);
151 	set_bit(KEY_RFKILL, data->idev->keybit);
152 	err = input_register_device(data->idev);
153 	if (err)
154 		return err;
155 
156 	id = acpi_match_acpi_device(device_ids, adev);
157 	if (!id)
158 		return 0;
159 
160 	data->hswc_params = (const struct hswc_params *)id->driver_data;
161 
162 	data->wq = create_singlethread_workqueue("asus_wireless_workqueue");
163 	if (!data->wq)
164 		return -ENOMEM;
165 	INIT_WORK(&data->led_work, led_state_update);
166 	data->led.name = "asus-wireless::airplane";
167 	data->led.brightness_set = led_state_set;
168 	data->led.brightness_get = led_state_get;
169 	data->led.flags = LED_CORE_SUSPENDRESUME;
170 	data->led.max_brightness = 1;
171 	data->led.default_trigger = "rfkill-none";
172 	err = devm_led_classdev_register(&pdev->dev, &data->led);
173 	if (err)
174 		goto err;
175 
176 	err = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY,
177 					      asus_wireless_notify, data);
178 	if (err) {
179 		devm_led_classdev_unregister(&pdev->dev, &data->led);
180 		goto err;
181 	}
182 	return 0;
183 
184 err:
185 	destroy_workqueue(data->wq);
186 	return err;
187 }
188 
189 static void asus_wireless_remove(struct platform_device *pdev)
190 {
191 	struct asus_wireless_data *data = platform_get_drvdata(pdev);
192 
193 	acpi_dev_remove_notify_handler(data->adev, ACPI_DEVICE_NOTIFY,
194 				       asus_wireless_notify);
195 	if (data->wq) {
196 		devm_led_classdev_unregister(&pdev->dev, &data->led);
197 		destroy_workqueue(data->wq);
198 	}
199 }
200 
201 static struct platform_driver asus_wireless_driver = {
202 	.probe = asus_wireless_probe,
203 	.remove = asus_wireless_remove,
204 	.driver = {
205 		.name = "Asus Wireless Radio Control Driver",
206 		.acpi_match_table = device_ids,
207 	},
208 };
209 module_platform_driver(asus_wireless_driver);
210 
211 MODULE_DESCRIPTION("Asus Wireless Radio Control Driver");
212 MODULE_AUTHOR("João Paulo Rechi Vita <jprvita@gmail.com>");
213 MODULE_LICENSE("GPL");
214