162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// ir-rc5-decoder.c - decoder for RC5(x) and StreamZap protocols 362306a36Sopenharmony_ci// 462306a36Sopenharmony_ci// Copyright (C) 2010 by Mauro Carvalho Chehab 562306a36Sopenharmony_ci// Copyright (C) 2010 by Jarod Wilson <jarod@redhat.com> 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci/* 862306a36Sopenharmony_ci * This decoder handles the 14 bit RC5 protocol, 15 bit "StreamZap" protocol 962306a36Sopenharmony_ci * and 20 bit RC5x protocol. 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include "rc-core-priv.h" 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#define RC5_NBITS 14 1662306a36Sopenharmony_ci#define RC5_SZ_NBITS 15 1762306a36Sopenharmony_ci#define RC5X_NBITS 20 1862306a36Sopenharmony_ci#define CHECK_RC5X_NBITS 8 1962306a36Sopenharmony_ci#define RC5_UNIT 889 /* us */ 2062306a36Sopenharmony_ci#define RC5_BIT_START (1 * RC5_UNIT) 2162306a36Sopenharmony_ci#define RC5_BIT_END (1 * RC5_UNIT) 2262306a36Sopenharmony_ci#define RC5X_SPACE (4 * RC5_UNIT) 2362306a36Sopenharmony_ci#define RC5_TRAILER (6 * RC5_UNIT) /* In reality, approx 100 */ 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cienum rc5_state { 2662306a36Sopenharmony_ci STATE_INACTIVE, 2762306a36Sopenharmony_ci STATE_BIT_START, 2862306a36Sopenharmony_ci STATE_BIT_END, 2962306a36Sopenharmony_ci STATE_CHECK_RC5X, 3062306a36Sopenharmony_ci STATE_FINISHED, 3162306a36Sopenharmony_ci}; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/** 3462306a36Sopenharmony_ci * ir_rc5_decode() - Decode one RC-5 pulse or space 3562306a36Sopenharmony_ci * @dev: the struct rc_dev descriptor of the device 3662306a36Sopenharmony_ci * @ev: the struct ir_raw_event descriptor of the pulse/space 3762306a36Sopenharmony_ci * 3862306a36Sopenharmony_ci * This function returns -EINVAL if the pulse violates the state machine 3962306a36Sopenharmony_ci */ 4062306a36Sopenharmony_cistatic int ir_rc5_decode(struct rc_dev *dev, struct ir_raw_event ev) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci struct rc5_dec *data = &dev->raw->rc5; 4362306a36Sopenharmony_ci u8 toggle; 4462306a36Sopenharmony_ci u32 scancode; 4562306a36Sopenharmony_ci enum rc_proto protocol; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci if (!is_timing_event(ev)) { 4862306a36Sopenharmony_ci if (ev.overflow) 4962306a36Sopenharmony_ci data->state = STATE_INACTIVE; 5062306a36Sopenharmony_ci return 0; 5162306a36Sopenharmony_ci } 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci if (!geq_margin(ev.duration, RC5_UNIT, RC5_UNIT / 2)) 5462306a36Sopenharmony_ci goto out; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ciagain: 5762306a36Sopenharmony_ci dev_dbg(&dev->dev, "RC5(x/sz) decode started at state %i (%uus %s)\n", 5862306a36Sopenharmony_ci data->state, ev.duration, TO_STR(ev.pulse)); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci if (!geq_margin(ev.duration, RC5_UNIT, RC5_UNIT / 2)) 6162306a36Sopenharmony_ci return 0; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci switch (data->state) { 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci case STATE_INACTIVE: 6662306a36Sopenharmony_ci if (!ev.pulse) 6762306a36Sopenharmony_ci break; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci data->state = STATE_BIT_START; 7062306a36Sopenharmony_ci data->count = 1; 7162306a36Sopenharmony_ci decrease_duration(&ev, RC5_BIT_START); 7262306a36Sopenharmony_ci goto again; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci case STATE_BIT_START: 7562306a36Sopenharmony_ci if (!ev.pulse && geq_margin(ev.duration, RC5_TRAILER, RC5_UNIT / 2)) { 7662306a36Sopenharmony_ci data->state = STATE_FINISHED; 7762306a36Sopenharmony_ci goto again; 7862306a36Sopenharmony_ci } 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci if (!eq_margin(ev.duration, RC5_BIT_START, RC5_UNIT / 2)) 8162306a36Sopenharmony_ci break; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci data->bits <<= 1; 8462306a36Sopenharmony_ci if (!ev.pulse) 8562306a36Sopenharmony_ci data->bits |= 1; 8662306a36Sopenharmony_ci data->count++; 8762306a36Sopenharmony_ci data->state = STATE_BIT_END; 8862306a36Sopenharmony_ci return 0; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci case STATE_BIT_END: 9162306a36Sopenharmony_ci if (data->count == CHECK_RC5X_NBITS) 9262306a36Sopenharmony_ci data->state = STATE_CHECK_RC5X; 9362306a36Sopenharmony_ci else 9462306a36Sopenharmony_ci data->state = STATE_BIT_START; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci decrease_duration(&ev, RC5_BIT_END); 9762306a36Sopenharmony_ci goto again; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci case STATE_CHECK_RC5X: 10062306a36Sopenharmony_ci if (!ev.pulse && geq_margin(ev.duration, RC5X_SPACE, RC5_UNIT / 2)) { 10162306a36Sopenharmony_ci data->is_rc5x = true; 10262306a36Sopenharmony_ci decrease_duration(&ev, RC5X_SPACE); 10362306a36Sopenharmony_ci } else 10462306a36Sopenharmony_ci data->is_rc5x = false; 10562306a36Sopenharmony_ci data->state = STATE_BIT_START; 10662306a36Sopenharmony_ci goto again; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci case STATE_FINISHED: 10962306a36Sopenharmony_ci if (ev.pulse) 11062306a36Sopenharmony_ci break; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci if (data->is_rc5x && data->count == RC5X_NBITS) { 11362306a36Sopenharmony_ci /* RC5X */ 11462306a36Sopenharmony_ci u8 xdata, command, system; 11562306a36Sopenharmony_ci if (!(dev->enabled_protocols & RC_PROTO_BIT_RC5X_20)) { 11662306a36Sopenharmony_ci data->state = STATE_INACTIVE; 11762306a36Sopenharmony_ci return 0; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci xdata = (data->bits & 0x0003F) >> 0; 12062306a36Sopenharmony_ci command = (data->bits & 0x00FC0) >> 6; 12162306a36Sopenharmony_ci system = (data->bits & 0x1F000) >> 12; 12262306a36Sopenharmony_ci toggle = (data->bits & 0x20000) ? 1 : 0; 12362306a36Sopenharmony_ci command += (data->bits & 0x40000) ? 0 : 0x40; 12462306a36Sopenharmony_ci scancode = system << 16 | command << 8 | xdata; 12562306a36Sopenharmony_ci protocol = RC_PROTO_RC5X_20; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci } else if (!data->is_rc5x && data->count == RC5_NBITS) { 12862306a36Sopenharmony_ci /* RC5 */ 12962306a36Sopenharmony_ci u8 command, system; 13062306a36Sopenharmony_ci if (!(dev->enabled_protocols & RC_PROTO_BIT_RC5)) { 13162306a36Sopenharmony_ci data->state = STATE_INACTIVE; 13262306a36Sopenharmony_ci return 0; 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci command = (data->bits & 0x0003F) >> 0; 13562306a36Sopenharmony_ci system = (data->bits & 0x007C0) >> 6; 13662306a36Sopenharmony_ci toggle = (data->bits & 0x00800) ? 1 : 0; 13762306a36Sopenharmony_ci command += (data->bits & 0x01000) ? 0 : 0x40; 13862306a36Sopenharmony_ci scancode = system << 8 | command; 13962306a36Sopenharmony_ci protocol = RC_PROTO_RC5; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci } else if (!data->is_rc5x && data->count == RC5_SZ_NBITS) { 14262306a36Sopenharmony_ci /* RC5 StreamZap */ 14362306a36Sopenharmony_ci u8 command, system; 14462306a36Sopenharmony_ci if (!(dev->enabled_protocols & RC_PROTO_BIT_RC5_SZ)) { 14562306a36Sopenharmony_ci data->state = STATE_INACTIVE; 14662306a36Sopenharmony_ci return 0; 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci command = (data->bits & 0x0003F) >> 0; 14962306a36Sopenharmony_ci system = (data->bits & 0x02FC0) >> 6; 15062306a36Sopenharmony_ci toggle = (data->bits & 0x01000) ? 1 : 0; 15162306a36Sopenharmony_ci scancode = system << 6 | command; 15262306a36Sopenharmony_ci protocol = RC_PROTO_RC5_SZ; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci } else 15562306a36Sopenharmony_ci break; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci dev_dbg(&dev->dev, "RC5(x/sz) scancode 0x%06x (p: %u, t: %u)\n", 15862306a36Sopenharmony_ci scancode, protocol, toggle); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci rc_keydown(dev, protocol, scancode, toggle); 16162306a36Sopenharmony_ci data->state = STATE_INACTIVE; 16262306a36Sopenharmony_ci return 0; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ciout: 16662306a36Sopenharmony_ci dev_dbg(&dev->dev, "RC5(x/sz) decode failed at state %i count %d (%uus %s)\n", 16762306a36Sopenharmony_ci data->state, data->count, ev.duration, TO_STR(ev.pulse)); 16862306a36Sopenharmony_ci data->state = STATE_INACTIVE; 16962306a36Sopenharmony_ci return -EINVAL; 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic const struct ir_raw_timings_manchester ir_rc5_timings = { 17362306a36Sopenharmony_ci .leader_pulse = RC5_UNIT, 17462306a36Sopenharmony_ci .clock = RC5_UNIT, 17562306a36Sopenharmony_ci .trailer_space = RC5_UNIT * 10, 17662306a36Sopenharmony_ci}; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic const struct ir_raw_timings_manchester ir_rc5x_timings[2] = { 17962306a36Sopenharmony_ci { 18062306a36Sopenharmony_ci .leader_pulse = RC5_UNIT, 18162306a36Sopenharmony_ci .clock = RC5_UNIT, 18262306a36Sopenharmony_ci .trailer_space = RC5X_SPACE, 18362306a36Sopenharmony_ci }, 18462306a36Sopenharmony_ci { 18562306a36Sopenharmony_ci .clock = RC5_UNIT, 18662306a36Sopenharmony_ci .trailer_space = RC5_UNIT * 10, 18762306a36Sopenharmony_ci }, 18862306a36Sopenharmony_ci}; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_cistatic const struct ir_raw_timings_manchester ir_rc5_sz_timings = { 19162306a36Sopenharmony_ci .leader_pulse = RC5_UNIT, 19262306a36Sopenharmony_ci .clock = RC5_UNIT, 19362306a36Sopenharmony_ci .trailer_space = RC5_UNIT * 10, 19462306a36Sopenharmony_ci}; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci/** 19762306a36Sopenharmony_ci * ir_rc5_encode() - Encode a scancode as a stream of raw events 19862306a36Sopenharmony_ci * 19962306a36Sopenharmony_ci * @protocol: protocol variant to encode 20062306a36Sopenharmony_ci * @scancode: scancode to encode 20162306a36Sopenharmony_ci * @events: array of raw ir events to write into 20262306a36Sopenharmony_ci * @max: maximum size of @events 20362306a36Sopenharmony_ci * 20462306a36Sopenharmony_ci * Returns: The number of events written. 20562306a36Sopenharmony_ci * -ENOBUFS if there isn't enough space in the array to fit the 20662306a36Sopenharmony_ci * encoding. In this case all @max events will have been written. 20762306a36Sopenharmony_ci * -EINVAL if the scancode is ambiguous or invalid. 20862306a36Sopenharmony_ci */ 20962306a36Sopenharmony_cistatic int ir_rc5_encode(enum rc_proto protocol, u32 scancode, 21062306a36Sopenharmony_ci struct ir_raw_event *events, unsigned int max) 21162306a36Sopenharmony_ci{ 21262306a36Sopenharmony_ci int ret; 21362306a36Sopenharmony_ci struct ir_raw_event *e = events; 21462306a36Sopenharmony_ci unsigned int data, xdata, command, commandx, system, pre_space_data; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci /* Detect protocol and convert scancode to raw data */ 21762306a36Sopenharmony_ci if (protocol == RC_PROTO_RC5) { 21862306a36Sopenharmony_ci /* decode scancode */ 21962306a36Sopenharmony_ci command = (scancode & 0x003f) >> 0; 22062306a36Sopenharmony_ci commandx = (scancode & 0x0040) >> 6; 22162306a36Sopenharmony_ci system = (scancode & 0x1f00) >> 8; 22262306a36Sopenharmony_ci /* encode data */ 22362306a36Sopenharmony_ci data = !commandx << 12 | system << 6 | command; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci /* First bit is encoded by leader_pulse */ 22662306a36Sopenharmony_ci ret = ir_raw_gen_manchester(&e, max, &ir_rc5_timings, 22762306a36Sopenharmony_ci RC5_NBITS - 1, data); 22862306a36Sopenharmony_ci if (ret < 0) 22962306a36Sopenharmony_ci return ret; 23062306a36Sopenharmony_ci } else if (protocol == RC_PROTO_RC5X_20) { 23162306a36Sopenharmony_ci /* decode scancode */ 23262306a36Sopenharmony_ci xdata = (scancode & 0x00003f) >> 0; 23362306a36Sopenharmony_ci command = (scancode & 0x003f00) >> 8; 23462306a36Sopenharmony_ci commandx = !(scancode & 0x004000); 23562306a36Sopenharmony_ci system = (scancode & 0x1f0000) >> 16; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci /* encode data */ 23862306a36Sopenharmony_ci data = commandx << 18 | system << 12 | command << 6 | xdata; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci /* First bit is encoded by leader_pulse */ 24162306a36Sopenharmony_ci pre_space_data = data >> (RC5X_NBITS - CHECK_RC5X_NBITS); 24262306a36Sopenharmony_ci ret = ir_raw_gen_manchester(&e, max, &ir_rc5x_timings[0], 24362306a36Sopenharmony_ci CHECK_RC5X_NBITS - 1, 24462306a36Sopenharmony_ci pre_space_data); 24562306a36Sopenharmony_ci if (ret < 0) 24662306a36Sopenharmony_ci return ret; 24762306a36Sopenharmony_ci ret = ir_raw_gen_manchester(&e, max - (e - events), 24862306a36Sopenharmony_ci &ir_rc5x_timings[1], 24962306a36Sopenharmony_ci RC5X_NBITS - CHECK_RC5X_NBITS, 25062306a36Sopenharmony_ci data); 25162306a36Sopenharmony_ci if (ret < 0) 25262306a36Sopenharmony_ci return ret; 25362306a36Sopenharmony_ci } else if (protocol == RC_PROTO_RC5_SZ) { 25462306a36Sopenharmony_ci /* RC5-SZ scancode is raw enough for Manchester as it is */ 25562306a36Sopenharmony_ci /* First bit is encoded by leader_pulse */ 25662306a36Sopenharmony_ci ret = ir_raw_gen_manchester(&e, max, &ir_rc5_sz_timings, 25762306a36Sopenharmony_ci RC5_SZ_NBITS - 1, 25862306a36Sopenharmony_ci scancode & 0x2fff); 25962306a36Sopenharmony_ci if (ret < 0) 26062306a36Sopenharmony_ci return ret; 26162306a36Sopenharmony_ci } else { 26262306a36Sopenharmony_ci return -EINVAL; 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci return e - events; 26662306a36Sopenharmony_ci} 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_cistatic struct ir_raw_handler rc5_handler = { 26962306a36Sopenharmony_ci .protocols = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_RC5X_20 | 27062306a36Sopenharmony_ci RC_PROTO_BIT_RC5_SZ, 27162306a36Sopenharmony_ci .decode = ir_rc5_decode, 27262306a36Sopenharmony_ci .encode = ir_rc5_encode, 27362306a36Sopenharmony_ci .carrier = 36000, 27462306a36Sopenharmony_ci .min_timeout = RC5_TRAILER, 27562306a36Sopenharmony_ci}; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_cistatic int __init ir_rc5_decode_init(void) 27862306a36Sopenharmony_ci{ 27962306a36Sopenharmony_ci ir_raw_handler_register(&rc5_handler); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci printk(KERN_INFO "IR RC5(x/sz) protocol handler initialized\n"); 28262306a36Sopenharmony_ci return 0; 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic void __exit ir_rc5_decode_exit(void) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci ir_raw_handler_unregister(&rc5_handler); 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_cimodule_init(ir_rc5_decode_init); 29162306a36Sopenharmony_cimodule_exit(ir_rc5_decode_exit); 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 29462306a36Sopenharmony_ciMODULE_AUTHOR("Mauro Carvalho Chehab and Jarod Wilson"); 29562306a36Sopenharmony_ciMODULE_AUTHOR("Red Hat Inc. (http://www.redhat.com)"); 29662306a36Sopenharmony_ciMODULE_DESCRIPTION("RC5(x/sz) IR protocol decoder"); 297