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
asus_wireless_method(acpi_handle handle,const char * method,int param,u64 * ret)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
led_state_get(struct led_classdev * led)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
led_state_update(struct work_struct * work)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
led_state_set(struct led_classdev * led,enum led_brightness value)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
asus_wireless_notify(acpi_handle handle,u32 event,void * context)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
asus_wireless_probe(struct platform_device * pdev)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
asus_wireless_remove(struct platform_device * pdev)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