/********************************  dwarfmain.h  ******************************
 * $Source: /home/cvs/ESP/gen2/software/msp430/include/dwarfmain.h,v $
 *  Copyright (C) 2004 MBARI
 *
 *  MBARI Proprietary Information. All rights reserved.
 * $Id: dwarfmain.h,v 1.37 2006/05/17 00:18:59 brent Exp $
 *
 * Generic framework for ESP Dwarf applications
 *  Intended to be #included into the mainline of each top level application
 *  after drawftop.h
 *
 *****************************************************************************/

#include "kickstart.h"  //defines the method of kickstarting the serial xmitter

// DATA

struct timer itimer[NTIMERS];    //interval timers


enum { //sundry internal flags
  //DWRFRTS is used to indicate that RTS flow control is enabled
  xmitterBusy = DWRFRTS == 1 ? 2 : 1   //last UTXIFG1 ISR loaded the transmitter
};

byte _mainFlags;

#ifdef I2CtraceDepth  //for debugging I2C message processing
#include "stdio.h"

static struct I2Ctrace { 
  I2Cvectors state;
  unsigned   mclk;
  byte       ifg,ie;
};

#if I2CtraceDepth
static byte I2CtraceNext = 0;
static struct I2Ctrace I2Ctrace[I2CtraceDepth];
#endif


static void flushSerialOut (void)
{
  while (!FIFOempty (&toHost))   //flush output
    if (IFG2 & UTXIFG1 || U1TCTL & TXEPT) TXBUF1=FIFOreadByte (&toHost);
}

static void dumpTraceLine (struct I2Ctrace *event)
{
  char line[60];
  sprintf (line, "@0x%04X, state=0x%04X, ifg=0x%02X, ie=0x%02X\r\n",
                  event->mclk, event->state, event->ifg, event->ie);
  parserPutString (DWARFdebugPort, line);
  flushSerialOut();
}
#endif

static inline void dumpTrace(void)  
{
#ifdef I2CtraceDepth  //dump out the trace for debugging
  struct I2Ctrace now;
  now.mclk = MCLKnow, now.state = DWARFI2Cstate,
    now.ifg = I2CIFG, now.ie = I2CIE;
  flushSerialOut();
  parserPutString (DWARFdebugPort, "\r\n");
#if I2CtraceDepth
  {
    byte index = I2CtraceNext;
    do {
      struct I2Ctrace *ev = I2Ctrace + index;
      if (ev->state) dumpTraceLine (ev);
      if (++index >= I2CtraceDepth) index=0;
    } while (index != I2CtraceNext);
  }
#endif
  dumpTraceLine (&now);
#endif
}


// Local Functions


//start host resynchronization process -- interrupts must be disabled on entry
static void startResync (byte USARTerrMask)
{
  if (appState == running         //no break already pending &&
#ifdef dwarfInErr
         && dwarfInErr(USARTerrMask)
#endif
  ) {  //app wants to restart
      appState = breakPending;    //wait for RTS and empty output fifo...
      dumpTrace();
      kickstart();
    }
}
    
static void kickstartXmit (void)
/*
  (re-)enable the serial transmitter interrupt
*/
{
  kickstart();
}

  
#macro xmitByteISR(signal)
/*
  Transmit next byte to Host
  signaling mechanism is typically either SIGNALEV or signalEV
*/
{
  if (FIFOempty (&toHost)) { //nothing left to send.
   switch (appState) {
      case running:
notBusy:
        _mainFlags &= ~xmitterBusy;
        goto exitXmitISR;
      case sendingBreak:
        ASSERTBRK();  //continue break while last 0 byte being clocked out
        clear8 (DWRFHANDOUT, DWRFCTS);  //host can send, we're awaiting break
        goto notBusy;
      default:  //handle invalid application state
        appState = breakPending;
      case breakPending:
        if (!FIFOempty (&fromHost)) goto notBusy;
        TXBUF1 = 0;  //clock NUL byte out of the TX buffer to start break
        appState = sendingBreak;
    }
  }else{  //stop xmit if handshake interrupt sense correct & host deasserted RTS
    if (_mainFlags & DWRFIRQIN & DWRFRTS) goto notBusy;
    TXBUF1 = FIFOreadByte (&toHost);
    dwarfOutResume ();
    signal();
  }
  _mainFlags |= xmitterBusy;
exitXmitISR: ;
}
#endm


#macro rcvByteISR(signal)
/*
  Receive available byte(s) from Host
  signaling mechanism is typically either SIGNALEV or signalEV
*/
{
  byte errMask;
  do {
    byte data;
    errMask = URCTL1;
    data = RXBUF1;
    if (errMask & RXERR) goto gotBadByte;
    if (appState != running) goto exitRcvISR;
    {
      unsigned spaceLeft = FIFOfree(&fromHost);
      if (spaceLeft < CTSstop) { //signal host to stop if fifo is nearly full
         set8 (DWRFHANDOUT, DWRFCTS);
         if (!spaceLeft) goto noSpace;  //input fifo overflow!
      }
      FIFOwriteByte (&fromHost, data);
    }
  } while (IFG2 & URXIFG1);  //try to cope with this int's low priority
  dwarfInResume ();
  signal();
  goto exitRcvISR;
noSpace:
  errMask = OE;
gotBadByte:
  rcvdBadByte (errMask);
exitRcvISR: ;
}
#endm

static void rcvdBadByte (byte errMask)
//factored out of rcvByteISR to ease placing debugging breakpoint on this event
{
  if (errMask & BRK) {  //was it the start of a break?
    clear8 (DWRFIRQIFG, DWRFRxD);  //clear out any pending transition interrupts
    set8 (DWRFIRQIFG, DWRFIRQIN & DWRFRxD);  //don't miss a quick transition
    set8 (DWRFIRQIE, DWRFRxD); //arm interrupt on break end (when RxD goes back high)
  }
  startResync(errMask);
}


void RX1ISR (void)
/*
  poll for available input
  called with interrupts disabled
  useful to prevent dropping bytes during long ISRs
  Do not call without first checking (IFG2 & URXIFG1) 
*/
{
  rcvByteISR(signalEV);
}


static void pollIn (void)
/*
  poll for available input
  called with interrupts disabled
  useful to prevent dropping bytes during long ISRs
*/
{
  if (IFG2 & URXIFG1) 
    rcvByteISR(signalEV);
}


// Interval Timer Service Routines

static inline void dwarfTrimDCO(void)
{
  mclk0 = 0;
  CCTL2 = CM_1 | CCIS_1 | CAP | CCIE; // CAP, ACLK
}

#ifdef DCOUPRATE

static void trimDCO (struct timer *array, unsigned index, struct timer *timer)
/*
  use timer A to capture the number of MCLKS in the next ACLK
  use this to adjust the DCO
*/
{
  dwarfTrimDCO();
}

#endif

// Interrupt Service Routines

interrupt[WDT_VECTOR] void WDT_ISR(void)
{
  timerServiceMaskType service;
  MASK_TIMER_INTERRUPTS();
  firstEINTinISR();
  service = advanceTimers (itimer, NTIMERS);  //allow other interrupts
  UNMASK_TIMER_INTERRUPTS();  //allow reentry while servicing timers
  if (service) 
    serviceTimers(itimer, service);
  dwarfTic();
  kickstart();
  _DINT();
  if (DWRFHANDOUT & DWRFCTS && FIFOfree(&fromHost) > CTSresume) 
    clear8(DWRFHANDOUT, DWRFCTS);  //host can send more if there's room in fifo
    
  propagateEV(WDT); 
}


interrupt[TIMERA1_VECTOR] void timerA_ISR(void)
/*
  handle timer A capture for DCO adjustment
*/
{
  if (TAIV == 4) { //got capture
    unsigned mclk = CCR2;
    if (mclk0) {
      unsigned cctl2 = CCTL2;
      CCTL2 = 0;       // Stop Capture      
      if (!(cctl2 & COV)) {
        firstEINTinISR();        //allow other interrupts while in adjustDCO   
        adjustDCO (MCLKSperACLK, mclk-mclk0);
        DINTandPropagateEV(TIMERA1);
      }
    }else
      mclk0 = mclk; //note: if !mclk we just try again.
  }
}


interrupt[USART1RX_VECTOR] void USART1RX_ISR(void)
/*
  Receive next byte from Host
*/
{
  rcvByteISR(SIGNALEV);
}


interrupt[USART1TX_VECTOR] void USART1TX_ISR(void)
/*
  transmit next byte to Host
    RTS from host masks and unmasks this interrupt for flow control
*/
{
  xmitByteISR(SIGNALEV);
}


interrupt[DWRF_VECTOR] void DWRF_ISR(void)
/*
  process transition on an interesting Port 2 GPIO bit
*/
{
  byte edges = DWRFIRQIFG & DWRFIRQIE;  //edges that generated this interrupt
  DWRFIRQIFG &= ~edges; 
#ifdef DWRFEDGEIRQ  
  DWRFEDGEIRQ(edges);  //application specific edge interrupt processing
#endif
  if (edges & DWRFCPLDIRQ) {
    clear8 (DWRFIRQIE, DWRFCPLDIRQ);  //prevent reentry
    do { //service pending CPLD interrupts
      byte pending = readDWRF(DWRFIFG) & DWRFIEimage;
      firstEINTinISR();  //allow other interrupts to signal events
      serviceDwarfIRQ (pending);
      _DINT();
      if (pending &= ~DWRFIEimage) writeDWRF(DWRFIFG, pending);
      propagateEV(DWRF);
    } while (DWRFIRQIN & DWRFCPLDIRQ);  //simulate level sensitive CPLDIRQ
    set8 (DWRFIRQIE, DWRFCPLDIRQ);
  }
  //simulate TXRDY interrupt if xmitter isn't busy 
  //or if TXEPT -- to recover just in case xmitterBusy got set incorrectly
  if (edges & DWRFRTS && (!(_mainFlags & xmitterBusy) || U1TCTL & TXEPT) ) 
    xmitByteISR(SIGNALEV);      
  if (edges & DWRFRxD) {  //end of break from host
    clear8 (DWRFIRQIE, DWRFRxD);  //so real data bits won't interrupt
    longjmp (restartEnv, appState);  //restart the app
  }
}


// send break to host and restart parser after host has echoed it back
//DOES NOT RETURN to caller
static void resynchronize (void)
{
  _DINT();  //disable all interrupts for call to startResync
  startResync(0);
  while (true) AWAITEV();  //enable interrupts and loop until longjmp()
}


#ifdef DWARFI2Cstate    //include standard I2C interrupt handlers

interrupt[USART0TX_VECTOR] void USART0TX_ISR(void)
/*
  Process interrupt according to the current state of I2C
  processing.  See the description of the I2CIV register
  in TI's MSP430 manual.
*/
{
#ifdef I2CtraceDepth  //append I2CIE and I2Cstate to a fifo for debugging
  byte next = I2CtraceNext;
  struct I2Ctrace *cursor = I2Ctrace + next;
  cursor->mclk = MCLKnow;
  cursor->state = DWARFI2Cstate, cursor->ifg = I2CIFG, cursor->ie = I2CIE;
  if (++next >= elementsIn (I2Ctrace)) next = 0;
  I2CtraceNext = next;
#endif
  allowINTinISR();
  (*(I2Cisr **)((byte *)DWARFI2Cstate + I2CIV)) (DWARFI2CISRargs);
//equivalent to:
//    DWARFI2Cstate[I2CIV/2] (&DWARFI2Cinstance);
  if (IFG2 & URXIFG1) 
    rcvByteISR(signalEV); //don't lose input due to this ints higher priority
#ifdef DWARFI2CendISR
  DWARFI2CendISR(&DWARFI2Cinstance);
#endif
  propagateEV(USART0TX);
}

void USART0_defaultISR (void *instance)
/*
  process a transmitter empty interrupt from USART0 if no 
  I2C interrupts are pending.  This MUST be the first
  entry in any I2CisrState function table
*/
{
  clear8 (IE1, UTXIE0);  //don't bother me again.
}


// asynchronous (non-I2C) USART0 RX interrupt vector table
static I2Cisr * const asyncUSART0state[] = {
  USART0_defaultISR  //there should be no I2C interrupt pending...
};

#ifdef USART0RX_VECTOR
interrupt[USART0RX_VECTOR] void USART0RX_ISR(void)
/*
  receive next byte -- dummy stub
*/
{
  clear8 (IE1, URXIE0);  //don't bother me again.
}
#endif

#endif


// Initialization

static void dwarfMainRestart (void)
/*
  restart from here on a soft restart (due to RS-232 break condition)
*/
{
  FIFOclear (&toHost); FIFOclear (&fromHost);
  _mainFlags = 0;
  endDWRFirqs (-1); //mask all CPLD irqs
  DWRFIRQIES = DWRFRTS;   //interrupt on falling edges of RTS from host
  DWRFIRQIFG = 0;         //cancel any pending interrupts
  DWRFIRQIE = DWRFCPLDIRQ | DWRFRTS; //interrupt on rising edge from CPLD or RTS
  DEASSERTBRK();          //deassert break to host
  clear8 (DWRFHANDOUT, DWRFCTS); //Tell host they can send again
  appState = running;
  UNMASK_TIMER_INTERRUPTS();
}


static void dwarfMainInit (void)
/*
  Initialize peripheral registers and set up to process interrupts
  Does NOT configure ports 4 and 6
*/
{
  set8 (BCSCTL1, DIVA_3|XT2OFF);  //configure a 4096 Hz ACLK & no high freq xtal
  initDWRF(RoscSrc);
   
  TACTL = TASSEL_2 | MC_2 | TACLR;    // Timer A2 counts SMCLK, cont-mode, clear
  {  //wait about 1500ms for crystal oscillator to stabilize
    unsigned i=150;
    do MCLKsleep (20000); while (--i);  //clock seems to come up at ~2Mhz
  }
  setDCO(MCLKSperACLK);  //tune SMCLK to the desired frequency
  endDWRFreset();        //let the other peripherals out of reset

  /* configure DCO and software interval timers */
  TACTL = TASSEL_2 | MC_2 | TACLR;    // Timer A2 counts SMCLK, cont-mode, clear
  initTimers (itimer, elementsIn(itimer));
#ifdef DCOUPRATE
  itimer[dcoTimer].countdown = itimer[dcoTimer].period = DCOUPRATE; 
  itimer[dcoTimer].action = trimDCO;
#endif
  
  U1CTL = SWRST | CHAR;   //8 bit data, no parity, 1 stop bit
  UTCTL1 = SSEL0 | SSEL1; //derive baud clock from SMCLK
  URCTL1 = URXEIE;        //receive bytes with errors
  UBR01 = MCLKSperBAUD;   //set baud clock divisor
  UBR11 = MCLKSperBAUD >> 8;
  ME2 = UTXE1 | URXE1;    //enable transmitter and receiver
  UCTL1 &= ~SWRST;        // Start USART
  IE2 = URXIE1|UTXIE1;    //unmask USART1 interrupts
  
  WDTCTL = WDT_ADLY_1_9;  //configure for 64hz WDT interrupts
  
#ifdef DWARFI2Cstate
  DWARFI2Cstate = asyncUSART0state;
#endif
  dwarfMainRestart();
}

