/*  Remote Control driver

    25.10.2001: tk, i should really implement a better buffer technique,
                to avoid the lengthy decoding during the timer interrupt.
                Debugging of IR decoder functions is a pain in the ass,
                and we might even loose some timer samples, if the deocding
                takes too long. (a real nice idea would be a generic decoder
                that can parse LIRC config files..) [see www.lirc.org]
    24.06.2001: tk, initial implementation.

    Copyright (c)2000 by Thomas Kindler, thomas.kindler@gmx.de

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of
    the License, or (at your option) any later version. Read the
    full License at http://www.gnu.org/copyleft for more details.

*/

// include files -----
//
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <dos.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include "sc12/amd186.h"
#include "sc12/rtos.h"
#include "sc12/hwapi.h"
#include "sc12/rtos.h"
#include "remote.h"

#define  RC_DMA_LEN    1024


/** DMA buffer for timing samples */
static volatile unsigned  dmabuf[RC_DMA_LEN];

/** pointer to the old interrupt handler */
static void interrupt     (*oldTimerHandler)(...);

/** queue for received keycodes */
static unsigned long      queue[RC_QUEUE_SIZE];
static volatile int       qHead, qCount;


// #define  RC_DEBUG

#ifdef RC_DEBUG
  static char  dstring[8000];
  static char *dstringptr = dstring;

  /**
   * Interrupt-safe printf version. Since all the decoding is
   * done in an interrupt, we have a serious debugging problem.
   * Use this function, and ddump() to print messages during
   * these functions.
   */
  static int dprintf(const char *format, ...)
  {
    if (dstringptr-dstring>sizeof(dstring)-256)
      return 0;
    va_list arglist;
    va_start(arglist, format);
    int ret = vsprintf(dstringptr, format, arglist);

    dstringptr += ret;
    assert(dstringptr < dstring+sizeof(dstring));

    va_end(arglist);
    return ret;
  }

  /**
   * Print the contents of the dprintf buffer. This function
   * must be not be called within an interrupt, because it
   * uses the printf function.
   */
  static void ddump()
  {
    printf("%s", dstring);
    dstring[0] = '\0';
    dstringptr = dstring;
  }
#endif



/**
 * RC5 decoding state machine. Based on Guy Carpenter's article
 * "An Efficient Algorithm for Decoding RC5 Remote Control Signals",
 * http://deep.clearwater.com.au/pcm-9574/rc5/rc5.html
 *
 * The RC5 Protocol is widely used by various Vendors, among them
 * Loewe, Philips, Grundig and Marantz
 *
 * \par RC5 protocol:
 *   SSTAAA AACCCCCC<br>
 *   Bits 13-12:  Startbits (11 or 10)<br>
 *   Bits 11:     Toggle bit (toggles between keypresses)<br>
 *   Bits 10-6:   System address<br>
 *   Bits 5-0:    Command code<br>
 *
 * \param   samples  pointer to cooked timer samples.
 * \return           valid keycode, or 0 if either a timing
 *                   or biphase error occured.
 */
static unsigned long DecodeRC5(unsigned const *samples)
{
  int state = 3;   // START0      MID0     START1     MID1
  int next[4][2] = {{  1, -1},{  0,  3},{  3, -1},{  2,  1}};
  int emit[4][2] = {{  0, -1},{ -1,  1},{  1, -1},{ -1,  0}};

  unsigned long  code = 0;
  for (int i=0, b=12; b>=0; i++) {
    unsigned t = samples[i];
    if (abs(t-RC5_SHORT) < RC5_MARGIN) {
      if (emit[state][0] == 0)
        b--;
      else if (emit[state][0] == 1)
        code |= 1L<<b--;
      state = next[state][0];
    } else if (abs(t-RC5_LONG) < RC5_MARGIN) {
      if (emit[state][1] == 0)
        b--;
      else if (emit[state][1] == 1)
        code |= 1L<<b--;
      state = next[state][1];
    } else {
      // timing error
      return 0;
    }
    // check for biphase error
    if (state == -1)
      return 0;
  }

  // assemble keycode, and check for repeat
  //
  unsigned long keycode;
  static unsigned long lastcode = 0;

  keycode = RCKEY_RC5 | (code & 0x7ff);

  if (~(code^lastcode) & 0x800)
    keycode |= RCKEY_REPEAT;
  lastcode = code;

  return keycode;
}


/**
 * Decode NEC Timing
 * \todo implement Sanyo code with 13 vendor bits
 *
 * \param  samples pointer to cooked timer values.
 * \return         valid keycode, or 0.
 */
static unsigned long DecodeNEC(unsigned const *samples)
{
  unsigned long keycode = RCKEY_NEC;
  unsigned long code = 0;
  static unsigned long lastcode = 0;

  if (abs(samples[0]-NEC_START1) > NEC_MARGIN)
    return 0;

  if (abs(samples[1]-NEC_START2) < NEC_MARGIN) {
    lastcode = 0;
    for (int i=0; i<32; i++) {
      int t1 = samples[i*2+2];
      int t2 = samples[i*2+3];

      if (abs(t1-NEC_PULSE)>NEC_MARGIN)
        return 0;

      if (abs(t2-NEC_PAUSE0)<NEC_MARGIN) {
        // 0
      } else if (abs(t2-NEC_PAUSE1)<NEC_MARGIN) {
        // 1
        code |= 1L << i;
      } else {
        return 0;
      }
    }
  } else if (abs(samples[1]-NEC_REPEAT) < NEC_MARGIN) {
    code = lastcode;
    keycode |= RCKEY_REPEAT;
  } else {
    return 0;
  }

  if (((code>>16) ^ ~(code>>24)) & 0xff)
    return 0;

  lastcode = code;
  keycode |= (((code>>16)&0xff) | ((code & 0xffff)<<8)) & RCKEY_CODE;

  return keycode;
}


/**
 * Decode SAMSUNG timing
 *
 * \param  samples  pointer to cooked timer values.
 * \return         valid keycode, or 0.
 */
static unsigned long DecodeSAMSUNG(unsigned const *samples)
{
  // check start bits
  //
  if ((abs(samples[0]-SAM_START1) > SAM_MARGIN) ||
      (abs(samples[1]-SAM_START2) > SAM_MARGIN))
    return 0;

  unsigned long code = 0;
  for (int i=0; i<32; i++) {
    int t1 = samples[i*2+2];
    int t2 = samples[i*2+3];

    if (abs(t1-SAM_PULSE)>SAM_MARGIN)
      return 0;

    if (abs(t2-SAM_PAUSE0)<SAM_MARGIN) {
      // 0
    } else if (abs(t2-SAM_PAUSE1)<SAM_MARGIN) {
      // 1
      code |= 1L << i;
    } else {
      return 0;
    }
  }

  // assemble keycode, and check for repeat
  //
  static unsigned long lastcode = 0;
  static unsigned long lasttime = 0;

  unsigned long time;
  unsigned long keycode;

  keycode = RCKEY_SAMSUNG | (code >> 16);

  RTX_Get_System_Ticks(&time);
  if ((code == lastcode) && (time-lasttime < SAM_REPEATMS))
    keycode |= RCKEY_REPEAT;

  lastcode = code;
  lasttime = time;

  return keycode;
}


/**
 * Try to decode raw timing samples. Because of the DMA logic, there
 * may be more than one sample per signal edge - we have to clean up
 * the timing buffer before we try to recognize any keycode.
 * After cleanup, different decoder functions are run on the array.
 * If a keycode was recognized, it gets added to the key queue
 *
 * \param   samples array of raw timer samples
 * \param   length  number of timer samples
 *
 * \todo  error on uneven cooked count! [and find out, why that
 *        happens, anyway..]
 */
static void DecodeSamples(unsigned *samples, unsigned length)
{
  int cooked = 0;
  for (int i=0; i<length-1; i++) {
    unsigned t = (samples[i+1] - samples[i]) / TICKS_PER_uS;
    if (t > RC_TRESHOLD)
      samples[cooked++] = t;
  }
  samples[cooked++] = 0;

  #ifdef RC_DEBUG
    dprintf("cooklen %d\n", cooked);
    dprintf("index   pulse   pause\n");
    for (int i=0; i<cooked; i+=2)
      dprintf(" %-4d   %5u   %5u\n", i, samples[i], samples[i+1]);
  #endif

  unsigned long key = 0;
  if (!key) key = DecodeRC5(samples);
  if (!key) key = DecodeNEC(samples);
  if (!key) key = DecodeSAMSUNG(samples);

  if (key && qCount < RC_QUEUE_SIZE) {
    queue[(qHead+qCount) % RC_QUEUE_SIZE] = key;
    qCount++;
  }
}


/**
 * Timer interrupt handler.
 * This interrupt is called whenever Timer 0 generates an overflow.
 * If there are bytes in the dmabuf buffer, and the DMA position
 * hasn't changed between two of the interrupts, we received a new
 * datagram.
 */
static void interrupt timerHandler(...)
{
  static unsigned lastpos;
  unsigned        position;

  // get transfer count without disturbing the channel.
  // (don't use GetDMAInfo()!!)
  //
  position = RC_DMA_LEN - inpw(RC_DMACHANNEL ? PCB_D1TC:PCB_D0TC);

  if (position == lastpos && position != 0)
  {
    // try to decode the timing samples
    //
    DecodeSamples((unsigned*)dmabuf, position);

    // Restart DMA channel
    //
    DmaInfo  dma;
    AMD_GetDmaInfo(RC_DMACHANNEL, &dma);
    dma.length  = RC_DMA_LEN;
    dma.dstMem  = (void*)dmabuf;
    AMD_StartDma(RC_DMACHANNEL, &dma);
  }
  lastpos = position;
  oldTimerHandler();
}


/**
 * Check for currently available keycodes. Any available
 * keycodes can be retrieved with RC_GetKey()
 * \sa      RC_GetKey()
 * \return  number of available keycodes.
 */
int RC_KeyAvail()
{
  #ifdef RC_DEBUG
    ddump();
  #endif

  return (int)qCount;
}


/**
 * Get a keycode from the queue. This function will block until
 * a keycode is received. Use the RC_KeyAvail() function to check
 * if there are codes in the queue.
 * \sa      RC_KeyAvail()
 * \return  valid keycode != 0
 */
unsigned long RC_GetKey()
{
  while (!RC_KeyAvail()) {
    RTX_Sleep_Time(10);
  }

  unsigned long key;
  key    = queue[qHead++];
  qHead %= RC_QUEUE_SIZE;
  qCount--;

  return key;
}


/**
 * This function must be called to initialize the
 * remote control subsystem.
 */
void RC_Init()
{
  // Enable chip select
  //
  pfe_enable_bus(0xff, true);
  pfe_enable_pcs(1);

  // Set up the timer --
  // We do this by hand, because the BIOS doesn't support
  // timers with only one terminal count. (Go figure. It
  // always writes to both CMPA and CMPB!)
  //
  oldTimerHandler = getvect(0x12);
  setvect(0x12, timerHandler);

  outpw(PCB_T1CON, TCON_STOP);
  outpw(PCB_T1CMPA, 0 );
  outpw(PCB_T1CNT, 0 );
  outpw(PCB_T1CON, TCON_CONT | TCON_INT | TCON_START);

  // Enable DMA channel and DMA Request pin
  //
  DmaInfo  dma;
  dma.control = DCON_BW | DCON_TC | DCON_DMIO | DCON_DINC | DCON_SSYNC | DCON_P;
  dma.length  = RC_DMA_LEN;
  dma.srcPort = PCB_T1CNT;
  dma.dstMem  = (void*)dmabuf;
  AMD_StartDma(RC_DMACHANNEL, &dma);
  AMD_EnableDrq(RC_DMACHANNEL, true);
}


/**
 * Shutdown the remote control subsystem, and free
 * all used resources.
 */
void RC_Done()
{
  // Restore timer interrupt
  //
  outpw(PCB_T1CON, TCON_STOP);
  if (oldTimerHandler)
    setvect(0x12, oldTimerHandler);

  // Disable the DMA channel
  //
  AMD_StopDma(RC_DMACHANNEL);
}
