/*  Infrared receiver driver

    15.10.2001: tk, code almost rewritten from scratch. The driver
                is now split into two files, one for the infrared-
                DMA handling, and one for the actual decoding.
    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 <assert.h>
#include "sc12/amd186.h"
#include "sc12/rtos.h"
#include "sc12/hwapi.h"
#include "irdriver.h"

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

/** Queue for received samples */
static unsigned     queue[IR_QUEUE_LEN];
static volatile int qHead, qCount;

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


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

  // read transfer count register without interrupting the
  // DMA channel (-> don't use GetDMAInfo()!!)
  //
  len = IR_DMA_LEN - inpw(IR_DMACHANNEL ? PCB_D1TC : PCB_D0TC);

  if (len==lastlen && len>0)
  {
    // Clean up the timer samples. Because of the DMA transfer
    // logic, there may be multiple samples per signal edge.
    //
    unsigned cooked = 0;
    for (int i=0; i<len-1; i++) {
      unsigned t = (dmabuf[i+1] - dmabuf[i]) / TICKS_PER_uS;
      if (t > IR_TRESHOLD)
        dmabuf[cooked++] = t;
    }
    dmabuf[cooked++] = 0;

    // If there's enough space left in the queue, store the
    // number of samples, followed by the timer samples.
    //
    if ((qCount+cooked+1) < IR_QUEUE_LEN)
    {
      queue[(qHead + qCount++) % IR_QUEUE_LEN] = cooked;
      for (int i=0; i<cooked; i++)
        queue[(qHead + qCount++) % IR_QUEUE_LEN] = dmabuf[i];
    }

    // restart DMA channel
    // (hopefully the above code didn't take too long..)
    //
    DmaInfo  dma;
    AMD_GetDmaInfo(IR_DMACHANNEL, &dma);
    dma.length  = IR_DMA_LEN;
    dma.dstMem  = (void*)dmabuf;
    AMD_StartDma(IR_DMACHANNEL, &dma);
  }
  lastlen = len;
  oldTimerHandler();
}


/**
 * Check if there are timer samples waiting in the queue.
 * The samples can be retrieved by IR_GetSamples().
 *
 * \sa      IR_GetSamples()
 * \return  number of available samples.
 */
int IR_NumSamples()
{
  // Return the first value in the queue. (the number of
  // samples is stored in front of the actual timer samples)
  //
  if (qCount>0)
    return queue[qHead];
  else
    return 0;
}


/**
 * Get timer samples from the queue. Use the IR_NumSamples()
 * function to check if there are samples in the queue.
 *
 * \sa  IR_NumSamples()
 *
 * \param  dest    buffer to receive the timer samples
 * \param  maxlen  maximum number of samples to copy
 * \return number of samples copied
 */
int IR_GetSamples(unsigned *dest, int maxlen)
{
  int i, numSamples;

  numSamples = IR_NumSamples();
  for (i=0; i<numSamples && i<maxlen; i++)
    *dest++ = queue[(qHead+i+1) % IR_QUEUE_LEN];

  disable();
  qHead   = (qHead+numSamples+1) % IR_QUEUE_LEN;
  qCount -= numSamples+1;
  enable();
  return i;
}


/**
 * This function must be called to initialize
 * the remote control receiver.
 */
void IR_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  = IR_DMA_LEN;
  dma.srcPort = PCB_T1CNT;
  dma.dstMem  = (void*)dmabuf;
  AMD_StartDma(IR_DMACHANNEL, &dma);
  AMD_EnableDrq(IR_DMACHANNEL, true);
}


/**
 * Free all used resources.
 */
void IR_Done()
{
  // Disable the DMA channel
  //
  AMD_StopDma(IR_DMACHANNEL);

  // Restore timer interrupt
  //
  outpw(PCB_T1CON, TCON_STOP);
  if (oldTimerHandler)
    setvect(0x12, oldTimerHandler);
}
