1 // SPDX-License-Identifier: GPL-2.0-only
3 * Asus Wireless Radio Control Driver
5 * Copyright (C) 2015-2016 Endless Mobile, Inc.
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/leds.h>
23 struct asus_wireless_data
{
24 struct input_dev
*idev
;
25 struct acpi_device
*adev
;
26 const struct hswc_params
*hswc_params
;
27 struct workqueue_struct
*wq
;
28 struct work_struct led_work
;
29 struct led_classdev led
;
33 static const struct hswc_params atk4001_id_params
= {
39 static const struct hswc_params atk4002_id_params
= {
45 static const struct acpi_device_id device_ids
[] = {
46 {"ATK4001", (kernel_ulong_t
)&atk4001_id_params
},
47 {"ATK4002", (kernel_ulong_t
)&atk4002_id_params
},
50 MODULE_DEVICE_TABLE(acpi
, device_ids
);
52 static acpi_status
asus_wireless_method(acpi_handle handle
, const char *method
,
55 struct acpi_object_list p
;
56 union acpi_object obj
;
59 acpi_handle_debug(handle
, "Evaluating method %s, parameter %#x\n",
61 obj
.type
= ACPI_TYPE_INTEGER
;
62 obj
.integer
.value
= param
;
66 s
= acpi_evaluate_integer(handle
, (acpi_string
) method
, &p
, ret
);
68 acpi_handle_err(handle
,
69 "Failed to eval method %s, param %#x (%d)\n",
72 acpi_handle_debug(handle
, "%s returned %#llx\n", method
, *ret
);
77 static enum led_brightness
led_state_get(struct led_classdev
*led
)
79 struct asus_wireless_data
*data
;
83 data
= container_of(led
, struct asus_wireless_data
, led
);
84 s
= asus_wireless_method(acpi_device_handle(data
->adev
), "HSWC",
85 data
->hswc_params
->status
, &ret
);
86 if (ACPI_SUCCESS(s
) && ret
== data
->hswc_params
->on
)
91 static void led_state_update(struct work_struct
*work
)
93 struct asus_wireless_data
*data
;
96 data
= container_of(work
, struct asus_wireless_data
, led_work
);
97 asus_wireless_method(acpi_device_handle(data
->adev
), "HSWC",
98 data
->led_state
, &ret
);
101 static void led_state_set(struct led_classdev
*led
, enum led_brightness value
)
103 struct asus_wireless_data
*data
;
105 data
= container_of(led
, struct asus_wireless_data
, led
);
106 data
->led_state
= value
== LED_OFF
? data
->hswc_params
->off
:
107 data
->hswc_params
->on
;
108 queue_work(data
->wq
, &data
->led_work
);
111 static void asus_wireless_notify(struct acpi_device
*adev
, u32 event
)
113 struct asus_wireless_data
*data
= acpi_driver_data(adev
);
115 dev_dbg(&adev
->dev
, "event=%#x\n", event
);
117 dev_notice(&adev
->dev
, "Unknown ASHS event: %#x\n", event
);
120 input_report_key(data
->idev
, KEY_RFKILL
, 1);
121 input_sync(data
->idev
);
122 input_report_key(data
->idev
, KEY_RFKILL
, 0);
123 input_sync(data
->idev
);
126 static int asus_wireless_add(struct acpi_device
*adev
)
128 struct asus_wireless_data
*data
;
129 const struct acpi_device_id
*id
;
132 data
= devm_kzalloc(&adev
->dev
, sizeof(*data
), GFP_KERNEL
);
135 adev
->driver_data
= data
;
138 data
->idev
= devm_input_allocate_device(&adev
->dev
);
141 data
->idev
->name
= "Asus Wireless Radio Control";
142 data
->idev
->phys
= "asus-wireless/input0";
143 data
->idev
->id
.bustype
= BUS_HOST
;
144 data
->idev
->id
.vendor
= PCI_VENDOR_ID_ASUSTEK
;
145 set_bit(EV_KEY
, data
->idev
->evbit
);
146 set_bit(KEY_RFKILL
, data
->idev
->keybit
);
147 err
= input_register_device(data
->idev
);
151 for (id
= device_ids
; id
->id
[0]; id
++) {
152 if (!strcmp((char *) id
->id
, acpi_device_hid(adev
))) {
154 (const struct hswc_params
*)id
->driver_data
;
158 if (!data
->hswc_params
)
161 data
->wq
= create_singlethread_workqueue("asus_wireless_workqueue");
164 INIT_WORK(&data
->led_work
, led_state_update
);
165 data
->led
.name
= "asus-wireless::airplane";
166 data
->led
.brightness_set
= led_state_set
;
167 data
->led
.brightness_get
= led_state_get
;
168 data
->led
.flags
= LED_CORE_SUSPENDRESUME
;
169 data
->led
.max_brightness
= 1;
170 data
->led
.default_trigger
= "rfkill-none";
171 err
= devm_led_classdev_register(&adev
->dev
, &data
->led
);
173 destroy_workqueue(data
->wq
);
178 static int asus_wireless_remove(struct acpi_device
*adev
)
180 struct asus_wireless_data
*data
= acpi_driver_data(adev
);
183 devm_led_classdev_unregister(&adev
->dev
, &data
->led
);
184 destroy_workqueue(data
->wq
);
189 static struct acpi_driver asus_wireless_driver
= {
190 .name
= "Asus Wireless Radio Control Driver",
194 .add
= asus_wireless_add
,
195 .remove
= asus_wireless_remove
,
196 .notify
= asus_wireless_notify
,
199 module_acpi_driver(asus_wireless_driver
);
201 MODULE_DESCRIPTION("Asus Wireless Radio Control Driver");
202 MODULE_AUTHOR("João Paulo Rechi Vita <jprvita@gmail.com>");
203 MODULE_LICENSE("GPL");