/*********************************  I2Cgate.c  *******************************
 * $Source: /home/cvs/ESP/gen2/software/msp430/lib/common/I2Cgate.c,v $
 *  Copyright (C) 2003 MBARI
 *
 *  MBARI Proprietary Information. All rights reserved.
 * $Id: I2Cgate.c,v 1.48 2005/01/15 01:29:38 brent Exp $
 *
 * I2C gateway message parsing and processing
 *    in a "sort of" object oriented fashion
 *
 * Theory of operation:
 *
 *  The primary design constraint is to conserve RAM and minimize latency
 *  through the gateway by eliminating copying.  To that end...
 *
 *  The main thread parses bytes in the serial input fifo into commands.
 *  Commands are not copied from the fifo into another (linear) buffer.
 *  To minimize latency, if the command is an encapsulated I2C message 
 *  to send, its transmission is begun as soon as the destination of message 
 *  is received.  This implies that the I2C bus will stall if the
 *  mechanism filling the input fifo does not keep up during the sending
 *  of the message body.  In practice, the input baud rate is typically 
 *  115200 while the I2C bus runs at 100kbs, so
 *  stalls should not happen often, and, if they occur, I2C's ability
 *  to stop its clock will insure against data loss.
 *  
 *  I2C bus traffic is received directly into the serial output fifo.
 *  Internal command responses are also written directly to this fifo.
 *  Internal command responses are delayed while an I2C message is 
 *  being received to prevent them from "interleaving".
 *  Unfortunately, this may cause the gateway to appear to "hang"
 *  if the I2C bus stalls for a significant amount of time.
 *
 *  The main worker thread can be thought of as a recusive decent parser.
 *  When a message "structure" is recognized, it is reduced to either
 *  a local action or fragment of I2C bus traffic.  External interrupts
 *  fill the input fifo and empty the output fifo.  Interrupts processed
 *  here append I2C-bus messages received onto the output fifo.
 *  The main thread blocks while this occurs.  Otherwise, it copies
 *  bytes from the input fifo to the I2C-bus when appropriate.
 *
 * Known Bugs:
 *
 *  This implmentation assumes that bus arbitration occurs during the address
 *  byte only.  This is true only if all nodes have unique addresses.
 *  If arbitration is lost while sending the body of a message, this master
 *  will drop that incoming message.  If all nodes have unique addresses,
 *  the only sensible case where this could occur is for general calls.
 *  Avoid situations where two nodes might generate a general call at
 *  the same time.
 *
 *  Also, there is no way to set up the controller to ignore general calls.
 *  If the gateway is configured to ignore them, the controller is simply
 *  reset as soon as it produces the GC interrupt.  This usually works, but
 *  may fail on messages less than two bytes long because the controller
 *  can receive the whole message without service from the MPU.
 *  In which case, the sender receives no NACK to indicate that the
 *  message was dropped, even if all receivers ignore it.
 *  This race condition can be avoided by always sending >2 byte long
 *  general calls and/or configuring at least one node to receive them.
 *
 *  If I2C interrupts are masked for more than one byte time, interpreting
 *  of I2CIFG register may become ambiguous as multiple events may have
 *  occurred and their relative order is unknown.  The is especially
 *  troublesome when a slave receives a short messaages as the controller
 *  can process one, set ARDYIFG or STTIFG and move on to the next.
 *
 *****************************************************************************/

#include "string.h"     //for strlen()
#include "dwarf.h"      //register and I/O definitions
#include "interrupts.h" //support for nested interrupts
#include "recode7.h"    //recoding 8-bit data in 7-bit streams
#include "crc8.h"       //crc byte support

#define BUILDING_I2CGATE
#include "I2Cgate.h"

// Macros & Constants

#define WELCOME " I2C Gateway v2.85"

#define PROTOCOL_VERSION (1)    //initial version of the I2C gateway protocol
        //lacks support for hostDelay

#define TICDELAY     ((TIMERHZ+2)/4)  //quarter second is minimum bus timeout
#define FLOWDELAY    ((TIMERHZ+16)/32)//1/32 for CRCNACKing slave flow control
#define HUNGDELAY    ((TIMERHZ+16)/32)//1/32 second between reset retries
#define INITIALDELAY ((TIMERHZ+4)/8)  //wait >eighth sec if lost track of master

#define PADbyte  (0xff)           //byte value for pad space
#define CRCACK   (CRC8seed^0xff)  //response byte to acknowledge a CRC message
#define CRCflow  (0x0)            //response to CRC message if slave wants bus

#define I2Ccmd  I2CSSEL_2 //I2C clock selection constant

#define DRBdepth        5         //max depth of the receive fifo (I2CIRB)

//In slave mode, I2C events will be lost if interrupts are disabled.
//To allow other tasks to access the host output fifo, I2CgateHold "diverts"
//the slave mode output to this fifo.  I2CgateRelease appends any
//diverted output to the main output fifo.

static FIFO (divertedOutput, DRBdepth+25);
static fifo *outFIFO;  //points to instance->parser.output or divertedOutput

#define outFIFOnearlyFull() (FIFOfree (outFIFO)<=DRBdepth+8)


//these just save typing the instance parameter over and over
#define putByte(byte)   parserPutByte(&instance->parser,(byte))
#define putWord(word)   parserPutWord(&instance->parser,(word))
#define getByte()       parserGetByte(&instance->parser)
#define getByteAhead()  parserGetByteAhead(&instance->parser, &cursor)
#define getWord()       parserGetWord(&instance->parser)
#define put8(byte)      FIFOwriteByteW(outFIFO,(byte))
#define put16(word)     FIFOwrite16(outFIFO, (word))
#define kick()          parserKickstart(&instance->parser)
#define pollIn()        parserPoll(&instance->parser)
#define readByteAhead() FIFOreadByteAhead (from, &cursor)


#define putString(s)        parserPutString(&instance->parser,(s))
#define putBlock(src, len)  parserPutBlock(&instance->parser, (src), (len))
#define skipInput(len)      parserSkipInput(&instance->parser, (len))
#define hold()              I2CgateHold(instance)
#define release()           I2CgateRelease(instance)

#define notifyOnHostRead()  (I2Cgate.busNACK & 0x80)
#define quickCRC()          (I2Cgate.masterNACK & 0x80)
#define suppressProbe()     (I2Cgate.adrNACK & 0x80)

//removes the 7-bit encoding on received messages (for debugging raw output)
//#undef put7
//#define put7(byte) put8(byte)


struct I2CgateConfig I2Cgate; //configuration received from host

// DATA -- dynamic processing state
static enum { //sundry internal state flags
  CRCready            = 0x1,  //CRCvalue contains a valid CRC response
  arbTimeout          = 0x2,  //arbTimer reached zero
  selected            = 0x4,  //selected
  enterLimboOnAL      = 0x8,  //enter limbo state of arbitration lost
  inTic               = 0x10, //prevents reentry into I2CgateTic
} flags;

static byte hostErr;    //most recent host (USART) serial data error mask

static byte matchedAdr;         //address by which we were last selected

static byte retriesRemaining;   //# of retries remaining

static byte I2CDRbytes;         //# of bytes loaded to/waiting in I2CDRB

static byte masterResult; //reason why master xfer terminated (interrupt mask)
static byte rcvIE;  //interrupt mask after next byte is received from host
static byte xmtIE;  //interrupt mask after next byte is sent to host

static byte hostResponseLen;    //# of bytes in cached hostResponse

static byte hostResponse[64];  //1st byte of hostResponse is its length
// this implementation requires sizeof(hostResponse) < 127

static byte *hostResponseEnd;
//always points just past valid hostResponse
// so that interrupt context can pass it through to an I2C master as
// it's being updated from the host without waiting for the whole thing.

static byte *responseCursor; //points to next byte to send in current response

static uint16 byteCount;            //# of message bytes transferred/to xfer
static uint16 Leftovers, BitsUnsent, BitsUnread; //for get/put7 in interrupts

static byte *cursor;  //so we can reread a message if an error occurs

#define CRCvalue (*hostResponse)   //CRC-8 accumulator

static uint16 busTics;    //I2Cgate.timeout in tics
static uint16 busTimer;   //countdown to bus command timeout (in tics)
static uint16 arbTimer;   //countdown to bus arbitration timeout


// I2C bus interupt handler utilities and macros

//current state of I2C interrupt processing
#define I2Cstate (*instance->I2CisrState)

// assign new slave interrupt processing state and I2C interrupt mask
#define enterSlaveState(newState,intMask) \
  (I2Cstate = (newState), I2CIE = (intMask))


//write the begining of a read response back to host
static inline void putLen (I2CgateInstance *instance, unsigned length)
{
  if (length < 126)
    put8 (length+0x82);
  else {
    put8(0xff);
    put16(length+2);
  }
  pollIn();     //don't drop input bytes...
  put8(0x88);
  put8(I2CSA);  //indicate the message source (in place of dest)
  pollIn();     //don't drop input bytes...
}

    
static void ignoreisr (I2CgateInstance *instance)
/*
  Null interrupt service function to ignore the event
  or as a placeholder
*/
{
}


static inline void holdSCL(void)
/*
  hold SCL low on its next falling edge
*/
{   
  clear8 (DWRFI2COUT, DWRFI2CHOLD|DWRFI2CINC);
}

static inline void releaseSCL(void)
/*
  reset the CPLD's I2C clock counter low to stop counting bits
*/
{   
  set8 (DWRFI2COUT, DWRFI2CHOLD|DWRFI2CINC);
}


static void releaseSlave (I2CgateInstance *instance)
/*
  Release bus in slave mode (by releasing SCL)
  when the output buffer is less full
*/
{
  if (outFIFOnearlyFull())
    I2CgateDefer.xmtISR = releaseSlave;
  else
    releaseSCL();
}


static void throttle (void)
/*
  try to stop bus in slave mode (by holding SCL low)
  when the output buffer is nearly full
*/
{
  if (outFIFOnearlyFull()) {
    holdSCL();
    xmtIE = I2CIE;
    I2CgateDefer.xmtISR = releaseSlave;
  }
}


static inline void unthrottle (void)
/*
  give up trying throttle the bus when master ends
  the transaction to spite our efforts to stop it
*/
{
  releaseSCL();
  I2CgateDefer.xmtISR = NULL;
}


static void deferXmtISR (I2Cisr *isr)
/*
  resume Master Mode processing by calling isr from I2CgateOutResume
  when there's more room in the output buffer
*/
{
  xmtIE = I2CIE, I2CIE &= ALIE|NACKIE;
  I2CgateDefer.xmtISR = isr;
}


static inline void deferRcvISR (I2Cisr *isr)
/*
  resume processing by calling isr from I2CgateInResume
  when there's more data received from the host
*/
{
  rcvIE = I2CIE, I2CIE &= ~TXRDYIE;
  I2CgateDefer.rcvISR = isr;
}


// I2C Interrupt processing states

extern I2Cisr USART0_defaultISR;
static I2Cisr TXRDYwrite, TXRDYwriteN, TXRDYwrite1, TXRDYlongWrt, TXRDYlimbo;
static I2Cisr TXRDYwriteCRC, TXRDYlongWrtCRC, TXRDYidle;
static I2Cisr RXRDYwrite, RXRDYread, RXRDYread1st, RXRDYreadAck;
static I2Cisr ARDYread, ARDYread0, ARDYwrite, ARDYwriteCRC, ARDYreadAck;
static I2Cisr ALmaster, ALerror, NACKmaster, NACKCRC;
static I2Cisr OAselect, OAreselect, OAreceived, OAmaster, OAresponded;
static I2Cisr GCselect, GCreselect, GCreceived, GCmaster;
static I2Cisr RXRDYreceive, RXRDYreceiving, RXRDYidle;
static I2Cisr TXRDYrespond, TXRDYresponding, TXRDYpadding, TXRDYreceived;
static I2Cisr ARDYreceived, ARDYresponded, ARDYwrite0, ARDYprobe;
static I2Cisr STTselecting, STTreceived, STTidle, STTlimbo;

//aliases to avoid repeating function definitions
#define STTresponded STTidle
#define NACKresponded STTidle
#define RXRDYresponded RXRDYidle

//selected by master
static I2Cisr * const selectI2Cstate[] = {
 USART0_defaultISR,
 ignoreisr,     //slaves don't do arbitration
 ignoreisr,     //can't have NACK yet
 OAreselect,    //repeated specific call
 ignoreisr,     //placeholder -- we must process STT, RXRDY, or TXRDY first
 RXRDYreceive,  //receive first byte from master
 TXRDYrespond,  //send first byte to master
 GCreselect,    //repeated general call
 STTselecting   //handle repeated start before first data byte
};
#define selectIE (ALIE|OAIE|RXRDYIE|TXRDYIE|GCIE|STTIE)
   
//receiving from master
static I2Cisr * const receiveI2Cstate[] = {
 USART0_defaultISR,
 ignoreisr,     //slaves don't do arbitration
 ignoreisr,     //no ACK expected while receiving
 OAreceived,    //repeated specific call
 ARDYreceived,  //transaction complete -- transition back to idle
 RXRDYreceiving,//receive next byte
 TXRDYreceived, //send first byte -- transition to responding
 GCreceived,    //repeated general call
 STTreceived    //repeated start in receive state
};
#define receiveIE (ALIE|OAIE|ARDYIE|RXRDYIE|TXRDYIE|GCIE|STTIE)


//responding to master
static I2Cisr * const respondI2Cstate[] = {
 USART0_defaultISR,
 ignoreisr,      //slaves don't do arbitration
 NACKresponded,  //NACKs from MASTER on our response -- transition back to idle
 OAresponded,    //repeated specific call -- transition back to select
 ARDYresponded,  //transaction complete -- transition back to idle
 RXRDYresponded, //receive first byte -- transition to receiving
 TXRDYresponding,//send another byte to master
 GCselect,       //repeated general call -- transition back to select
 STTresponded    //repeated start in respond state -- back to idle
};
//sending pad bytes after master has read our complete response
static I2Cisr * const paddingI2Cstate[] = {
 USART0_defaultISR,
 ignoreisr,      //slaves don't do arbitration
 NACKresponded,  //NACKs from MASTER on our response -- transition back to idle
 OAresponded,    //repeated specific call -- transition back to select
 ARDYresponded,  //transaction complete -- transition back to idle
 RXRDYresponded, //receive first byte -- transition to receiving
 TXRDYpadding,  //send another pad byte to master
 GCselect,       //repeated general call -- transition back to select
 STTresponded    //repeated start in respond state -- back to idle
};
#define respondIE (ALIE|NACKIE|OAIE|ARDYIE|RXRDYIE|TXRDYIE|GCIE|STTIE)

//dropping message where abritration was lost AFTER sending the address byte
static I2Cisr * const limboI2Cstate[] = {
 USART0_defaultISR,
 ignoreisr,      //lost arbitration (shouldn't happen)
 ignoreisr,      //missing ACK from slave (?)
 OAresponded,    //got slave address -- transition to selected
 ARDYresponded,  //transaction complete -- transition back to idle
 RXRDYwrite,     //throw byte away
 TXRDYlimbo,     //we're lost, just throw master a fish
 GCselect,       //got general call -- transition to selected
 STTlimbo        //repeated start -- NACK CRC and return to idle
};
#define limboIE  respondIE

//controller will not reset!
//must be just before the initialI2Cstate
static I2Cisr * const hungI2Cstate[] = {
  USART0_defaultISR
};
#define hungIE  (0)

//awaiting own address or general call before syncing to the bus
//must be just before the idleI2Cstate
static I2Cisr * const initialI2Cstate[] = {
 USART0_defaultISR,
 ignoreisr,    //lost arbitration (shouldn't happen)
 ignoreisr,    //missing ACK from slave (?)
 OAselect,     //got 1st slave address -- transition to selected
 ignoreisr,    //shouldn't complete operation without first seeing OA or GC
 RXRDYidle,    //error unless GCIFG
 TXRDYidle,    //error unless GCIFG
 GCselect,     //got 1st general call -- transition to selected
 STTidle       //initial bus synchronization complete
};
#define initialIE  (ALIE|OAIE|GCIE|TXRDYIE|RXRDYIFG|STTIE)

//awaiting own address or general call
static I2Cisr * const idleI2Cstate[] = {
 USART0_defaultISR,
 ignoreisr,    //lost arbitration (shouldn't happen)
 ignoreisr,    //missing ACK from slave (?)
 OAselect,     //got slave address -- transition to selected
 ignoreisr,    //shouldn't complete operation without first seeing OA or GC
 RXRDYidle,    //error unless GCIFG
 TXRDYidle,    //error unless GCIFG
 GCselect      //got general call -- transition to selected
};
#define idleIE  (ALIE|OAIE|TXRDYIE|RXRDYIFG|GCIE)

//idleI2Cstate must be the last slave state defined
//all the states past here are master mode states


//writing uncounted 7-bit encoded message
static I2Cisr * const writeI2Cstate[] = {
 USART0_defaultISR,
 ALmaster,     //lost arbitration while master
 NACKmaster,   //missing ACK from slave
 OAmaster,     //we selected ourselves
 ARDYwrite,    //master write transaction complete
 RXRDYwrite,   //we're talking to ourselves
 TXRDYwrite,   //master ready to transmit a byte
 GCmaster      //we selected ourselves
};
//writing short message to a slave using 8-bit (counted) encoding
static I2Cisr * const shortWriteI2Cstate[] = {
 USART0_defaultISR,
 ALmaster,     //lost arbitration while master
 NACKmaster,   //missing ACK from slave
 OAmaster,     //we selected ourselves
 ARDYwrite,    //master write transaction complete
 RXRDYwrite,   //master received a byte while writing (?)
 TXRDYwriteN,  //master ready to transmit a byte
 GCmaster      //we selected ourselves
};
//writing short CRC message to a slave using 8-bit (counted) encoding
static I2Cisr * const crcWriteI2Cstate[] = {
 USART0_defaultISR,
 ALmaster,     //lost arbitration while master
 NACKmaster,   //missing ACK from slave
 OAmaster,     //we selected ourselves
 ARDYwriteCRC, //master write transaction complete
 RXRDYwrite,   //master received a byte while writing (?)
 TXRDYwriteCRC,//master ready to transmit a byte
 GCmaster      //we selected ourselves
};
//broadcasting CRC message to a slaves using 8-bit (counted) encoding
static I2Cisr * const crcGCwriteI2Cstate[] = {
 USART0_defaultISR,
 ALmaster,     //lost arbitration while master
 NACKmaster,   //missing ACK from slave
 OAmaster,     //we selected ourselves
 ARDYwrite,    //master write transaction complete
 RXRDYwrite,   //master received a byte while writing (?)
 TXRDYwriteCRC,//master ready to transmit a byte
 GCmaster      //we selected ourselves
};
//writing long CRC message to a slave using 8-bit encoding
static I2Cisr * const longCrcWriteI2Cstate[] = {
 USART0_defaultISR,
 ALmaster,     //lost arbitration while master
 NACKmaster,   //missing ACK from slave
 OAmaster,     //we selected ourselves
 ARDYwrite,    //master write transaction complete
 RXRDYwrite,   //master received a byte while writing (?)
 TXRDYlongWrtCRC, //master ready to transmit a byte
 GCmaster      //we selected ourselves
};
//write single 8-bit value to slave 
static I2Cisr * const write1I2Cstate[] = {
 USART0_defaultISR,
 ALmaster,     //lost arbitration while master
 NACKmaster,   //missing ACK from slave
 OAmaster,     //we selected ourselves
 ARDYwrite,    //master write transaction complete
 RXRDYwrite,   //master received a byte while writing (?)
 TXRDYwrite1,  //master ready to transmit the byte
 GCmaster      //we selected ourselves
};
//write null message to slave 
static I2Cisr * const writeNullI2Cstate[] = {
 USART0_defaultISR,
 ALmaster,     //lost arbitration while master
 NACKmaster,   //missing ACK from slave
 OAmaster,     //we selected ourselves
 ARDYwrite0,   //null master write transaction complete
 RXRDYwrite,   //master received a byte while writing (?)
 TXRDYwrite1,  //master ready to transmit the byte
 GCmaster      //we selected ourselves
};
//writing to a slave using 8-bit (counted) encoding
static I2Cisr * const longWriteI2Cstate[] = {
 USART0_defaultISR,
 ALmaster,     //lost arbitration while master
 NACKmaster,   //missing ACK from slave
 OAmaster,     //we selected ourselves
 ARDYwrite,    //master write transaction complete
 RXRDYwrite,   //master received a byte while writing (?)
 TXRDYlongWrt, //master ready to transmit a byte
 GCmaster      //we selected ourselves
};
//awaiting ARDY after null read to probe a proposed address
static I2Cisr * const probeI2Cstate[] = {
 USART0_defaultISR,
 ALmaster,     //lost arbitration while master
 NACKmaster,   //missing ACK from slave
 OAmaster,     //we selected ourselves
 ARDYprobe,    //master probe transaction complete
 RXRDYwrite,   ///master received a byte while writing (?)
 TXRDYlimbo,   //master ready to transmit while reading??
 GCmaster      //we selected ourselves
};
//awaiting only byte in slave's msg ACK response 
static I2Cisr * const readAckI2Cstate[] = {
 USART0_defaultISR,
 ALerror,      //lost arbitration when it shouldn't happen
 NACKCRC,      //missing CRC ACK from slave
 OAmaster,     //we selected ourselves
 ARDYreadAck,  //master CRC read transaction complete
 RXRDYreadAck, //master received CRC ack byte from slave
 TXRDYlimbo,   //master ready to transmit while reading??
 GCmaster      //we selected ourselves
};
//awaiting 1st byte in slave's response 
static I2Cisr * const read1stI2Cstate[] = {
 USART0_defaultISR,
 ALmaster,     //lost arbitration while master
 NACKmaster,   //missing ACK from slave
 OAmaster,     //we selected ourselves
 ARDYread,     //master read transaction complete
 RXRDYread1st, //master received 1st byte
 TXRDYlimbo,   //master ready to transmit while reading??
 GCmaster      //we selected ourselves
};
//awaiting subsequent bytes in slave's response
static I2Cisr * const readI2Cstate[] = {
 USART0_defaultISR,
 ALerror,      //lost arbitration at a time when it shouldn't happen
 NACKmaster,   //missing ACK from slave
 OAmaster,     //we selected ourselves
 ARDYread,     //master read transaction complete
 RXRDYread,    //master received next byte
 TXRDYlimbo,   //master ready to transmit while reading??
 GCmaster      //we selected ourselves
};
#define masterIE (ALIE|NACKIE|OAIE|ARDYIE|RXRDYIE|TXRDYIE|GCIE) 


static void getBlockI (I2CgateInstance *instance, byte **dst, unsigned len)
/*
  like parserGetBlock but...
  input len bytes to *dst while synchronizing with interrupt context
*/
{
  if (len) {
    fifo *from = instance->parser.input;
    do {
      unsigned chunkLen;
      if (!(chunkLen = FIFOpeek (from))) {
        unsigned oldSR = _BIC_SR (GIE);  //ensure interrupts are disabled
retest:
        if (!(chunkLen = FIFOpeek (from))) {
          AWAITEV();
          _DINT();
          goto retest;
        }
        _BIS_SR (oldSR);    //restore GIE state
      }
      if (chunkLen > len) chunkLen = len;
      FIFOreadChunk (from, *dst, chunkLen);
      *dst += chunkLen;
//TXRDYIFG is enabled in all states, so testing I2Cstate here is unnessesary
      set8 (I2CIE, TXRDYIE);  //resume passing thru response to master
      len -= chunkLen;
    } while (len);
  }
}

// Various ways to beat a confused I2C controller into submission

static inline void cancelI2C (void)
// cancel master operation in progress.  leaves controller disabled
{
  I2CTCTL = I2Ccmd;
  clear8 (U0CTL, I2CEN);
  U0CTL = I2C | SYNC | LISTEN; 
    //Looping back the data may help keep controller from hanging if it is
    //resumed while the bus is busy
}


static inline void resumeI2Cslave (void)
// resume normal master operations after a cancelI2C
{
  U0CTL = I2C | SYNC | I2CEN;  //finally, re-enable I2C on USART0
  I2CIFG = 0;
}


static inline void resumeI2C (void)
// resume all normal operations after a cancelI2C
{
  releaseSCL();
  resumeI2Cslave();
}


static void abortCmd (void)
{
  releaseSCL();
  clear8 (DWRFIRQIE, DWRFI2CSCL);
  I2CgateDefer.rcvISR = NULL; //always cancel deferred ISRs
  I2CgateDefer.xmtISR = NULL;
  disableDWRFirqs (DWRFI2CHELD);    //just in case we were awaiting countdown
}


//cancel all pending and future I2C interrupts
//global interrupts must be disabled on entry
static void endI2Cinterrupts (void)
{
  I2CIFG = I2CIE = 0;
  abortCmd();
}


//try anything to get the controller to reset
//enter hungI2Cstate if all else fails
//Also updates I2COA with the currently configured I2C address
//must be called with global interrupts enabled
static boolean coreI2Creset (I2CgateInstance *instance)
{
  I2CIE = 0;
  busTimer = 0;  //stop processing timeouts
  _EINT();
  flags &= inTic|arbTimeout;
//putByte ('R');
  cancelI2C();
  signalEV();
  I2COA = (unsigned)I2Cgate.address & 0x7f;
  if (I2CDCTL & I2CBUSY || I2CTCTL != I2Ccmd) { //controller is hung
    I2CNDAT = 1;
//putByte ('H');
    clear8 (P3SEL, I2Cclock|I2Cdata);  //disconnect controller from the bus
    resumeI2C();  //this time, let the command complete...
    set8 (U0CTL, MST | LISTEN);
    I2CTCTL = I2Ccmd | I2CTRX | I2CSTT;
    I2Cstate = hungI2Cstate;  //enter hung state
    busTimer = HUNGDELAY;     // and reset again later
    if (TXRDYIFG) I2CDRB = PADbyte;
    return false;
  }
  resumeI2C();
  return true;
}



static void resetI2C (I2CgateInstance *instance)
{
  if (coreI2Creset (instance)) {
    enterSlaveState (idleI2Cstate, idleIE);
    busTimer = busTics;
    _EINT();
  }
}


static void resetI2CafterNACK (I2CgateInstance *instance)
{
  if (coreI2Creset (instance)) {
    enterSlaveState (initialI2Cstate, initialIE);
    busTimer = 2;
    _EINT();
  }
}


static void initialI2Creset (I2CgateInstance *instance)
{
  if (I2Cstate == hungI2Cstate) cancelI2C();
  connectI2C();
  if (coreI2Creset (instance)) {
    enterSlaveState (initialI2Cstate, initialIE);
    busTimer = (MCLKnow & (TIMERHZ/2-1)) + INITIALDELAY;  //random backoff
    _EINT();
  }       
}


static void restartI2C (I2CgateInstance *instance)
{
  cancelI2C();
  if (I2CDCTL & I2CBUSY || I2CTCTL != I2Ccmd) //controller is hung
    resetI2CafterNACK(instance);
  else {
    enterSlaveState (idleI2Cstate, idleIE);
    resumeI2C();
  }
}


//interrupts must be disabled
static inline void startArbTimer (void)
{
  uint16 tics = busTics;
  arbTimer = tics + (tics >> 2);  //25% longer than busTics
  flags &= ~arbTimeout;
}

//start counting down for bus arbitration timeout if bus already synchronized
static inline void restartArbTimer (void)
{
  _DINT();
  startArbTimer();
  _EINT();
}


//quickly and quietly reset in slave mode
static inline void resyncSlave (I2CgateInstance *instance) 
{
  restartI2C(instance);
}


/***************** I2C Slave processing ************/


//reenter idle state and signal background
#define enterIdle() {enterSlaveState(idleI2Cstate,idleIE); signalEV();}


//inform host of a null message written to us (requires 3 bytes output free)
static void putNullWrite(I2CgateInstance *instance)
{
  if (!(I2Cgate.slaveNACK & 0x80)) { //null write reporting not suppressed...
    put8((unsigned)matchedAdr); put8(0x80);  //report zero length write
    {  //with optional message status
      unsigned ok = I2Cgate.OK;
      if (ok > 0x80) put8 (ok - 0x80);
    }
    kick();
  }
}


static inline void beSelected (void)
/*
  every select is preceeded by a start, but we don't see it until
  later because STTIFG is lowest priority.  So, clear STTIFG
  if it's coincident with select so that it is not mistaken
  for a restart condition in the current message.
*/
{
  clear8 (I2CIFG, STTIFG);
  flags |= selected;
  busTimer = busTics;
}


//enter select state after matching slave adr
static void slaveSelected (I2CgateInstance *instance)
{
  if (!matchedAdr && I2Cgate.address & 0x80) 
    resyncSlave (instance);
  else{
    beSelected();
    if (!(I2CIFG & TXRDYIFG))  //if master wants to send us something...
      throttle();              //stop SCL if out buffer almost full
    enterSlaveState (selectI2Cstate, selectIE);
  }
}


//matched slave adr (by RESTART) in selected state
static void slaveReselected (I2CgateInstance *instance)
{
  beSelected();
  if (!(I2CIFG & TXRDYIFG)) {
    putNullWrite (instance);
    throttle();
  }
}  


//process end of received message that ended with a STOP condition
//needs at least 4 bytes free in output fifo
static void msgRcvd (I2CgateInstance *instance, unsigned ok)
{
  uint16 leftovers = Leftovers, bitsUnsent = BitsUnsent;
  put7 (put8, BitsUnread);
  pollIn();
  put7end (put8);
  if (ok > 0x80) put8 (ok - 0x80);
  kick();
}


//process end of received message that ended with a STOP condition
static void inline nonCRCmsgRcvd (I2CgateInstance *instance)
{
  msgRcvd (instance, I2Cgate.OK);
  flags &= ~CRCready;   //CRC response is no longer valid
}

//process end of received message that should be rejected by the host
static void inline badMsgRcvd (I2CgateInstance *instance)
{
  msgRcvd (instance, I2Cgate.busNACK);
}


//process end of received message that ended without a STOP condition
//needs at least 3 bytes free in output fifo
static void crcMsgRcvd (I2CgateInstance *instance)
{
//clear8 (P5OUT, 0x40);
//set8 (P5OUT, 0x40);
  {
    uint16 leftovers = Leftovers;
    if (!((I2Cgate.CRCOK | I2Cgate.CRCNACK) & 0x80)) {
      uint16 bitsUnsent = BitsUnsent;
      pollIn();
      put7 (put8, BitsUnread); //output crc received as last byte if no msg stat
    }
    put7end (put8);  //finish message
  }
  pollIn();
  {   //return CRC result to host and calculate message ACK byte
    unsigned crcstat = I2Cgate.CRCOK;
    unsigned crcRemainder = CRCvalue;
    CRCvalue = CRCACK;
    if (crcRemainder ^ BitsUnread) {
      CRCvalue = ~CRCACK;  //unlikely to be morphed into ~CRCACK by noise
      crcstat = I2Cgate.CRCNACK;
    }
    if (crcstat > 0x80) put8 (crcstat - 0x80);
  }
  flags |= CRCready;   //CRC response is ready
  kick();
}


//Own Address Match in selected state
static void OAreselect (I2CgateInstance *instance)
{
  matchedAdr = I2COA;
  slaveReselected(instance);
}

//General Call address match in selected state
static void GCreselect(I2CgateInstance *instance)
{
  matchedAdr = 0;
  slaveReselected(instance);
}


//received our first START condition.  Now we can access the bus
static void STTidle (I2CgateInstance *instance)
{
  enterIdle();        //exit initialI2Cstate
}


//Own Address Match in idle state -- transition to selected state
static void OAselect (I2CgateInstance *instance)
{
  matchedAdr = I2COA;
  slaveSelected(instance);
}

//General Call address match in idle state -- transition to selected state
static void GCselect(I2CgateInstance *instance)
{
  matchedAdr = 0;
  slaveSelected(instance);
}


static void STTselecting (I2CgateInstance *instance)
/*
  A null write message won't be terminated by ARDYIFG, even
  if it ends with a STOP condition.  If the next message
  is to a different node, all we'll see it the STTIFG...
*/
{
  putNullWrite (instance);  //report null write
  enterIdle();
}


//null messages don't generate an ARDY interrupt even after a STOP
//invoke this frequently (w/interrupts disabled) to check for end of null msgs
#define pollSelect(state) \
  if ((state)==selectI2Cstate && I2CIE&STTIE && !(I2CDCTL&I2CBB)) STTselecting (instance)


//We suddenly switched to responding intervening select
//This happens if we missed an OAIFG interrupt earlier
static void TXRDYreceived (I2CgateInstance *instance)
{  //the receive fifo was cleared by the START condition -- we're lost
  resyncSlave (instance);  
  badMsgRcvd (instance);   //report bad message to host
}

  
//add all remaining bytes from I2CDRB to the output fifo
//assumes RXRDYIFG set and >= DRBsize bytes free in the output fifo
static void inline flushRcvrCore (I2CgateInstance *instance)
{
  uint16 leftovers = Leftovers, bitsUnsent = BitsUnsent;
  unsigned count = DRBdepth;
  for (;;) {
    unsigned next = I2CDRB;
    CRC8next (CRCvalue, BitsUnread);
    put7 (put8, BitsUnread);
    pollIn();
    BitsUnread = next;
    if (!(I2CIFG & RXRDYIFG)) break;
    if (!--count) {
      TXRDYreceived (instance);   //report partial message to host
      return;
    }
  }
  Leftovers = leftovers; BitsUnsent = bitsUnsent;
}


//add all remaining bytes from I2CDRB to the output fifo
//assumes >= DRBsize bytes free in output fifo
#macro flushRcvr(instance)
{
  if (I2CIFG & RXRDYIFG) flushRcvrCore(instance);
}
#endm


//Received byte in select state -- process first byte & enter receive
static void RXRDYreceive (I2CgateInstance *instance)
{
  BitsUnread = I2CDRB;
  CRC8init (CRCvalue);  CRC8next (CRCvalue, (unsigned)matchedAdr);
  put7initFast(Leftovers, BitsUnsent);   //init the globals
  put8 ((unsigned)matchedAdr);
  enterSlaveState (receiveI2Cstate, receiveIE);
  kick();
  flags &= ~CRCready;   //indicate that CRCvalue is being computed
}


//Received next byte in receive state
static void RXRDYreceiving (I2CgateInstance *instance)
{
  flushRcvrCore(instance);
  throttle();
  kick();
}


//Send a one bit CRC ACK if quickCRC's are configured
#macro ackQuickCRC(crcRemainder)
  if (quickCRC()) {                   //CRC checking w/single bit ACK enabled?
    set8 (P3DIR, I2Cclock|I2Cdata);   //drive SDA to signal message ACK
    clear8 (P3SEL, I2Cclock);   //drive just SCL low
    if ((crcRemainder)
//           || !(MCLKnow & 0xf)  //if uncommented, produces sporatic errors testing
    ) clear8 (P3DIR, I2Cdata);  //SDA will float when its SEL bit cleared below      
    //send single bit ACK or NACK to sender if msg ended in repeated start
    clear8 (P3SEL, I2Cdata);    //drive SDA low to signal ACK, float for NACK
    clear8 (DWRFIRQIFG, DWRFI2CSCL);
    set8 (DWRFIRQIE, DWRFI2CSCL); //interrupt on next SCL rising edge

    //rev B silicon is confused our CRC ACK bit -- so, reset again
    clear8 (U0CTL, I2CEN);
    U0CTL = I2C | SYNC; 
    U0CTL = I2C | SYNC | I2CEN;  //finally, re-enable I2C on USART0

    clear8 (P3DIR, I2Cclock);     //release SCL
  }  //SCL will rise after the master reads SDA
#endm


//Process message that ended with a REPEATED START condition
//but not followed immediately by another address
static void STTreceived (I2CgateInstance *instance)
{
  unthrottle();
  ackQuickCRC(CRCvalue ^ BitsUnread);
  enterIdle();
  crcMsgRcvd (instance);
}  


//Process message that ended with a REPEATED START condition
//but not followed immediately by another address if we're in limbo state
static void STTlimbo (I2CgateInstance *instance)
{
  unthrottle();
  ackQuickCRC(true);  //force a CRC NACK
  enterIdle();
}  


//Access Ready in receive state -- finish non-CRC message & enter idle
static void ARDYreceived (I2CgateInstance *instance)
{
  byte ifg = I2CIFG;
  unthrottle();
  enterIdle();
  flushRcvr(instance);
  if (ifg & STTIFG) { //sometimes a START will generate a spurious ARDYIFG!
    ackQuickCRC(CRCvalue ^ BitsUnread);    //if so, ignore the ARDYIFG
    crcMsgRcvd(instance); //& process msg as though it ended w/REPEATED START
  }else
    nonCRCmsgRcvd(instance);
}


//Own Address in receive state -- finish message & enter selected
static void OAreceived (I2CgateInstance *instance)
{
  byte ifg = I2CIFG;
  matchedAdr = I2COA;
  slaveSelected (instance);
  flushRcvr(instance);
  if (ifg & ARDYIFG)
    nonCRCmsgRcvd (instance);
  else
    crcMsgRcvd (instance);   //never saw STOP, so it's a CRC message
}


//General Call in receive state -- finish message & enter selected
static void GCreceived (I2CgateInstance *instance)
{
  matchedAdr = 0;
  slaveSelected (instance);
  crcMsgRcvd (instance);  //we would not be in this state if we'd seen a STOP
}  


//Transmitter Ready in respond state -- send next byte of response
static void TXRDYresponding (I2CgateInstance *instance)
{
  if (responseCursor < hostResponseEnd)
    I2CDRB = *responseCursor, responseCursor++;
  else if (hostResponseEnd > hostResponse+hostResponseLen) {
    I2Cstate = paddingI2Cstate;  //start padding past the end of response
    TXRDYpadding (instance);
  }else //wait for host to finish updating our response
    clear8 (I2CIE, TXRDYIE);
}


//Transmitter Ready in padding state -- send next pad byte of response
static void TXRDYpadding (I2CgateInstance *instance)
{
  unsigned count = DRBdepth;
  for (;;) {
    I2CDRB = PADbyte;
    if (!(I2CIFG & TXRDYIFG)) break;
    pollIn();
    if (!--count) {
      resyncSlave (instance);   //give up if controller appears to be hung
      return;
    }
  }
}


//Transmitter Ready in select state -- start response & enter respond
static void TXRDYrespond (I2CgateInstance *instance)
{
  enterSlaveState (respondI2Cstate, respondIE);
  if (notifyOnHostRead()) {
    put8(0x80);  //inform host we're being read
    throttle();
    kick();
  }
  responseCursor = hostResponse+1;
  if (flags & CRCready && !quickCRC())
    --responseCursor;  //prefix response with CRC check result
  if (responseCursor < hostResponseEnd)
    I2CDRB = *responseCursor, responseCursor++;  //1st byte of response
  else if (hostResponseEnd > hostResponse+hostResponseLen)
    I2CDRB = PADbyte;  //just in case we're trying to throttle the bus
  else //wait for host to finish updating our response
    clear8 (I2CIE, TXRDYIE);
}


//Transmitter Ready in read state -- flag that we're lost
static void TXRDYlimbo (I2CgateInstance *instance)
{
  I2CDRB = PADbyte;
  flags |= enterLimboOnAL;
}


//Access Ready in respond state -- enter idle
static void ARDYresponded (I2CgateInstance *instance)
{
  flags &= ~CRCready;     //remember that we saw a STOP
  enterIdle();
}


//Own Address Match in responding state -- transition to selected state
static void OAresponded (I2CgateInstance *instance)
{
  if (I2CIFG & ARDYIFG) {  //pending ARDY being superceded by OA?
    clear8 (I2CIFG, ARDYIFG);
    flags &= ~CRCready;  //remember that we've seen a STOP
  }
  matchedAdr = I2COA;
  slaveSelected(instance);
}


//Receiver or Transmitter ready in the idle state
//This usually means that select occured twice while interrupts were disabled
//or that GCIFG has been superceded by TXRDY or RXRDYIFG.
//GCIFG should have been a higher priority -- we must check it here explicitly
static void RXRDYidle (I2CgateInstance *instance)
{
  if (I2CIFG & GCIFG) {
    clear8 (I2CIFG, GCIFG);
    GCselect(instance);
    pollIn();
  }
  RXRDYreceive (instance);
}

static void TXRDYidle (I2CgateInstance *instance)
{
  if (I2CIFG & GCIFG) {
    clear8 (I2CIFG, GCIFG);
    GCselect(instance);
    pollIn();
  }
  TXRDYrespond (instance);
}


/***************** I2C Master processing ************/

static inline void nineMoreSCLs(void)
/*
  pulse the CPLD's I2C clock counter low to allow nine more bits
  to transfer  (8 + 1 for ACKnlowlege)
*/
{   
  clear8 (DWRFI2COUT, DWRFI2CINC), set8 (DWRFI2COUT, DWRFI2CINC);
}


#macro oneMoreByte(mask)
//incement I2CDRbytes after writing or reading to/from I2CDRB
//mask is either TXRDYIFG for writing or RXRDYIFG for reading
{
  I2CDRbytes++;
  if (!(I2CIFG & mask)) do
    nineMoreSCLs();
  while (--I2CDRbytes);
}
#endm

#macro creditLastByte()
//credit the last byte transferred
{
  I2CDRbytes++;
  do nineMoreSCLs(); while (--I2CDRbytes);
}
#endm

#macro creditAllBytes()
//credit all bytes that have been transferred
{
  if (I2CDRbytes) do nineMoreSCLs(); while (--I2CDRbytes);
}
#endm

#define creditDRBwrite()  oneMoreByte(TXRDYIFG)
#define creditDRBread()   oneMoreByte(RXRDYIFG)


//quit master mode after an error occurred (& I2C interrupts already "ended")
static inline void quitMaster(I2CgateInstance *instance)
{
  I2Cstate = idleI2Cstate;
  signalEV();
}


//resume slave processing and signal wakeup
static inline void exitMaster (I2CgateInstance *instance)
{
  enterIdle();  //for now, same as slave entering idle
}


//resume slave processing, signal wakeup and set success indicator
static inline void masterSuccess (I2CgateInstance *instance)
{
  set8 (masterResult, ARDYIE);  //flag that all went well
  exitMaster (instance);
}


// wait to complete the current slave message
//returns with interrupts disabled!
void wait4idle (I2CgateInstance *instance)
{
  for (;;) {
    I2Cvectors state;
    _DINT();       //ensure state stable while probed
    state = I2Cstate;  
    if (state >= hungI2Cstate) break;  //if no slave transaction in progress 
    pollSelect (state);  //hurry back to the idle state in case of null write
    AWAITEV();    //sleep until something interesting happens
  }
}


static unsigned bytesWritten (unsigned bytesLoaded)
/*
  Bit down counter is sampled when an error occurs so that
  Leftovers = 9 * # of bytes loaded to hardware FIFO but not transmitted
*/
{
  unsigned countAtWrtErr = BitsUnsent+1;  //account for I2C START condition
  unsigned result = 0;
  unsigned bytesUnsent = I2CDRbytes;
  while (countAtWrtErr >= 9) {countAtWrtErr -= 9; bytesUnsent++;}
  if (bytesLoaded > bytesUnsent) result = bytesLoaded - bytesUnsent;
  return result;
}


static void stopI2C (I2CgateInstance *instance, I2Cisr *reset)
/*
  Bit bang an I2C STOP condition
*/
{
  set8 (P3DIR, I2Cclock|I2Cdata);
  clear8 (P3SEL, I2Cclock);          //drive just SCL low
  clear8 (DWRFIRQIFG, DWRFI2CSCL);
  set8 (DWRFIRQIE, DWRFI2CSCL); //interrupt on next SCL rising edge
  clear8 (P3SEL, I2Cdata);           //drive SDA low too
  reset(instance);  //while both SDA and SCL are being driven low
  clear8 (P3DIR, I2Cclock);          //let just the clock line float
  //resulting I2CSCL interrupt invokes connectI2C() which floats SDA
}


//Own Address Match in master state -- just update matched address
static void OAmaster (I2CgateInstance *instance)
{
  matchedAdr = I2COA;
  beSelected();
}

//General Call address match in master state -- just update matched address
static void GCmaster(I2CgateInstance *instance)
{
  matchedAdr = 0;
  beSelected();
}


//lost arbitration as master at a point when it should not be possible to do so
static void ALerror(I2CgateInstance *instance)
{ 
  I2CTCTL = I2Ccmd;
  endI2Cinterrupts();
  quitMaster(instance);
//put8('E');
}


//lost arbitration while master
static void ALmaster(I2CgateInstance *instance)
{
  I2CTCTL = I2Ccmd;
  clear8 (I2CIFG, TXRDYIFG);  //clear any pending TXRDY from aborted operation
  if (flags & arbTimeout)   //been trying to acquire the bus too long...
    ALerror (instance);
  else{
    abortCmd();
    enterIdle();
//put8('A');
    if (flags & (selected|enterLimboOnAL))
      if (!matchedAdr && I2Cgate.address & 0x80) {
        cancelI2C(); resumeI2C(); //ignore general calls
      }else if (flags & enterLimboOnAL) 
        enterSlaveState (limboI2Cstate, limboIE);
      else if (flags & selected)  //enter slave selected state immediately
        enterSlaveState (selectI2Cstate, selectIE);
    set8 (masterResult, ALIE);
  }
}

//missing ACK from slave
static void NACKmaster(I2CgateInstance *instance)
{
  I2CTCTL = I2Ccmd;
  BitsUnsent = readDWRF (DWRFI2CSCLCNT); //store bits unxferred for error report
  endI2Cinterrupts();
  set8 (masterResult, NACKIE);
  quitMaster(instance);
}


//master write transaction complete
static void ARDYwrite(I2CgateInstance *instance)
{
  unsigned confirmCode;
  I2CTCTL = I2Ccmd;
  masterSuccess(instance);
  if (confirmCode = (unsigned)I2Cgate.OK & 0x7f) {
    put8 (confirmCode);
    kick();
  }
}


//master null probe transaction complete
static void ARDYprobe(I2CgateInstance *instance)
{
  masterSuccess(instance);   //some other node responded to our new address
}


//master null transaction complete
static void ARDYwrite0(I2CgateInstance *instance)
{
  unsigned confirmCode;
  stopI2C(instance, restartI2C);  //relinquish bus & reset the hung controller
  if (confirmCode = (unsigned)I2Cgate.OK & 0x7f) {
    put8 (confirmCode);
    kick();
  }
  set8 (masterResult, ARDYIE);  //flag that all went well
  signalEV();
}


//master read transaction complete
static void ARDYread(I2CgateInstance *instance)
{
  I2CTCTL = I2Ccmd;
  releaseSCL();  //may still be waiting to receieve final bytes if I2CRM set
  set8 (masterResult, ARDYIE);
  if (!(I2CIE & RXRDYIE))
    exitMaster(instance);
}


//trying to write to ourselves and/or another node has our address
static void RXRDYwrite(I2CgateInstance *instance)
{ 
  volatile ignore = I2CDRB;
  flags |= enterLimboOnAL;
}


static void writeLastByte (I2CgateInstance *instance, unsigned lastByte)
/*
  Write the last byte of a message being transferred in repeat mode w/I2CRM
*/
{
  creditAllBytes();
  clear8 (I2CIE, TXRDYIE);
  //Wait until last byte is being clocked out before setting I2CSTP
  while (readDWRF (DWRFI2CSCLCNT) > 8) {
    _EINT(); _DINT();
    if (I2Cstate <= idleI2Cstate) return; //in case err caused abort
  }
  set8 (I2CTCTL, I2CSTP);
  I2CDRB = lastByte;
  releaseSCL();  //stop counting clocks
}

//true iff controller is ready to accept a new address or command respectively
#define ready4adr() (!I2CgateDefer.xmtISR && !(DWRFIRQIE & DWRFI2CSCL))

//busy parameter is either I2CBUSY or I2CBUSY|I2CBB|I2CSCLLOW
#define ready4cmd(busy)  (ready4adr() && rdy4core(busy))

//use this if already tested I2CgateDefer.xmtISR
#define ready4cmd0(busy) (!(DWRFIRQIE & DWRFI2CSCL) && rdy4core(busy))

#define rdy4core(busy) (I2CTCTL==I2Ccmd && !(I2CDCTL & (busy)))
    

static byte startMaster(I2CgateInstance *instance, byte ndat, byte cmd)
/*
  enter specified master interrupt processing state
  issue cmd to start the transaction
  returns non-zero "startResult" if command failed with ineterrupts enabled.
  returns with interrupts disabled if command successfully started.
*/
{
  I2CDRbytes = 0;
  for (;;) {  //wait to reach idleI2Cstate or arbitration timeout
    I2Cvectors state;
    _DINT();       //ensure state stable while probed
    state = I2Cstate;
    if (flags & arbTimeout) break;
    if (state == idleI2Cstate) {
      if (ready4cmd(I2CBUSY|I2CBB|I2CSCLLOW)) {
        if (!ndat) { //for null write case...
          clear8 (DWRFI2COUT, DWRFI2CINC);  //count down from 10 rather than 9
          I2CNDAT = 1;
        }else 
          I2CNDAT = ndat;
        set8 (U0CTL, MST);
        clear8 (DWRFI2COUT, DWRFI2CHOLD);  //enable the I2C clock counter
        I2CTCTL = cmd;
        if (U0CTL & MST || I2CIFG & (ARDYIFG|ALIFG)) goto startedOK;
//put8('F');  //keep trying if command was ignored
        I2CTCTL = I2Ccmd;
        releaseSCL();  //disable the I2C clock counter
      }
      _EINT();
      continue;
    }else
      pollSelect(state);  //hurry back to the idle state in case of null write
    AWAITEV();
  }
  _EINT();
  return masterResult = ALIE|STTIE;  //unrecoverable arbitration error
startedOK:    
  busTimer = busTics;  //allow command to finish before checking bus again
  flags &= ~(selected|enterLimboOnAL);
  return masterResult = 0;
}


static inline byte masterEnd (I2CgateInstance *instance)
/*
  wait for transaction to complete
  returns masterResult
  interrupts are enabled
  NOTE: This assumes that the successive const structs are assembled into
        ascending memory locations.  Every compiler I've used does this.
        But, I don't think there's a hard rule that this must always be so.
*/
{
  do
    AWAITEV(), _DINT();   //sleep while message is transferred via interrupt
  while (I2Cstate > idleI2Cstate);  //while still in a master mode state
  _EINT();
  return masterResult;
}

static inline
  byte masterWrite (I2CgateInstance *instance, I2Cisr * const *masterState,
                    byte ndat, byte cmd)
/*
  enter specified master write interrupt processing state
  issue cmd to start the transaction
  wait for transaction to complete
  returns masterResult  
*/
{
  byte result = startMaster (instance, ndat, cmd);
  if (!result) {
    I2Cstate = masterState;
    I2CIE = masterIE;
    result = masterEnd(instance);
  }
  return result;
}


static inline
  byte masterCRCwrite (I2CgateInstance *instance, I2Cisr * const *masterState,
                       byte ndat, byte cmd, unsigned crcSeed)
/*
  enter specified master write interrupt processing state
  issue cmd to start the transaction
  wait for transaction to complete
  returns masterResult  
*/
{
  byte result = startMaster (instance, ndat, cmd);
  if (!result) {
    I2Cstate = masterState;
    I2CIE = masterIE;
    CRCvalue = crcSeed;
    result = masterEnd(instance);
  }
  return result;
}


static inline
  byte masterRead (I2CgateInstance *instance, I2Cisr * const *masterState,
                   byte ndat, byte cmd)
/*
  enter specified master read interrupt processing state
  issue cmd to start the transaction
  wait for transaction to complete
  returns masterResult
  
*/
{
  byte result = startMaster (instance, ndat, cmd);
  if (!result) {
    nineMoreSCLs(); nineMoreSCLs();  //account 16-bit wide I2CDR input buffer
    I2Cstate = masterState;
    I2CIE = masterIE;
    result = masterEnd(instance);
  }
  return result;
}


static 
  byte masterNullWrite (I2CgateInstance *instance, I2Cisr * const *masterState)
/*
  enter specified master null message interrupt processing state
  issue cmd to start the transaction
  wait for transaction to complete
  returns masterResult
*/
{
  byte result;
  result = startMaster (instance, 0, I2Ccmd|I2CTRX|I2CSTT);
  set8 (DWRFI2COUT, DWRFI2CINC); 
  if (!result) {
    I2Cstate = masterState;
    I2CIE = masterIE & ~TXRDYIE;
    enableDWRFirqs (DWRFI2CHELD);
    I2CDRB = 0;  //throw the xmitter a fish just to clear the interrupt
    result = masterEnd(instance);
  }
  return result;
}


static void putSelectError (I2CgateInstance *instance) 
{
  unsigned nackCode = (unsigned)I2Cgate.adrNACK & 0x7f;
  if (nackCode) {
    hold();
    putByte (nackCode);
    release();
  }
}


//one-time init of master retry mechanism
static void addressSlave (unsigned slaveAdr)
{
  I2CSA = slaveAdr;
  retriesRemaining = I2Cgate.retries & 0x7f;
  restartArbTimer();
}


static
 void putResponse (I2CgateInstance *instance, unsigned len)
//len must be <= responseLen
{
  hold();
  putByte (len + 0x81);
  putByte (0x81);
  putBlock (hostResponse+1, len);
  release();