/*********************************  I2Cnode.h  *******************************
 * $Source: /home/cvs/ESP/gen2/software/msp430/include/I2Cnode.h,v $
 *  Copyright (C) 2003 MBARI
 *
 *  MBARI Proprietary Information. All rights reserved.
 * $Id: I2Cnode.h,v 1.17 2005/08/30 05:49:28 brent Exp $
 *
 * I2C "dwarf" node message handling
 *    in a "sort of" object oriented fashion
 *
 * Theory of operation:
 *
 *  Unlike the gateway, which bridges an RS-232 port to the I2C bus,
 *  I2Cnodes are simply endpoints on the bus.  They may receive or originate
 *  messages and interface with other nodes and gateways as peers.
 *
 *  Variable length I2C events are logged in a byte oriented "event" fifo.
 *  The first byte represents the event's type.  
 *  This event type byte indicates whether the event was an caused by a master
 *  writing to or reading this node.  In the case of writes, it also indicates
 *  whether the write was followed by a CRC or not.
 *
 *  Each event type is followed by a length byte.  The total
 *  number of bytes in each event is 2+this length byte.
 *  that determines the number of bytes that follow.
 *  Note that any messages longer than 255 bytes are silently dropped.
 *
 *  The main (background) processing loop removes and parses these events
 *  and may output events to other nodes.  Note that there is no output
 *  event fifo.  Instead, the main processing loop blocks while mastering
 *  the I2C bus.
 *
 *  The I2C interrupt service routines call an event "filter" hook
 *  to notify the application of each incoming message.  The filter function
 *  may either ignore the message, handle it (quickly!) in the context
 *  of the ISR, or append the event to the application's event queue
 *  for later processing in its main loop.  If an event is deferred,
 *  the filter will also need to invoke signalEV() to wake up the background
 *  in case it was sleeping.
 *
 *  When a master sends us a read request, the I2C interrupt service
 *  routines call the readReq application hook. As the I2C bus is hung
 *  awaiting the application's response, the recommended practice is
 *  that readReq should call I2CnodeAnswer() directly.
 *
 *****************************************************************************/

#ifndef I2Cnode_h
#define I2Cnode_h

#include "msp430.h"
#include "interrupts.h"
#include "fifos.h"

#define I2Cclock  0x08    //special function bits in port 3
#define I2Cdata   0x02

typedef const struct I2CnodeInstance I2CnodeInstance;

typedef void I2Cisr (I2CnodeInstance *instance);

typedef I2Cisr * const* I2Cvectors; 


typedef enum {
  noI2CnodeEvent,                //No events pending
  I2CnodeCRCmsg,      //CRC checked message received
  I2CnodeNonCRCmsg,   //non-CRC checked message received
  I2CnodeGCCRCmsg,    //general call CRC checked message received
  I2CnodeGCmsg,       //general call non-CRC checked message received
  I2CnodeReadRequest, //read notification
  
  I2CnodeEventTypes
} I2CnodeEventType;


typedef enum {  //errors from I2C bus master operations
  I2CnodeOK,
  I2CnodeNACK,    //negative acknowledge -- see I2CnodeBytesXferred()
  I2CnodeCRCNACK, //negative CRC acknowledge after all bytes transferred
  I2CnodeBusErr,  //unrecoverable bus error
  I2CnodeTimeOut, //operation timed out
  I2CnodeArgErr,  //invalid arguments
  I2CnodeInUse,   //address in use
  I2CnodeRetryErr,
  
  I2CnodeErrs
} I2CnodeErr;


struct I2Cnode {       //I2C node processing state -- in RAM
  I2Cvectors state;    //I2C/USART0 interrupt processing state
  byte  *headEvent;    //event at the head of the event fifo
  byte  ownAdr;      //node's own address on the I2C bus or zero if none
                     //MSB set if node should ignore general calls
};

struct I2CnodeInstance {      //in ROM
  struct I2Cnode *current;    //dynamic configuration & state
  fifo   *events;             //event fifo
  I2Cisr *filter;   //called in interrupt context when a new msg is received
  I2Cisr *readReq;  //called from ISR when master reads us
  uint16 timeout;   //bus timeout in tics, 0 if none
  byte   retries;   //# of times to retry write if CRC fails or NACK received
                    //MSB set to use quick, non-standard CRC acknowledge
  byte   lowPeriod, highPeriod;  //MCLKS per I2C low and high clock periods
};


//return pointer to the type of the most recent I2C event
//used in the filter function
static inline
  I2CnodeEventType *I2CnodeNewEvent (I2CnodeInstance *instance)
{
  return (I2CnodeEventType *) FIFOtail(instance->events);
}


//given pointer to event's type, return pointer to its body and its length
static inline 
  byte *I2CnodeEventBody (I2CnodeInstance *instance, 
                              I2CnodeEventType *evType, unsigned *eventLen)
{
  fifo *events = instance->events;
  byte *lengthByte = FIFOinc (events, evType);
  *eventLen = *lengthByte;
  return FIFOinc (events, lengthByte);
}

 
//append the header for custom event
//returns the new tail of the event fifo
//Use FIFOaddByte() to advance this as needed for the event body.
//call I2CnodeCloseEvent() after body has been added
//caller must 1st insure that event fifo has sufficient space
//  and that no events can occur between open and close operations
static inline 
  byte *I2CnodeOpenEvent (I2CnodeInstance *instance)
{
  fifo *events = instance->events;
  return FIFOadvance (events,FIFOtail(events),2);
}


//call after I2CnodeOpenEvent()
//eventEnd points to the new tail of the event fifo
static inline 
  void I2CnodeCloseEvent (I2CnodeInstance *instance, 
                              I2CnodeEventType evType, byte *eventEnd)
{
  fifo *events = instance->events;
  byte *tail = FIFOtail(events);
  unsigned eventLen = eventEnd - tail;
  FIFOaddByte (events, FIFOaddByte (events,tail,evType), eventLen);
  FIFOtail(events) = eventEnd;
  signalEV();
}


//append an event having no body to the event fifo
//see I2CnodeOpenEvent()
static inline
  void I2CnodeAddEvent (I2CnodeInstance *instance, I2CnodeEventType ev)
{
  fifo *events = instance->events;
  FIFOtail(events) = 
    FIFOaddByte(events, FIFOaddByte(events, FIFOtail(events), ev), 0);
  signalEV();
//equivalent to:  
//  I2CnodeCloseEvent (instance, I2CnodeReadReq, I2CnodeOpenEvent (instance));
}


//called by filter function to add the newly received event to the events fifo
//note that if filter fn calls this, it must be before it calls I2CnodeAnswer
void acceptNewI2CnodeEvent (I2CnodeInstance *instance);


//service current I2CnodeReadReq event (may be called from filter function)
I2CnodeErr 
  I2CnodeAnswer (I2CnodeInstance *instance, byte *buffer, unsigned len);


// One time initialization of the node's message processor
void I2CnodeInit (I2CnodeInstance *instance);


// Restart the node's message processor
void I2CnodeRestart (I2CnodeInstance *instance, uint16 ownAdr);


//remove consumed event from the head of the events queue
void retireI2CnodeEvent (I2CnodeInstance *instance);


/* start processing events at the head of the input fifo
   all functions return the event type
   eventLen points to a word that is assigned the event's length
   if the returned event type != noI2CnodeEv
   
   CheckNextEvent returns noI2CnodeEv if the fifo is empty.
   
   AwaitNextEvent blocks until an event occurs
   
   nextEvent assumes that at least one event is pending
*/
I2CnodeEventType
  nextI2CnodeEvent (I2CnodeInstance *instance, unsigned *eventLen);

static inline I2CnodeEventType
  checkNextI2CnodeEvent (I2CnodeInstance *instance, unsigned *eventLen)
{
  retireI2CnodeEvent (instance);
  if (FIFOempty(instance->events)) return noI2CnodeEvent;
  return nextI2CnodeEvent (instance, eventLen);
}

static inline I2CnodeEventType
  awaitNextI2CnodeEvent (I2CnodeInstance *instance, unsigned *eventLen)
{
  retireI2CnodeEvent (instance);
  {
    fifo *events = instance->events;
    if (FIFOempty(events)) {
      while (_DINT(), FIFOempty(events)) AWAITEV();
      _EINT();
    }
  }
  return nextI2CnodeEvent (instance, eventLen);
}



//return after all pending bus transactions are complete and
//bus appears to be quiet
I2CnodeErr I2CnodeQuiet (I2CnodeInstance *instance);


//Set own address and probe it to insure address not already in use
//It's best to call this just after I2CnodeQuiet returns
//returns I2CnodeOK if successful
//returns I2CnodeInUse if newAdr is already assigned to another node
//and the address reverts to zero.
//may return other errors on serious bus errors
I2CnodeErr 
  setI2CnodeAdr (I2CnodeInstance *instance, uint16 newAdr);


//Read length bytes from I2Csrc address into buffer
I2CnodeErr I2CnodeRead(I2CnodeInstance *instance, uint16 I2Csrc, 
                            byte *buffer, unsigned length);


//Write length byte message in buffer to I2Cdst

#define noI2CnodeCRC 0x80 //set in I2Cdst to disable that CRC generation
                          //for nodes that do not support CRCs

I2CnodeErr I2CnodeWrite(I2CnodeInstance *instance, uint16 I2Cdst, 
                              byte *buffer, unsigned length);


//return the number of bytes successfully transferred before last I2CnodeNACK
unsigned I2CnodeBytesXferred(I2CnodeInstance *instance);


// call once per Timer tic (TimerHZ/sec)
void I2CnodeTic (I2CnodeInstance *instance);

// call when dwarf CPLD's I2C clock counter decriments to zero
void I2CnodeCkHeld (I2CnodeInstance *instance);


static inline void connectI2C (void)
{
  set8 (P3SEL, I2Cclock|I2Cdata);  //allow controller to drive SDA & SCL
}

// when an interesting I2C clock (rising) edge occurs
static inline void I2CnodeCkEdge (I2CnodeInstance *instance)
{
  connectI2C();  //reattach controller, may signal STOP
  clear8 (DWRFIRQIE, DWRFI2CSCL);
}


// resume processing I2C messages via interrupt
// if they had been held due to a nearly full serial output fifo
// call with interrupts disabled
#define I2CnodeOutResume(instance)

// resume processing I2C messages via interrupt
// if they had been held due to an empty serial input fifo
// call with interrupts disabled
#define I2CnodeInResume(instance)


#endif
