132cf86f6SMauro Carvalho Chehab /* ir-rc6-decoder.c - A decoder for the RC6 IR protocol 232cf86f6SMauro Carvalho Chehab * 332cf86f6SMauro Carvalho Chehab * Copyright (C) 2010 by David Härdeman <david@hardeman.nu> 432cf86f6SMauro Carvalho Chehab * 532cf86f6SMauro Carvalho Chehab * This program is free software; you can redistribute it and/or modify 632cf86f6SMauro Carvalho Chehab * it under the terms of the GNU General Public License as published by 732cf86f6SMauro Carvalho Chehab * the Free Software Foundation version 2 of the License. 832cf86f6SMauro Carvalho Chehab * 932cf86f6SMauro Carvalho Chehab * This program is distributed in the hope that it will be useful, 1032cf86f6SMauro Carvalho Chehab * but WITHOUT ANY WARRANTY; without even the implied warranty of 1132cf86f6SMauro Carvalho Chehab * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1232cf86f6SMauro Carvalho Chehab * GNU General Public License for more details. 1332cf86f6SMauro Carvalho Chehab */ 1432cf86f6SMauro Carvalho Chehab 15f62de675SMauro Carvalho Chehab #include "rc-core-priv.h" 167a707b89SPaul Gortmaker #include <linux/module.h> 1732cf86f6SMauro Carvalho Chehab 1832cf86f6SMauro Carvalho Chehab /* 1932cf86f6SMauro Carvalho Chehab * This decoder currently supports: 2032cf86f6SMauro Carvalho Chehab * RC6-0-16 (standard toggle bit in header) 21db9bc660Slawrence rust * RC6-6A-20 (no toggle bit) 2232cf86f6SMauro Carvalho Chehab * RC6-6A-24 (no toggle bit) 2332cf86f6SMauro Carvalho Chehab * RC6-6A-32 (MCE version with toggle bit in body) 2432cf86f6SMauro Carvalho Chehab */ 2532cf86f6SMauro Carvalho Chehab 26db9bc660Slawrence rust #define RC6_UNIT 444444 /* nanosecs */ 2732cf86f6SMauro Carvalho Chehab #define RC6_HEADER_NBITS 4 /* not including toggle bit */ 2832cf86f6SMauro Carvalho Chehab #define RC6_0_NBITS 16 29db9bc660Slawrence rust #define RC6_6A_32_NBITS 32 30db9bc660Slawrence rust #define RC6_6A_NBITS 128 /* Variable 8..128 */ 3132cf86f6SMauro Carvalho Chehab #define RC6_PREFIX_PULSE (6 * RC6_UNIT) 3232cf86f6SMauro Carvalho Chehab #define RC6_PREFIX_SPACE (2 * RC6_UNIT) 3332cf86f6SMauro Carvalho Chehab #define RC6_BIT_START (1 * RC6_UNIT) 3432cf86f6SMauro Carvalho Chehab #define RC6_BIT_END (1 * RC6_UNIT) 3532cf86f6SMauro Carvalho Chehab #define RC6_TOGGLE_START (2 * RC6_UNIT) 3632cf86f6SMauro Carvalho Chehab #define RC6_TOGGLE_END (2 * RC6_UNIT) 37db9bc660Slawrence rust #define RC6_SUFFIX_SPACE (6 * RC6_UNIT) 3832cf86f6SMauro Carvalho Chehab #define RC6_MODE_MASK 0x07 /* for the header bits */ 3932cf86f6SMauro Carvalho Chehab #define RC6_STARTBIT_MASK 0x08 /* for the header bits */ 4032cf86f6SMauro Carvalho Chehab #define RC6_6A_MCE_TOGGLE_MASK 0x8000 /* for the body bits */ 41db9bc660Slawrence rust #define RC6_6A_LCC_MASK 0xffff0000 /* RC6-6A-32 long customer code mask */ 42db9bc660Slawrence rust #define RC6_6A_MCE_CC 0x800f0000 /* MCE customer code */ 43db9bc660Slawrence rust #ifndef CHAR_BIT 44db9bc660Slawrence rust #define CHAR_BIT 8 /* Normally in <limits.h> */ 45db9bc660Slawrence rust #endif 4632cf86f6SMauro Carvalho Chehab 4732cf86f6SMauro Carvalho Chehab enum rc6_mode { 4832cf86f6SMauro Carvalho Chehab RC6_MODE_0, 4932cf86f6SMauro Carvalho Chehab RC6_MODE_6A, 5032cf86f6SMauro Carvalho Chehab RC6_MODE_UNKNOWN, 5132cf86f6SMauro Carvalho Chehab }; 5232cf86f6SMauro Carvalho Chehab 5332cf86f6SMauro Carvalho Chehab enum rc6_state { 5432cf86f6SMauro Carvalho Chehab STATE_INACTIVE, 5532cf86f6SMauro Carvalho Chehab STATE_PREFIX_SPACE, 5632cf86f6SMauro Carvalho Chehab STATE_HEADER_BIT_START, 5732cf86f6SMauro Carvalho Chehab STATE_HEADER_BIT_END, 5832cf86f6SMauro Carvalho Chehab STATE_TOGGLE_START, 5932cf86f6SMauro Carvalho Chehab STATE_TOGGLE_END, 6032cf86f6SMauro Carvalho Chehab STATE_BODY_BIT_START, 6132cf86f6SMauro Carvalho Chehab STATE_BODY_BIT_END, 6232cf86f6SMauro Carvalho Chehab STATE_FINISHED, 6332cf86f6SMauro Carvalho Chehab }; 6432cf86f6SMauro Carvalho Chehab 6532cf86f6SMauro Carvalho Chehab static enum rc6_mode rc6_mode(struct rc6_dec *data) 6632cf86f6SMauro Carvalho Chehab { 6732cf86f6SMauro Carvalho Chehab switch (data->header & RC6_MODE_MASK) { 6832cf86f6SMauro Carvalho Chehab case 0: 6932cf86f6SMauro Carvalho Chehab return RC6_MODE_0; 7032cf86f6SMauro Carvalho Chehab case 6: 7132cf86f6SMauro Carvalho Chehab if (!data->toggle) 7232cf86f6SMauro Carvalho Chehab return RC6_MODE_6A; 7332cf86f6SMauro Carvalho Chehab /* fall through */ 7432cf86f6SMauro Carvalho Chehab default: 7532cf86f6SMauro Carvalho Chehab return RC6_MODE_UNKNOWN; 7632cf86f6SMauro Carvalho Chehab } 7732cf86f6SMauro Carvalho Chehab } 7832cf86f6SMauro Carvalho Chehab 7932cf86f6SMauro Carvalho Chehab /** 8032cf86f6SMauro Carvalho Chehab * ir_rc6_decode() - Decode one RC6 pulse or space 81d8b4b582SDavid Härdeman * @dev: the struct rc_dev descriptor of the device 8232cf86f6SMauro Carvalho Chehab * @ev: the struct ir_raw_event descriptor of the pulse/space 8332cf86f6SMauro Carvalho Chehab * 8432cf86f6SMauro Carvalho Chehab * This function returns -EINVAL if the pulse violates the state machine 8532cf86f6SMauro Carvalho Chehab */ 86d8b4b582SDavid Härdeman static int ir_rc6_decode(struct rc_dev *dev, struct ir_raw_event ev) 8732cf86f6SMauro Carvalho Chehab { 88d8b4b582SDavid Härdeman struct rc6_dec *data = &dev->raw->rc6; 8932cf86f6SMauro Carvalho Chehab u32 scancode; 9032cf86f6SMauro Carvalho Chehab u8 toggle; 91*120703f9SDavid Härdeman enum rc_type protocol; 9232cf86f6SMauro Carvalho Chehab 931a1934faSJames Hogan if (!rc_protocols_enabled(dev, RC_BIT_RC6_0 | RC_BIT_RC6_6A_20 | 941a1934faSJames Hogan RC_BIT_RC6_6A_24 | RC_BIT_RC6_6A_32 | 951a1934faSJames Hogan RC_BIT_RC6_MCE)) 9632cf86f6SMauro Carvalho Chehab return 0; 9732cf86f6SMauro Carvalho Chehab 9832cf86f6SMauro Carvalho Chehab if (!is_timing_event(ev)) { 9932cf86f6SMauro Carvalho Chehab if (ev.reset) 10032cf86f6SMauro Carvalho Chehab data->state = STATE_INACTIVE; 10132cf86f6SMauro Carvalho Chehab return 0; 10232cf86f6SMauro Carvalho Chehab } 10332cf86f6SMauro Carvalho Chehab 10432cf86f6SMauro Carvalho Chehab if (!geq_margin(ev.duration, RC6_UNIT, RC6_UNIT / 2)) 10532cf86f6SMauro Carvalho Chehab goto out; 10632cf86f6SMauro Carvalho Chehab 10732cf86f6SMauro Carvalho Chehab again: 10832cf86f6SMauro Carvalho Chehab IR_dprintk(2, "RC6 decode started at state %i (%uus %s)\n", 10932cf86f6SMauro Carvalho Chehab data->state, TO_US(ev.duration), TO_STR(ev.pulse)); 11032cf86f6SMauro Carvalho Chehab 11132cf86f6SMauro Carvalho Chehab if (!geq_margin(ev.duration, RC6_UNIT, RC6_UNIT / 2)) 11232cf86f6SMauro Carvalho Chehab return 0; 11332cf86f6SMauro Carvalho Chehab 11432cf86f6SMauro Carvalho Chehab switch (data->state) { 11532cf86f6SMauro Carvalho Chehab 11632cf86f6SMauro Carvalho Chehab case STATE_INACTIVE: 11732cf86f6SMauro Carvalho Chehab if (!ev.pulse) 11832cf86f6SMauro Carvalho Chehab break; 11932cf86f6SMauro Carvalho Chehab 12032cf86f6SMauro Carvalho Chehab /* Note: larger margin on first pulse since each RC6_UNIT 12132cf86f6SMauro Carvalho Chehab is quite short and some hardware takes some time to 12232cf86f6SMauro Carvalho Chehab adjust to the signal */ 12332cf86f6SMauro Carvalho Chehab if (!eq_margin(ev.duration, RC6_PREFIX_PULSE, RC6_UNIT)) 12432cf86f6SMauro Carvalho Chehab break; 12532cf86f6SMauro Carvalho Chehab 12632cf86f6SMauro Carvalho Chehab data->state = STATE_PREFIX_SPACE; 12732cf86f6SMauro Carvalho Chehab data->count = 0; 12832cf86f6SMauro Carvalho Chehab return 0; 12932cf86f6SMauro Carvalho Chehab 13032cf86f6SMauro Carvalho Chehab case STATE_PREFIX_SPACE: 13132cf86f6SMauro Carvalho Chehab if (ev.pulse) 13232cf86f6SMauro Carvalho Chehab break; 13332cf86f6SMauro Carvalho Chehab 13432cf86f6SMauro Carvalho Chehab if (!eq_margin(ev.duration, RC6_PREFIX_SPACE, RC6_UNIT / 2)) 13532cf86f6SMauro Carvalho Chehab break; 13632cf86f6SMauro Carvalho Chehab 13732cf86f6SMauro Carvalho Chehab data->state = STATE_HEADER_BIT_START; 138db9bc660Slawrence rust data->header = 0; 13932cf86f6SMauro Carvalho Chehab return 0; 14032cf86f6SMauro Carvalho Chehab 14132cf86f6SMauro Carvalho Chehab case STATE_HEADER_BIT_START: 14232cf86f6SMauro Carvalho Chehab if (!eq_margin(ev.duration, RC6_BIT_START, RC6_UNIT / 2)) 14332cf86f6SMauro Carvalho Chehab break; 14432cf86f6SMauro Carvalho Chehab 14532cf86f6SMauro Carvalho Chehab data->header <<= 1; 14632cf86f6SMauro Carvalho Chehab if (ev.pulse) 14732cf86f6SMauro Carvalho Chehab data->header |= 1; 14832cf86f6SMauro Carvalho Chehab data->count++; 14932cf86f6SMauro Carvalho Chehab data->state = STATE_HEADER_BIT_END; 15032cf86f6SMauro Carvalho Chehab return 0; 15132cf86f6SMauro Carvalho Chehab 15232cf86f6SMauro Carvalho Chehab case STATE_HEADER_BIT_END: 153d8b4b582SDavid Härdeman if (!is_transition(&ev, &dev->raw->prev_ev)) 15432cf86f6SMauro Carvalho Chehab break; 15532cf86f6SMauro Carvalho Chehab 15632cf86f6SMauro Carvalho Chehab if (data->count == RC6_HEADER_NBITS) 15732cf86f6SMauro Carvalho Chehab data->state = STATE_TOGGLE_START; 15832cf86f6SMauro Carvalho Chehab else 15932cf86f6SMauro Carvalho Chehab data->state = STATE_HEADER_BIT_START; 16032cf86f6SMauro Carvalho Chehab 16132cf86f6SMauro Carvalho Chehab decrease_duration(&ev, RC6_BIT_END); 16232cf86f6SMauro Carvalho Chehab goto again; 16332cf86f6SMauro Carvalho Chehab 16432cf86f6SMauro Carvalho Chehab case STATE_TOGGLE_START: 16532cf86f6SMauro Carvalho Chehab if (!eq_margin(ev.duration, RC6_TOGGLE_START, RC6_UNIT / 2)) 16632cf86f6SMauro Carvalho Chehab break; 16732cf86f6SMauro Carvalho Chehab 16832cf86f6SMauro Carvalho Chehab data->toggle = ev.pulse; 16932cf86f6SMauro Carvalho Chehab data->state = STATE_TOGGLE_END; 17032cf86f6SMauro Carvalho Chehab return 0; 17132cf86f6SMauro Carvalho Chehab 17232cf86f6SMauro Carvalho Chehab case STATE_TOGGLE_END: 173d8b4b582SDavid Härdeman if (!is_transition(&ev, &dev->raw->prev_ev) || 17432cf86f6SMauro Carvalho Chehab !geq_margin(ev.duration, RC6_TOGGLE_END, RC6_UNIT / 2)) 17532cf86f6SMauro Carvalho Chehab break; 17632cf86f6SMauro Carvalho Chehab 17732cf86f6SMauro Carvalho Chehab if (!(data->header & RC6_STARTBIT_MASK)) { 17832cf86f6SMauro Carvalho Chehab IR_dprintk(1, "RC6 invalid start bit\n"); 17932cf86f6SMauro Carvalho Chehab break; 18032cf86f6SMauro Carvalho Chehab } 18132cf86f6SMauro Carvalho Chehab 18232cf86f6SMauro Carvalho Chehab data->state = STATE_BODY_BIT_START; 18332cf86f6SMauro Carvalho Chehab decrease_duration(&ev, RC6_TOGGLE_END); 18432cf86f6SMauro Carvalho Chehab data->count = 0; 185db9bc660Slawrence rust data->body = 0; 18632cf86f6SMauro Carvalho Chehab 18732cf86f6SMauro Carvalho Chehab switch (rc6_mode(data)) { 18832cf86f6SMauro Carvalho Chehab case RC6_MODE_0: 18932cf86f6SMauro Carvalho Chehab data->wanted_bits = RC6_0_NBITS; 19032cf86f6SMauro Carvalho Chehab break; 19132cf86f6SMauro Carvalho Chehab case RC6_MODE_6A: 192db9bc660Slawrence rust data->wanted_bits = RC6_6A_NBITS; 19332cf86f6SMauro Carvalho Chehab break; 19432cf86f6SMauro Carvalho Chehab default: 19532cf86f6SMauro Carvalho Chehab IR_dprintk(1, "RC6 unknown mode\n"); 19632cf86f6SMauro Carvalho Chehab goto out; 19732cf86f6SMauro Carvalho Chehab } 19832cf86f6SMauro Carvalho Chehab goto again; 19932cf86f6SMauro Carvalho Chehab 20032cf86f6SMauro Carvalho Chehab case STATE_BODY_BIT_START: 201db9bc660Slawrence rust if (eq_margin(ev.duration, RC6_BIT_START, RC6_UNIT / 2)) { 202db9bc660Slawrence rust /* Discard LSB's that won't fit in data->body */ 203db9bc660Slawrence rust if (data->count++ < CHAR_BIT * sizeof data->body) { 20432cf86f6SMauro Carvalho Chehab data->body <<= 1; 20532cf86f6SMauro Carvalho Chehab if (ev.pulse) 20632cf86f6SMauro Carvalho Chehab data->body |= 1; 207db9bc660Slawrence rust } 20832cf86f6SMauro Carvalho Chehab data->state = STATE_BODY_BIT_END; 20932cf86f6SMauro Carvalho Chehab return 0; 210db9bc660Slawrence rust } else if (RC6_MODE_6A == rc6_mode(data) && !ev.pulse && 211db9bc660Slawrence rust geq_margin(ev.duration, RC6_SUFFIX_SPACE, RC6_UNIT / 2)) { 212db9bc660Slawrence rust data->state = STATE_FINISHED; 213db9bc660Slawrence rust goto again; 214db9bc660Slawrence rust } 215db9bc660Slawrence rust break; 21632cf86f6SMauro Carvalho Chehab 21732cf86f6SMauro Carvalho Chehab case STATE_BODY_BIT_END: 218d8b4b582SDavid Härdeman if (!is_transition(&ev, &dev->raw->prev_ev)) 21932cf86f6SMauro Carvalho Chehab break; 22032cf86f6SMauro Carvalho Chehab 22132cf86f6SMauro Carvalho Chehab if (data->count == data->wanted_bits) 22232cf86f6SMauro Carvalho Chehab data->state = STATE_FINISHED; 22332cf86f6SMauro Carvalho Chehab else 22432cf86f6SMauro Carvalho Chehab data->state = STATE_BODY_BIT_START; 22532cf86f6SMauro Carvalho Chehab 22632cf86f6SMauro Carvalho Chehab decrease_duration(&ev, RC6_BIT_END); 22732cf86f6SMauro Carvalho Chehab goto again; 22832cf86f6SMauro Carvalho Chehab 22932cf86f6SMauro Carvalho Chehab case STATE_FINISHED: 23032cf86f6SMauro Carvalho Chehab if (ev.pulse) 23132cf86f6SMauro Carvalho Chehab break; 23232cf86f6SMauro Carvalho Chehab 23332cf86f6SMauro Carvalho Chehab switch (rc6_mode(data)) { 23432cf86f6SMauro Carvalho Chehab case RC6_MODE_0: 235db9bc660Slawrence rust scancode = data->body; 23632cf86f6SMauro Carvalho Chehab toggle = data->toggle; 237*120703f9SDavid Härdeman protocol = RC_TYPE_RC6_0; 23832cf86f6SMauro Carvalho Chehab IR_dprintk(1, "RC6(0) scancode 0x%04x (toggle: %u)\n", 23932cf86f6SMauro Carvalho Chehab scancode, toggle); 24032cf86f6SMauro Carvalho Chehab break; 241*120703f9SDavid Härdeman 24232cf86f6SMauro Carvalho Chehab case RC6_MODE_6A: 243db9bc660Slawrence rust if (data->count > CHAR_BIT * sizeof data->body) { 244db9bc660Slawrence rust IR_dprintk(1, "RC6 too many (%u) data bits\n", 245db9bc660Slawrence rust data->count); 246db9bc660Slawrence rust goto out; 24732cf86f6SMauro Carvalho Chehab } 24832cf86f6SMauro Carvalho Chehab 249db9bc660Slawrence rust scancode = data->body; 250*120703f9SDavid Härdeman switch (data->count) { 251*120703f9SDavid Härdeman case 20: 252*120703f9SDavid Härdeman protocol = RC_TYPE_RC6_6A_20; 253*120703f9SDavid Härdeman toggle = 0; 254*120703f9SDavid Härdeman break; 255*120703f9SDavid Härdeman case 24: 256*120703f9SDavid Härdeman protocol = RC_BIT_RC6_6A_24; 257*120703f9SDavid Härdeman toggle = 0; 258*120703f9SDavid Härdeman break; 259*120703f9SDavid Härdeman case 32: 260*120703f9SDavid Härdeman if ((scancode & RC6_6A_LCC_MASK) == RC6_6A_MCE_CC) { 261*120703f9SDavid Härdeman protocol = RC_TYPE_RC6_MCE; 262db9bc660Slawrence rust scancode &= ~RC6_6A_MCE_TOGGLE_MASK; 263*120703f9SDavid Härdeman toggle = !!(scancode & RC6_6A_MCE_TOGGLE_MASK); 264db9bc660Slawrence rust } else { 265*120703f9SDavid Härdeman protocol = RC_BIT_RC6_6A_32; 266db9bc660Slawrence rust toggle = 0; 267db9bc660Slawrence rust } 268*120703f9SDavid Härdeman break; 269*120703f9SDavid Härdeman default: 270*120703f9SDavid Härdeman IR_dprintk(1, "RC6(6A) unsupported length\n"); 271*120703f9SDavid Härdeman goto out; 272*120703f9SDavid Härdeman } 273*120703f9SDavid Härdeman 274*120703f9SDavid Härdeman IR_dprintk(1, "RC6(6A) proto 0x%04x, scancode 0x%08x (toggle: %u)\n", 275*120703f9SDavid Härdeman protocol, scancode, toggle); 27632cf86f6SMauro Carvalho Chehab break; 27732cf86f6SMauro Carvalho Chehab default: 27832cf86f6SMauro Carvalho Chehab IR_dprintk(1, "RC6 unknown mode\n"); 27932cf86f6SMauro Carvalho Chehab goto out; 28032cf86f6SMauro Carvalho Chehab } 28132cf86f6SMauro Carvalho Chehab 282*120703f9SDavid Härdeman rc_keydown(dev, protocol, scancode, toggle); 28332cf86f6SMauro Carvalho Chehab data->state = STATE_INACTIVE; 28432cf86f6SMauro Carvalho Chehab return 0; 28532cf86f6SMauro Carvalho Chehab } 28632cf86f6SMauro Carvalho Chehab 28732cf86f6SMauro Carvalho Chehab out: 28832cf86f6SMauro Carvalho Chehab IR_dprintk(1, "RC6 decode failed at state %i (%uus %s)\n", 28932cf86f6SMauro Carvalho Chehab data->state, TO_US(ev.duration), TO_STR(ev.pulse)); 29032cf86f6SMauro Carvalho Chehab data->state = STATE_INACTIVE; 29132cf86f6SMauro Carvalho Chehab return -EINVAL; 29232cf86f6SMauro Carvalho Chehab } 29332cf86f6SMauro Carvalho Chehab 29432cf86f6SMauro Carvalho Chehab static struct ir_raw_handler rc6_handler = { 295c003ab1bSDavid Härdeman .protocols = RC_BIT_RC6_0 | RC_BIT_RC6_6A_20 | 296c003ab1bSDavid Härdeman RC_BIT_RC6_6A_24 | RC_BIT_RC6_6A_32 | 297c003ab1bSDavid Härdeman RC_BIT_RC6_MCE, 29832cf86f6SMauro Carvalho Chehab .decode = ir_rc6_decode, 29932cf86f6SMauro Carvalho Chehab }; 30032cf86f6SMauro Carvalho Chehab 30132cf86f6SMauro Carvalho Chehab static int __init ir_rc6_decode_init(void) 30232cf86f6SMauro Carvalho Chehab { 30332cf86f6SMauro Carvalho Chehab ir_raw_handler_register(&rc6_handler); 30432cf86f6SMauro Carvalho Chehab 30532cf86f6SMauro Carvalho Chehab printk(KERN_INFO "IR RC6 protocol handler initialized\n"); 30632cf86f6SMauro Carvalho Chehab return 0; 30732cf86f6SMauro Carvalho Chehab } 30832cf86f6SMauro Carvalho Chehab 30932cf86f6SMauro Carvalho Chehab static void __exit ir_rc6_decode_exit(void) 31032cf86f6SMauro Carvalho Chehab { 31132cf86f6SMauro Carvalho Chehab ir_raw_handler_unregister(&rc6_handler); 31232cf86f6SMauro Carvalho Chehab } 31332cf86f6SMauro Carvalho Chehab 31432cf86f6SMauro Carvalho Chehab module_init(ir_rc6_decode_init); 31532cf86f6SMauro Carvalho Chehab module_exit(ir_rc6_decode_exit); 31632cf86f6SMauro Carvalho Chehab 31732cf86f6SMauro Carvalho Chehab MODULE_LICENSE("GPL"); 31832cf86f6SMauro Carvalho Chehab MODULE_AUTHOR("David Härdeman <david@hardeman.nu>"); 31932cf86f6SMauro Carvalho Chehab MODULE_DESCRIPTION("RC6 IR protocol decoder"); 320