| @@ -0,0 +1,193 @@ |
| 1 | +// SPDX-License-Identifier: GPL-2.0+ |
| 2 | +// ir-imon-decoder.c - handle iMon protocol |
| 3 | +// |
| 4 | +// Copyright (C) 2018 by Sean Young <sean@mess.org> |
| 5 | + |
| 6 | +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 7 | + |
| 8 | +#include <linux/module.h> |
| 9 | +#include "rc-core-priv.h" |
| 10 | + |
| 11 | +#define IMON_UNIT 415662 /* ns */ |
| 12 | +#define IMON_BITS 30 |
| 13 | +#define IMON_CHKBITS (BIT(30) | BIT(25) | BIT(24) | BIT(22) | \ |
| 14 | + BIT(21) | BIT(20) | BIT(19) | BIT(18) | \ |
| 15 | + BIT(17) | BIT(16) | BIT(14) | BIT(13) | \ |
| 16 | + BIT(12) | BIT(11) | BIT(10) | BIT(9)) |
| 17 | + |
| 18 | +/* |
| 19 | + * This protocol has 30 bits. The format is one IMON_UNIT header pulse, |
| 20 | + * followed by 30 bits. Each bit is one IMON_UNIT check field, and then |
| 21 | + * one IMON_UNIT field with the actual bit (1=space, 0=pulse). |
| 22 | + * The check field is always space for some bits, for others it is pulse if |
| 23 | + * both the preceding and current bit are zero, else space. IMON_CHKBITS |
| 24 | + * defines which bits are of type check. |
| 25 | + * |
| 26 | + * There is no way to distinguish an incomplete message from one where |
| 27 | + * the lower bits are all set, iow. the last pulse is for the lowest |
| 28 | + * bit which is 0. |
| 29 | + */ |
| 30 | +enum imon_state { |
| 31 | + STATE_INACTIVE, |
| 32 | + STATE_BIT_CHK, |
| 33 | + STATE_BIT_START, |
| 34 | + STATE_FINISHED |
| 35 | +}; |
| 36 | + |
| 37 | +/** |
| 38 | + * ir_imon_decode() - Decode one iMON pulse or space |
| 39 | + * @dev: the struct rc_dev descriptor of the device |
| 40 | + * @ev: the struct ir_raw_event descriptor of the pulse/space |
| 41 | + * |
| 42 | + * This function returns -EINVAL if the pulse violates the state machine |
| 43 | + */ |
| 44 | +static int ir_imon_decode(struct rc_dev *dev, struct ir_raw_event ev) |
| 45 | +{ |
| 46 | + struct imon_dec *data = &dev->raw->imon; |
| 47 | + |
| 48 | + if (!is_timing_event(ev)) { |
| 49 | + if (ev.reset) |
| 50 | + data->state = STATE_INACTIVE; |
| 51 | + return 0; |
| 52 | + } |
| 53 | + |
| 54 | + dev_dbg(&dev->dev, |
| 55 | + "iMON decode started at state %d bitno %d (%uus %s)\n", |
| 56 | + data->state, data->count, TO_US(ev.duration), |
| 57 | + TO_STR(ev.pulse)); |
| 58 | + |
| 59 | + for (;;) { |
| 60 | + if (!geq_margin(ev.duration, IMON_UNIT, IMON_UNIT / 2)) |
| 61 | + return 0; |
| 62 | + |
| 63 | + decrease_duration(&ev, IMON_UNIT); |
| 64 | + |
| 65 | + switch (data->state) { |
| 66 | + case STATE_INACTIVE: |
| 67 | + if (ev.pulse) { |
| 68 | + data->state = STATE_BIT_CHK; |
| 69 | + data->bits = 0; |
| 70 | + data->count = IMON_BITS; |
| 71 | + } |
| 72 | + break; |
| 73 | + case STATE_BIT_CHK: |
| 74 | + if (IMON_CHKBITS & BIT(data->count)) |
| 75 | + data->last_chk = ev.pulse; |
| 76 | + else if (ev.pulse) |
| 77 | + goto err_out; |
| 78 | + data->state = STATE_BIT_START; |
| 79 | + break; |
| 80 | + case STATE_BIT_START: |
| 81 | + data->bits <<= 1; |
| 82 | + if (!ev.pulse) |
| 83 | + data->bits |= 1; |
| 84 | + |
| 85 | + if (IMON_CHKBITS & BIT(data->count)) { |
| 86 | + if (data->last_chk != !(data->bits & 3)) |
| 87 | + goto err_out; |
| 88 | + } |
| 89 | + |
| 90 | + if (!data->count--) |
| 91 | + data->state = STATE_FINISHED; |
| 92 | + else |
| 93 | + data->state = STATE_BIT_CHK; |
| 94 | + break; |
| 95 | + case STATE_FINISHED: |
| 96 | + if (ev.pulse) |
| 97 | + goto err_out; |
| 98 | + rc_keydown(dev, RC_PROTO_IMON, data->bits, 0); |
| 99 | + data->state = STATE_INACTIVE; |
| 100 | + break; |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | +err_out: |
| 105 | + dev_dbg(&dev->dev, |
| 106 | + "iMON decode failed at state %d bitno %d (%uus %s)\n", |
| 107 | + data->state, data->count, TO_US(ev.duration), |
| 108 | + TO_STR(ev.pulse)); |
| 109 | + |
| 110 | + data->state = STATE_INACTIVE; |
| 111 | + |
| 112 | + return -EINVAL; |
| 113 | +} |
| 114 | + |
| 115 | +/** |
| 116 | + * ir_imon_encode() - Encode a scancode as a stream of raw events |
| 117 | + * |
| 118 | + * @protocol: protocol to encode |
| 119 | + * @scancode: scancode to encode |
| 120 | + * @events: array of raw ir events to write into |
| 121 | + * @max: maximum size of @events |
| 122 | + * |
| 123 | + * Returns: The number of events written. |
| 124 | + * -ENOBUFS if there isn't enough space in the array to fit the |
| 125 | + * encoding. In this case all @max events will have been written. |
| 126 | + */ |
| 127 | +static int ir_imon_encode(enum rc_proto protocol, u32 scancode, |
| 128 | + struct ir_raw_event *events, unsigned int max) |
| 129 | +{ |
| 130 | + struct ir_raw_event *e = events; |
| 131 | + int i, pulse; |
| 132 | + |
| 133 | + if (!max--) |
| 134 | + return -ENOBUFS; |
| 135 | + init_ir_raw_event_duration(e, 1, IMON_UNIT); |
| 136 | + |
| 137 | + for (i = IMON_BITS; i >= 0; i--) { |
| 138 | + if (BIT(i) & IMON_CHKBITS) |
| 139 | + pulse = !(scancode & (BIT(i) | BIT(i + 1))); |
| 140 | + else |
| 141 | + pulse = 0; |
| 142 | + |
| 143 | + if (pulse == e->pulse) { |
| 144 | + e->duration += IMON_UNIT; |
| 145 | + } else { |
| 146 | + if (!max--) |
| 147 | + return -ENOBUFS; |
| 148 | + init_ir_raw_event_duration(++e, pulse, IMON_UNIT); |
| 149 | + } |
| 150 | + |
| 151 | + pulse = !(scancode & BIT(i)); |
| 152 | + |
| 153 | + if (pulse == e->pulse) { |
| 154 | + e->duration += IMON_UNIT; |
| 155 | + } else { |
| 156 | + if (!max--) |
| 157 | + return -ENOBUFS; |
| 158 | + init_ir_raw_event_duration(++e, pulse, IMON_UNIT); |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + if (e->pulse) |
| 163 | + e++; |
| 164 | + |
| 165 | + return e - events; |
| 166 | +} |
| 167 | + |
| 168 | +static struct ir_raw_handler imon_handler = { |
| 169 | + .protocols = RC_PROTO_BIT_IMON, |
| 170 | + .decode = ir_imon_decode, |
| 171 | + .encode = ir_imon_encode, |
| 172 | + .carrier = 38000, |
| 173 | +}; |
| 174 | + |
| 175 | +static int __init ir_imon_decode_init(void) |
| 176 | +{ |
| 177 | + ir_raw_handler_register(&imon_handler); |
| 178 | + |
| 179 | + pr_info("IR iMON protocol handler initialized\n"); |
| 180 | + return 0; |
| 181 | +} |
| 182 | + |
| 183 | +static void __exit ir_imon_decode_exit(void) |
| 184 | +{ |
| 185 | + ir_raw_handler_unregister(&imon_handler); |
| 186 | +} |
| 187 | + |
| 188 | +module_init(ir_imon_decode_init); |
| 189 | +module_exit(ir_imon_decode_exit); |
| 190 | + |
| 191 | +MODULE_LICENSE("GPL"); |
| 192 | +MODULE_AUTHOR("Sean Young <sean@mess.org>"); |
| 193 | +MODULE_DESCRIPTION("iMON IR protocol decoder"); |