/*
    ------------------------------------------------------------------------------------------------------
    i2c_t3 - I2C library for Teensy 3.x & LC

    - (v11.0) Modified 01Dec18 by Brian (nox771 at gmail.com)

    Full changelog at end of file
    ------------------------------------------------------------------------------------------------------
    Copyright (c) 2013-2018, Brian (nox771 at gmail.com)

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
    associated documentation files (the "Software"), to deal in the Software without restriction, 
    including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
    and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
    subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all copies or substantial 
    portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
    LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    ------------------------------------------------------------------------------------------------------
*/

#if defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) || \
    defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.0/3.1-3.2/LC/3.5/3.6

#include "i2c_t3.h"


// ------------------------------------------------------------------------------------------------------
// Static inits
//
#define I2C_STRUCT(a1,f,c1,s,d,c2,flt,ra,smb,a2,slth,sltl,scl,sda) \
    {a1, f, c1, s, d, c2, flt, ra, smb, a2, slth, sltl, {}, 0, 0, {}, 0, 0, I2C_OP_MODE_ISR, I2C_MASTER, scl, sda, I2C_PULLUP_EXT, 100000, \
     I2C_STOP, I2C_WAITING, 0, 0, 0, 0, I2C_DMA_OFF, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 0, {}, 0, 0 }

struct i2cStruct i2c_t3::i2cData[] =
{
    I2C_STRUCT(&I2C0_A1, &I2C0_F, &I2C0_C1, &I2C0_S, &I2C0_D, &I2C0_C2, &I2C0_FLT, &I2C0_RA, &I2C0_SMB, &I2C0_A2, &I2C0_SLTH, &I2C0_SLTL, 19, 18)
#if (I2C_BUS_NUM >= 2) && defined(__MK20DX256__) // 3.1/3.2
   ,I2C_STRUCT(&I2C1_A1, &I2C1_F, &I2C1_C1, &I2C1_S, &I2C1_D, &I2C1_C2, &I2C1_FLT, &I2C1_RA, &I2C1_SMB, &I2C1_A2, &I2C1_SLTH, &I2C1_SLTL, 29, 30)
#elif (I2C_BUS_NUM >= 2) && defined(__MKL26Z64__) // LC
   ,I2C_STRUCT(&I2C1_A1, &I2C1_F, &I2C1_C1, &I2C1_S, &I2C1_D, &I2C1_C2, &I2C1_FLT, &I2C1_RA, &I2C1_SMB, &I2C1_A2, &I2C1_SLTH, &I2C1_SLTL, 22, 23)
#elif (I2C_BUS_NUM >= 2) && (defined(__MK64FX512__) || defined(__MK66FX1M0__))  // 3.5/3.6
   ,I2C_STRUCT(&I2C1_A1, &I2C1_F, &I2C1_C1, &I2C1_S, &I2C1_D, &I2C1_C2, &I2C1_FLT, &I2C1_RA, &I2C1_SMB, &I2C1_A2, &I2C1_SLTH, &I2C1_SLTL, 37, 38)
#endif
#if (I2C_BUS_NUM >= 3) && (defined(__MK64FX512__) || defined(__MK66FX1M0__))  // 3.5/3.6
   ,I2C_STRUCT(&I2C2_A1, &I2C2_F, &I2C2_C1, &I2C2_S, &I2C2_D, &I2C2_C2, &I2C2_FLT, &I2C2_RA, &I2C2_SMB, &I2C2_A2, &I2C2_SLTH, &I2C2_SLTL, 3, 4)
#endif
#if (I2C_BUS_NUM >= 4) && defined(__MK66FX1M0__) // 3.6
   ,I2C_STRUCT(&I2C3_A1, &I2C3_F, &I2C3_C1, &I2C3_S, &I2C3_D, &I2C3_C2, &I2C3_FLT, &I2C3_RA, &I2C3_SMB, &I2C3_A2, &I2C3_SLTH, &I2C3_SLTL, 57, 56)
#endif
};

volatile uint8_t i2c_t3::isrActive = 0;


// ------------------------------------------------------------------------------------------------------
// Constructor/Destructor
//
i2c_t3::i2c_t3(uint8_t i2c_bus)
{
    bus = i2c_bus;
    i2c = &i2cData[bus];
}
i2c_t3::~i2c_t3()
{
    // if DMA active, delete DMA object
    if(i2c->opMode == I2C_OP_MODE_DMA)
        delete i2c->DMA;
}


// ------------------------------------------------------------------------------------------------------
// Initialize I2C - initializes I2C as Master or address range Slave
// return: none
// parameters (optional parameters marked '^'):
//      mode = I2C_MASTER, I2C_SLAVE
//      address1 = 7bit slave address when configured as Slave (ignored for Master mode)
//    ^ address2 = 2nd 7bit address for specifying Slave address range (ignored for Master mode)
//    ^ pins = pins to use, can be specified as 'i2c_pins' enum,
//             or as 'SCL,SDA' pair (using any valid SCL or SDA), options are:
//                                 Pin Name
//          Interface  Devices   (deprecated)    SCL    SDA
//          ---------  -------  --------------  -----  -----    (note: in almost all cases SCL is the
//             Wire      All    I2C_PINS_16_17    16     17      lower pin #, except cases marked *)
//             Wire      All    I2C_PINS_18_19    19     18  *
//             Wire    3.5/3.6  I2C_PINS_7_8       7      8
//             Wire    3.5/3.6  I2C_PINS_33_34    33     34
//             Wire    3.5/3.6  I2C_PINS_47_48    47     48
//            Wire1       LC    I2C_PINS_22_23    22     23
//            Wire1    3.1/3.2  I2C_PINS_26_31    26     31
//            Wire1    3.1/3.2  I2C_PINS_29_30    29     30
//            Wire1    3.5/3.6  I2C_PINS_37_38    37     38
//            Wire2    3.5/3.6  I2C_PINS_3_4       3      4
//            Wire3      3.6    I2C_PINS_56_57    57     56  *
//    ^ pullup = I2C_PULLUP_EXT, I2C_PULLUP_INT
//    ^ rate = I2C frequency to use, can be specified directly in Hz, eg. 400000 for 400kHz, or using one of the
//             following enum values (deprecated):
//             I2C_RATE_100, I2C_RATE_200, I2C_RATE_300, I2C_RATE_400,
//             I2C_RATE_600, I2C_RATE_800, I2C_RATE_1000, I2C_RATE_1200,
//             I2C_RATE_1500, I2C_RATE_1800, I2C_RATE_2000, I2C_RATE_2400,
//             I2C_RATE_2800, I2C_RATE_3000
//    ^ opMode = I2C_OP_MODE_IMM, I2C_OP_MODE_ISR, I2C_OP_MODE_DMA (ignored for Slave mode, defaults to ISR)
//
void i2c_t3::begin_(struct i2cStruct* i2c, uint8_t bus, i2c_mode mode, uint8_t address1, uint8_t address2,
                    uint8_t pinSCL, uint8_t pinSDA, i2c_pullup pullup, uint32_t rate, i2c_op_mode opMode)
{
    // Enable I2C internal clock
    if(bus == 0)
        SIM_SCGC4 |= SIM_SCGC4_I2C0;
    #if I2C_BUS_NUM >= 2
        if(bus == 1)
            SIM_SCGC4 |= SIM_SCGC4_I2C1;
    #endif
    #if I2C_BUS_NUM >= 3
        if(bus == 2)
            SIM_SCGC1 |= SIM_SCGC1_I2C2;
    #endif
    #if I2C_BUS_NUM >= 4
        if(bus == 3)
            SIM_SCGC1 |= SIM_SCGC1_I2C3;
    #endif

    i2c->currentMode = mode; // Set mode
    i2c->currentStatus = I2C_WAITING; // reset status

    // Set Master/Slave address
    if(i2c->currentMode == I2C_MASTER)
    {
        *(i2c->C2) = I2C_C2_HDRS; // Set high drive select
        //*(i2c->A1) = 0;
        //*(i2c->RA) = 0;
    }
    else
    {
        *(i2c->C2) = (address2) ? (I2C_C2_HDRS|I2C_C2_RMEN) // Set high drive select and range-match enable
                                : I2C_C2_HDRS;              // Set high drive select
        // set Slave address, if two addresses are given, setup range and put lower address in A1, higher in RA
        *(i2c->A1) = (address2) ? ((address1 < address2) ? (address1<<1) : (address2<<1))
                                : (address1<<1);
        *(i2c->RA) = (address2) ? ((address1 < address2) ? (address2<<1) : (address1<<1))
                                : 0;
    }

    // Setup pins - As noted in original TwoWire.cpp, internal 3.0/3.1/3.2 pullup is strong (about 190 ohms), 
    // but it can work if other devices on bus have strong enough pulldowns.
    //
    if(!pinSCL) pinSCL = i2c->currentSCL; // if either pin specified as 0, then use current settings
    if(!pinSDA) pinSDA = i2c->currentSDA;
    pinConfigure_(i2c, bus, pinSCL, pinSDA, pullup, i2c->configuredSCL, i2c->configuredSDA);

    // Set I2C rate
    #if defined(__MKL26Z64__) // LC
        if(bus == 1)
            setRate_(i2c, (uint32_t)F_CPU, rate); // LC Wire1 bus uses system clock (F_CPU) instead of bus clock (F_BUS)
        else
            setRate_(i2c, (uint32_t)F_BUS, rate);
    #else
        setRate_(i2c, (uint32_t)F_BUS, rate);
    #endif

    // Set config registers and operating mode
    setOpMode_(i2c, bus, opMode);
    if(i2c->currentMode == I2C_MASTER)
        *(i2c->C1) = I2C_C1_IICEN; // Master - enable I2C (hold in Rx mode, intr disabled)
    else
        *(i2c->C1) = I2C_C1_IICEN|I2C_C1_IICIE; // Slave - enable I2C and interrupts
}


// ------------------------------------------------------------------------------------------------------
// Valid pin checks - verify if SCL or SDA pin is valid on given bus, intended for internal use only
// return: alt setting, 0=not valid
// parameters:
//      bus = bus number
//      pin = pin number to check
//      offset = array offset
//
uint8_t i2c_t3::validPin_(uint8_t bus, uint8_t pin, uint8_t offset)
{
    for(uint8_t idx=0; idx < I2C_PINS_COUNT-1; idx++)
        if(i2c_valid_pins[idx*4] == bus && i2c_valid_pins[idx*4+offset] == pin) return i2c_valid_pins[idx*4+3];
    return 0;
}


// Set Operating Mode - this configures operating mode of the I2C as either Immediate, ISR, or DMA.
//                      By default Arduino-style begin() calls will initialize to ISR mode.  This can
//                      only be called when the bus is idle (no changing mode in the middle of Tx/Rx).
//                      Note that Slave mode can only use ISR operation.
// return: 1=success, 0=fail (bus busy)
// parameters:
//      opMode = I2C_OP_MODE_ISR, I2C_OP_MODE_DMA, I2C_OP_MODE_IMM
//
uint8_t i2c_t3::setOpMode_(struct i2cStruct* i2c, uint8_t bus, i2c_op_mode opMode)
{
    if(*(i2c->S) & I2C_S_BUSY) return 0; // return immediately if bus busy

    *(i2c->C1) = I2C_C1_IICEN; // reset I2C modes, stop intr, stop DMA
    *(i2c->S) = I2C_S_IICIF | I2C_S_ARBL; // clear status flags just in case

    // Slaves can only use ISR
    if(i2c->currentMode == I2C_SLAVE) opMode = I2C_OP_MODE_ISR;

    if(opMode == I2C_OP_MODE_IMM)
    {
        i2c->opMode = I2C_OP_MODE_IMM;
    }
    if(opMode == I2C_OP_MODE_ISR || opMode == I2C_OP_MODE_DMA)
    {
        // Nested Vec Interrupt Ctrl - enable I2C interrupt
        if(bus == 0)
        {
            NVIC_ENABLE_IRQ(IRQ_I2C0);
            I2C0_INTR_FLAG_INIT; // init I2C0 interrupt flag if used
        }
        #if I2C_BUS_NUM >= 2
            if(bus == 1)
            {
                NVIC_ENABLE_IRQ(IRQ_I2C1);
                I2C1_INTR_FLAG_INIT; // init I2C1 interrupt flag if used
            }
        #endif
        #if I2C_BUS_NUM >= 3
            if(bus == 2)
            {
                NVIC_ENABLE_IRQ(IRQ_I2C2);
                I2C2_INTR_FLAG_INIT; // init I2C2 interrupt flag if used
            }
        #endif
        #if I2C_BUS_NUM >= 4
            if(bus == 3)
            {
                NVIC_ENABLE_IRQ(IRQ_I2C3);
                I2C3_INTR_FLAG_INIT; // init I2C3 interrupt flag if used
            }
        #endif
        if(opMode == I2C_OP_MODE_DMA)
        {
            // attempt to get a DMA Channel (if not already allocated)
            if(i2c->DMA == nullptr)
                i2c->DMA = new DMAChannel();
            // check if object created but no available channel
            if(i2c->DMA != nullptr && i2c->DMA->channel == DMA_NUM_CHANNELS)
            {
                // revert to ISR mode if no DMA channels avail
                delete i2c->DMA;
                i2c->DMA = nullptr;
                i2c->opMode = I2C_OP_MODE_ISR;
            }
            else
            {
                // DMA object has valid channel
                if(bus == 0)
                {
                    // setup static DMA settings
                    i2c->DMA->disableOnCompletion();
                    i2c->DMA->attachInterrupt(i2c0_isr);
                    i2c->DMA->interruptAtCompletion();
                    i2c->DMA->triggerAtHardwareEvent(DMAMUX_SOURCE_I2C0);
                }
                #if I2C_BUS_NUM >= 2
                    if(bus == 1)
                    {
                        // setup static DMA settings
                        i2c->DMA->disableOnCompletion();
                        i2c->DMA->attachInterrupt(i2c1_isr);
                        i2c->DMA->interruptAtCompletion();
                        i2c->DMA->triggerAtHardwareEvent(DMAMUX_SOURCE_I2C1);
                    }
                #endif
                #if I2C_BUS_NUM >= 3
                    // note: on T3.6 I2C2 shares DMAMUX with I2C1
                    if(bus == 2)
                    {
                        // setup static DMA settings
                        i2c->DMA->disableOnCompletion();
                        i2c->DMA->attachInterrupt(i2c2_isr);
                        i2c->DMA->interruptAtCompletion();
                        i2c->DMA->triggerAtHardwareEvent(DMAMUX_SOURCE_I2C2);
                    }
                #endif
                #if I2C_BUS_NUM >= 4
                    // note: on T3.6 I2C3 shares DMAMUX with I2C0
                    if(bus == 3)
                    {
                        // setup static DMA settings
                        i2c->DMA->disableOnCompletion();
                        i2c->DMA->attachInterrupt(i2c3_isr);
                        i2c->DMA->interruptAtCompletion();
                        i2c->DMA->triggerAtHardwareEvent(DMAMUX_SOURCE_I2C3);
                    }
                #endif
                i2c->activeDMA = I2C_DMA_OFF;
                i2c->opMode = I2C_OP_MODE_DMA;
            }
        }
        else
            i2c->opMode = I2C_OP_MODE_ISR;
    }
    return 1;
}


// Set I2C rate - reconfigures I2C frequency divider based on supplied bus freq and desired I2C freq.
//                This will be done assuming an idealized I2C rate, even though at high I2C rates
//                the actual throughput is much lower than theoretical value.
//
//                Since the division ratios are quantized with non-uniform spacing, the selected rate
//                will be the one using the nearest available divider.
// return: none
// parameters:
//      busFreq = bus frequency, typically F_BUS unless reconfigured
//      freq = desired I2C frequency (will be quantized to nearest rate), or can be I2C_RATE_XXX enum (deprecated),
//             such as I2C_RATE_100, I2C_RATE_400, etc...
//
// Max I2C rate is 1/20th F_BUS.  Some examples:
//
//     F_CPU      F_BUS     Max I2C
//     (MHz)      (MHz)       Rate
// -------------  -----    ----------
//    240/120      120        6.0M    bus overclock
//      216        108        5.4M    bus overclock
//     192/96       96        4.8M    bus overclock
//      180         90        4.5M    bus overclock
//      240         80        4.0M    bus overclock
//   216/144/72     72        3.6M    bus overclock
//      192         64        3.2M    bus overclock
//  240/180/120     60        3.0M
//      168         56        2.8M
//      216         54        2.7M
// 192/144/96/48    48        2.4M
//       72         36        1.8M
//       24         24        1.2M
//       16         16        800k
//        8          8        400k
//        4          4        200k
//        2          2        100k
//
void i2c_t3::setRate_(struct i2cStruct* i2c, uint32_t busFreq, uint32_t i2cFreq)
{
    int32_t target_div = ((busFreq/1000)<<8)/(i2cFreq/1000);
    size_t idx;
    // find closest divide ratio
    for(idx=0; idx < sizeof(i2c_div_num)/sizeof(i2c_div_num[0]) && (i2c_div_num[idx]<<8) <= target_div; idx++);
    if(idx && abs(target_div-(i2c_div_num[idx-1]<<8)) <= abs(target_div-(i2c_div_num[idx]<<8))) idx--;
    // Set divider to set rate
    *(i2c->F) = i2c_div_ratio[idx];
    // save current rate setting
    i2c->currentRate = busFreq/i2c_div_num[idx];

    // Set filter
    if(busFreq >= 48000000)
        *(i2c->FLT) = 4;
    else
        *(i2c->FLT) = busFreq/12000000;
}


// ------------------------------------------------------------------------------------------------------
// Configure I2C pins - reconfigures active I2C pins on-the-fly (only works when bus is idle).  If reconfig
//                      set then inactive pins will switch to input mode using same pullup configuration.
// return: 1=success, 0=fail (bus busy or incompatible pins)
// parameters:
//      pins = pins to use, can be specified as 'i2c_pins' enum,
//             or as 'SCL,SDA' pair (using any valid SCL or SDA), options are:
//                                 Pin Name
//          Interface  Devices   (deprecated)    SCL    SDA
//          ---------  -------  --------------  -----  -----    (note: in almost all cases SCL is the
//             Wire      All    I2C_PINS_16_17    16     17      lower pin #, except cases marked *)
//             Wire      All    I2C_PINS_18_19    19     18  *
//             Wire    3.5/3.6  I2C_PINS_7_8       7      8
//             Wire    3.5/3.6  I2C_PINS_33_34    33     34
//             Wire    3.5/3.6  I2C_PINS_47_48    47     48
//            Wire1       LC    I2C_PINS_22_23    22     23
//            Wire1    3.1/3.2  I2C_PINS_26_31    26     31
//            Wire1    3.1/3.2  I2C_PINS_29_30    29     30
//            Wire1    3.5/3.6  I2C_PINS_37_38    37     38
//            Wire2    3.5/3.6  I2C_PINS_3_4       3      4
//            Wire3      3.6    I2C_PINS_56_57    57     56  *
//      pullup = I2C_PULLUP_EXT, I2C_PULLUP_INT
//      reconfig = 1=reconfigure old pins, 0=do not reconfigure old pins (base routine only)
//
#define PIN_CONFIG_ALT(name,alt) uint32_t name = (pullup == I2C_PULLUP_EXT) ? (PORT_PCR_MUX(alt)|PORT_PCR_ODE|PORT_PCR_SRE|PORT_PCR_DSE) \
                                                                            : (PORT_PCR_MUX(alt)|PORT_PCR_PE|PORT_PCR_PS)

uint8_t i2c_t3::pinConfigure_(struct i2cStruct* i2c, uint8_t bus, uint8_t pinSCL, uint8_t pinSDA, i2c_pullup pullup, 
                              uint8_t configuredSCL, uint8_t configuredSDA)
{
    uint8_t validAltSCL, validAltSDA;
    volatile uint32_t* pcr;

    if((configuredSCL && configuredSDA) && (*(i2c->S) & I2C_S_BUSY)) return 0; // if configured return immediately if bus busy

    // Verify new SCL pin is different or not configured, and valid
    //
    validAltSCL = validPin_(bus, pinSCL, 1);
    if((pinSCL != i2c->currentSCL || !configuredSCL) && validAltSCL)
    {
        // If configured, switch previous pin to non-I2C input
        if(configuredSCL) pinMode(i2c->currentSCL, (i2c->currentPullup == I2C_PULLUP_EXT) ? INPUT : INPUT_PULLUP);
        // Config new pin
        PIN_CONFIG_ALT(configSCL, validAltSCL);
        pcr = portConfigRegister(pinSCL);
        *pcr = configSCL;
        i2c->currentSCL = pinSCL;
        i2c->currentPullup = pullup;
        i2c->configuredSCL = 1;
    }

    // Verify new SDA pin is different or not configured, and valid
    //
    validAltSDA = validPin_(bus, pinSDA, 2);
    if((pinSDA != i2c->currentSDA || !configuredSDA) && validAltSDA) 
    {
        // If reconfig set, switch previous pin to non-I2C input
        if(configuredSDA) pinMode(i2c->currentSDA, (i2c->currentPullup == I2C_PULLUP_EXT) ? INPUT : INPUT_PULLUP);
        // Config new pin
        PIN_CONFIG_ALT(configSDA, validAltSDA);
        pcr = portConfigRegister(pinSDA);
        *pcr = configSDA;
        i2c->currentSDA = pinSDA;
        i2c->currentPullup = pullup;
        i2c->configuredSDA = 1;
    }

    return (validAltSCL && validAltSDA);
}


// ------------------------------------------------------------------------------------------------------
// Acquire Bus - acquires bus in Master mode and escalates priority as needed, intended
//               for internal use only
// return: 1=success, 0=fail (cannot acquire bus)
// parameters:
//      timeout = timeout in microseconds
//      forceImm = flag to indicate if immediate mode is required
//
uint8_t i2c_t3::acquireBus_(struct i2cStruct* i2c, uint8_t bus, uint32_t timeout, uint8_t& forceImm)
{
    elapsedMicros deltaT;

    // update timeout
    timeout = (timeout == 0) ? i2c->defTimeout : timeout;

    // TODO may need to check bus busy before issuing START if multi-master

    // start timer, then take control of the bus
    deltaT = 0;
    if(*(i2c->C1) & I2C_C1_MST)
    {
        // we are already the bus master, so send a repeated start
        *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_RSTA | I2C_C1_TX;
    }
    else
    {
        while(timeout == 0 || deltaT < timeout)
        {
            // we are not currently the bus master, so check if bus ready
            if(!(*(i2c->S) & I2C_S_BUSY))
            {
                // become the bus master in transmit mode (send start)
                i2c->currentMode = I2C_MASTER;
                *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
                break;
            }
        }
        #if defined(I2C_AUTO_RETRY)
            // if not master and auto-retry set, then reset bus and try one last time
            if(!(*(i2c->C1) & I2C_C1_MST))
            {
                resetBus_(i2c,bus);
                I2C_ERR_INC(I2C_ERRCNT_RESET_BUS);
                if(!(*(i2c->S) & I2C_S_BUSY))
                {
                    // become the bus master in transmit mode (send start)
                    i2c->currentMode = I2C_MASTER;
                    *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
                }
            }
        #endif
        // check if not master
        if(!(*(i2c->C1) & I2C_C1_MST))
        {
            i2c->currentStatus = I2C_NOT_ACQ; // bus not acquired
            I2C_ERR_INC(I2C_ERRCNT_NOT_ACQ);
            if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if cannot acquire bus
            return 0;
        }
    }

    #ifndef I2C_DISABLE_PRIORITY_CHECK
        // For ISR operation, check if current routine has higher priority than I2C IRQ, and if so
        // either escalate priority of I2C IRQ or send I2C using immediate mode.
        //
        // This check is disabled if the routine is called during an active I2C ISR (assumes it is
        // called from ISR callback).  This is to prevent runaway escalation with nested Wire calls.
        //
        int irqPriority, currPriority;
        if(!i2c_t3::isrActive && (i2c->opMode == I2C_OP_MODE_ISR || i2c->opMode == I2C_OP_MODE_DMA))
        {
            currPriority = nvic_execution_priority();
            switch(bus)
            {
            case 0:  irqPriority = NVIC_GET_PRIORITY(IRQ_I2C0); break;
            #if defined(__MKL26Z64__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.1/3.2/3.5/3.6
            case 1:  irqPriority = NVIC_GET_PRIORITY(IRQ_I2C1); break;
            #endif
            #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6
            case 2:  irqPriority = NVIC_GET_PRIORITY(IRQ_I2C2); break;
            #endif
            #if defined(__MK66FX1M0__) // 3.6
            case 3:  irqPriority = NVIC_GET_PRIORITY(IRQ_I2C3); break;
            #endif
            default: irqPriority = NVIC_GET_PRIORITY(IRQ_I2C0); break;
            }
            if(currPriority <= irqPriority)
            {
                if(currPriority < 16)
                    forceImm = 1; // current priority cannot be surpassed, force Immediate mode
                else
                {
                    switch(bus)
                    {
                    case 0:  NVIC_SET_PRIORITY(IRQ_I2C0, currPriority-16); break;
                    #if defined(__MKL26Z64__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.1/3.2/3.5/3.6
                    case 1:  NVIC_SET_PRIORITY(IRQ_I2C1, currPriority-16); break;
                    #endif
                    #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6
                    case 2:  NVIC_SET_PRIORITY(IRQ_I2C2, currPriority-16); break;
                    #endif
                    #if defined(__MK66FX1M0__) // 3.6
                    case 3:  NVIC_SET_PRIORITY(IRQ_I2C3, currPriority-16); break;
                    #endif
                    default: NVIC_SET_PRIORITY(IRQ_I2C0, currPriority-16); break;
                    }
                }
            }
        }
    #endif

    return 1;
}


// ------------------------------------------------------------------------------------------------------
// Reset Bus - toggles SCL until SDA line is released (9 clocks max).  This is used to correct
//             a hung bus in which a Slave device missed some clocks and remains stuck outputting
//             a low signal on SDA (thereby preventing START/STOP signaling).
// return: none
//
void i2c_t3::resetBus_(struct i2cStruct* i2c, uint8_t bus)
{
    uint8_t scl = i2c->currentSCL;
    uint8_t sda = i2c->currentSDA;

    // change pin mux to digital I/O
    pinMode(sda,((i2c->currentPullup == I2C_PULLUP_EXT) ? INPUT : INPUT_PULLUP));
    digitalWrite(scl,HIGH);
    pinMode(scl,OUTPUT);

    for(uint8_t count=0; digitalRead(sda) == 0 && count < 9; count++)
    {
        digitalWrite(scl,LOW);
        delayMicroseconds(5);       // 10us period == 100kHz
        digitalWrite(scl,HIGH);
        delayMicroseconds(5);
    }

    // reconfigure pins for I2C
    pinConfigure_(i2c, bus, scl, sda, i2c->currentPullup, 0, 0);

    // reset config and status
    if(*(i2c->S) & 0x7F) // reset config if any residual status bits are set
    {
        *(i2c->C1) = 0x00; // disable I2C, intr disabled
        delayMicroseconds(5);
        *(i2c->C1) = I2C_C1_IICEN; // enable I2C, intr disabled, Rx mode
        delayMicroseconds(5);
    }
    i2c->currentStatus = I2C_WAITING;
}


// ------------------------------------------------------------------------------------------------------
// Setup Master Transmit - initialize Tx buffer for transmit to slave at address
// return: none
// parameters:
//      address = target 7bit slave address
//
void i2c_t3::beginTransmission(uint8_t address)
{
    i2c->txBuffer[0] = (address << 1); // store target addr
    i2c->txBufferLength = 1;
    clearWriteError(); // clear any previous write error
    i2c->currentStatus = I2C_WAITING; // reset status
}


// ------------------------------------------------------------------------------------------------------
// Master Transmit - blocking routine with timeout, transmits Tx buffer to slave. i2c_stop parameter can be used
//                   to indicate if command should end with a STOP(I2C_STOP) or not (I2C_NOSTOP).
// return: 0=success, 1=data too long, 2=recv addr NACK, 3=recv data NACK, 4=other error
// parameters:
//      i2c_stop = I2C_NOSTOP, I2C_STOP
//      timeout = timeout in microseconds
//
uint8_t i2c_t3::endTransmission(struct i2cStruct* i2c, uint8_t bus, i2c_stop sendStop, uint32_t timeout)
{
    sendTransmission_(i2c, bus, sendStop, timeout);

    // wait for completion or timeout
    finish_(i2c, bus, timeout);

    return getError();
}


// ------------------------------------------------------------------------------------------------------
// Send Master Transmit - non-blocking routine, starts transmit of Tx buffer to slave. i2c_stop parameter can be
//                        used to indicate if command should end with a STOP (I2C_STOP) or not (I2C_NOSTOP). Use
//                        done() or finish() to determine completion and status() to determine success/fail.
// return: none
// parameters:
//      i2c_stop = I2C_NOSTOP, I2C_STOP
//      timeout = timeout in microseconds (only used for Immediate operation)
//
void i2c_t3::sendTransmission_(struct i2cStruct* i2c, uint8_t bus, i2c_stop sendStop, uint32_t timeout)
{
    uint8_t status, forceImm=0;
    size_t idx;

    // exit immediately if sending 0 bytes
    if(i2c->txBufferLength == 0) return;

    // update timeout
    timeout = (timeout == 0) ? i2c->defTimeout : timeout;

    // clear the status flags
    #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
        *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF;  // clear STOP/START intr
        *(i2c->FLT) &= ~I2C_FLT_SSIE;                   // disable STOP/START intr (not used in Master mode)
    #endif
    *(i2c->S) = I2C_S_IICIF | I2C_S_ARBL; // clear intr, arbl

    // try to take control of the bus
    if(!acquireBus_(i2c, bus, timeout, forceImm)) return;

    //
    // Immediate mode - blocking
    //
    if(i2c->opMode == I2C_OP_MODE_IMM || forceImm)
    {
        elapsedMicros deltaT;
        i2c->currentStatus = I2C_SENDING;
        i2c->currentStop = sendStop;

        for(idx=0; idx < i2c->txBufferLength && (timeout == 0 || deltaT < timeout); idx++)
        {
            // send data, wait for done
            *(i2c->D) = i2c->txBuffer[idx];

            // wait for byte
            while(!(*(i2c->S) & I2C_S_IICIF) && (timeout == 0 || deltaT < timeout));
            *(i2c->S) = I2C_S_IICIF;
            if(timeout && deltaT >= timeout) break;

            status = *(i2c->S);

            // check arbitration
            if(status & I2C_S_ARBL)
            {
                i2c->currentStatus = I2C_ARB_LOST;
                *(i2c->S) = I2C_S_ARBL; // clear arbl flag
                // TODO: this is clearly not right, after ARBL it should drop into IMM slave mode if IAAS=1
                //       Right now Rx message would be ignored regardless of IAAS
                *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?)
                I2C_ERR_INC(I2C_ERRCNT_ARBL);
                if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if ARBL
                return;
            }
            // check if slave ACK'd
            else if(status & I2C_S_RXAK)
            {
                if(idx == 0)
                {
                    i2c->currentStatus = I2C_ADDR_NAK; // NAK on Addr
                    I2C_ERR_INC(I2C_ERRCNT_ADDR_NAK);
                }
                else
                {
                    i2c->currentStatus = I2C_DATA_NAK; // NAK on Data
                    I2C_ERR_INC(I2C_ERRCNT_DATA_NAK);
                }
                *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
                if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if NAK
                return;
            }
        }

        // send STOP if configured
        if(i2c->currentStop == I2C_STOP)
            *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
        else
            *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; // no STOP, stay in Tx mode, intr disabled

        // Set final status
        if(idx < i2c->txBufferLength)
        {
            i2c->currentStatus = I2C_TIMEOUT; // Tx incomplete, mark as timeout
            I2C_ERR_INC(I2C_ERRCNT_TIMEOUT);
            if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if timeout
        }
        else
        {
            i2c->currentStatus = I2C_WAITING; // Tx complete, change to waiting state
            if(i2c->user_onTransmitDone != nullptr) i2c->user_onTransmitDone(); // Call Master Tx complete callback
        }
    }
    //
    // ISR/DMA mode - non-blocking
    //
    else if(i2c->opMode == I2C_OP_MODE_ISR || i2c->opMode == I2C_OP_MODE_DMA)
    {
        // send target addr and enable interrupts
        i2c->currentStatus = I2C_SENDING;
        i2c->currentStop = sendStop;
        i2c->txBufferIndex = 0;
        if(i2c->opMode == I2C_OP_MODE_DMA && i2c->txBufferLength >= 5) // limit transfers less than 5 bytes to ISR method
        {
            // init DMA, let the hack begin
            i2c->activeDMA = I2C_DMA_ADDR;
            i2c->DMA->sourceBuffer(&i2c->txBuffer[2],i2c->txBufferLength-3); // DMA sends all except first/second/last bytes
            i2c->DMA->destination(*(i2c->D));
        }
        // start ISR
        *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX; // enable intr
        *(i2c->D) = i2c->txBuffer[0]; // writing first data byte will start ISR
    }
}


// ------------------------------------------------------------------------------------------------------
// Master Receive - blocking routine with timeout, requests length bytes from slave at address. Receive data will
//                  be placed in the Rx buffer. i2c_stop parameter can be used to indicate if command should end
//                  with a STOP (I2C_STOP) or not (I2C_NOSTOP).
// return: #bytes received = success, 0=fail (0 length request, NAK, timeout, or bus error)
// parameters:
//      address = target 7bit slave address
//      length = number of bytes requested
//      i2c_stop = I2C_NOSTOP, I2C_STOP
//      timeout = timeout in microseconds
//
size_t i2c_t3::requestFrom_(struct i2cStruct* i2c, uint8_t bus, uint8_t addr, size_t len, i2c_stop sendStop, uint32_t timeout)
{
    // exit immediately if request for 0 bytes
    if(len == 0) return 0;

    sendRequest_(i2c, bus, addr, len, sendStop, timeout);

    // wait for completion or timeout
    if(finish_(i2c, bus, timeout))
        return i2c->rxBufferLength;
    else
        return 0; // NAK, timeout or bus error
}


// ------------------------------------------------------------------------------------------------------
// Start Master Receive - non-blocking routine, starts request for length bytes from slave at address. Receive
//                        data will be placed in the Rx buffer. i2c_stop parameter can be used to indicate if
//                        command should end with a STOP (I2C_STOP) or not (I2C_NOSTOP). Use done() or finish()
//                        to determine completion and status() to determine success/fail.
// return: none
// parameters:
//      address = target 7bit slave address
//      length = number of bytes requested
//      i2c_stop = I2C_NOSTOP, I2C_STOP
//      timeout = timeout in microseconds (only used for Immediate operation)
//
void i2c_t3::sendRequest_(struct i2cStruct* i2c, uint8_t bus, uint8_t addr, size_t len, i2c_stop sendStop, uint32_t timeout)
{
    uint8_t status, data, chkTimeout=0, forceImm=0;

    // exit immediately if request for 0 bytes or request too large
    if(len == 0) return;
    if(len > I2C_RX_BUFFER_LENGTH) { i2c->currentStatus=I2C_BUF_OVF; return; }

    i2c->reqCount = len; // store request length
    i2c->rxBufferIndex = 0; // reset buffer
    i2c->rxBufferLength = 0;
    timeout = (timeout == 0) ? i2c->defTimeout : timeout;

    // clear the status flags
    #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
        *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF;  // clear STOP/START intr
        *(i2c->FLT) &= ~I2C_FLT_SSIE;                   // disable STOP/START intr (not used in Master mode)
    #endif
    *(i2c->S) = I2C_S_IICIF | I2C_S_ARBL; // clear intr, arbl

    // try to take control of the bus
    if(!acquireBus_(i2c, bus, timeout, forceImm)) return;

    //
    // Immediate mode - blocking
    //
    if(i2c->opMode == I2C_OP_MODE_IMM || forceImm)
    {
        elapsedMicros deltaT;
        i2c->currentStatus = I2C_SEND_ADDR;
        i2c->currentStop = sendStop;

        // Send target address
        *(i2c->D) = (addr << 1) | 1; // address + READ

        // wait for byte
        while(!(*(i2c->S) & I2C_S_IICIF) && (timeout == 0 || deltaT < timeout));
        *(i2c->S) = I2C_S_IICIF;
        if(timeout && deltaT >= timeout)
        {
            *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
            i2c->currentStatus = I2C_TIMEOUT; // Rx incomplete, mark as timeout
            I2C_ERR_INC(I2C_ERRCNT_TIMEOUT);
            if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if timeout
            return;
        }

        status = *(i2c->S);

        // check arbitration
        if(status & I2C_S_ARBL)
        {
            i2c->currentStatus = I2C_ARB_LOST;
            *(i2c->S) = I2C_S_ARBL; // clear arbl flag
            // TODO: this is clearly not right, after ARBL it should drop into IMM slave mode if IAAS=1
            //       Right now Rx message would be ignored regardless of IAAS
            *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?)
            I2C_ERR_INC(I2C_ERRCNT_ARBL);
            if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if ARBL
            return;
        }
        // check if slave ACK'd
        else if(status & I2C_S_RXAK)
        {
            i2c->currentStatus = I2C_ADDR_NAK; // NAK on Addr
            *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
            I2C_ERR_INC(I2C_ERRCNT_ADDR_NAK);
            if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if NAK
            return;
        }
        else
        {
            // Slave addr ACK, change to Rx mode
            i2c->currentStatus = I2C_RECEIVING;
            if(i2c->reqCount == 1)
                *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TXAK; // no STOP, Rx, NAK on recv
            else
                *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST; // no STOP, change to Rx
            data = *(i2c->D); // dummy read

            // Master receive loop
            while(i2c->rxBufferLength < i2c->reqCount && i2c->currentStatus == I2C_RECEIVING)
            {
                while(!(*(i2c->S) & I2C_S_IICIF) && (timeout == 0 || deltaT < timeout));
                *(i2c->S) = I2C_S_IICIF;
                chkTimeout = (timeout != 0 && deltaT >= timeout);
                // check if 2nd to last byte or timeout
                if((i2c->rxBufferLength+2) == i2c->reqCount || (chkTimeout && !i2c->timeoutRxNAK))
                {
                    *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TXAK; // no STOP, Rx, NAK on recv
                }
                // if last byte or timeout send STOP
                if((i2c->rxBufferLength+1) >= i2c->reqCount || (chkTimeout && i2c->timeoutRxNAK))
                {
                    i2c->timeoutRxNAK = 0; // clear flag
                    // change to Tx mode
                    *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
                    // grab last data
                    data = *(i2c->D);
                    i2c->rxBuffer[i2c->rxBufferLength++] = data;
                    if(i2c->currentStop == I2C_STOP) // NAK then STOP
                    {
                        delayMicroseconds(1); // empirical patch, lets things settle before issuing STOP
                        *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
                    }
                    // else NAK no STOP

                    // Set final status
                    if(chkTimeout)
                    {
                        i2c->currentStatus = I2C_TIMEOUT; // Rx incomplete, mark as timeout
                        I2C_ERR_INC(I2C_ERRCNT_TIMEOUT);
                        if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if timeout
                    }
                    else
                    {
                        i2c->currentStatus = I2C_WAITING; // Rx complete, change to waiting state
                        if(i2c->user_onReqFromDone != nullptr) i2c->user_onReqFromDone(); // Call Master Rx complete callback
                    }
                }
                else
                {
                    // grab next data, not last byte, will ACK
                    i2c->rxBuffer[i2c->rxBufferLength++] = *(i2c->D);
                }
                if(chkTimeout) i2c->timeoutRxNAK = 1; // set flag to indicate NAK sent
            }
        }
    }
    //
    // ISR/DMA mode - non-blocking
    //
    else if(i2c->opMode == I2C_OP_MODE_ISR || i2c->opMode == I2C_OP_MODE_DMA)
    {
        // send 1st data and enable interrupts
        i2c->currentStatus = I2C_SEND_ADDR;
        i2c->currentStop = sendStop;
        if(i2c->opMode == I2C_OP_MODE_DMA && i2c->reqCount >= 5) // limit transfers less than 5 bytes to ISR method
        {
            // init DMA, let the hack begin
            i2c->activeDMA = I2C_DMA_ADDR;
            i2c->DMA->source(*(i2c->D));
            i2c->DMA->destinationBuffer(&i2c->rxBuffer[0],i2c->reqCount-1); // DMA gets all except last byte
        }
        // start ISR
        *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX; // enable intr
        *(i2c->D) = (addr << 1) | 1; // address + READ
    }
}


// ------------------------------------------------------------------------------------------------------
// Get Wire Error - returns "Wire" error code from a failed Tx/Rx command
// return: 0=success, 1=data too long, 2=recv addr NACK, 3=recv data NACK, 4=other error (timeout, arb lost)
//
uint8_t i2c_t3::getError(void)
{
    // convert status to Arduino return values (give these a higher priority than buf overflow error)
    switch(i2c->currentStatus)
    {
    case I2C_BUF_OVF:  return 1;
    case I2C_ADDR_NAK: return 2;
    case I2C_DATA_NAK: return 3;
    case I2C_ARB_LOST: return 4;
    case I2C_TIMEOUT:  return 4;
    case I2C_NOT_ACQ:  return 4;
    default: break;
    }
    if(getWriteError()) return 1; // if write_error was set then flag as buffer overflow
    return 0; // no errors
}


// ------------------------------------------------------------------------------------------------------
// Done Check - returns simple complete/not-complete value to indicate I2C status
// return: 1=Tx/Rx complete (with or without errors), 0=still running
//
uint8_t i2c_t3::done_(struct i2cStruct* i2c)
{
    return (i2c->currentStatus < I2C_SENDING);
}


// ------------------------------------------------------------------------------------------------------
// Finish - blocking routine with timeout, loops until Tx/Rx is complete or timeout occurs
// return: 1=success (Tx or Rx completed, no error), 0=fail (NAK, timeout or Arb Lost)
// parameters:
//      timeout = timeout in microseconds
//
uint8_t i2c_t3::finish_(struct i2cStruct* i2c, uint8_t bus, uint32_t timeout)
{
    elapsedMicros deltaT;

    // update timeout
    timeout = (timeout == 0) ? i2c->defTimeout : timeout;

    // wait for completion or timeout
    deltaT = 0;
    while(!done_(i2c) && (timeout == 0 || deltaT < timeout));

    // DMA mode and timeout
    if(timeout != 0 && deltaT >= timeout && i2c->opMode == I2C_OP_MODE_DMA && i2c->activeDMA != I2C_DMA_OFF)
    {
        // If DMA mode times out, then wait for transfer to end then mark it as timeout.
        // This is done this way because abruptly ending the DMA seems to cause
        // the I2C_S_BUSY flag to get stuck, and I cannot find a reliable way to clear it.
        while(!done_(i2c));
        i2c->currentStatus = I2C_TIMEOUT;
    }

    // check exit status, if not done then timeout occurred
    if(!done_(i2c)) i2c->currentStatus = I2C_TIMEOUT; // set to timeout state

    // delay to allow bus to settle - allow Timeout or STOP to complete and be recognized.  Timeouts must
    //                                propagate through ISR, and STOP must be recognized on both
    //                                Master and Slave sides
    delayMicroseconds(4);

    // note that onTransmitDone, onReqFromDone, onError callbacks are handled in ISR, this is done
    // because use of this function is optional on background transfers
    if(i2c->currentStatus == I2C_WAITING) return 1;
    return 0;
}


// ------------------------------------------------------------------------------------------------------
// Write - write data to Tx buffer
// return: #bytes written = success, 0=fail
// parameters:
//      data = data byte
//
size_t i2c_t3::write(uint8_t data)
{
    if(i2c->txBufferLength < I2C_TX_BUFFER_LENGTH)
    {
        i2c->txBuffer[i2c->txBufferLength++] = data;
        return 1;
    }
    setWriteError();
    return 0;
}


// ------------------------------------------------------------------------------------------------------
// Write Array - write count number of bytes from data array to Tx buffer
// return: #bytes written = success, 0=fail
// parameters:
//      data = pointer to uint8_t array of data
//      count = number of bytes to write
//
size_t i2c_t3::write(const uint8_t* data, size_t count)
{
    if(i2c->txBufferLength < I2C_TX_BUFFER_LENGTH)
    {
        size_t avail = I2C_TX_BUFFER_LENGTH - i2c->txBufferLength;
        uint8_t* dest = i2c->txBuffer + i2c->txBufferLength;

        if(count > avail)
        {
            count = avail; // truncate to space avail if needed
            setWriteError();
        }
        memcpy(dest, data, count);
        i2c->txBufferLength += count;
        return count;
    }
    setWriteError();
    return 0;
}


// ------------------------------------------------------------------------------------------------------
// Read - returns next data byte (signed int) from Rx buffer
// return: data, -1 if buffer empty
//
int i2c_t3::read_(struct i2cStruct* i2c)
{
    if(i2c->rxBufferIndex >= i2c->rxBufferLength) return -1;
    return i2c->rxBuffer[i2c->rxBufferIndex++];
}


// ------------------------------------------------------------------------------------------------------
// Read Array - read count number of bytes from Rx buffer to data array
// return: #bytes read
// parameters:
//      data = pointer to uint8_t array of data
//      count = number of bytes to write
//
size_t i2c_t3::read_(struct i2cStruct* i2c, uint8_t* data, size_t count)
{
    if(i2c->rxBufferLength > i2c->rxBufferIndex)
    {
        size_t avail = i2c->rxBufferLength - i2c->rxBufferIndex;
        uint8_t* src = i2c->rxBuffer + i2c->rxBufferIndex;

        if(count > avail) count = avail; // truncate to data avail if needed
        memcpy(data, src, count);
        i2c->rxBufferIndex += count;
        return count;
    }
    return 0;
}


// ------------------------------------------------------------------------------------------------------
// Peek - returns next data byte (signed int) from Rx buffer without removing it from Rx buffer
// return: data, -1 if buffer empty
//
int i2c_t3::peek_(struct i2cStruct* i2c)
{
    if(i2c->rxBufferIndex >= i2c->rxBufferLength) return -1;
    return i2c->rxBuffer[i2c->rxBufferIndex];
}


// ------------------------------------------------------------------------------------------------------
// Read Byte - returns next data byte (uint8_t) from Rx buffer
// return: data, 0 if buffer empty
//
uint8_t i2c_t3::readByte_(struct i2cStruct* i2c)
{
    if(i2c->rxBufferIndex >= i2c->rxBufferLength) return 0;
    return i2c->rxBuffer[i2c->rxBufferIndex++];
}


// ------------------------------------------------------------------------------------------------------
// Peek Byte - returns next data byte (uint8_t) from Rx buffer without removing it from Rx buffer
// return: data, 0 if buffer empty
//
uint8_t i2c_t3::peekByte_(struct i2cStruct* i2c)
{
    if(i2c->rxBufferIndex >= i2c->rxBufferLength) return 0;
    return i2c->rxBuffer[i2c->rxBufferIndex];
}


// ======================================================================================================
// ------------------------------------------------------------------------------------------------------
// I2C Interrupt Service Routine
// ------------------------------------------------------------------------------------------------------
// ======================================================================================================


void i2c0_isr(void) // I2C0 ISR
{
    I2C0_INTR_FLAG_ON;
    i2c_isr_handler(&(i2c_t3::i2cData[0]),0);
    I2C0_INTR_FLAG_OFF;
}
#if I2C_BUS_NUM >= 2
    void i2c1_isr(void) // I2C1 ISR
    {
        I2C1_INTR_FLAG_ON;
        i2c_isr_handler(&(i2c_t3::i2cData[1]),1);
        I2C1_INTR_FLAG_OFF;
    }
#endif
#if I2C_BUS_NUM >= 3
    void i2c2_isr(void) // I2C2 ISR
    {
        I2C2_INTR_FLAG_ON;
        i2c_isr_handler(&(i2c_t3::i2cData[2]),2);
        I2C2_INTR_FLAG_OFF;
    }
#endif
#if I2C_BUS_NUM >= 4
    void i2c3_isr(void) // I2C3 ISR
    {
        I2C3_INTR_FLAG_ON;
        i2c_isr_handler(&(i2c_t3::i2cData[3]),3);
        I2C3_INTR_FLAG_OFF;
    }
#endif

//
// I2C ISR base handler
//
void i2c_isr_handler(struct i2cStruct* i2c, uint8_t bus)
{
    uint8_t status, c1, data;
    i2c_t3::isrActive++;

    status = *(i2c->S);
    c1 = *(i2c->C1);
    #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
        uint8_t flt = *(i2c->FLT);  // store flags
    #endif

    if(c1 & I2C_C1_MST)
    {
        //
        // Master Mode
        //
        if(c1 & I2C_C1_TX)
        {
            if(i2c->activeDMA == I2C_DMA_BULK || i2c->activeDMA == I2C_DMA_LAST)
            {
                if(i2c->DMA->complete() && i2c->activeDMA == I2C_DMA_BULK)
                {
                    // clear DMA interrupt, final byte should trigger another ISR
                    i2c->DMA->clearInterrupt();
                    *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX; // intr en, Tx mode, DMA disabled
                    // DMA says complete at the beginning of its last byte, need to
                    // wait until end of its last byte to re-engage ISR
                    i2c->activeDMA = I2C_DMA_LAST;
                    *(i2c->S) = I2C_S_IICIF; // clear intr
                }
                else if(i2c->activeDMA == I2C_DMA_LAST)
                {
                    // wait for TCF
                    while(!(*(i2c->S) & I2C_S_TCF));
                    // clear DMA, only do this after TCF
                    i2c->DMA->clearComplete();
                    // re-engage ISR for last byte
                    i2c->activeDMA = I2C_DMA_OFF;
                    i2c->txBufferIndex = i2c->txBufferLength-1;
                    *(i2c->D) = i2c->txBuffer[i2c->txBufferIndex];
                    *(i2c->S) = I2C_S_IICIF; // clear intr
                }
                else if(i2c->DMA->error())
                {
                    i2c->DMA->clearError();
                    i2c->DMA->clearInterrupt();
                    i2c->activeDMA = I2C_DMA_OFF;
                    i2c->currentStatus = I2C_DMA_ERR;
                    I2C_ERR_INC(I2C_ERRCNT_DMA_ERR);
                    // check arbitration
                    if(status & I2C_S_ARBL)
                    {
                        // Arbitration Lost
                        i2c->currentStatus = I2C_ARB_LOST;
                        I2C_ERR_INC(I2C_ERRCNT_ARBL);
                        *(i2c->S) = I2C_S_ARBL; // clear arbl flag
                        i2c->txBufferIndex = 0; // reset Tx buffer index to prepare for resend
                        // TODO does this need to check IAAS and drop to Slave Rx? if so set Rx + dummy read. not sure if this would work for DMA
                    }
                    *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled, DMA disabled
                    *(i2c->S) = I2C_S_IICIF; // clear intr
                    if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if DMA error or ARBL
                }
                i2c_t3::isrActive--;
                return;
            } // end DMA Tx
            else
            {
                // Continue Master Transmit
                // check if Master Tx or Rx
                if(i2c->currentStatus == I2C_SENDING)
                {
                    // check arbitration
                    if(status & I2C_S_ARBL)
                    {
                        // Arbitration Lost
                        i2c->activeDMA = I2C_DMA_OFF; // clear pending DMA (if happens on address byte)
                        i2c->currentStatus = I2C_ARB_LOST;
                        *(i2c->S) = I2C_S_ARBL; // clear arbl flag
                        *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?)
                        i2c->txBufferIndex = 0; // reset Tx buffer index to prepare for resend
                        // TODO does this need to check IAAS and drop to Slave Rx? if so set Rx + dummy read.
                        *(i2c->S) = I2C_S_IICIF; // clear intr
                        I2C_ERR_INC(I2C_ERRCNT_ARBL);
                        if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if ARBL
                    }
                    // check if slave ACK'd
                    else if(status & I2C_S_RXAK)
                    {
                        i2c->activeDMA = I2C_DMA_OFF; // clear pending DMA (if happens on address byte)
                        if(i2c->txBufferIndex == 0)
                        {
                            i2c->currentStatus = I2C_ADDR_NAK; // NAK on Addr
                            I2C_ERR_INC(I2C_ERRCNT_ADDR_NAK);
                        }
                        else
                        {
                            i2c->currentStatus = I2C_DATA_NAK; // NAK on Data
                            I2C_ERR_INC(I2C_ERRCNT_DATA_NAK);
                        }
                        // send STOP, change to Rx mode, intr disabled
                        // note: Slave NAK is an error, so send STOP regardless of setting
                        *(i2c->C1) = I2C_C1_IICEN;
                        *(i2c->S) = I2C_S_IICIF; // clear intr
                        if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if NAK
                    }
                    else
                    {
                        // check if last byte transmitted
                        if(++i2c->txBufferIndex >= i2c->txBufferLength)
                        {
                            // Tx complete, change to waiting state
                            i2c->currentStatus = I2C_WAITING;
                            // send STOP if configured
                            if(i2c->currentStop == I2C_STOP)
                                *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
                            else
                                *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; // no STOP, stay in Tx mode, intr disabled
                            // run TransmitDone callback when done
                            *(i2c->S) = I2C_S_IICIF; // clear intr
                            if(i2c->user_onTransmitDone != nullptr) i2c->user_onTransmitDone();
                        }
                        else if(i2c->activeDMA == I2C_DMA_ADDR)
                        {
                            // Start DMA
                            i2c->activeDMA = I2C_DMA_BULK;
                            *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX | I2C_C1_DMAEN; // intr en, Tx mode, DMA en
                            i2c->DMA->enable();
                            *(i2c->D) = i2c->txBuffer[1]; // DMA will start on next request
                            *(i2c->S) = I2C_S_IICIF; // clear intr
                        }
                        else
                        {
                            // ISR transmit next byte
                            *(i2c->D) = i2c->txBuffer[i2c->txBufferIndex];
                            *(i2c->S) = I2C_S_IICIF; // clear intr
                        }
                    }
                    i2c_t3::isrActive--;
                    return;
                }
                else if(i2c->currentStatus == I2C_SEND_ADDR)
                {
                    // Master Receive, addr sent
                    if(status & I2C_S_ARBL)
                    {
                        // Arbitration Lost
                        i2c->currentStatus = I2C_ARB_LOST;
                        *(i2c->S) = I2C_S_ARBL; // clear arbl flag
                        *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?)
                        // TODO does this need to check IAAS and drop to Slave Rx? if so set Rx + dummy read. not sure if this would work for DMA
                        *(i2c->S) = I2C_S_IICIF; // clear intr
                        I2C_ERR_INC(I2C_ERRCNT_ARBL);
                        if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if ARBL
                    }
                    else if(status & I2C_S_RXAK)
                    {
                        // Slave addr NAK
                        i2c->currentStatus = I2C_ADDR_NAK; // NAK on Addr
                        // send STOP, change to Rx mode, intr disabled
                        // note: Slave NAK is an error, so send STOP regardless of setting
                        *(i2c->C1) = I2C_C1_IICEN;
                        *(i2c->S) = I2C_S_IICIF; // clear intr
                        I2C_ERR_INC(I2C_ERRCNT_ADDR_NAK);
                        if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if NAK
                    }
                    else if(i2c->activeDMA == I2C_DMA_ADDR)
                    {
                        // Start DMA
                        i2c->activeDMA = I2C_DMA_BULK;
                        *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_DMAEN; // intr en, no STOP, change to Rx, DMA en
                        i2c->DMA->enable();
                        data = *(i2c->D); // dummy read
                        *(i2c->S) = I2C_S_IICIF; // clear intr
                    }
                    else
                    {
                        // Slave addr ACK, change to Rx mode
                        i2c->currentStatus = I2C_RECEIVING;
                        if(i2c->reqCount == 1)
                            *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TXAK; // no STOP, Rx, NAK on recv
                        else
                            *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST; // no STOP, change to Rx
                        data = *(i2c->D); // dummy read
                        *(i2c->S) = I2C_S_IICIF; // clear intr
                    }
                    i2c_t3::isrActive--;
                    return;
                }
                else if(i2c->currentStatus == I2C_TIMEOUT)
                {
                    // send STOP if configured
                    if(i2c->currentStop == I2C_STOP)
                        *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
                    else
                        *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; // no STOP, stay in Tx mode, intr disabled
                    *(i2c->S) = I2C_S_IICIF; // clear intr
                    I2C_ERR_INC(I2C_ERRCNT_TIMEOUT);
                    if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if timeout
                    i2c_t3::isrActive--;
                    return;
                }
                else
                {
                    // Should not be in Tx mode if not sending
                    // send STOP, change to Rx mode, intr disabled
                    *(i2c->C1) = I2C_C1_IICEN;
                    *(i2c->S) = I2C_S_IICIF; // clear intr
                    i2c_t3::isrActive--;
                    return;
                }
            } // end ISR Tx
        }
        else
        {
            // Continue Master Receive
            //
            if(i2c->activeDMA == I2C_DMA_BULK || i2c->activeDMA == I2C_DMA_LAST)
            {
                if(i2c->DMA->complete() && i2c->activeDMA == I2C_DMA_BULK) // 2nd to last byte
                {
                    // clear DMA interrupt, final byte should trigger another ISR
                    i2c->DMA->clearInterrupt();
                    *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TXAK; // intr en, Rx mode, DMA disabled, NAK on recv
                    i2c->activeDMA = I2C_DMA_LAST;
                    *(i2c->S) = I2C_S_IICIF; // clear intr
                }
                else if(i2c->activeDMA == I2C_DMA_LAST) // last byte
                {
                    // clear DMA
                    i2c->DMA->clearComplete();
                    i2c->activeDMA = I2C_DMA_OFF;
                    // change to Tx mode
                    *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
                    // grab last data
                    i2c->rxBufferLength = i2c->reqCount-1;
                    i2c->rxBuffer[i2c->rxBufferLength++] = *(i2c->D);
                    if(i2c->currentStop == I2C_STOP) // NAK then STOP
                    {
                        delayMicroseconds(1); // empirical patch, lets things settle before issuing STOP
                        *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
                    }
                    // else NAK no STOP
                    *(i2c->S) = I2C_S_IICIF; // clear intr
                    i2c->currentStatus = I2C_WAITING; // Rx complete, change to waiting state
                    if(i2c->user_onReqFromDone != nullptr) i2c->user_onReqFromDone(); // Call Master Rx complete callback
                }
                else if(i2c->DMA->error()) // not sure what would cause this...
                {
                    i2c->DMA->clearError();
                    i2c->DMA->clearInterrupt();
                    i2c->activeDMA = I2C_DMA_OFF;
                    i2c->currentStatus = I2C_DMA_ERR;
                    *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled, DMA disabled
                    *(i2c->S) = I2C_S_IICIF; // clear intr
                    I2C_ERR_INC(I2C_ERRCNT_DMA_ERR);
                    if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if DMA error
                }
                i2c_t3::isrActive--;
                return;
            }
            else
            {
                // check if 2nd to last byte or timeout
                if((i2c->rxBufferLength+2) == i2c->reqCount || (i2c->currentStatus == I2C_TIMEOUT && !i2c->timeoutRxNAK))
                {
                    *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TXAK; // no STOP, Rx, NAK on recv
                }
                // if last byte or timeout send STOP
                if((i2c->rxBufferLength+1) >= i2c->reqCount || (i2c->currentStatus == I2C_TIMEOUT && i2c->timeoutRxNAK))
                {
                    i2c->timeoutRxNAK = 0; // clear flag
                    // change to Tx mode
                    *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
                    // grab last data
                    i2c->rxBuffer[i2c->rxBufferLength++] = *(i2c->D);
                    if(i2c->currentStop == I2C_STOP) // NAK then STOP
                    {
                        delayMicroseconds(1); // empirical patch, lets things settle before issuing STOP
                        *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
                    }
                    // else NAK no STOP
                    *(i2c->S) = I2C_S_IICIF; // clear intr
                    // Rx complete
                    if(i2c->currentStatus == I2C_TIMEOUT)
                    {
                        I2C_ERR_INC(I2C_ERRCNT_TIMEOUT);
                        if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if timeout
                    }
                    else
                    {
                        i2c->currentStatus = I2C_WAITING;
                        if(i2c->user_onReqFromDone != nullptr) i2c->user_onReqFromDone(); // Call Master Rx complete callback
                    }
                }
                else
                {
                    // grab next data, not last byte, will ACK
                    i2c->rxBuffer[i2c->rxBufferLength++] = *(i2c->D);
                    *(i2c->S) = I2C_S_IICIF; // clear intr
                }
                if(i2c->currentStatus == I2C_TIMEOUT && !i2c->timeoutRxNAK)
                    i2c->timeoutRxNAK = 1; // set flag to indicate NAK sent

                i2c_t3::isrActive--;
                return;
            }
        }
    }
    else
    {
        //
        // Slave Mode
        //

        // ARBL makes no sense on Slave, but this might get set if there is a pullup problem and
        // SCL/SDA get stuck.  This is primarily to guard against ARBL flag getting stuck.
        if(status & I2C_S_ARBL)
        {
            // Arbitration Lost
            *(i2c->S) = I2C_S_ARBL; // clear arbl flag
            if(!(status & I2C_S_IAAS))
            {
                #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
                    *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF;  // clear STOP/START intr
                #endif
                *(i2c->S) = I2C_S_IICIF; // clear intr
                i2c_t3::isrActive--;
                return;
            }
        }
        if(status & I2C_S_IAAS)
        {
            // If in Slave Rx already, then RepSTART occured, run callback
            if(i2c->currentStatus == I2C_SLAVE_RX && i2c->user_onReceive != nullptr)
            {
                i2c->rxBufferIndex = 0;
                i2c->user_onReceive(i2c->rxBufferLength);
            }

            // Is Addressed As Slave
            if(status & I2C_S_SRW)
            {
                // Addressed Slave Transmit
                //
                i2c->currentStatus = I2C_SLAVE_TX;
                i2c->txBufferLength = 0;
                *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_TX;
                i2c->rxAddr = (*(i2c->D) >> 1); // read to get target addr
                if(i2c->user_onRequest != nullptr) i2c->user_onRequest(); // load Slave Tx buffer with data
                if(i2c->txBufferLength == 0) i2c->txBuffer[0] = 0; // send 0's if buffer empty
                *(i2c->D) = i2c->txBuffer[0]; // send first data
                i2c->txBufferIndex = 1;
            }
            else
            {
                // Addressed Slave Receive
                //
                // setup SDA-rising ISR - required for STOP detection in Slave Rx mode for 3.0/3.1/3.2
                #if defined(__MK20DX256__) && I2C_BUS_NUM == 2 // 3.1/3.2 (dual-bus)
                    i2c->irqCount = 0;
                    attachInterrupt(i2c->currentSDA, (bus == 0) ? i2c_t3::sda0_rising_isr : i2c_t3::sda1_rising_isr, RISING);
                #elif defined(__MK20DX128__) || defined(__MK20DX256__) // 3.0/3.1/3.2 (single-bus)
                    i2c->irqCount = 0;
                    attachInterrupt(i2c->currentSDA, i2c_t3::sda0_rising_isr, RISING);
                #elif defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
                    *(i2c->FLT) |= I2C_FLT_SSIE; // enable START/STOP intr for LC/3.5/3.6
                #endif
                i2c->currentStatus = I2C_SLAVE_RX;
                i2c->rxBufferLength = 0;
                *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE;
                i2c->rxAddr = (*(i2c->D) >> 1); // read to get target addr
            }
            #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
                *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF;  // clear STOP/START intr
            #endif
            *(i2c->S) = I2C_S_IICIF; // clear intr
            i2c_t3::isrActive--;
            return;
        }
        if(c1 & I2C_C1_TX)
        {
            // Continue Slave Transmit
            if((status & I2C_S_RXAK) == 0)
            {
                // Master ACK'd previous byte
                if(i2c->txBufferIndex < i2c->txBufferLength)
                    data = i2c->txBuffer[i2c->txBufferIndex++];
                else
                    data = 0; // send 0's if buffer empty
                *(i2c->D) = data;
                *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_TX;
            }
            else
            {
                // Master did not ACK previous byte
                *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE; // switch to Rx mode
                data = *(i2c->D); // dummy read
                i2c->currentStatus = I2C_WAITING;
            }
        }
        else if(i2c->currentStatus == I2C_SLAVE_RX)
        {
            #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
                if(flt & (I2C_FLT_STOPF|I2C_FLT_STARTF)) // STOP/START detected, run callback
                {
                    // LC (MKL26) appears to have the same I2C_FLT reg definition as 3.6 (K66)
                    // There is both STOPF and STARTF and they are both enabled via SSIE, and they must both
                    // be cleared in order to work
                    *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF;  // clear STOP/START intr
                    *(i2c->FLT) &= ~I2C_FLT_SSIE;                   // disable STOP/START intr (will re-enable on next IAAS)
                    *(i2c->S) = I2C_S_IICIF; // clear intr
                    i2c->currentStatus = I2C_WAITING;
                    // Slave Rx complete, run callback
                    if(i2c->user_onReceive != nullptr)
                    {
                        i2c->rxBufferIndex = 0;
                        i2c->user_onReceive(i2c->rxBufferLength);
                    }
                    i2c_t3::isrActive--;
                    return;
                }
            #endif
            // Continue Slave Receive
            //
            // setup SDA-rising ISR - required for STOP detection in Slave Rx mode for 3.0/3.1/3.2
            #if defined(__MK20DX256__) && I2C_BUS_NUM == 2 // 3.1/3.2 (dual-bus)
                i2c->irqCount = 0;
                attachInterrupt(i2c->currentSDA, (bus == 0) ? i2c_t3::sda0_rising_isr : i2c_t3::sda1_rising_isr, RISING);
            #elif defined(__MK20DX128__) || defined(__MK20DX256__) // 3.0/3.1/3.2 (single-bus)
                i2c->irqCount = 0;
                attachInterrupt(i2c->currentSDA, i2c_t3::sda0_rising_isr, RISING);
            #endif
            data = *(i2c->D);
            if(i2c->rxBufferLength < I2C_RX_BUFFER_LENGTH)
                i2c->rxBuffer[i2c->rxBufferLength++] = data;
        }
        #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
            *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF;  // clear STOP/START intr
        #endif
        *(i2c->S) = I2C_S_IICIF; // clear intr
    }
}

#if defined(__MK20DX128__) || defined(__MK20DX256__) // 3.0/3.1/3.2
// ------------------------------------------------------------------------------------------------------
// SDA-Rising Interrupt Service Routine - 3.0/3.1/3.2 only
//
// Detects the stop condition that terminates a slave receive transfer.
//

// I2C0 SDA ISR
void i2c_t3::sda0_rising_isr(void)
{
    i2c_t3::sda_rising_isr_handler(&(i2c_t3::i2cData[0]),0);
}

#if I2C_BUS_NUM >= 2
    // I2C1 SDA ISR
    void i2c_t3::sda1_rising_isr(void)
    {
        i2c_t3::sda_rising_isr_handler(&(i2c_t3::i2cData[1]),1);
    }
#endif

//
// SDA ISR base handler
//
void i2c_t3::sda_rising_isr_handler(struct i2cStruct* i2c, uint8_t bus)
{
    uint8_t status = *(i2c->S); // capture status first, can change if ISR is too slow
    if(!(status & I2C_S_BUSY))
    {
        i2c->currentStatus = I2C_WAITING;
        detachInterrupt(i2c->currentSDA);
        if(i2c->user_onReceive != nullptr)
        {
            i2c->rxBufferIndex = 0;
            i2c->user_onReceive(i2c->rxBufferLength);
        }
    }
    else
    {
        if(++(i2c->irqCount) >= 2 || !(i2c->currentMode == I2C_SLAVE))
            detachInterrupt(i2c->currentSDA);
    }
}
#endif // sda_rising_isr

// ------------------------------------------------------------------------------------------------------
// Instantiate
//
i2c_t3 Wire  = i2c_t3(0);       // I2C0
#if I2C_BUS_NUM >= 2
    i2c_t3 Wire1 = i2c_t3(1);   // I2C1
#endif
#if I2C_BUS_NUM >= 3
    i2c_t3 Wire2 = i2c_t3(2);   // I2C2
#endif
#if I2C_BUS_NUM >= 4
    i2c_t3 Wire3 = i2c_t3(3);   // I2C3
#endif

#endif // i2c_t3

/*
   ------------------------------------------------------------------------------------------------------
   Changelog
   ------------------------------------------------------------------------------------------------------

    - (v11.0) Modified 01Dec18 by Brian (nox771 at gmail.com)
        - Added state variables and modified pinConfigure_() to recognize unconfigured SCL/SDA pins, 
          allowing for setSCL()/setSDA() prior to begin(), which was previously blocked by bus busy 
          check on unconfigured pins.
        - Header change to MIT permissive license

    - (v10.1) Modified 02Jan18 by Brian (nox771 at gmail.com)
        - Added User #define to disable priority checks entirely
        - Added i2c_t3::isrActive flag to dynamically disable priority checks during ISR & callbacks.
          This is to prevent runaway priority escalation in cases of callbacks with nested Wire calls.

    - (v10.0) Modified 21Oct17 by Brian (nox771 at gmail.com)
        - Default assignments have been added to many functions for pins/pullup/rate/op_mode, so
          all those parameters are now optional in many function calls (marked ^ below)
        - Unbound SCL/SDA pin assignment.  Pins can be specified with either i2c_pins enum or by direct
          SCL,SDA pin definition (using any valid SCL and SDA pin).  New function summary is:
            - begin(mode, address1, ^i2c_pins, ^i2c_pullup, ^rate, ^i2c_op_mode)
            - begin(mode, address1, ^pinSCL, ^pinSDA, ^i2c_pullup, ^rate, ^i2c_op_mode)
            - pinConfigure(i2c_pins, ^pullup)
            - pinConfigure(pinSCL, pinSDA, ^pullup)
            - setSCL(pin)
            - setSDA(pin)
            - getSCL()
            - getSDA()
          Note: internal to i2c structure, currentPins has been replaced by currentSCL and currentSDA
        - Added Master callback functions for completion of transfers.  Primarily for
          sendTransmission/sendRequest, but these will also work on foreground commands
          endTransmission/requestFrom.  Also added an Error callback for Master bus errors.
            - onTransmitDone(function) - where function() is called when Master Transmit is complete
            - onReqFromDone(function) - where function() is called when Master Receive is complete
            - onError(function) - where function() is called upon any I2C error which terminates the
                                  Master bus operation (eg. NAK, timeout, acquire fail, etc)
        - Fixed blocking conditions that could occur in immediate mode
        - Added error counters which may be optionally enabled via I2C_ERROR_COUNTERS define.  When
          enabled it will track (Master mode only): Reset Bus (auto-retry only), Timeout, Addr NAK,
          Data NAK, Arb Lost, Bus Not Acquired, DMA Errors.
            - i2c_err_count enum, getErrorCount(), and zeroErrorCount() functions added

    - (v9.4) Modified 01Oct17 by Brian (nox771 at gmail.com)
        - Fixed Slave ISR for LC/3.5/3.6 not properly recognizing RepSTART
        - Fixed nested Wire calls during Slave ISR receive (calling Wire inside Wire1 Slave ISR)
        - Added uint8_t and char array read functions - Wire.read(databuf, count);

    - (v9.3) Modified 20Sep17 by Brian (nox771 at gmail.com)
        - Fixed Slave ISR for LC/3.5/3.6

    - (v9.2) Modified 29Dec16 by Brian (nox771 at gmail.com)
        - improved resetBus() function to reset C1 state (thanks hw999)

    - (v9.1) Modified 16Oct16 by Brian (nox771 at gmail.com)
        - applied two fixes due to bug reports:
            - removed I2C_F_DIV120 setting (120 divide-ratio) for I2C clock
            - disabled I2C_AUTO_RETRY by default (setting remains but must be manually enabled)

    - (v9) Modified 01Jul16 by Brian (nox771 at gmail.com)
        - Added support for Teensy 3.5/3.6:
            - fully supported (Master/Slave modes, IMM/ISR/DMA operation)
            - supports all available pin/bus options on Wire/Wire1/Wire2/Wire3
        - Fixed LC slave bug, whereby it was incorrectly detecting STOPs directed to other slaves
        - I2C rate is now set using a much more flexible method than previously used (this is partially
          motivated by increasing device count and frequencies).  As a result, the fixed set of rate
          enums are no longer needed (however they are currently still supported), and desired I2C
          frequency can be directly specified, eg. for 400kHz, I2C_RATE_400 can be replaced by 400000.
          Some setRate() functions are deprecated due to these changes.

    - (v8) Modified 02Apr15 by Brian (nox771 at gmail.com)
        - added support for Teensy LC:
            - fully supported (Master/Slave modes, IMM/ISR/DMA operation)
            - Wire: pins 16/17 or 18/19, rate limited to I2C_RATE_1200
            - Wire1: pins 22/23, rate limited to I2C_RATE_2400
        - added timeout on acquiring bus (prevents lockup when bus cannot be acquired)
        - added setDefaultTimeout() function for setting the default timeout to apply to all commands
        - added resetBus() function for toggling SCL to release stuck Slave devices
        - added setRate(rate) function, similar to setClock(freq), but using rate specifiers (does not
                require specifying busFreq)
        - added I2C_AUTO_RETRY user define

    - (v7) Modified 09Jan15 by Brian (nox771 at gmail.com)
        - added support for F_BUS frequencies: 60MHz, 56MHz, 48MHz, 36MHz, 24MHz, 16MHz, 8MHz, 4MHz, 2MHz
        - added new rates: I2C_RATE_1800, I2C_RATE_2800, I2C_RATE_3000
        - added new priority escalation - in cases where I2C ISR is blocked by having a lower priority than
                                          calling function, the I2C will either adjust I2C ISR to a higher
                                          priority, or switch to Immediate mode as needed.
        - added new operating mode control - I2C can be set to operate in ISR mode, DMA mode (Master only),
                                             or Immediate Mode (Master only)
        - added new begin() functions to allow setting the initial operating mode:
            - begin(i2c_mode mode, uint8_t address, i2c_pins pins, i2c_pullup pullup, i2c_rate rate,
                    i2c_op_mode opMode)
            - begin(i2c_mode mode, uint8_t address1, uint8_t address2, i2c_pins pins, i2c_pullup pullup,
                    i2c_rate rate, i2c_op_mode opMode)
        - added new functions:
            - uint8_t setOpMode(i2c_op_mode opMode) - used to change operating mode on the fly
                                                      (only when bus is idle)
            - void sendTransmission() - non-blocking Tx with implicit I2C_STOP, added for symmetry
                                        with endTransmission()
            - uint8_t setRate(uint32_t busFreq, i2c_rate rate) - used to set I2C clock dividers to get
                                                                 desired rate, i2c_rate argument
            - uint8_t setRate(uint32_t busFreq, uint32_t i2cFreq) - used to set I2C clock dividers to get
                                                                    desired SCL freq, uint32_t argument
                                                                    (quantized to nearest i2c_rate)
        - added new Wire compatibility functions:
            - void setClock(uint32_t i2cFreq) - (note: degenerate form of setRate() with busFreq == F_BUS)
            - uint8_t endTransmission(uint8_t sendStop)
            - uint8_t requestFrom(uint8_t addr, uint8_t len)
            - uint8_t requestFrom(uint8_t addr, uint8_t len, uint8_t sendStop)
        - fixed bug in Slave Range code whereby onRequest() callback occurred prior to updating rxAddr
          instead of after
        - fixed bug in arbitration, was missing from Master Tx mode
        - removed I2C1 defines (now included in kinetis.h)
        - removed all debug code (eliminates rbuf dependency)

    - (v6) Modified 16Jan14 by Brian (nox771 at gmail.com)
        - all new structure using dereferenced pointers instead of hardcoding. This allows functions
          (including ISRs) to be reused across multiple I2C buses.  Most functions moved to static,
          which in turn are called by inline user functions.  Added new struct (i2cData) for holding all
          bus information.
        - added support for Teensy 3.1 and I2C1 interface on pins 29/30 and 26/31.
        - added header define (I2C_BUS_ENABLE n) to control number of enabled buses (eg. both I2C0 & I2C1
          or just I2C0).  When using only I2C0 the code and ram usage will be lower.
        - added interrupt flag (toggles pin high during ISR) with independent defines for I2C0 and
          I2C1 (refer to header file), useful for logic analyzer trigger

    - (v5) Modified 09Jun13 by Brian (nox771 at gmail.com)
        - fixed bug in ISR timeout code in which timeout condition could fail to reset in certain cases
        - fixed bug in Slave mode in sda_rising_isr attach, whereby it was not getting attached on the addr byte
        - moved debug routines so they are entirely defined internal to the library (no end user code req'd)
        - debug routines now use IntervalTimer library
        - added support for range of Slave addresses
        - added getRxAddr() for Slave using addr range to determine its called address
        - removed virtual keyword from all functions (is not a base class)

    - (v1-v4) Modified 26Feb13 by Brian (nox771 at gmail.com)
        - Reworked begin function:
            - added option for pins to use (SCL:SDA on 19:18 or 16:17 - note pin order difference)
            - added option for internal pullup - as mentioned in previous code pullup is very strong,
                                                 approx 190 ohms, but is possibly useful for high speed I2C
            - added option for rates - 100kHz, 200kHz, 300kHz, 400kHz, 600kHz, 800kHz, 1MHz, 1.2MHz, <-- 24/48MHz bus
                                       1.5MHz, 2.0MHz, 2.4MHz                                        <-- 48MHz bus only
        - Removed string.h dependency (memcpy)
        - Changed Master modes to interrupt driven
        - Added non-blocking Tx/Rx routines, and status/done/finish routines:
            - sendTransmission() - non-blocking transmit
            - sendRequest() - non-blocking receive
            - status() - reports current status
            - done() - indicates Tx/Rx complete (for main loop polling if I2C is running in background)
            - finish() - loops until Tx/Rx complete or bus error
        - Added readByte()/peekByte() for uint8_t return values (note: returns 0 instead of -1 if buf empty)
        - Added fixes for Slave Rx mode - in short Slave Rx on this part is fubar
          (as proof, notice the difference in the I2Cx_FLT register in the KL25 Sub-Family parts)
            - the SDA-rising ISR hack can work but only detects STOP conditons.
              A slave Rx followed by RepSTART won't be detected since bus remains busy.
              To fix this if IAAS occurs while already in Slave Rx mode then it will
              assume RepSTART occurred and trigger onReceive callback.
        - Separated Tx/Rx buffer sizes for asymmetric devices (adjustable in i2c_t3.h)
        - Changed Tx/Rx buffer indicies to size_t to allow for large (>256 byte) buffers
        - Left debug routines in place (controlled via header defines - default is OFF).  If debug is
            enabled, note that it can easily overrun the Debug queue on large I2C transfers, yielding
            garbage output.  Adjust ringbuf size (in rbuf.h) and possibly PIT interrupt rate to adjust
            data flow to Serial (note also the buffer in Serial can overflow if written too quickly).
        - Added getError() function to return Wire error code
        - Added pinConfigure() function for changing pins on the fly (only when bus not busy)
        - Added timeouts to endTransmission(), requestFrom(), and finish()

    ------------------------------------------------------------------------------------------------------
*/
