diff options
author | David Härdeman <david@hardeman.nu> | 2010-04-08 19:04:40 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2010-05-19 11:57:17 -0400 |
commit | 784a493168f71ce02b5f9eab0ec19df29b999631 (patch) | |
tree | 8e7e45dfbb6b4b9be8db010b84617ffdff9eafb9 /drivers/media/IR | |
parent | 25bb10c11e6cca461f09df8f59c2eb501a9e4d52 (diff) |
V4L/DVB: Add RC6 support to ir-core
This patch adds an RC6 decoder (modes 0 and 6A) to ir-core.
Signed-off-by: David Härdeman <david@hardeman.nu>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/IR')
-rw-r--r-- | drivers/media/IR/Kconfig | 9 | ||||
-rw-r--r-- | drivers/media/IR/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/IR/ir-core-priv.h | 7 | ||||
-rw-r--r-- | drivers/media/IR/ir-raw-event.c | 1 | ||||
-rw-r--r-- | drivers/media/IR/ir-rc6-decoder.c | 412 | ||||
-rw-r--r-- | drivers/media/IR/ir-sysfs.c | 2 |
6 files changed, 432 insertions, 0 deletions
diff --git a/drivers/media/IR/Kconfig b/drivers/media/IR/Kconfig index ba81bda3513d..28d336d98187 100644 --- a/drivers/media/IR/Kconfig +++ b/drivers/media/IR/Kconfig | |||
@@ -27,3 +27,12 @@ config IR_RC5_DECODER | |||
27 | ---help--- | 27 | ---help--- |
28 | Enable this option if you have IR with RC-5 protocol, and | 28 | Enable this option if you have IR with RC-5 protocol, and |
29 | if the IR is decoded in software | 29 | if the IR is decoded in software |
30 | |||
31 | config IR_RC6_DECODER | ||
32 | tristate "Enable IR raw decoder for the RC6 protocol" | ||
33 | depends on IR_CORE | ||
34 | default y | ||
35 | |||
36 | ---help--- | ||
37 | Enable this option if you have an infrared remote control which | ||
38 | uses the RC6 protocol, and you need software decoding support. | ||
diff --git a/drivers/media/IR/Makefile b/drivers/media/IR/Makefile index 62e12d5c443a..792d9cad7efc 100644 --- a/drivers/media/IR/Makefile +++ b/drivers/media/IR/Makefile | |||
@@ -7,3 +7,4 @@ obj-$(CONFIG_IR_CORE) += ir-core.o | |||
7 | obj-$(CONFIG_VIDEO_IR) += ir-common.o | 7 | obj-$(CONFIG_VIDEO_IR) += ir-common.o |
8 | obj-$(CONFIG_IR_NEC_DECODER) += ir-nec-decoder.o | 8 | obj-$(CONFIG_IR_NEC_DECODER) += ir-nec-decoder.o |
9 | obj-$(CONFIG_IR_RC5_DECODER) += ir-rc5-decoder.o | 9 | obj-$(CONFIG_IR_RC5_DECODER) += ir-rc5-decoder.o |
10 | obj-$(CONFIG_IR_RC6_DECODER) += ir-rc6-decoder.o | ||
diff --git a/drivers/media/IR/ir-core-priv.h b/drivers/media/IR/ir-core-priv.h index ea546659e4ce..59d5ed7e103e 100644 --- a/drivers/media/IR/ir-core-priv.h +++ b/drivers/media/IR/ir-core-priv.h | |||
@@ -108,4 +108,11 @@ void ir_raw_init(void); | |||
108 | #define load_rc5_decode() 0 | 108 | #define load_rc5_decode() 0 |
109 | #endif | 109 | #endif |
110 | 110 | ||
111 | /* from ir-rc6-decoder.c */ | ||
112 | #ifdef CONFIG_IR_RC5_DECODER_MODULE | ||
113 | #define load_rc6_decode() request_module("ir-rc6-decoder") | ||
114 | #else | ||
115 | #define load_rc6_decode() 0 | ||
116 | #endif | ||
117 | |||
111 | #endif /* _IR_RAW_EVENT */ | 118 | #endif /* _IR_RAW_EVENT */ |
diff --git a/drivers/media/IR/ir-raw-event.c b/drivers/media/IR/ir-raw-event.c index b49d01256be3..674442b2f0a8 100644 --- a/drivers/media/IR/ir-raw-event.c +++ b/drivers/media/IR/ir-raw-event.c | |||
@@ -226,6 +226,7 @@ static void init_decoders(struct work_struct *work) | |||
226 | 226 | ||
227 | load_nec_decode(); | 227 | load_nec_decode(); |
228 | load_rc5_decode(); | 228 | load_rc5_decode(); |
229 | load_rc6_decode(); | ||
229 | 230 | ||
230 | /* If needed, we may later add some init code. In this case, | 231 | /* If needed, we may later add some init code. In this case, |
231 | it is needed to change the CONFIG_MODULE test at ir-core.h | 232 | it is needed to change the CONFIG_MODULE test at ir-core.h |
diff --git a/drivers/media/IR/ir-rc6-decoder.c b/drivers/media/IR/ir-rc6-decoder.c new file mode 100644 index 000000000000..ccc5be240f82 --- /dev/null +++ b/drivers/media/IR/ir-rc6-decoder.c | |||
@@ -0,0 +1,412 @@ | |||
1 | /* ir-rc6-decoder.c - A decoder for the RC6 IR protocol | ||
2 | * | ||
3 | * Copyright (C) 2010 by David Härdeman <david@hardeman.nu> | ||
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 version 2 of the License. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | */ | ||
14 | |||
15 | #include "ir-core-priv.h" | ||
16 | |||
17 | /* | ||
18 | * This decoder currently supports: | ||
19 | * RC6-0-16 (standard toggle bit in header) | ||
20 | * RC6-6A-24 (no toggle bit) | ||
21 | * RC6-6A-32 (MCE version with toggle bit in body) | ||
22 | */ | ||
23 | |||
24 | #define RC6_UNIT 444444 /* us */ | ||
25 | #define RC6_HEADER_NBITS 4 /* not including toggle bit */ | ||
26 | #define RC6_0_NBITS 16 | ||
27 | #define RC6_6A_SMALL_NBITS 24 | ||
28 | #define RC6_6A_LARGE_NBITS 32 | ||
29 | #define RC6_PREFIX_PULSE PULSE(6) | ||
30 | #define RC6_PREFIX_SPACE SPACE(2) | ||
31 | #define RC6_MODE_MASK 0x07 /* for the header bits */ | ||
32 | #define RC6_STARTBIT_MASK 0x08 /* for the header bits */ | ||
33 | #define RC6_6A_MCE_TOGGLE_MASK 0x8000 /* for the body bits */ | ||
34 | |||
35 | /* Used to register rc6_decoder clients */ | ||
36 | static LIST_HEAD(decoder_list); | ||
37 | static DEFINE_SPINLOCK(decoder_lock); | ||
38 | |||
39 | enum rc6_mode { | ||
40 | RC6_MODE_0, | ||
41 | RC6_MODE_6A, | ||
42 | RC6_MODE_UNKNOWN, | ||
43 | }; | ||
44 | |||
45 | enum rc6_state { | ||
46 | STATE_INACTIVE, | ||
47 | STATE_PREFIX_SPACE, | ||
48 | STATE_HEADER_BIT_START, | ||
49 | STATE_HEADER_BIT_END, | ||
50 | STATE_TOGGLE_START, | ||
51 | STATE_TOGGLE_END, | ||
52 | STATE_BODY_BIT_START, | ||
53 | STATE_BODY_BIT_END, | ||
54 | STATE_FINISHED, | ||
55 | }; | ||
56 | |||
57 | struct decoder_data { | ||
58 | struct list_head list; | ||
59 | struct ir_input_dev *ir_dev; | ||
60 | int enabled:1; | ||
61 | |||
62 | /* State machine control */ | ||
63 | enum rc6_state state; | ||
64 | u8 header; | ||
65 | u32 body; | ||
66 | int last_unit; | ||
67 | bool toggle; | ||
68 | unsigned count; | ||
69 | unsigned wanted_bits; | ||
70 | }; | ||
71 | |||
72 | |||
73 | /** | ||
74 | * get_decoder_data() - gets decoder data | ||
75 | * @input_dev: input device | ||
76 | * | ||
77 | * Returns the struct decoder_data that corresponds to a device | ||
78 | */ | ||
79 | static struct decoder_data *get_decoder_data(struct ir_input_dev *ir_dev) | ||
80 | { | ||
81 | struct decoder_data *data = NULL; | ||
82 | |||
83 | spin_lock(&decoder_lock); | ||
84 | list_for_each_entry(data, &decoder_list, list) { | ||
85 | if (data->ir_dev == ir_dev) | ||
86 | break; | ||
87 | } | ||
88 | spin_unlock(&decoder_lock); | ||
89 | return data; | ||
90 | } | ||
91 | |||
92 | static ssize_t store_enabled(struct device *d, | ||
93 | struct device_attribute *mattr, | ||
94 | const char *buf, | ||
95 | size_t len) | ||
96 | { | ||
97 | unsigned long value; | ||
98 | struct ir_input_dev *ir_dev = dev_get_drvdata(d); | ||
99 | struct decoder_data *data = get_decoder_data(ir_dev); | ||
100 | |||
101 | if (!data) | ||
102 | return -EINVAL; | ||
103 | |||
104 | if (strict_strtoul(buf, 10, &value) || value > 1) | ||
105 | return -EINVAL; | ||
106 | |||
107 | data->enabled = value; | ||
108 | |||
109 | return len; | ||
110 | } | ||
111 | |||
112 | static ssize_t show_enabled(struct device *d, | ||
113 | struct device_attribute *mattr, char *buf) | ||
114 | { | ||
115 | struct ir_input_dev *ir_dev = dev_get_drvdata(d); | ||
116 | struct decoder_data *data = get_decoder_data(ir_dev); | ||
117 | |||
118 | if (!data) | ||
119 | return -EINVAL; | ||
120 | |||
121 | if (data->enabled) | ||
122 | return sprintf(buf, "1\n"); | ||
123 | else | ||
124 | return sprintf(buf, "0\n"); | ||
125 | } | ||
126 | |||
127 | static DEVICE_ATTR(enabled, S_IRUGO | S_IWUSR, show_enabled, store_enabled); | ||
128 | |||
129 | static struct attribute *decoder_attributes[] = { | ||
130 | &dev_attr_enabled.attr, | ||
131 | NULL | ||
132 | }; | ||
133 | |||
134 | static struct attribute_group decoder_attribute_group = { | ||
135 | .name = "rc6_decoder", | ||
136 | .attrs = decoder_attributes, | ||
137 | }; | ||
138 | |||
139 | static enum rc6_mode rc6_mode(struct decoder_data *data) { | ||
140 | switch (data->header & RC6_MODE_MASK) { | ||
141 | case 0: | ||
142 | return RC6_MODE_0; | ||
143 | case 6: | ||
144 | if (!data->toggle) | ||
145 | return RC6_MODE_6A; | ||
146 | /* fall through */ | ||
147 | default: | ||
148 | return RC6_MODE_UNKNOWN; | ||
149 | } | ||
150 | } | ||
151 | |||
152 | /** | ||
153 | * ir_rc6_decode() - Decode one RC6 pulse or space | ||
154 | * @input_dev: the struct input_dev descriptor of the device | ||
155 | * @duration: duration of pulse/space in ns | ||
156 | * | ||
157 | * This function returns -EINVAL if the pulse violates the state machine | ||
158 | */ | ||
159 | static int ir_rc6_decode(struct input_dev *input_dev, s64 duration) | ||
160 | { | ||
161 | struct decoder_data *data; | ||
162 | struct ir_input_dev *ir_dev = input_get_drvdata(input_dev); | ||
163 | u32 scancode; | ||
164 | u8 toggle; | ||
165 | int u; | ||
166 | |||
167 | data = get_decoder_data(ir_dev); | ||
168 | if (!data) | ||
169 | return -EINVAL; | ||
170 | |||
171 | if (!data->enabled) | ||
172 | return 0; | ||
173 | |||
174 | if (IS_RESET(duration)) { | ||
175 | data->state = STATE_INACTIVE; | ||
176 | return 0; | ||
177 | } | ||
178 | |||
179 | u = TO_UNITS(duration, RC6_UNIT); | ||
180 | if (DURATION(u) == 0) | ||
181 | goto out; | ||
182 | |||
183 | again: | ||
184 | IR_dprintk(2, "RC6 decode started at state %i (%i units, %ius)\n", | ||
185 | data->state, u, TO_US(duration)); | ||
186 | |||
187 | if (DURATION(u) == 0 && data->state != STATE_FINISHED) | ||
188 | return 0; | ||
189 | |||
190 | switch (data->state) { | ||
191 | |||
192 | case STATE_INACTIVE: | ||
193 | if (u >= RC6_PREFIX_PULSE - 1 && u <= RC6_PREFIX_PULSE + 1) { | ||
194 | data->state = STATE_PREFIX_SPACE; | ||
195 | data->count = 0; | ||
196 | return 0; | ||
197 | } | ||
198 | break; | ||
199 | |||
200 | case STATE_PREFIX_SPACE: | ||
201 | if (u == RC6_PREFIX_SPACE) { | ||
202 | data->state = STATE_HEADER_BIT_START; | ||
203 | return 0; | ||
204 | } | ||
205 | break; | ||
206 | |||
207 | case STATE_HEADER_BIT_START: | ||
208 | if (DURATION(u) == 1) { | ||
209 | data->header <<= 1; | ||
210 | if (IS_PULSE(u)) | ||
211 | data->header |= 1; | ||
212 | data->count++; | ||
213 | data->last_unit = u; | ||
214 | data->state = STATE_HEADER_BIT_END; | ||
215 | return 0; | ||
216 | } | ||
217 | break; | ||
218 | |||
219 | case STATE_HEADER_BIT_END: | ||
220 | if (IS_TRANSITION(u, data->last_unit)) { | ||
221 | if (data->count == RC6_HEADER_NBITS) | ||
222 | data->state = STATE_TOGGLE_START; | ||
223 | else | ||
224 | data->state = STATE_HEADER_BIT_START; | ||
225 | |||
226 | DECREASE_DURATION(u, 1); | ||
227 | goto again; | ||
228 | } | ||
229 | break; | ||
230 | |||
231 | case STATE_TOGGLE_START: | ||
232 | if (DURATION(u) == 2) { | ||
233 | data->toggle = IS_PULSE(u); | ||
234 | data->last_unit = u; | ||
235 | data->state = STATE_TOGGLE_END; | ||
236 | return 0; | ||
237 | } | ||
238 | break; | ||
239 | |||
240 | case STATE_TOGGLE_END: | ||
241 | if (IS_TRANSITION(u, data->last_unit) && DURATION(u) >= 2) { | ||
242 | data->state = STATE_BODY_BIT_START; | ||
243 | data->last_unit = u; | ||
244 | DECREASE_DURATION(u, 2); | ||
245 | data->count = 0; | ||
246 | |||
247 | if (!(data->header & RC6_STARTBIT_MASK)) { | ||
248 | IR_dprintk(1, "RC6 invalid start bit\n"); | ||
249 | break; | ||
250 | } | ||
251 | |||
252 | switch (rc6_mode(data)) { | ||
253 | case RC6_MODE_0: | ||
254 | data->wanted_bits = RC6_0_NBITS; | ||
255 | break; | ||
256 | case RC6_MODE_6A: | ||
257 | /* This might look weird, but we basically | ||
258 | check the value of the first body bit to | ||
259 | determine the number of bits in mode 6A */ | ||
260 | if ((DURATION(u) == 0 && IS_SPACE(data->last_unit)) || DURATION(u) > 0) | ||
261 | data->wanted_bits = RC6_6A_LARGE_NBITS; | ||
262 | else | ||
263 | data->wanted_bits = RC6_6A_SMALL_NBITS; | ||
264 | break; | ||
265 | default: | ||
266 | IR_dprintk(1, "RC6 unknown mode\n"); | ||
267 | goto out; | ||
268 | } | ||
269 | goto again; | ||
270 | } | ||
271 | break; | ||
272 | |||
273 | case STATE_BODY_BIT_START: | ||
274 | if (DURATION(u) == 1) { | ||
275 | data->body <<= 1; | ||
276 | if (IS_PULSE(u)) | ||
277 | data->body |= 1; | ||
278 | data->count++; | ||
279 | data->last_unit = u; | ||
280 | |||
281 | /* | ||
282 | * If the last bit is one, a space will merge | ||
283 | * with the silence after the command. | ||
284 | */ | ||
285 | if (IS_PULSE(u) && data->count == data->wanted_bits) { | ||
286 | data->state = STATE_FINISHED; | ||
287 | goto again; | ||
288 | } | ||
289 | |||
290 | data->state = STATE_BODY_BIT_END; | ||
291 | return 0; | ||
292 | } | ||
293 | break; | ||
294 | |||
295 | case STATE_BODY_BIT_END: | ||
296 | if (IS_TRANSITION(u, data->last_unit)) { | ||
297 | if (data->count == data->wanted_bits) | ||
298 | data->state = STATE_FINISHED; | ||
299 | else | ||
300 | data->state = STATE_BODY_BIT_START; | ||
301 | |||
302 | DECREASE_DURATION(u, 1); | ||
303 | goto again; | ||
304 | } | ||
305 | break; | ||
306 | |||
307 | case STATE_FINISHED: | ||
308 | switch (rc6_mode(data)) { | ||
309 | case RC6_MODE_0: | ||
310 | scancode = data->body & 0xffff; | ||
311 | toggle = data->toggle; | ||
312 | IR_dprintk(1, "RC6(0) scancode 0x%04x (toggle: %u)\n", | ||
313 | scancode, toggle); | ||
314 | break; | ||
315 | case RC6_MODE_6A: | ||
316 | if (data->wanted_bits == RC6_6A_LARGE_NBITS) { | ||
317 | toggle = data->body & RC6_6A_MCE_TOGGLE_MASK ? 1 : 0; | ||
318 | scancode = data->body & ~RC6_6A_MCE_TOGGLE_MASK; | ||
319 | } else { | ||
320 | toggle = 0; | ||
321 | scancode = data->body & 0xffffff; | ||
322 | } | ||
323 | |||
324 | IR_dprintk(1, "RC6(6A) scancode 0x%08x (toggle: %u)\n", | ||
325 | scancode, toggle); | ||
326 | break; | ||
327 | default: | ||
328 | IR_dprintk(1, "RC6 unknown mode\n"); | ||
329 | goto out; | ||
330 | } | ||
331 | |||
332 | ir_keydown(input_dev, scancode, toggle); | ||
333 | data->state = STATE_INACTIVE; | ||
334 | return 0; | ||
335 | } | ||
336 | |||
337 | out: | ||
338 | IR_dprintk(1, "RC6 decode failed at state %i (%i units, %ius)\n", | ||
339 | data->state, u, TO_US(duration)); | ||
340 | data->state = STATE_INACTIVE; | ||
341 | return -EINVAL; | ||
342 | } | ||
343 | |||
344 | static int ir_rc6_register(struct input_dev *input_dev) | ||
345 | { | ||
346 | struct ir_input_dev *ir_dev = input_get_drvdata(input_dev); | ||
347 | struct decoder_data *data; | ||
348 | int rc; | ||
349 | |||
350 | rc = sysfs_create_group(&ir_dev->dev.kobj, &decoder_attribute_group); | ||
351 | if (rc < 0) | ||
352 | return rc; | ||
353 | |||
354 | data = kzalloc(sizeof(*data), GFP_KERNEL); | ||
355 | if (!data) { | ||
356 | sysfs_remove_group(&ir_dev->dev.kobj, &decoder_attribute_group); | ||
357 | return -ENOMEM; | ||
358 | } | ||
359 | |||
360 | data->ir_dev = ir_dev; | ||
361 | data->enabled = 1; | ||
362 | |||
363 | spin_lock(&decoder_lock); | ||
364 | list_add_tail(&data->list, &decoder_list); | ||
365 | spin_unlock(&decoder_lock); | ||
366 | |||
367 | return 0; | ||
368 | } | ||
369 | |||
370 | static int ir_rc6_unregister(struct input_dev *input_dev) | ||
371 | { | ||
372 | struct ir_input_dev *ir_dev = input_get_drvdata(input_dev); | ||
373 | static struct decoder_data *data; | ||
374 | |||
375 | data = get_decoder_data(ir_dev); | ||
376 | if (!data) | ||
377 | return 0; | ||
378 | |||
379 | sysfs_remove_group(&ir_dev->dev.kobj, &decoder_attribute_group); | ||
380 | |||
381 | spin_lock(&decoder_lock); | ||
382 | list_del(&data->list); | ||
383 | spin_unlock(&decoder_lock); | ||
384 | |||
385 | return 0; | ||
386 | } | ||
387 | |||
388 | static struct ir_raw_handler rc6_handler = { | ||
389 | .decode = ir_rc6_decode, | ||
390 | .raw_register = ir_rc6_register, | ||
391 | .raw_unregister = ir_rc6_unregister, | ||
392 | }; | ||
393 | |||
394 | static int __init ir_rc6_decode_init(void) | ||
395 | { | ||
396 | ir_raw_handler_register(&rc6_handler); | ||
397 | |||
398 | printk(KERN_INFO "IR RC6 protocol handler initialized\n"); | ||
399 | return 0; | ||
400 | } | ||
401 | |||
402 | static void __exit ir_rc6_decode_exit(void) | ||
403 | { | ||
404 | ir_raw_handler_unregister(&rc6_handler); | ||
405 | } | ||
406 | |||
407 | module_init(ir_rc6_decode_init); | ||
408 | module_exit(ir_rc6_decode_exit); | ||
409 | |||
410 | MODULE_LICENSE("GPL"); | ||
411 | MODULE_AUTHOR("David Härdeman <david@hardeman.nu>"); | ||
412 | MODULE_DESCRIPTION("RC6 IR protocol decoder"); | ||
diff --git a/drivers/media/IR/ir-sysfs.c b/drivers/media/IR/ir-sysfs.c index 57f09c4ca9fb..073b5c0dc654 100644 --- a/drivers/media/IR/ir-sysfs.c +++ b/drivers/media/IR/ir-sysfs.c | |||
@@ -61,6 +61,8 @@ static ssize_t show_protocol(struct device *d, | |||
61 | s = "pulse-distance"; | 61 | s = "pulse-distance"; |
62 | else if (ir_type == IR_TYPE_NEC) | 62 | else if (ir_type == IR_TYPE_NEC) |
63 | s = "nec"; | 63 | s = "nec"; |
64 | else if (ir_type == IR_TYPE_RC6) | ||
65 | s = "rc6"; | ||
64 | else | 66 | else |
65 | s = "other"; | 67 | s = "other"; |
66 | 68 | ||