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