/*
 * Q-STEER IR remote controller support code
 *
 * Thanks for Q-STEER IR signaling information availabe on below.
 *  http://homepage2.nifty.com/stear/doc/QSir.htm
 */
#include <t_services.h>
#include "cqnxparm.h"
#include "nxpmp3.h"
#include "kernel_id.h"
#include "qsteer.h"
#include "touch.h"

typedef enum {
  IR_IDLE, IR_BITS,
} IR_STATE;

struct t_ir_softs {
  IR_STATE state;
  int bits;
  int data;
  int last_data;
  int count;
} ir_softs;

static int ir_min, ir_max;

/*
 * Timer 0 is used to detect IR receiver signal.
 * Note that IR receiver signal is connected to both of CAP0.0 and CAP0.1.
 * Timer value is loaded to CR0 at falling edge, and loaded to CR1 at
 * rising edge. Then, interrupt is generated at rising edge. Also note
 * that IR receiver signal is 'L' active.
 */
void ir_init(void)
{
    T0TCR = 2;		/* reset timer */
    T0TCR = 0;		/* stop timer */
    T0CTCR = 0;		/* Timer mode */
    T0PR = 0;		/* No prescale */

#define	CAP0FE	(1<<1)
#define	CAP0I	(1<<2)
#define	CAP1RE	(1<<3)
#define	CAP1FE	(1<<4)
#define	CAP1I	(1<<5)
    /*
     * Enable interrupt on CAP1 event (rising edge). When we encounter
     * this event, CR0 has already loaded when we saw falling edge.
     */
    T0CCR = CAP0FE | CAP1RE | CAP1I;
    /* Start counter */
    T0TCR = 1;

    ir_softs.state = IR_IDLE;

    ir_max = 0;
    ir_min = 1 << 20;

    ena_int(INTNO_TIMER0);
}

static int last_cr;
static int ir_count, ir_data, ir_bits, ir_got;
static int ir_bcount;
#ifdef DEBUG
static int ir_button;
#endif

#define	CR1_INT	(1<<5)

void ir_handler(void)
{
    int pw_low;
    struct t_ir_softs *psofts = &ir_softs;
    int button, newbutton;

    if (T0IR & CR1_INT) {	/* CR1 interrupt */
        ir_count++;
        pw_low = T0CR1 - T0CR0;
        if (pw_low < 0) pw_low = -pw_low;
        last_cr = T0CR1;
        if (pw_low < ir_min) ir_min = pw_low;
        if (pw_low > ir_max) ir_max = pw_low;

        switch (psofts->state) {
        case IR_IDLE:   /* Waiting for leader part */
            if (pw_low >= T_MIN_LEADER && pw_low <= T_MAX_LEADER) {
                psofts->state = IR_BITS;
                ir_bits++;
                psofts->bits = 0;
                psofts->data = 0;
            }
            break;
        case IR_BITS:	/* Found a bit */
            if (pw_low >= T_MIN_SIG0 && pw_low <= T_MAX_SIG0) {	/* BIT 0 */
                psofts->bits++;
                psofts->data <<= 1;
            } else if (pw_low >= T_MIN_SIG1 && pw_low <= T_MAX_SIG1) {	/* BIT 1 */
                psofts->bits++;
                psofts->data <<= 1;
                psofts->data++;
            } else if (pw_low >= T_MIN_LEADER && pw_low <= T_MAX_LEADER) {
                /* It looks we encounter leader bit again. Re-initialize IR_BITS state. */
                psofts->state = IR_BITS;
                psofts->bits = 0;
                psofts->data = 0;
            } else {
                /* Bogus bit. Ignore and return to IR_IDLE state */
                psofts->state = IR_IDLE;
                psofts->bits = 0;
                psofts->data = 0;
            }
            if (psofts->bits == IR_MAX_BITS) { /* Finished to collect all channel/control bits */
                psofts->state = IR_IDLE;
                ir_got++;
                if ((psofts->data & BAND_MASK) != MY_BAND) {
                    /* Not interested in other bands. Ignore this data. */
                    break;
                }
                ir_data = newbutton = psofts->data & CMD_MASK;
                button = psofts->last_data;
                if (newbutton != button) {
                    if ((newbutton & CMD_MASK) == IR_CMD_STOP) {
                        /* Button has released.
                         * Determine the encoded button name based
                         * on the period of button press.
                         */
                        if (psofts->count > 10) {
                            switch (button) {
                            case IR_CMD_RIGHT:
                                button = IR_CMD_NEXT_AL;
                                break;
                            case IR_CMD_LEFT:
                                button = IR_CMD_PREV_AL;
                                break;
                            }
                        } else if (psofts->count > 4) {
                            switch (button) {
                            case IR_CMD_RIGHT:
                                button = IR_CMD_NEXT;
                                break;
                            case IR_CMD_LEFT:
                                button = IR_CMD_PREV;
                                break;
                            }
                        }
#ifdef DEBUG
                        ir_button = button;
#endif
                        /* Send button event into DTQ_EVENT */
                        ipsnd_dtq(DTQ_EVENT, (VP_INT) (IR_MARK | button));
                    }
                    psofts->last_data = newbutton;
                    psofts->count = 0;
                } else { /* Detect same buffton code */
                    if (newbutton != IR_CMD_STOP) {
                        psofts->count++;
                        ir_bcount = psofts->count;
                    }
                }
            }
            break;
        }
        T0IR = CR1_INT;              /* reset CR1 interrupt */
    }
}

void irstat_command(int argc, char **argv)
{
    syslog(LOG_NOTICE, "ir_count = %d, ir_data = %x", ir_count, ir_data);
    syslog(LOG_NOTICE, "ir_bits = %d, ir_got = %d", ir_bits, ir_got);
    syslog(LOG_NOTICE, "ir_bcount = %x", ir_bcount);
    syslog(LOG_NOTICE, "ir_min = %d, ir_max = %d", ir_min, ir_max);
#ifdef DEBUG
    syslog(LOG_NOTICE, "ir_button = %x", ir_button);
#endif
}
