Commit | Line | Data |
---|---|---|
f6a6bbae JPRV |
1 | /* |
2 | * Asus Wireless Radio Control Driver | |
3 | * | |
4 | * Copyright (C) 2015-2016 Endless Mobile, Inc. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | */ | |
10 | ||
11 | #include <linux/kernel.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/init.h> | |
14 | #include <linux/types.h> | |
15 | #include <linux/acpi.h> | |
16 | #include <linux/input.h> | |
17 | #include <linux/pci_ids.h> | |
2c1a49c9 JPRV |
18 | #include <linux/leds.h> |
19 | ||
4b7fb9fc JPRV |
20 | struct hswc_params { |
21 | u8 on; | |
22 | u8 off; | |
23 | u8 status; | |
24 | }; | |
f6a6bbae JPRV |
25 | |
26 | struct asus_wireless_data { | |
27 | struct input_dev *idev; | |
2c1a49c9 | 28 | struct acpi_device *adev; |
4b7fb9fc | 29 | const struct hswc_params *hswc_params; |
2c1a49c9 JPRV |
30 | struct workqueue_struct *wq; |
31 | struct work_struct led_work; | |
32 | struct led_classdev led; | |
33 | int led_state; | |
f6a6bbae JPRV |
34 | }; |
35 | ||
4b7fb9fc JPRV |
36 | static const struct hswc_params atk4001_id_params = { |
37 | .on = 0x0, | |
38 | .off = 0x1, | |
39 | .status = 0x2, | |
40 | }; | |
41 | ||
42 | static const struct hswc_params atk4002_id_params = { | |
43 | .on = 0x5, | |
44 | .off = 0x4, | |
45 | .status = 0x2, | |
46 | }; | |
47 | ||
48 | static const struct acpi_device_id device_ids[] = { | |
49 | {"ATK4001", (kernel_ulong_t)&atk4001_id_params}, | |
50 | {"ATK4002", (kernel_ulong_t)&atk4002_id_params}, | |
51 | {"", 0}, | |
52 | }; | |
53 | MODULE_DEVICE_TABLE(acpi, device_ids); | |
54 | ||
2c1a49c9 JPRV |
55 | static u64 asus_wireless_method(acpi_handle handle, const char *method, |
56 | int param) | |
57 | { | |
58 | struct acpi_object_list p; | |
59 | union acpi_object obj; | |
60 | acpi_status s; | |
61 | u64 ret; | |
62 | ||
63 | acpi_handle_debug(handle, "Evaluating method %s, parameter %#x\n", | |
64 | method, param); | |
65 | obj.type = ACPI_TYPE_INTEGER; | |
66 | obj.integer.value = param; | |
67 | p.count = 1; | |
68 | p.pointer = &obj; | |
69 | ||
70 | s = acpi_evaluate_integer(handle, (acpi_string) method, &p, &ret); | |
71 | if (ACPI_FAILURE(s)) | |
72 | acpi_handle_err(handle, | |
73 | "Failed to eval method %s, param %#x (%d)\n", | |
74 | method, param, s); | |
75 | acpi_handle_debug(handle, "%s returned %#x\n", method, (uint) ret); | |
76 | return ret; | |
77 | } | |
78 | ||
79 | static enum led_brightness led_state_get(struct led_classdev *led) | |
80 | { | |
81 | struct asus_wireless_data *data; | |
82 | int s; | |
83 | ||
84 | data = container_of(led, struct asus_wireless_data, led); | |
85 | s = asus_wireless_method(acpi_device_handle(data->adev), "HSWC", | |
4b7fb9fc JPRV |
86 | data->hswc_params->status); |
87 | if (s == data->hswc_params->on) | |
2c1a49c9 JPRV |
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 | ||
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); | |
99 | } | |
100 | ||
4ac20e62 | 101 | static void led_state_set(struct led_classdev *led, enum led_brightness value) |
2c1a49c9 JPRV |
102 | { |
103 | struct asus_wireless_data *data; | |
104 | ||
105 | data = container_of(led, struct asus_wireless_data, led); | |
4b7fb9fc JPRV |
106 | data->led_state = value == LED_OFF ? data->hswc_params->off : |
107 | data->hswc_params->on; | |
2c1a49c9 JPRV |
108 | queue_work(data->wq, &data->led_work); |
109 | } | |
110 | ||
f6a6bbae JPRV |
111 | static void asus_wireless_notify(struct acpi_device *adev, u32 event) |
112 | { | |
113 | struct asus_wireless_data *data = acpi_driver_data(adev); | |
114 | ||
115 | dev_dbg(&adev->dev, "event=%#x\n", event); | |
116 | if (event != 0x88) { | |
117 | dev_notice(&adev->dev, "Unknown ASHS event: %#x\n", event); | |
118 | return; | |
119 | } | |
120 | input_report_key(data->idev, KEY_RFKILL, 1); | |
121 | input_report_key(data->idev, KEY_RFKILL, 0); | |
122 | input_sync(data->idev); | |
123 | } | |
124 | ||
125 | static int asus_wireless_add(struct acpi_device *adev) | |
126 | { | |
127 | struct asus_wireless_data *data; | |
4b7fb9fc | 128 | const struct acpi_device_id *id; |
2c1a49c9 | 129 | int err; |
f6a6bbae JPRV |
130 | |
131 | data = devm_kzalloc(&adev->dev, sizeof(*data), GFP_KERNEL); | |
132 | if (!data) | |
133 | return -ENOMEM; | |
134 | adev->driver_data = data; | |
4b7fb9fc | 135 | data->adev = adev; |
f6a6bbae JPRV |
136 | |
137 | data->idev = devm_input_allocate_device(&adev->dev); | |
138 | if (!data->idev) | |
139 | return -ENOMEM; | |
140 | data->idev->name = "Asus Wireless Radio Control"; | |
141 | data->idev->phys = "asus-wireless/input0"; | |
142 | data->idev->id.bustype = BUS_HOST; | |
143 | data->idev->id.vendor = PCI_VENDOR_ID_ASUSTEK; | |
144 | set_bit(EV_KEY, data->idev->evbit); | |
145 | set_bit(KEY_RFKILL, data->idev->keybit); | |
2c1a49c9 JPRV |
146 | err = input_register_device(data->idev); |
147 | if (err) | |
148 | return err; | |
149 | ||
4b7fb9fc JPRV |
150 | for (id = device_ids; id->id[0]; id++) { |
151 | if (!strcmp((char *) id->id, acpi_device_hid(adev))) { | |
152 | data->hswc_params = | |
153 | (const struct hswc_params *)id->driver_data; | |
154 | break; | |
155 | } | |
156 | } | |
157 | if (!data->hswc_params) | |
158 | return 0; | |
159 | ||
2c1a49c9 JPRV |
160 | data->wq = create_singlethread_workqueue("asus_wireless_workqueue"); |
161 | if (!data->wq) | |
162 | return -ENOMEM; | |
163 | INIT_WORK(&data->led_work, led_state_update); | |
164 | data->led.name = "asus-wireless::airplane"; | |
165 | data->led.brightness_set = led_state_set; | |
166 | data->led.brightness_get = led_state_get; | |
167 | data->led.flags = LED_CORE_SUSPENDRESUME; | |
168 | data->led.max_brightness = 1; | |
169 | err = devm_led_classdev_register(&adev->dev, &data->led); | |
170 | if (err) | |
171 | destroy_workqueue(data->wq); | |
4b7fb9fc | 172 | |
2c1a49c9 | 173 | return err; |
f6a6bbae JPRV |
174 | } |
175 | ||
176 | static int asus_wireless_remove(struct acpi_device *adev) | |
177 | { | |
2c1a49c9 JPRV |
178 | struct asus_wireless_data *data = acpi_driver_data(adev); |
179 | ||
180 | if (data->wq) | |
181 | destroy_workqueue(data->wq); | |
f6a6bbae JPRV |
182 | return 0; |
183 | } | |
184 | ||
f6a6bbae JPRV |
185 | static struct acpi_driver asus_wireless_driver = { |
186 | .name = "Asus Wireless Radio Control Driver", | |
187 | .class = "hotkey", | |
188 | .ids = device_ids, | |
189 | .ops = { | |
190 | .add = asus_wireless_add, | |
191 | .remove = asus_wireless_remove, | |
192 | .notify = asus_wireless_notify, | |
193 | }, | |
194 | }; | |
195 | module_acpi_driver(asus_wireless_driver); | |
196 | ||
197 | MODULE_DESCRIPTION("Asus Wireless Radio Control Driver"); | |
198 | MODULE_AUTHOR("João Paulo Rechi Vita <jprvita@gmail.com>"); | |
199 | MODULE_LICENSE("GPL"); |