Skip to content

Commit 817a5cd

Browse files
palidvhart
authored andcommitted
dell-rbtn: Dell Airplane Mode Switch driver
This is an ACPI driver for Dell laptops which receive HW slider radio switch or hotkey toggle wifi button events. It exports rfkill device dell-rbtn (which provide correct hard rfkill state) or hotkey input device. Alex Hung is author of original hotkey input device code. Signed-off-by: Pali Rohár <[email protected]> Tested-by: Gabriele Mazzotta <[email protected]> Cc: Alex Hung <[email protected]> [[email protected]: rbtn_ops can be static] Signed-off-by: Fengguang Wu <[email protected]> [[email protected]: Correct multi-line comment formatting] Signed-off-by: Darren Hart <[email protected]>
1 parent 9330dcd commit 817a5cd

File tree

4 files changed

+354
-0
lines changed

4 files changed

+354
-0
lines changed

MAINTAINERS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3071,6 +3071,11 @@ L: [email protected]
30713071
S: Maintained
30723072
F: drivers/platform/x86/dell-laptop.c
30733073

3074+
DELL LAPTOP RBTN DRIVER
3075+
M: Pali Rohár <[email protected]>
3076+
S: Maintained
3077+
F: drivers/platform/x86/dell-rbtn.*
3078+
30743079
DELL LAPTOP FREEFALL DRIVER
30753080
M: Pali Rohár <[email protected]>
30763081
S: Maintained

drivers/platform/x86/Kconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,22 @@ config DELL_SMO8800
138138
To compile this driver as a module, choose M here: the module will
139139
be called dell-smo8800.
140140

141+
config DELL_RBTN
142+
tristate "Dell Airplane Mode Switch driver"
143+
depends on ACPI
144+
depends on INPUT
145+
depends on RFKILL
146+
---help---
147+
Say Y here if you want to support Dell Airplane Mode Switch ACPI
148+
device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN.
149+
This driver register rfkill device or input hotkey device depending
150+
on hardware type (hw switch slider or keyboard toggle button). For
151+
rfkill devices it receive HW switch events and set correct hard
152+
rfkill state.
153+
154+
To compile this driver as a module, choose M here: the module will
155+
be called dell-rbtn.
156+
141157

142158
config FUJITSU_LAPTOP
143159
tristate "Fujitsu Laptop Extras"

drivers/platform/x86/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o
1414
obj-$(CONFIG_DELL_WMI) += dell-wmi.o
1515
obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o
1616
obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o
17+
obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o
1718
obj-$(CONFIG_ACER_WMI) += acer-wmi.o
1819
obj-$(CONFIG_ACERHDF) += acerhdf.o
1920
obj-$(CONFIG_HP_ACCEL) += hp_accel.o

drivers/platform/x86/dell-rbtn.c

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
/*
2+
Dell Airplane Mode Switch driver
3+
Copyright (C) 2014-2015 Pali Rohár <[email protected]>
4+
5+
This program is free software; you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation; either version 2 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
*/
15+
16+
#include <linux/module.h>
17+
#include <linux/acpi.h>
18+
#include <linux/rfkill.h>
19+
#include <linux/input.h>
20+
21+
enum rbtn_type {
22+
RBTN_UNKNOWN,
23+
RBTN_TOGGLE,
24+
RBTN_SLIDER,
25+
};
26+
27+
struct rbtn_data {
28+
enum rbtn_type type;
29+
struct rfkill *rfkill;
30+
struct input_dev *input_dev;
31+
};
32+
33+
34+
/*
35+
* acpi functions
36+
*/
37+
38+
static enum rbtn_type rbtn_check(struct acpi_device *device)
39+
{
40+
unsigned long long output;
41+
acpi_status status;
42+
43+
status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output);
44+
if (ACPI_FAILURE(status))
45+
return RBTN_UNKNOWN;
46+
47+
switch (output) {
48+
case 0:
49+
case 1:
50+
return RBTN_TOGGLE;
51+
case 2:
52+
case 3:
53+
return RBTN_SLIDER;
54+
default:
55+
return RBTN_UNKNOWN;
56+
}
57+
}
58+
59+
static int rbtn_get(struct acpi_device *device)
60+
{
61+
unsigned long long output;
62+
acpi_status status;
63+
64+
status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output);
65+
if (ACPI_FAILURE(status))
66+
return -EINVAL;
67+
68+
return !output;
69+
}
70+
71+
static int rbtn_acquire(struct acpi_device *device, bool enable)
72+
{
73+
struct acpi_object_list input;
74+
union acpi_object param;
75+
acpi_status status;
76+
77+
param.type = ACPI_TYPE_INTEGER;
78+
param.integer.value = enable;
79+
input.count = 1;
80+
input.pointer = &param;
81+
82+
status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL);
83+
if (ACPI_FAILURE(status))
84+
return -EINVAL;
85+
86+
return 0;
87+
}
88+
89+
90+
/*
91+
* rfkill device
92+
*/
93+
94+
static void rbtn_rfkill_query(struct rfkill *rfkill, void *data)
95+
{
96+
struct acpi_device *device = data;
97+
int state;
98+
99+
state = rbtn_get(device);
100+
if (state < 0)
101+
return;
102+
103+
rfkill_set_states(rfkill, state, state);
104+
}
105+
106+
static int rbtn_rfkill_set_block(void *data, bool blocked)
107+
{
108+
/* NOTE: setting soft rfkill state is not supported */
109+
return -EINVAL;
110+
}
111+
112+
static struct rfkill_ops rbtn_ops = {
113+
.query = rbtn_rfkill_query,
114+
.set_block = rbtn_rfkill_set_block,
115+
};
116+
117+
static int rbtn_rfkill_init(struct acpi_device *device)
118+
{
119+
struct rbtn_data *rbtn_data = device->driver_data;
120+
int ret;
121+
122+
if (rbtn_data->rfkill)
123+
return 0;
124+
125+
/*
126+
* NOTE: rbtn controls all radio devices, not only WLAN
127+
* but rfkill interface does not support "ANY" type
128+
* so "WLAN" type is used
129+
*/
130+
rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev,
131+
RFKILL_TYPE_WLAN, &rbtn_ops, device);
132+
if (!rbtn_data->rfkill)
133+
return -ENOMEM;
134+
135+
ret = rfkill_register(rbtn_data->rfkill);
136+
if (ret) {
137+
rfkill_destroy(rbtn_data->rfkill);
138+
rbtn_data->rfkill = NULL;
139+
return ret;
140+
}
141+
142+
return 0;
143+
}
144+
145+
static void rbtn_rfkill_exit(struct acpi_device *device)
146+
{
147+
struct rbtn_data *rbtn_data = device->driver_data;
148+
149+
if (!rbtn_data->rfkill)
150+
return;
151+
152+
rfkill_unregister(rbtn_data->rfkill);
153+
rfkill_destroy(rbtn_data->rfkill);
154+
rbtn_data->rfkill = NULL;
155+
}
156+
157+
static void rbtn_rfkill_event(struct acpi_device *device)
158+
{
159+
struct rbtn_data *rbtn_data = device->driver_data;
160+
161+
if (rbtn_data->rfkill)
162+
rbtn_rfkill_query(rbtn_data->rfkill, device);
163+
}
164+
165+
166+
/*
167+
* input device
168+
*/
169+
170+
static int rbtn_input_init(struct rbtn_data *rbtn_data)
171+
{
172+
int ret;
173+
174+
rbtn_data->input_dev = input_allocate_device();
175+
if (!rbtn_data->input_dev)
176+
return -ENOMEM;
177+
178+
rbtn_data->input_dev->name = "DELL Wireless hotkeys";
179+
rbtn_data->input_dev->phys = "dellabce/input0";
180+
rbtn_data->input_dev->id.bustype = BUS_HOST;
181+
rbtn_data->input_dev->evbit[0] = BIT(EV_KEY);
182+
set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit);
183+
184+
ret = input_register_device(rbtn_data->input_dev);
185+
if (ret) {
186+
input_free_device(rbtn_data->input_dev);
187+
rbtn_data->input_dev = NULL;
188+
return ret;
189+
}
190+
191+
return 0;
192+
}
193+
194+
static void rbtn_input_exit(struct rbtn_data *rbtn_data)
195+
{
196+
input_unregister_device(rbtn_data->input_dev);
197+
rbtn_data->input_dev = NULL;
198+
}
199+
200+
static void rbtn_input_event(struct rbtn_data *rbtn_data)
201+
{
202+
input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1);
203+
input_sync(rbtn_data->input_dev);
204+
input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0);
205+
input_sync(rbtn_data->input_dev);
206+
}
207+
208+
209+
/*
210+
* acpi driver
211+
*/
212+
213+
static int rbtn_add(struct acpi_device *device);
214+
static int rbtn_remove(struct acpi_device *device);
215+
static void rbtn_notify(struct acpi_device *device, u32 event);
216+
217+
static const struct acpi_device_id rbtn_ids[] = {
218+
{ "DELRBTN", 0 },
219+
{ "DELLABCE", 0 },
220+
{ "", 0 },
221+
};
222+
223+
static struct acpi_driver rbtn_driver = {
224+
.name = "dell-rbtn",
225+
.ids = rbtn_ids,
226+
.ops = {
227+
.add = rbtn_add,
228+
.remove = rbtn_remove,
229+
.notify = rbtn_notify,
230+
},
231+
.owner = THIS_MODULE,
232+
};
233+
234+
235+
/*
236+
* acpi driver functions
237+
*/
238+
239+
static int rbtn_add(struct acpi_device *device)
240+
{
241+
struct rbtn_data *rbtn_data;
242+
enum rbtn_type type;
243+
int ret = 0;
244+
245+
type = rbtn_check(device);
246+
if (type == RBTN_UNKNOWN) {
247+
dev_info(&device->dev, "Unknown device type\n");
248+
return -EINVAL;
249+
}
250+
251+
ret = rbtn_acquire(device, true);
252+
if (ret < 0) {
253+
dev_err(&device->dev, "Cannot enable device\n");
254+
return ret;
255+
}
256+
257+
rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL);
258+
if (!rbtn_data)
259+
return -ENOMEM;
260+
261+
rbtn_data->type = type;
262+
device->driver_data = rbtn_data;
263+
264+
switch (rbtn_data->type) {
265+
case RBTN_TOGGLE:
266+
ret = rbtn_input_init(rbtn_data);
267+
break;
268+
case RBTN_SLIDER:
269+
ret = rbtn_rfkill_init(device);
270+
break;
271+
default:
272+
ret = -EINVAL;
273+
}
274+
275+
return ret;
276+
277+
}
278+
279+
static int rbtn_remove(struct acpi_device *device)
280+
{
281+
struct rbtn_data *rbtn_data = device->driver_data;
282+
283+
switch (rbtn_data->type) {
284+
case RBTN_TOGGLE:
285+
rbtn_input_exit(rbtn_data);
286+
break;
287+
case RBTN_SLIDER:
288+
rbtn_rfkill_exit(device);
289+
break;
290+
default:
291+
break;
292+
}
293+
294+
rbtn_acquire(device, false);
295+
device->driver_data = NULL;
296+
297+
return 0;
298+
}
299+
300+
static void rbtn_notify(struct acpi_device *device, u32 event)
301+
{
302+
struct rbtn_data *rbtn_data = device->driver_data;
303+
304+
if (event != 0x80) {
305+
dev_info(&device->dev, "Received unknown event (0x%x)\n",
306+
event);
307+
return;
308+
}
309+
310+
switch (rbtn_data->type) {
311+
case RBTN_TOGGLE:
312+
rbtn_input_event(rbtn_data);
313+
break;
314+
case RBTN_SLIDER:
315+
rbtn_rfkill_event(device);
316+
break;
317+
default:
318+
break;
319+
}
320+
}
321+
322+
323+
/*
324+
* module functions
325+
*/
326+
327+
module_acpi_driver(rbtn_driver);
328+
329+
MODULE_DEVICE_TABLE(acpi, rbtn_ids);
330+
MODULE_DESCRIPTION("Dell Airplane Mode Switch driver");
331+
MODULE_AUTHOR("Pali Rohár <[email protected]>");
332+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)