Commit | Line | Data |
---|---|---|
cf4328cd ID |
1 | /* |
2 | * Input layer to RF Kill interface connector | |
3 | * | |
4 | * Copyright (c) 2007 Dmitry Torokhov | |
5 | */ | |
6 | ||
7 | /* | |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms of the GNU General Public License version 2 as published | |
10 | * by the Free Software Foundation. | |
11 | */ | |
12 | ||
13 | #include <linux/module.h> | |
14 | #include <linux/input.h> | |
15 | #include <linux/slab.h> | |
16 | #include <linux/workqueue.h> | |
17 | #include <linux/init.h> | |
18 | #include <linux/rfkill.h> | |
19 | ||
fe242cfd ID |
20 | #include "rfkill-input.h" |
21 | ||
cf4328cd ID |
22 | MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>"); |
23 | MODULE_DESCRIPTION("Input layer to RF switch connector"); | |
24 | MODULE_LICENSE("GPL"); | |
25 | ||
26 | struct rfkill_task { | |
27 | struct work_struct work; | |
28 | enum rfkill_type type; | |
29 | struct mutex mutex; /* ensures that task is serialized */ | |
30 | spinlock_t lock; /* for accessing last and desired state */ | |
31 | unsigned long last; /* last schedule */ | |
32 | enum rfkill_state desired_state; /* on/off */ | |
33 | enum rfkill_state current_state; /* on/off */ | |
34 | }; | |
35 | ||
36 | static void rfkill_task_handler(struct work_struct *work) | |
37 | { | |
38 | struct rfkill_task *task = container_of(work, struct rfkill_task, work); | |
39 | enum rfkill_state state; | |
40 | ||
41 | mutex_lock(&task->mutex); | |
42 | ||
43 | /* | |
44 | * Use temp variable to fetch desired state to keep it | |
45 | * consistent even if rfkill_schedule_toggle() runs in | |
46 | * another thread or interrupts us. | |
47 | */ | |
48 | state = task->desired_state; | |
49 | ||
50 | if (state != task->current_state) { | |
51 | rfkill_switch_all(task->type, state); | |
52 | task->current_state = state; | |
53 | } | |
54 | ||
55 | mutex_unlock(&task->mutex); | |
56 | } | |
57 | ||
58 | static void rfkill_schedule_toggle(struct rfkill_task *task) | |
59 | { | |
e6c9116d | 60 | unsigned long flags; |
cf4328cd ID |
61 | |
62 | spin_lock_irqsave(&task->lock, flags); | |
63 | ||
64 | if (time_after(jiffies, task->last + msecs_to_jiffies(200))) { | |
65 | task->desired_state = !task->desired_state; | |
66 | task->last = jiffies; | |
67 | schedule_work(&task->work); | |
68 | } | |
69 | ||
70 | spin_unlock_irqrestore(&task->lock, flags); | |
71 | } | |
72 | ||
73 | #define DEFINE_RFKILL_TASK(n, t) \ | |
74 | struct rfkill_task n = { \ | |
75 | .work = __WORK_INITIALIZER(n.work, \ | |
76 | rfkill_task_handler), \ | |
77 | .type = t, \ | |
78 | .mutex = __MUTEX_INITIALIZER(n.mutex), \ | |
79 | .lock = __SPIN_LOCK_UNLOCKED(n.lock), \ | |
80 | .desired_state = RFKILL_STATE_ON, \ | |
81 | .current_state = RFKILL_STATE_ON, \ | |
82 | } | |
83 | ||
84 | static DEFINE_RFKILL_TASK(rfkill_wlan, RFKILL_TYPE_WLAN); | |
85 | static DEFINE_RFKILL_TASK(rfkill_bt, RFKILL_TYPE_BLUETOOTH); | |
e0665486 | 86 | static DEFINE_RFKILL_TASK(rfkill_uwb, RFKILL_TYPE_UWB); |
303d9bf6 | 87 | static DEFINE_RFKILL_TASK(rfkill_wimax, RFKILL_TYPE_WIMAX); |
cf4328cd ID |
88 | |
89 | static void rfkill_event(struct input_handle *handle, unsigned int type, | |
2b81bff4 | 90 | unsigned int code, int down) |
cf4328cd ID |
91 | { |
92 | if (type == EV_KEY && down == 1) { | |
93 | switch (code) { | |
94 | case KEY_WLAN: | |
95 | rfkill_schedule_toggle(&rfkill_wlan); | |
96 | break; | |
97 | case KEY_BLUETOOTH: | |
98 | rfkill_schedule_toggle(&rfkill_bt); | |
99 | break; | |
e0665486 ID |
100 | case KEY_UWB: |
101 | rfkill_schedule_toggle(&rfkill_uwb); | |
102 | break; | |
303d9bf6 IPG |
103 | case KEY_WIMAX: |
104 | rfkill_schedule_toggle(&rfkill_wimax); | |
105 | break; | |
cf4328cd ID |
106 | default: |
107 | break; | |
108 | } | |
109 | } | |
110 | } | |
111 | ||
112 | static int rfkill_connect(struct input_handler *handler, struct input_dev *dev, | |
113 | const struct input_device_id *id) | |
114 | { | |
115 | struct input_handle *handle; | |
116 | int error; | |
117 | ||
118 | handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); | |
119 | if (!handle) | |
120 | return -ENOMEM; | |
121 | ||
122 | handle->dev = dev; | |
123 | handle->handler = handler; | |
124 | handle->name = "rfkill"; | |
125 | ||
126 | error = input_register_handle(handle); | |
127 | if (error) | |
128 | goto err_free_handle; | |
129 | ||
130 | error = input_open_device(handle); | |
131 | if (error) | |
132 | goto err_unregister_handle; | |
133 | ||
134 | return 0; | |
135 | ||
136 | err_unregister_handle: | |
137 | input_unregister_handle(handle); | |
138 | err_free_handle: | |
139 | kfree(handle); | |
140 | return error; | |
141 | } | |
142 | ||
143 | static void rfkill_disconnect(struct input_handle *handle) | |
144 | { | |
145 | input_close_device(handle); | |
146 | input_unregister_handle(handle); | |
147 | kfree(handle); | |
148 | } | |
149 | ||
150 | static const struct input_device_id rfkill_ids[] = { | |
151 | { | |
152 | .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, | |
7b19ada2 JS |
153 | .evbit = { BIT_MASK(EV_KEY) }, |
154 | .keybit = { [BIT_WORD(KEY_WLAN)] = BIT_MASK(KEY_WLAN) }, | |
cf4328cd ID |
155 | }, |
156 | { | |
157 | .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, | |
7b19ada2 JS |
158 | .evbit = { BIT_MASK(EV_KEY) }, |
159 | .keybit = { [BIT_WORD(KEY_BLUETOOTH)] = BIT_MASK(KEY_BLUETOOTH) }, | |
cf4328cd | 160 | }, |
e0665486 ID |
161 | { |
162 | .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, | |
7b19ada2 JS |
163 | .evbit = { BIT_MASK(EV_KEY) }, |
164 | .keybit = { [BIT_WORD(KEY_UWB)] = BIT_MASK(KEY_UWB) }, | |
e0665486 | 165 | }, |
303d9bf6 IPG |
166 | { |
167 | .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, | |
168 | .evbit = { BIT_MASK(EV_KEY) }, | |
169 | .keybit = { [BIT_WORD(KEY_WIMAX)] = BIT_MASK(KEY_WIMAX) }, | |
170 | }, | |
cf4328cd ID |
171 | { } |
172 | }; | |
173 | ||
174 | static struct input_handler rfkill_handler = { | |
175 | .event = rfkill_event, | |
176 | .connect = rfkill_connect, | |
177 | .disconnect = rfkill_disconnect, | |
178 | .name = "rfkill", | |
179 | .id_table = rfkill_ids, | |
180 | }; | |
181 | ||
182 | static int __init rfkill_handler_init(void) | |
183 | { | |
184 | return input_register_handler(&rfkill_handler); | |
185 | } | |
186 | ||
187 | static void __exit rfkill_handler_exit(void) | |
188 | { | |
189 | input_unregister_handler(&rfkill_handler); | |
190 | flush_scheduled_work(); | |
191 | } | |
192 | ||
193 | module_init(rfkill_handler_init); | |
194 | module_exit(rfkill_handler_exit); |