
/*
  CLASSiC DAC, Copyright 2013 SILICON CHIP Publications
  Infrared.c: interrupt-based functions to decode Philips RC5 and NEC IR commands
  Written by Nicholas Vinen, 2009-2013
*/

//// change these to suit the application ////

#define IR_PIN_PORT  B
#define IR_INPUT_PIN 1
#define IR_TIMER     4
#define SUPPORT_NEC
#define IR_TIMER_DIV 8

//////////////////////////////////////////////

#define _SUPPRESS_PLIB_WARNING
#define _DISABLE_OPENADC10_CONFIGPORT_WARNING
#include <plib.h>
#include "Infrared.h"
#include "util.h"

#define _TRISname(port,pin) TRIS##port##bits.TRIS##port##pin
#define TRISname(port,pin) _TRISname(port,pin)
#define _PORTname(port,pin) PORT##port##bits.R##port##pin
#define PORTname(port,pin) _PORTname(port,pin)
#define _ANSELname(port,pin) ANSEL##port##bits.ANS##port##pin
#define ANSELname(port,pin) _ANSELname(port,pin)
#define _CNENname(port,pin) CNEN##port##bits.CNIE##port##pin
#define CNENname(port,pin) _CNENname(port,pin)
#define _CNCONname(port) CNCON##port##bits
#define CNCONname(port) _CNCONname(port)
#define _CNIFname(port) IFS1bits.CN##port##IF
#define CNIFname(port) _CNIFname(port)
#define _CNIEname(port) IEC1bits.CN##port##IE
#define CNIEname(port) _CNIEname(port)
#define _TCONname(which) T##which##CONbits
#define TCONname(which) _TCONname(which)
#define _TInterruptname(which) _T##which##Interrupt
#define TInterruptname(which) _TInterruptname(which)
#define _TIMER_VECTORname(which) _TIMER_##which##_VECTOR
#define TIMER_VECTORname(which) _TIMER_VECTORname(which)
#define _TMRname(which) TMR##which
#define TMRname(which) _TMRname(which)
#define _PRname(which) PR##which
#define PRname(which) _PRname(which)
#define _TIFname(which) IFS0bits.T##which##IF
#define TIFname(which) _TIFname(which)
#define _TIEname(which) IEC0bits.T##which##IE
#define TIEname(which) _TIEname(which)
#define _TIPCname(which) IPC##which##bits.T##which##IP
#define TIPCname(which) _TIPCname(which)
#define TMR_DIV_TO_TCKPS(x) ((x) == 1 ? 0 : ((x) == 2 ? 1 : ((x) == 4 ? 2 : ((x) == 8 ? 3 : ((x) == 16 ? 4 : ((x) == 32 ? 5 : ((x) == 64 ? 6 : 7)))))))
#define TMR_TCKPS_TO_DIV(X) ((x) < 7 ? (1<<(x)) : 256)

unsigned short rc5_code, rc5_timing, last_rc5_code;
#ifdef SUPPORT_NEC
unsigned short last_NEC_code;
unsigned long NEC_code;
#endif
unsigned char ir_bit, rc5_reset_timer, last_ir_state;
volatile unsigned long ir_final_code;

unsigned short RC5_MIN, RC5_MAX;
unsigned short ir_OSCCON_cache;
#ifdef SUPPORT_NEC
unsigned short NEC_startpulse_min, NEC_startpulse_max, NEC_startpulse;
#endif

void (*ir_cn_interrupt_chain)(void);

// figure out, based on the current CPU clock frequency, how many timer cycles IR pulses should generally take
__attribute__((mips16)) static void calc_rc5_timing() {
    unsigned long timer_hz = get_PBCLK() / IR_TIMER_DIV;
    unsigned short rc5_cycles = timer_hz / 1125;
#ifdef SUPPORT_NEC
    unsigned short timer_khz = timer_hz / 1000;
#endif
    RC5_MIN = rc5_cycles * 2 * 3 / 4;
    RC5_MAX = rc5_cycles * 2 * 4 / 3;

#ifdef SUPPORT_NEC
    NEC_startpulse = timer_khz * 9;
    NEC_startpulse_min = NEC_startpulse * 1 / 3;
    NEC_startpulse_max = NEC_startpulse * 5 / 4;
#endif

    ir_OSCCON_cache = OSCCON;
}

__attribute__((mips16)) void Infrared_Setup() {
    TRISname(IR_PIN_PORT,IR_INPUT_PIN) = 1;
    ANSELname(IR_PIN_PORT,IR_INPUT_PIN) = 0;
    CNENname(IR_PIN_PORT,IR_INPUT_PIN) = 1;
    CNCONname(IR_PIN_PORT).ON = 1;
    CNIFname(IR_PIN_PORT) = 0;
    CNIEname(IR_PIN_PORT) = 1;
    IPC8bits.CNIP = 3;
    PORTname(IR_PIN_PORT,IR_INPUT_PIN);

    TCONname(IR_TIMER).TCKPS = TMR_DIV_TO_TCKPS(IR_TIMER_DIV);
    TIEname(IR_TIMER) = 1;
    TIFname(IR_TIMER) = 0;
    TIPCname(IR_TIMER) = 2;

    TCONname(IR_TIMER).ON = 1;
    calc_rc5_timing();
}

__attribute__((mips16)) void Infrared_Teardown() {
    CNENname(IR_PIN_PORT,IR_INPUT_PIN) = 0;
    CNCONname(IR_PIN_PORT).ON = 0;
    CNIEname(IR_PIN_PORT) = 0;
    CNIFname(IR_PIN_PORT) = 0;

    TIEname(IR_TIMER) = 0;
    TIFname(IR_TIMER) = 0;

    TCONname(IR_TIMER).ON = 0;
}

__attribute__((mips16)) inline static void start_ir_timer() {
    TMRname(IR_TIMER) = 0;
    TCONname(IR_TIMER).ON = 1;
}

__attribute__((mips16)) inline static void stop_ir_timer() {
    TCONname(IR_TIMER).ON = 0;
}

__attribute__((mips16)) inline static void cancel_rc5_reception() {
    stop_ir_timer();
    if( ir_bit != 0 )
        ir_bit = 0;
}

// IR decoding is done by the two functions below - pretty complex but it works!

__attribute__((mips16)) static void IRTimerInterrupt() {
    TIFname(IR_TIMER) = 0;
#ifdef SUPPORT_NEC
    if( ir_bit == 1 || ir_bit == 31 || rc5_reset_timer ) {
#else
    if( ir_bit == 1 || rc5_reset_timer ) {
#endif
        cancel_rc5_reception();
    } else {
        unsigned char level = PORTname(IR_PIN_PORT,IR_INPUT_PIN);
        rc5_code <<= 1;
        rc5_code |= level != 0;
        if( ++ir_bit == 15 ) {
            rc5_code >>= 1;
            ir_final_code = RC5((rc5_code&0x7FF)|(rc5_code == last_rc5_code ? IR_REPEAT : 0));
            last_rc5_code = rc5_code;
            cancel_rc5_reception();
        } else {
            PRname(IR_TIMER) = rc5_timing + (rc5_timing>>1);
            rc5_reset_timer = 1;
        }
    }
}

void __ISR(TIMER_VECTORname(IR_TIMER),IPL2AUTO) TInterruptname(IR_TIMER)(void) {
    IRTimerInterrupt();
}

extern unsigned char timer_running;
__attribute__((mips16)) static void IRCNInterrupt() {
    PORTname(IR_PIN_PORT,IR_INPUT_PIN);
    CNIFname(IR_PIN_PORT) = 0;

    if( ir_bit == 0 || last_ir_state != PORTname(IR_PIN_PORT,IR_INPUT_PIN) ) {
        last_ir_state = PORTname(IR_PIN_PORT,IR_INPUT_PIN);
        if( ir_bit <= 1 ) {
            if( ir_bit == 0 ) {
                if( !timer_running )
                    TMRname(IR_TIMER) = 0;
                if( !PORTname(IR_PIN_PORT,IR_INPUT_PIN) ) {
                    if( ir_OSCCON_cache != OSCCON )
                        calc_rc5_timing();
                    // start reception
#ifdef SUPPORT_NEC
                    PRname(IR_TIMER) = NEC_startpulse_max;
#else
                    PRname(IR_TIMER) = RC5_MAX;
#endif
                    start_ir_timer();
                    rc5_reset_timer = 0;
                    ir_bit = 1;
                    rc5_code = 0;
                }
            } else {
                if( !PORTname(IR_PIN_PORT,IR_INPUT_PIN) ) {
                    rc5_timing = TMRname(IR_TIMER);
                    cancel_rc5_reception();
                    if( rc5_timing >= RC5_MIN && rc5_timing <= RC5_MAX ) {
                        unsigned short temp = rc5_timing>>1;
                        PRname(IR_TIMER) = (temp) + (temp>>1);
                        start_ir_timer();
                        ir_bit = 2;
                    }
#ifdef SUPPORT_NEC
                } else if( TMRname(IR_TIMER) >= NEC_startpulse_min ) {
                    rc5_timing = TMRname(IR_TIMER)>>1;
                    stop_ir_timer();
                    PRname(IR_TIMER) = rc5_timing + (rc5_timing>>2);
                    start_ir_timer();
                    ir_bit = 31;
#endif
                }
            }
#ifdef SUPPORT_NEC
        } else if( ir_bit >= 31 ) {
            if( ir_bit == 31 && !PORTname(IR_PIN_PORT,IR_INPUT_PIN) ) {
                if( TMRname(IR_TIMER) >= rc5_timing - (rc5_timing>>2) ) {
                    // start alternative IR reception
                    stop_ir_timer();
                    PRname(IR_TIMER) = (rc5_timing>>3) + (rc5_timing>>4);
                    start_ir_timer();
                    NEC_code = 0;
                    ir_bit = 32;
                } else if( TMRname(IR_TIMER) >= (rc5_timing>>2) + (rc5_timing>>3) ) {
                    ir_final_code = IR_REPEAT|NEC(last_NEC_code);
                    cancel_rc5_reception();
                } else {
                    cancel_rc5_reception();
                }
            } else {
                if( PORTname(IR_PIN_PORT,IR_INPUT_PIN) ) {
                    stop_ir_timer();
                    PRname(IR_TIMER) = (rc5_timing>>2) + (rc5_timing>>3) + (rc5_timing>>4);
                    start_ir_timer();
                } else {
                    NEC_code <<= 1;
                    NEC_code |= TMRname(IR_TIMER) >= (rc5_timing>>3) + (rc5_timing>>4);
                    ++ir_bit;

                    if( ir_bit == 64 ) {
                        union {
                            unsigned long l;
                            unsigned char b[4];
                        } temp;
                        temp.l = NEC_code;
                        cancel_rc5_reception();
                        if( temp.b[0] == 0xFF-temp.b[1] && temp.b[2] == 0xFF-temp.b[3] ) {
                            last_NEC_code = temp.b[1]|(((unsigned short)temp.b[3])<<8);
                            ir_final_code = NEC(last_NEC_code);
                        }
                    } else {
                        stop_ir_timer();
                        PRname(IR_TIMER) = (rc5_timing>>3) + (rc5_timing>>4);
                        start_ir_timer();
                    }
                }
            }
#endif
        } else if( rc5_reset_timer ) {
            unsigned short temp = rc5_timing>>1;
            TMRname(IR_TIMER) = 0;
            PRname(IR_TIMER) = (temp) + (temp>>1);
            rc5_reset_timer = 0;
        }
    }
}

void __ISR(_CHANGE_NOTICE_VECTOR,IPL3AUTO) _CNInterrupt(void) {
    IRCNInterrupt();
    if( ir_cn_interrupt_chain )
        (*ir_cn_interrupt_chain)();
}
