blob: ccc5be240f82c80990c571aa57b9a081b514ec5a [file] [log] [blame]
David Härdeman784a4932010-04-08 20:04:40 -03001/* 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 */
36static LIST_HEAD(decoder_list);
37static DEFINE_SPINLOCK(decoder_lock);
38
39enum rc6_mode {
40 RC6_MODE_0,
41 RC6_MODE_6A,
42 RC6_MODE_UNKNOWN,
43};
44
45enum 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
57struct 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 */
79static 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
92static 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
112static 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
127static DEVICE_ATTR(enabled, S_IRUGO | S_IWUSR, show_enabled, store_enabled);
128
129static struct attribute *decoder_attributes[] = {
130 &dev_attr_enabled.attr,
131 NULL
132};
133
134static struct attribute_group decoder_attribute_group = {
135 .name = "rc6_decoder",
136 .attrs = decoder_attributes,
137};
138
139static 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 */
159static 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
183again:
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
337out:
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
344static 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
370static 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
388static 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
394static 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
402static void __exit ir_rc6_decode_exit(void)
403{
404 ir_raw_handler_unregister(&rc6_handler);
405}
406
407module_init(ir_rc6_decode_init);
408module_exit(ir_rc6_decode_exit);
409
410MODULE_LICENSE("GPL");
411MODULE_AUTHOR("David Härdeman <david@hardeman.nu>");
412MODULE_DESCRIPTION("RC6 IR protocol decoder");