2019-05-29 17:17:59 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2014-07-27 00:28:26 +04:00
/* ir-xmp-decoder.c - handle XMP IR Pulse/Space protocol
*
* Copyright ( C ) 2014 by Marcel Mol
*
* - Based on info from http : //www.hifi-remote.com
* - Ignore Toggle = 9 frames
* - Ignore XMP - 1 XMP - 2 difference , always store 16 bit OBC
*/
# include <linux/bitrev.h>
# include <linux/module.h>
# include "rc-core-priv.h"
# define XMP_UNIT 136000 /* ns */
# define XMP_LEADER 210000 /* ns */
# define XMP_NIBBLE_PREFIX 760000 /* ns */
# define XMP_HALFFRAME_SPACE 13800000 /* ns */
# define XMP_TRAILER_SPACE 20000000 /* should be 80ms but not all dureation supliers can go that high */
enum xmp_state {
STATE_INACTIVE ,
STATE_LEADER_PULSE ,
STATE_NIBBLE_SPACE ,
} ;
/**
* ir_xmp_decode ( ) - Decode one XMP pulse or space
* @ dev : the struct rc_dev descriptor of the device
2017-11-29 11:46:30 +03:00
* @ ev : the struct ir_raw_event descriptor of the pulse / space
2014-07-27 00:28:26 +04:00
*
* This function returns - EINVAL if the pulse violates the state machine
*/
static int ir_xmp_decode ( struct rc_dev * dev , struct ir_raw_event ev )
{
struct xmp_dec * data = & dev - > raw - > xmp ;
if ( ! is_timing_event ( ev ) ) {
if ( ev . reset )
data - > state = STATE_INACTIVE ;
return 0 ;
}
2018-02-12 15:20:52 +03:00
dev_dbg ( & dev - > dev , " XMP decode started at state %d %d (%uus %s) \n " ,
data - > state , data - > count , TO_US ( ev . duration ) , TO_STR ( ev . pulse ) ) ;
2014-07-27 00:28:26 +04:00
switch ( data - > state ) {
case STATE_INACTIVE :
if ( ! ev . pulse )
break ;
if ( eq_margin ( ev . duration , XMP_LEADER , XMP_UNIT / 2 ) ) {
data - > count = 0 ;
data - > state = STATE_NIBBLE_SPACE ;
}
return 0 ;
case STATE_LEADER_PULSE :
if ( ! ev . pulse )
break ;
if ( eq_margin ( ev . duration , XMP_LEADER , XMP_UNIT / 2 ) )
data - > state = STATE_NIBBLE_SPACE ;
return 0 ;
case STATE_NIBBLE_SPACE :
if ( ev . pulse )
break ;
if ( geq_margin ( ev . duration , XMP_TRAILER_SPACE , XMP_NIBBLE_PREFIX ) ) {
int divider , i ;
u8 addr , subaddr , subaddr2 , toggle , oem , obc1 , obc2 , sum1 , sum2 ;
u32 * n ;
u32 scancode ;
if ( data - > count ! = 16 ) {
2018-02-12 15:20:52 +03:00
dev_dbg ( & dev - > dev , " received TRAILER period at index %d: %u \n " ,
2014-07-27 00:28:26 +04:00
data - > count , ev . duration ) ;
data - > state = STATE_INACTIVE ;
return - EINVAL ;
}
n = data - > durations ;
/*
* the 4 th nibble should be 15 so base the divider on this
2019-02-18 22:29:01 +03:00
* to transform durations into nibbles . Subtract 2000 from
2014-07-27 00:28:26 +04:00
* the divider to compensate for fluctuations in the signal
*/
divider = ( n [ 3 ] - XMP_NIBBLE_PREFIX ) / 15 - 2000 ;
if ( divider < 50 ) {
2018-02-12 15:20:52 +03:00
dev_dbg ( & dev - > dev , " divider to small %d. \n " ,
divider ) ;
2014-07-27 00:28:26 +04:00
data - > state = STATE_INACTIVE ;
return - EINVAL ;
}
/* convert to nibbles and do some sanity checks */
for ( i = 0 ; i < 16 ; i + + )
n [ i ] = ( n [ i ] - XMP_NIBBLE_PREFIX ) / divider ;
sum1 = ( 15 + n [ 0 ] + n [ 1 ] + n [ 2 ] + n [ 3 ] +
n [ 4 ] + n [ 5 ] + n [ 6 ] + n [ 7 ] ) % 16 ;
sum2 = ( 15 + n [ 8 ] + n [ 9 ] + n [ 10 ] + n [ 11 ] +
n [ 12 ] + n [ 13 ] + n [ 14 ] + n [ 15 ] ) % 16 ;
if ( sum1 ! = 15 | | sum2 ! = 15 ) {
2018-02-12 15:20:52 +03:00
dev_dbg ( & dev - > dev , " checksum errors sum1=0x%X sum2=0x%X \n " ,
2014-07-27 00:28:26 +04:00
sum1 , sum2 ) ;
data - > state = STATE_INACTIVE ;
return - EINVAL ;
}
subaddr = n [ 0 ] < < 4 | n [ 2 ] ;
subaddr2 = n [ 8 ] < < 4 | n [ 11 ] ;
oem = n [ 4 ] < < 4 | n [ 5 ] ;
addr = n [ 6 ] < < 4 | n [ 7 ] ;
toggle = n [ 10 ] ;
obc1 = n [ 12 ] < < 4 | n [ 13 ] ;
obc2 = n [ 14 ] < < 4 | n [ 15 ] ;
if ( subaddr ! = subaddr2 ) {
2018-02-12 15:20:52 +03:00
dev_dbg ( & dev - > dev , " subaddress nibbles mismatch 0x%02X != 0x%02X \n " ,
2014-07-27 00:28:26 +04:00
subaddr , subaddr2 ) ;
data - > state = STATE_INACTIVE ;
return - EINVAL ;
}
if ( oem ! = 0x44 )
2018-02-12 15:20:52 +03:00
dev_dbg ( & dev - > dev , " Warning: OEM nibbles 0x%02X. Expected 0x44 \n " ,
2014-07-27 00:28:26 +04:00
oem ) ;
scancode = addr < < 24 | subaddr < < 16 |
obc1 < < 8 | obc2 ;
2018-02-12 15:20:52 +03:00
dev_dbg ( & dev - > dev , " XMP scancode 0x%06x \n " , scancode ) ;
2014-07-27 00:28:26 +04:00
if ( toggle = = 0 ) {
2017-08-07 23:20:58 +03:00
rc_keydown ( dev , RC_PROTO_XMP , scancode , 0 ) ;
2014-07-27 00:28:26 +04:00
} else {
rc_repeat ( dev ) ;
2018-02-12 15:20:52 +03:00
dev_dbg ( & dev - > dev , " Repeat last key \n " ) ;
2014-07-27 00:28:26 +04:00
}
data - > state = STATE_INACTIVE ;
return 0 ;
} else if ( geq_margin ( ev . duration , XMP_HALFFRAME_SPACE , XMP_NIBBLE_PREFIX ) ) {
/* Expect 8 or 16 nibble pulses. 16 in case of 'final' frame */
if ( data - > count = = 16 ) {
2018-02-12 15:20:52 +03:00
dev_dbg ( & dev - > dev , " received half frame pulse at index %d. Probably a final frame key-up event: %u \n " ,
2014-07-27 00:28:26 +04:00
data - > count , ev . duration ) ;
/*
* TODO : for now go back to half frame position
* so trailer can be found and key press
* can be handled .
*/
data - > count = 8 ;
}
else if ( data - > count ! = 8 )
2018-02-12 15:20:52 +03:00
dev_dbg ( & dev - > dev , " received half frame pulse at index %d: %u \n " ,
2014-07-27 00:28:26 +04:00
data - > count , ev . duration ) ;
data - > state = STATE_LEADER_PULSE ;
return 0 ;
} else if ( geq_margin ( ev . duration , XMP_NIBBLE_PREFIX , XMP_UNIT ) ) {
/* store nibble raw data, decode after trailer */
if ( data - > count = = 16 ) {
2018-02-12 15:20:52 +03:00
dev_dbg ( & dev - > dev , " to many pulses (%d) ignoring: %u \n " ,
2014-07-27 00:28:26 +04:00
data - > count , ev . duration ) ;
data - > state = STATE_INACTIVE ;
return - EINVAL ;
}
data - > durations [ data - > count ] = ev . duration ;
data - > count + + ;
data - > state = STATE_LEADER_PULSE ;
return 0 ;
}
break ;
}
2018-02-12 15:20:52 +03:00
dev_dbg ( & dev - > dev , " XMP decode failed at count %d state %d (%uus %s) \n " ,
data - > count , data - > state , TO_US ( ev . duration ) , TO_STR ( ev . pulse ) ) ;
2014-07-27 00:28:26 +04:00
data - > state = STATE_INACTIVE ;
return - EINVAL ;
}
static struct ir_raw_handler xmp_handler = {
2017-08-07 23:20:58 +03:00
. protocols = RC_PROTO_BIT_XMP ,
2014-07-27 00:28:26 +04:00
. decode = ir_xmp_decode ,
2018-03-23 23:47:37 +03:00
. min_timeout = XMP_TRAILER_SPACE ,
2014-07-27 00:28:26 +04:00
} ;
static int __init ir_xmp_decode_init ( void )
{
ir_raw_handler_register ( & xmp_handler ) ;
printk ( KERN_INFO " IR XMP protocol handler initialized \n " ) ;
return 0 ;
}
static void __exit ir_xmp_decode_exit ( void )
{
ir_raw_handler_unregister ( & xmp_handler ) ;
}
module_init ( ir_xmp_decode_init ) ;
module_exit ( ir_xmp_decode_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Marcel Mol <marcel@mesa.nl> " ) ;
MODULE_AUTHOR ( " MESA Consulting (http://www.mesa.nl) " ) ;
MODULE_DESCRIPTION ( " XMP IR protocol decoder " ) ;