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 | ||
20 | MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>"); | |
21 | MODULE_DESCRIPTION("Input layer to RF switch connector"); | |
22 | MODULE_LICENSE("GPL"); | |
23 | ||
24 | struct rfkill_task { | |
25 | struct work_struct work; | |
26 | enum rfkill_type type; | |
27 | struct mutex mutex; /* ensures that task is serialized */ | |
28 | spinlock_t lock; /* for accessing last and desired state */ | |
29 | unsigned long last; /* last schedule */ | |
30 | enum rfkill_state desired_state; /* on/off */ | |
31 | enum rfkill_state current_state; /* on/off */ | |
32 | }; | |
33 | ||
34 | static void rfkill_task_handler(struct work_struct *work) | |
35 | { | |
36 | struct rfkill_task *task = container_of(work, struct rfkill_task, work); | |
37 | enum rfkill_state state; | |
38 | ||
39 | mutex_lock(&task->mutex); | |
40 | ||
41 | /* | |
42 | * Use temp variable to fetch desired state to keep it | |
43 | * consistent even if rfkill_schedule_toggle() runs in | |
44 | * another thread or interrupts us. | |
45 | */ | |
46 | state = task->desired_state; | |
47 | ||
48 | if (state != task->current_state) { | |
49 | rfkill_switch_all(task->type, state); | |
50 | task->current_state = state; | |
51 | } | |
52 | ||
53 | mutex_unlock(&task->mutex); | |
54 | } | |
55 | ||
56 | static void rfkill_schedule_toggle(struct rfkill_task *task) | |
57 | { | |
e6c9116d | 58 | unsigned long flags; |
cf4328cd ID |
59 | |
60 | spin_lock_irqsave(&task->lock, flags); | |
61 | ||
62 | if (time_after(jiffies, task->last + msecs_to_jiffies(200))) { | |
63 | task->desired_state = !task->desired_state; | |
64 | task->last = jiffies; | |
65 | schedule_work(&task->work); | |
66 | } | |
67 | ||
68 | spin_unlock_irqrestore(&task->lock, flags); | |
69 | } | |
70 | ||
71 | #define DEFINE_RFKILL_TASK(n, t) \ | |
72 | struct rfkill_task n = { \ | |
73 | .work = __WORK_INITIALIZER(n.work, \ | |
74 | rfkill_task_handler), \ | |
75 | .type = t, \ | |
76 | .mutex = __MUTEX_INITIALIZER(n.mutex), \ | |
77 | .lock = __SPIN_LOCK_UNLOCKED(n.lock), \ | |
78 | .desired_state = RFKILL_STATE_ON, \ | |
79 | .current_state = RFKILL_STATE_ON, \ | |
80 | } | |
81 | ||
82 | static DEFINE_RFKILL_TASK(rfkill_wlan, RFKILL_TYPE_WLAN); | |
83 | static DEFINE_RFKILL_TASK(rfkill_bt, RFKILL_TYPE_BLUETOOTH); | |
84 | ||
85 | static void rfkill_event(struct input_handle *handle, unsigned int type, | |
86 | unsigned int code, int down) | |
87 | { | |
88 | if (type == EV_KEY && down == 1) { | |
89 | switch (code) { | |
90 | case KEY_WLAN: | |
91 | rfkill_schedule_toggle(&rfkill_wlan); | |
92 | break; | |
93 | case KEY_BLUETOOTH: | |
94 | rfkill_schedule_toggle(&rfkill_bt); | |
95 | break; | |
96 | default: | |
97 | break; | |
98 | } | |
99 | } | |
100 | } | |
101 | ||
102 | static int rfkill_connect(struct input_handler *handler, struct input_dev *dev, | |
103 | const struct input_device_id *id) | |
104 | { | |
105 | struct input_handle *handle; | |
106 | int error; | |
107 | ||
108 | handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); | |
109 | if (!handle) | |
110 | return -ENOMEM; | |
111 | ||
112 | handle->dev = dev; | |
113 | handle->handler = handler; | |
114 | handle->name = "rfkill"; | |
115 | ||
116 | error = input_register_handle(handle); | |
117 | if (error) | |
118 | goto err_free_handle; | |
119 | ||
120 | error = input_open_device(handle); | |
121 | if (error) | |
122 | goto err_unregister_handle; | |
123 | ||
124 | return 0; | |
125 | ||
126 | err_unregister_handle: | |
127 | input_unregister_handle(handle); | |
128 | err_free_handle: | |
129 | kfree(handle); | |
130 | return error; | |
131 | } | |
132 | ||
133 | static void rfkill_disconnect(struct input_handle *handle) | |
134 | { | |
135 | input_close_device(handle); | |
136 | input_unregister_handle(handle); | |
137 | kfree(handle); | |
138 | } | |
139 | ||
140 | static const struct input_device_id rfkill_ids[] = { | |
141 | { | |
142 | .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, | |
143 | .evbit = { BIT(EV_KEY) }, | |
144 | .keybit = { [LONG(KEY_WLAN)] = BIT(KEY_WLAN) }, | |
145 | }, | |
146 | { | |
147 | .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, | |
148 | .evbit = { BIT(EV_KEY) }, | |
149 | .keybit = { [LONG(KEY_BLUETOOTH)] = BIT(KEY_BLUETOOTH) }, | |
150 | }, | |
151 | { } | |
152 | }; | |
153 | ||
154 | static struct input_handler rfkill_handler = { | |
155 | .event = rfkill_event, | |
156 | .connect = rfkill_connect, | |
157 | .disconnect = rfkill_disconnect, | |
158 | .name = "rfkill", | |
159 | .id_table = rfkill_ids, | |
160 | }; | |
161 | ||
162 | static int __init rfkill_handler_init(void) | |
163 | { | |
164 | return input_register_handler(&rfkill_handler); | |
165 | } | |
166 | ||
167 | static void __exit rfkill_handler_exit(void) | |
168 | { | |
169 | input_unregister_handler(&rfkill_handler); | |
170 | flush_scheduled_work(); | |
171 | } | |
172 | ||
173 | module_init(rfkill_handler_init); | |
174 | module_exit(rfkill_handler_exit); |