Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /* ir-xmp-decoder.c - handle XMP IR Pulse/Space protocol
0003  *
0004  * Copyright (C) 2014 by Marcel Mol
0005  *
0006  * - Based on info from http://www.hifi-remote.com
0007  * - Ignore Toggle=9 frames
0008  * - Ignore XMP-1 XMP-2 difference, always store 16 bit OBC
0009  */
0010 
0011 #include <linux/bitrev.h>
0012 #include <linux/module.h>
0013 #include "rc-core-priv.h"
0014 
0015 #define XMP_UNIT          136 /* us */
0016 #define XMP_LEADER        210 /* us */
0017 #define XMP_NIBBLE_PREFIX     760 /* us */
0018 #define XMP_HALFFRAME_SPACE 13800 /* us */
0019 /* should be 80ms but not all duration supliers can go that high */
0020 #define XMP_TRAILER_SPACE   20000
0021 
0022 enum xmp_state {
0023     STATE_INACTIVE,
0024     STATE_LEADER_PULSE,
0025     STATE_NIBBLE_SPACE,
0026 };
0027 
0028 /**
0029  * ir_xmp_decode() - Decode one XMP pulse or space
0030  * @dev:    the struct rc_dev descriptor of the device
0031  * @ev:     the struct ir_raw_event descriptor of the pulse/space
0032  *
0033  * This function returns -EINVAL if the pulse violates the state machine
0034  */
0035 static int ir_xmp_decode(struct rc_dev *dev, struct ir_raw_event ev)
0036 {
0037     struct xmp_dec *data = &dev->raw->xmp;
0038 
0039     if (!is_timing_event(ev)) {
0040         if (ev.overflow)
0041             data->state = STATE_INACTIVE;
0042         return 0;
0043     }
0044 
0045     dev_dbg(&dev->dev, "XMP decode started at state %d %d (%uus %s)\n",
0046         data->state, data->count, ev.duration, TO_STR(ev.pulse));
0047 
0048     switch (data->state) {
0049 
0050     case STATE_INACTIVE:
0051         if (!ev.pulse)
0052             break;
0053 
0054         if (eq_margin(ev.duration, XMP_LEADER, XMP_UNIT / 2)) {
0055             data->count = 0;
0056             data->state = STATE_NIBBLE_SPACE;
0057         }
0058 
0059         return 0;
0060 
0061     case STATE_LEADER_PULSE:
0062         if (!ev.pulse)
0063             break;
0064 
0065         if (eq_margin(ev.duration, XMP_LEADER, XMP_UNIT / 2))
0066             data->state = STATE_NIBBLE_SPACE;
0067 
0068         return 0;
0069 
0070     case STATE_NIBBLE_SPACE:
0071         if (ev.pulse)
0072             break;
0073 
0074         if (geq_margin(ev.duration, XMP_TRAILER_SPACE, XMP_NIBBLE_PREFIX)) {
0075             int divider, i;
0076             u8 addr, subaddr, subaddr2, toggle, oem, obc1, obc2, sum1, sum2;
0077             u32 *n;
0078             u32 scancode;
0079 
0080             if (data->count != 16) {
0081                 dev_dbg(&dev->dev, "received TRAILER period at index %d: %u\n",
0082                     data->count, ev.duration);
0083                 data->state = STATE_INACTIVE;
0084                 return -EINVAL;
0085             }
0086 
0087             n = data->durations;
0088             /*
0089              * the 4th nibble should be 15 so base the divider on this
0090              * to transform durations into nibbles. Subtract 2000 from
0091              * the divider to compensate for fluctuations in the signal
0092              */
0093             divider = (n[3] - XMP_NIBBLE_PREFIX) / 15 - 2000;
0094             if (divider < 50) {
0095                 dev_dbg(&dev->dev, "divider to small %d.\n",
0096                     divider);
0097                 data->state = STATE_INACTIVE;
0098                 return -EINVAL;
0099             }
0100 
0101             /* convert to nibbles and do some sanity checks */
0102             for (i = 0; i < 16; i++)
0103                 n[i] = (n[i] - XMP_NIBBLE_PREFIX) / divider;
0104             sum1 = (15 + n[0] + n[1] + n[2] + n[3] +
0105                 n[4] + n[5] + n[6] + n[7]) % 16;
0106             sum2 = (15 + n[8] + n[9] + n[10] + n[11] +
0107                 n[12] + n[13] + n[14] + n[15]) % 16;
0108 
0109             if (sum1 != 15 || sum2 != 15) {
0110                 dev_dbg(&dev->dev, "checksum errors sum1=0x%X sum2=0x%X\n",
0111                     sum1, sum2);
0112                 data->state = STATE_INACTIVE;
0113                 return -EINVAL;
0114             }
0115 
0116             subaddr  = n[0] << 4 | n[2];
0117             subaddr2 = n[8] << 4 | n[11];
0118             oem      = n[4] << 4 | n[5];
0119             addr     = n[6] << 4 | n[7];
0120             toggle   = n[10];
0121             obc1 = n[12] << 4 | n[13];
0122             obc2 = n[14] << 4 | n[15];
0123             if (subaddr != subaddr2) {
0124                 dev_dbg(&dev->dev, "subaddress nibbles mismatch 0x%02X != 0x%02X\n",
0125                     subaddr, subaddr2);
0126                 data->state = STATE_INACTIVE;
0127                 return -EINVAL;
0128             }
0129             if (oem != 0x44)
0130                 dev_dbg(&dev->dev, "Warning: OEM nibbles 0x%02X. Expected 0x44\n",
0131                     oem);
0132 
0133             scancode = addr << 24 | subaddr << 16 |
0134                    obc1 << 8 | obc2;
0135             dev_dbg(&dev->dev, "XMP scancode 0x%06x\n", scancode);
0136 
0137             if (toggle == 0) {
0138                 rc_keydown(dev, RC_PROTO_XMP, scancode, 0);
0139             } else {
0140                 rc_repeat(dev);
0141                 dev_dbg(&dev->dev, "Repeat last key\n");
0142             }
0143             data->state = STATE_INACTIVE;
0144 
0145             return 0;
0146 
0147         } else if (geq_margin(ev.duration, XMP_HALFFRAME_SPACE, XMP_NIBBLE_PREFIX)) {
0148             /* Expect 8 or 16 nibble pulses. 16 in case of 'final' frame */
0149             if (data->count == 16) {
0150                 dev_dbg(&dev->dev, "received half frame pulse at index %d. Probably a final frame key-up event: %u\n",
0151                     data->count, ev.duration);
0152                 /*
0153                  * TODO: for now go back to half frame position
0154                  *   so trailer can be found and key press
0155                  *   can be handled.
0156                  */
0157                 data->count = 8;
0158             }
0159 
0160             else if (data->count != 8)
0161                 dev_dbg(&dev->dev, "received half frame pulse at index %d: %u\n",
0162                     data->count, ev.duration);
0163             data->state = STATE_LEADER_PULSE;
0164 
0165             return 0;
0166 
0167         } else if (geq_margin(ev.duration, XMP_NIBBLE_PREFIX, XMP_UNIT)) {
0168             /* store nibble raw data, decode after trailer */
0169             if (data->count == 16) {
0170                 dev_dbg(&dev->dev, "too many pulses (%d) ignoring: %u\n",
0171                     data->count, ev.duration);
0172                 data->state = STATE_INACTIVE;
0173                 return -EINVAL;
0174             }
0175             data->durations[data->count] = ev.duration;
0176             data->count++;
0177             data->state = STATE_LEADER_PULSE;
0178 
0179             return 0;
0180 
0181         }
0182 
0183         break;
0184     }
0185 
0186     dev_dbg(&dev->dev, "XMP decode failed at count %d state %d (%uus %s)\n",
0187         data->count, data->state, ev.duration, TO_STR(ev.pulse));
0188     data->state = STATE_INACTIVE;
0189     return -EINVAL;
0190 }
0191 
0192 static struct ir_raw_handler xmp_handler = {
0193     .protocols  = RC_PROTO_BIT_XMP,
0194     .decode     = ir_xmp_decode,
0195     .min_timeout    = XMP_TRAILER_SPACE,
0196 };
0197 
0198 static int __init ir_xmp_decode_init(void)
0199 {
0200     ir_raw_handler_register(&xmp_handler);
0201 
0202     printk(KERN_INFO "IR XMP protocol handler initialized\n");
0203     return 0;
0204 }
0205 
0206 static void __exit ir_xmp_decode_exit(void)
0207 {
0208     ir_raw_handler_unregister(&xmp_handler);
0209 }
0210 
0211 module_init(ir_xmp_decode_init);
0212 module_exit(ir_xmp_decode_exit);
0213 
0214 MODULE_LICENSE("GPL");
0215 MODULE_AUTHOR("Marcel Mol <marcel@mesa.nl>");
0216 MODULE_AUTHOR("MESA Consulting (http://www.mesa.nl)");
0217 MODULE_DESCRIPTION("XMP IR protocol decoder");