256 lines
5.3 KiB
C
256 lines
5.3 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
// ir-rcmm-decoder.c - A decoder for the RCMM IR protocol
|
|
//
|
|
// Copyright (C) 2018 by Patrick Lerda <patrick9876@free.fr>
|
|
|
|
#include "rc-core-priv.h"
|
|
#include <linux/module.h>
|
|
|
|
#define RCMM_UNIT 166 /* microseconds */
|
|
#define RCMM_PREFIX_PULSE 417 /* 166.666666666666*2.5 */
|
|
#define RCMM_PULSE_0 278 /* 166.666666666666*(1+2/3) */
|
|
#define RCMM_PULSE_1 444 /* 166.666666666666*(2+2/3) */
|
|
#define RCMM_PULSE_2 611 /* 166.666666666666*(3+2/3) */
|
|
#define RCMM_PULSE_3 778 /* 166.666666666666*(4+2/3) */
|
|
|
|
enum rcmm_state {
|
|
STATE_INACTIVE,
|
|
STATE_LOW,
|
|
STATE_BUMP,
|
|
STATE_VALUE,
|
|
STATE_FINISHED,
|
|
};
|
|
|
|
static bool rcmm_mode(const struct rcmm_dec *data)
|
|
{
|
|
return !((0x000c0000 & data->bits) == 0x000c0000);
|
|
}
|
|
|
|
static int rcmm_miscmode(struct rc_dev *dev, struct rcmm_dec *data)
|
|
{
|
|
switch (data->count) {
|
|
case 24:
|
|
if (dev->enabled_protocols & RC_PROTO_BIT_RCMM24) {
|
|
rc_keydown(dev, RC_PROTO_RCMM24, data->bits, 0);
|
|
data->state = STATE_INACTIVE;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
|
|
case 12:
|
|
if (dev->enabled_protocols & RC_PROTO_BIT_RCMM12) {
|
|
rc_keydown(dev, RC_PROTO_RCMM12, data->bits, 0);
|
|
data->state = STATE_INACTIVE;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* ir_rcmm_decode() - Decode one RCMM pulse or space
|
|
* @dev: the struct rc_dev descriptor of the device
|
|
* @ev: the struct ir_raw_event descriptor of the pulse/space
|
|
*
|
|
* This function returns -EINVAL if the pulse violates the state machine
|
|
*/
|
|
static int ir_rcmm_decode(struct rc_dev *dev, struct ir_raw_event ev)
|
|
{
|
|
struct rcmm_dec *data = &dev->raw->rcmm;
|
|
u32 scancode;
|
|
u8 toggle;
|
|
int value;
|
|
|
|
if (!(dev->enabled_protocols & (RC_PROTO_BIT_RCMM32 |
|
|
RC_PROTO_BIT_RCMM24 |
|
|
RC_PROTO_BIT_RCMM12)))
|
|
return 0;
|
|
|
|
if (!is_timing_event(ev)) {
|
|
if (ev.overflow)
|
|
data->state = STATE_INACTIVE;
|
|
return 0;
|
|
}
|
|
|
|
switch (data->state) {
|
|
case STATE_INACTIVE:
|
|
if (!ev.pulse)
|
|
break;
|
|
|
|
if (!eq_margin(ev.duration, RCMM_PREFIX_PULSE, RCMM_UNIT))
|
|
break;
|
|
|
|
data->state = STATE_LOW;
|
|
data->count = 0;
|
|
data->bits = 0;
|
|
return 0;
|
|
|
|
case STATE_LOW:
|
|
if (ev.pulse)
|
|
break;
|
|
|
|
if (!eq_margin(ev.duration, RCMM_PULSE_0, RCMM_UNIT))
|
|
break;
|
|
|
|
data->state = STATE_BUMP;
|
|
return 0;
|
|
|
|
case STATE_BUMP:
|
|
if (!ev.pulse)
|
|
break;
|
|
|
|
if (!eq_margin(ev.duration, RCMM_UNIT, RCMM_UNIT / 2))
|
|
break;
|
|
|
|
data->state = STATE_VALUE;
|
|
return 0;
|
|
|
|
case STATE_VALUE:
|
|
if (ev.pulse)
|
|
break;
|
|
|
|
if (eq_margin(ev.duration, RCMM_PULSE_0, RCMM_UNIT / 2))
|
|
value = 0;
|
|
else if (eq_margin(ev.duration, RCMM_PULSE_1, RCMM_UNIT / 2))
|
|
value = 1;
|
|
else if (eq_margin(ev.duration, RCMM_PULSE_2, RCMM_UNIT / 2))
|
|
value = 2;
|
|
else if (eq_margin(ev.duration, RCMM_PULSE_3, RCMM_UNIT / 2))
|
|
value = 3;
|
|
else
|
|
value = -1;
|
|
|
|
if (value == -1) {
|
|
if (!rcmm_miscmode(dev, data))
|
|
return 0;
|
|
break;
|
|
}
|
|
|
|
data->bits <<= 2;
|
|
data->bits |= value;
|
|
|
|
data->count += 2;
|
|
|
|
if (data->count < 32)
|
|
data->state = STATE_BUMP;
|
|
else
|
|
data->state = STATE_FINISHED;
|
|
|
|
return 0;
|
|
|
|
case STATE_FINISHED:
|
|
if (!ev.pulse)
|
|
break;
|
|
|
|
if (!eq_margin(ev.duration, RCMM_UNIT, RCMM_UNIT / 2))
|
|
break;
|
|
|
|
if (rcmm_mode(data)) {
|
|
toggle = !!(0x8000 & data->bits);
|
|
scancode = data->bits & ~0x8000;
|
|
} else {
|
|
toggle = 0;
|
|
scancode = data->bits;
|
|
}
|
|
|
|
if (dev->enabled_protocols & RC_PROTO_BIT_RCMM32) {
|
|
rc_keydown(dev, RC_PROTO_RCMM32, scancode, toggle);
|
|
data->state = STATE_INACTIVE;
|
|
return 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
dev_dbg(&dev->dev, "RC-MM decode failed at count %d state %d (%uus %s)\n",
|
|
data->count, data->state, ev.duration, TO_STR(ev.pulse));
|
|
data->state = STATE_INACTIVE;
|
|
return -EINVAL;
|
|
}
|
|
|
|
static const int rcmmspace[] = {
|
|
RCMM_PULSE_0,
|
|
RCMM_PULSE_1,
|
|
RCMM_PULSE_2,
|
|
RCMM_PULSE_3,
|
|
};
|
|
|
|
static int ir_rcmm_rawencoder(struct ir_raw_event **ev, unsigned int max,
|
|
unsigned int n, u32 data)
|
|
{
|
|
int i;
|
|
int ret;
|
|
|
|
ret = ir_raw_gen_pulse_space(ev, &max, RCMM_PREFIX_PULSE, RCMM_PULSE_0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = n - 2; i >= 0; i -= 2) {
|
|
const unsigned int space = rcmmspace[(data >> i) & 3];
|
|
|
|
ret = ir_raw_gen_pulse_space(ev, &max, RCMM_UNIT, space);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return ir_raw_gen_pulse_space(ev, &max, RCMM_UNIT, RCMM_PULSE_3 * 2);
|
|
}
|
|
|
|
static int ir_rcmm_encode(enum rc_proto protocol, u32 scancode,
|
|
struct ir_raw_event *events, unsigned int max)
|
|
{
|
|
struct ir_raw_event *e = events;
|
|
int ret;
|
|
|
|
switch (protocol) {
|
|
case RC_PROTO_RCMM32:
|
|
ret = ir_rcmm_rawencoder(&e, max, 32, scancode);
|
|
break;
|
|
case RC_PROTO_RCMM24:
|
|
ret = ir_rcmm_rawencoder(&e, max, 24, scancode);
|
|
break;
|
|
case RC_PROTO_RCMM12:
|
|
ret = ir_rcmm_rawencoder(&e, max, 12, scancode);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return e - events;
|
|
}
|
|
|
|
static struct ir_raw_handler rcmm_handler = {
|
|
.protocols = RC_PROTO_BIT_RCMM32 |
|
|
RC_PROTO_BIT_RCMM24 |
|
|
RC_PROTO_BIT_RCMM12,
|
|
.decode = ir_rcmm_decode,
|
|
.encode = ir_rcmm_encode,
|
|
.carrier = 36000,
|
|
.min_timeout = RCMM_PULSE_3 + RCMM_UNIT,
|
|
};
|
|
|
|
static int __init ir_rcmm_decode_init(void)
|
|
{
|
|
ir_raw_handler_register(&rcmm_handler);
|
|
|
|
pr_info("IR RCMM protocol handler initialized\n");
|
|
return 0;
|
|
}
|
|
|
|
static void __exit ir_rcmm_decode_exit(void)
|
|
{
|
|
ir_raw_handler_unregister(&rcmm_handler);
|
|
}
|
|
|
|
module_init(ir_rcmm_decode_init);
|
|
module_exit(ir_rcmm_decode_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Patrick Lerda");
|
|
MODULE_DESCRIPTION("RCMM IR protocol decoder");
|