1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* ir-xmp-decoder.c - handle XMP IR Pulse/Space protocol |
3 | * |
4 | * Copyright (C) 2014 by Marcel Mol |
5 | * |
6 | * - Based on info from http://www.hifi-remote.com |
7 | * - Ignore Toggle=9 frames |
8 | * - Ignore XMP-1 XMP-2 difference, always store 16 bit OBC |
9 | */ |
10 | |
11 | #include <linux/bitrev.h> |
12 | #include <linux/module.h> |
13 | #include "rc-core-priv.h" |
14 | |
15 | #define XMP_UNIT 136 /* us */ |
16 | #define XMP_LEADER 210 /* us */ |
17 | #define XMP_NIBBLE_PREFIX 760 /* us */ |
18 | #define XMP_HALFFRAME_SPACE 13800 /* us */ |
19 | /* should be 80ms but not all duration supliers can go that high */ |
20 | #define XMP_TRAILER_SPACE 20000 |
21 | |
22 | enum xmp_state { |
23 | STATE_INACTIVE, |
24 | STATE_LEADER_PULSE, |
25 | STATE_NIBBLE_SPACE, |
26 | }; |
27 | |
28 | /** |
29 | * ir_xmp_decode() - Decode one XMP pulse or space |
30 | * @dev: the struct rc_dev descriptor of the device |
31 | * @ev: the struct ir_raw_event descriptor of the pulse/space |
32 | * |
33 | * This function returns -EINVAL if the pulse violates the state machine |
34 | */ |
35 | static int ir_xmp_decode(struct rc_dev *dev, struct ir_raw_event ev) |
36 | { |
37 | struct xmp_dec *data = &dev->raw->xmp; |
38 | |
39 | if (!is_timing_event(ev)) { |
40 | if (ev.overflow) |
41 | data->state = STATE_INACTIVE; |
42 | return 0; |
43 | } |
44 | |
45 | dev_dbg(&dev->dev, "XMP decode started at state %d %d (%uus %s)\n" , |
46 | data->state, data->count, ev.duration, TO_STR(ev.pulse)); |
47 | |
48 | switch (data->state) { |
49 | |
50 | case STATE_INACTIVE: |
51 | if (!ev.pulse) |
52 | break; |
53 | |
54 | if (eq_margin(d1: ev.duration, XMP_LEADER, XMP_UNIT / 2)) { |
55 | data->count = 0; |
56 | data->state = STATE_NIBBLE_SPACE; |
57 | } |
58 | |
59 | return 0; |
60 | |
61 | case STATE_LEADER_PULSE: |
62 | if (!ev.pulse) |
63 | break; |
64 | |
65 | if (eq_margin(d1: ev.duration, XMP_LEADER, XMP_UNIT / 2)) |
66 | data->state = STATE_NIBBLE_SPACE; |
67 | |
68 | return 0; |
69 | |
70 | case STATE_NIBBLE_SPACE: |
71 | if (ev.pulse) |
72 | break; |
73 | |
74 | if (geq_margin(d1: ev.duration, XMP_TRAILER_SPACE, XMP_NIBBLE_PREFIX)) { |
75 | int divider, i; |
76 | u8 addr, subaddr, subaddr2, toggle, oem, obc1, obc2, sum1, sum2; |
77 | u32 *n; |
78 | u32 scancode; |
79 | |
80 | if (data->count != 16) { |
81 | dev_dbg(&dev->dev, "received TRAILER period at index %d: %u\n" , |
82 | data->count, ev.duration); |
83 | data->state = STATE_INACTIVE; |
84 | return -EINVAL; |
85 | } |
86 | |
87 | n = data->durations; |
88 | /* |
89 | * the 4th nibble should be 15 so base the divider on this |
90 | * to transform durations into nibbles. Subtract 2000 from |
91 | * the divider to compensate for fluctuations in the signal |
92 | */ |
93 | divider = (n[3] - XMP_NIBBLE_PREFIX) / 15 - 2000; |
94 | if (divider < 50) { |
95 | dev_dbg(&dev->dev, "divider to small %d.\n" , |
96 | divider); |
97 | data->state = STATE_INACTIVE; |
98 | return -EINVAL; |
99 | } |
100 | |
101 | /* convert to nibbles and do some sanity checks */ |
102 | for (i = 0; i < 16; i++) |
103 | n[i] = (n[i] - XMP_NIBBLE_PREFIX) / divider; |
104 | sum1 = (15 + n[0] + n[1] + n[2] + n[3] + |
105 | n[4] + n[5] + n[6] + n[7]) % 16; |
106 | sum2 = (15 + n[8] + n[9] + n[10] + n[11] + |
107 | n[12] + n[13] + n[14] + n[15]) % 16; |
108 | |
109 | if (sum1 != 15 || sum2 != 15) { |
110 | dev_dbg(&dev->dev, "checksum errors sum1=0x%X sum2=0x%X\n" , |
111 | sum1, sum2); |
112 | data->state = STATE_INACTIVE; |
113 | return -EINVAL; |
114 | } |
115 | |
116 | subaddr = n[0] << 4 | n[2]; |
117 | subaddr2 = n[8] << 4 | n[11]; |
118 | oem = n[4] << 4 | n[5]; |
119 | addr = n[6] << 4 | n[7]; |
120 | toggle = n[10]; |
121 | obc1 = n[12] << 4 | n[13]; |
122 | obc2 = n[14] << 4 | n[15]; |
123 | if (subaddr != subaddr2) { |
124 | dev_dbg(&dev->dev, "subaddress nibbles mismatch 0x%02X != 0x%02X\n" , |
125 | subaddr, subaddr2); |
126 | data->state = STATE_INACTIVE; |
127 | return -EINVAL; |
128 | } |
129 | if (oem != 0x44) |
130 | dev_dbg(&dev->dev, "Warning: OEM nibbles 0x%02X. Expected 0x44\n" , |
131 | oem); |
132 | |
133 | scancode = addr << 24 | subaddr << 16 | |
134 | obc1 << 8 | obc2; |
135 | dev_dbg(&dev->dev, "XMP scancode 0x%06x\n" , scancode); |
136 | |
137 | if (toggle == 0) { |
138 | rc_keydown(dev, protocol: RC_PROTO_XMP, scancode, toggle: 0); |
139 | } else { |
140 | rc_repeat(dev); |
141 | dev_dbg(&dev->dev, "Repeat last key\n" ); |
142 | } |
143 | data->state = STATE_INACTIVE; |
144 | |
145 | return 0; |
146 | |
147 | } else if (geq_margin(d1: ev.duration, XMP_HALFFRAME_SPACE, XMP_NIBBLE_PREFIX)) { |
148 | /* Expect 8 or 16 nibble pulses. 16 in case of 'final' frame */ |
149 | if (data->count == 16) { |
150 | dev_dbg(&dev->dev, "received half frame pulse at index %d. Probably a final frame key-up event: %u\n" , |
151 | data->count, ev.duration); |
152 | /* |
153 | * TODO: for now go back to half frame position |
154 | * so trailer can be found and key press |
155 | * can be handled. |
156 | */ |
157 | data->count = 8; |
158 | } |
159 | |
160 | else if (data->count != 8) |
161 | dev_dbg(&dev->dev, "received half frame pulse at index %d: %u\n" , |
162 | data->count, ev.duration); |
163 | data->state = STATE_LEADER_PULSE; |
164 | |
165 | return 0; |
166 | |
167 | } else if (geq_margin(d1: ev.duration, XMP_NIBBLE_PREFIX, XMP_UNIT)) { |
168 | /* store nibble raw data, decode after trailer */ |
169 | if (data->count == 16) { |
170 | dev_dbg(&dev->dev, "too many pulses (%d) ignoring: %u\n" , |
171 | data->count, ev.duration); |
172 | data->state = STATE_INACTIVE; |
173 | return -EINVAL; |
174 | } |
175 | data->durations[data->count] = ev.duration; |
176 | data->count++; |
177 | data->state = STATE_LEADER_PULSE; |
178 | |
179 | return 0; |
180 | |
181 | } |
182 | |
183 | break; |
184 | } |
185 | |
186 | dev_dbg(&dev->dev, "XMP decode failed at count %d state %d (%uus %s)\n" , |
187 | data->count, data->state, ev.duration, TO_STR(ev.pulse)); |
188 | data->state = STATE_INACTIVE; |
189 | return -EINVAL; |
190 | } |
191 | |
192 | static struct ir_raw_handler xmp_handler = { |
193 | .protocols = RC_PROTO_BIT_XMP, |
194 | .decode = ir_xmp_decode, |
195 | .min_timeout = XMP_TRAILER_SPACE, |
196 | }; |
197 | |
198 | static int __init ir_xmp_decode_init(void) |
199 | { |
200 | ir_raw_handler_register(ir_raw_handler: &xmp_handler); |
201 | |
202 | printk(KERN_INFO "IR XMP protocol handler initialized\n" ); |
203 | return 0; |
204 | } |
205 | |
206 | static void __exit ir_xmp_decode_exit(void) |
207 | { |
208 | ir_raw_handler_unregister(ir_raw_handler: &xmp_handler); |
209 | } |
210 | |
211 | module_init(ir_xmp_decode_init); |
212 | module_exit(ir_xmp_decode_exit); |
213 | |
214 | MODULE_LICENSE("GPL" ); |
215 | MODULE_AUTHOR("Marcel Mol <marcel@mesa.nl>" ); |
216 | MODULE_AUTHOR("MESA Consulting (http://www.mesa.nl)" ); |
217 | MODULE_DESCRIPTION("XMP IR protocol decoder" ); |
218 | |