
#include "Wire.h"


#if defined(__IMXRT1062__)

//#include "debug/printf.h"

#define PINCONFIG (IOMUXC_PAD_ODE | IOMUXC_PAD_SRE | IOMUXC_PAD_DSE(4) | IOMUXC_PAD_SPEED(1) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3) | IOMUXC_PAD_HYS)


//***************************************************
//  Master Mode
//***************************************************

FLASHMEM void TwoWire::begin(void)
{
	// use 24 MHz clock
	CCM_CSCDR2 = (CCM_CSCDR2 & ~CCM_CSCDR2_LPI2C_CLK_PODF(63)) | CCM_CSCDR2_LPI2C_CLK_SEL;
	hardware.clock_gate_register |= hardware.clock_gate_mask;

	IMXRT_LPI2C_t* port = (IMXRT_LPI2C_t*)portAddr;
	port->MCR = LPI2C_MCR_RST;
	setClock(100000);
	// setSDA() & setSCL() may be called before or after begin()
	configSDApin(sda_pin_index_); // Setup SDA register
	configSCLpin(scl_pin_index_); // setup SCL register
}

void TwoWire::end()
{
}


size_t TwoWire::write(uint8_t data)
{
	if (transmitting || slave_mode) {
		if (txBufferLength >= BUFFER_LENGTH+1) {
			setWriteError();
			return 0;
		}
		txBuffer[txBufferLength++] = data;
		return 1;
	}
	return 0;
}

size_t TwoWire::write(const uint8_t *data, size_t quantity)
{
	if (transmitting || slave_mode) {
		size_t avail = BUFFER_LENGTH+1 - txBufferLength;
		if (quantity > avail) {
			quantity = avail;
			setWriteError();
		}
		memcpy(txBuffer + txBufferLength, data, quantity);
		txBufferLength += quantity;
		return quantity;
	}
	return 0;
}

// 2      BBF = Bus Busy Flag
// 1      MBF = Master Busy Flag
//   40   DMF = Data Match Flag
//   20   PLTF = Pin Low Timeout Flag
//   10   FEF = FIFO Error Flag
//   08   ALF = Arbitration Lost Flag
//   04   NDF = NACK Detect Flag
//   02   SDF = STOP Detect Flag
//   01   EPF = End Packet Flag
//      2 RDF = Receive Data Flag
//      1 TDF = Transmit Data Flag

bool TwoWire::wait_idle()
{
	IMXRT_LPI2C_t* port = (IMXRT_LPI2C_t*)portAddr;
	elapsedMillis timeout = 0;
	while (1) {
		uint32_t status = port->MSR; // pg 2899 & 2892
		if (!(status & LPI2C_MSR_BBF)) break; // bus is available
		if (status & LPI2C_MSR_MBF) break; // we already have bus control
		if (timeout > 16) {
			//Serial.printf("timeout waiting for idle, MSR = %x\n", status);
			if (force_clock()) break;
			//Serial.printf("unable to get control of I2C bus\n");
			return false;
		}
	}
	port->MSR = 0x00007F00; // clear all prior flags
	return true;
}


uint8_t TwoWire::endTransmission(uint8_t sendStop)
{
	IMXRT_LPI2C_t* port = (IMXRT_LPI2C_t*)portAddr;
	uint32_t tx_len = txBufferLength;
	if (!tx_len) return 4; // no address for transmit
	if (!wait_idle()) return 4;
	uint32_t tx_index = 0; // 0=start, 1=addr, 2-(N-1)=data, N=stop
	elapsedMillis timeout = 0;
	while (1) {
		// transmit stuff, if we haven't already
		if (tx_index <= tx_len) {
			uint32_t fifo_used = port->MFSR & 0x07; // pg 2914
			while (fifo_used < 4) {
				if (tx_index == 0) {
					port->MTDR = LPI2C_MTDR_CMD_START | txBuffer[0];
					tx_index = 1;
				} else if (tx_index < tx_len) {
					port->MTDR = LPI2C_MTDR_CMD_TRANSMIT | txBuffer[tx_index++];
				} else {
					if (sendStop) port->MTDR = LPI2C_MTDR_CMD_STOP;
					tx_index++;
					break;
				}
				fifo_used++;
			}
		}
		// monitor status
		uint32_t status = port->MSR; // pg 2884 & 2891
		if (status & LPI2C_MSR_ALF) {
			port->MCR |= LPI2C_MCR_RTF | LPI2C_MCR_RRF; // clear FIFOs
			return 4; // we lost bus arbitration to another master
		}
		
		if (status & LPI2C_MSR_FEF) {
			port->MCR |= LPI2C_MCR_RTF | LPI2C_MCR_RRF; // clear FIFOs
			// empirical evidence suggests NOT trying to send a STOP condition!
			return 5; // FIFO error
		}
		
		if (status & LPI2C_MSR_NDF) {
			port->MCR |= LPI2C_MCR_RTF | LPI2C_MCR_RRF; // clear FIFOs
			port->MTDR = LPI2C_MTDR_CMD_STOP;
			return 2; // NACK (assume address, TODO: how to tell address from data)
		}
		if ((status & LPI2C_MSR_PLTF) || timeout > 50) {
			port->MCR |= LPI2C_MCR_RTF | LPI2C_MCR_RRF; // clear FIFOs
			port->MTDR = LPI2C_MTDR_CMD_STOP; // try to send a stop
			return 4; // clock stretched too long or generic timeout
		}
		// are we done yet?
		if (tx_index > tx_len) {
			uint32_t tx_fifo = port->MFSR & 0x07;
			if (tx_fifo == 0 && ((status & LPI2C_MSR_SDF) || !sendStop)) {
				return 0;
			}
		}
		yield();
	}
}

uint8_t TwoWire::requestFrom(uint8_t address, uint8_t length, uint8_t sendStop)
{
	IMXRT_LPI2C_t* port = (IMXRT_LPI2C_t*)portAddr;
	if (!wait_idle()) return 4;
	address = (address & 0x7F) << 1;
	if (length < 1) length = 1;
	if (length > 255) length = 255;
	rxBufferIndex = 0;
	rxBufferLength = 0;
	uint32_t tx_state = 0; // 0=begin, 1=start, 2=data, 3=stop
	elapsedMillis timeout = 0;
	while (1) {
		// transmit stuff, if we haven't already
		if (tx_state < 3) {
			uint32_t tx_fifo = port->MFSR & 0x07; // pg 2914
			while (tx_fifo < 4 && tx_state < 3) {
				if (tx_state == 0) {
					port->MTDR = LPI2C_MTDR_CMD_START | 1 | address;
				} else if (tx_state == 1) {
					port->MTDR = LPI2C_MTDR_CMD_RECEIVE | (length - 1);
				} else {
					if (sendStop) port->MTDR = LPI2C_MTDR_CMD_STOP;
				}
				tx_state++;
				tx_fifo--;
			}
		}
		// receive stuff
		if (rxBufferLength < sizeof(rxBuffer)) {
			uint32_t rx_fifo = (port->MFSR >> 16) & 0x07;
			while (rx_fifo > 0 && rxBufferLength < sizeof(rxBuffer)) {
				rxBuffer[rxBufferLength++] = port->MRDR;
				rx_fifo--;
			}
		}
		// monitor status, check for error conditions
		uint32_t status = port->MSR; // pg 2884 & 2891
		if (status & LPI2C_MSR_ALF) {
			port->MCR |= LPI2C_MCR_RTF | LPI2C_MCR_RRF; // clear FIFOs
			break;
		}
		if ((status & LPI2C_MSR_NDF) || (status & LPI2C_MSR_PLTF) || timeout > 50) {
			port->MCR |= LPI2C_MCR_RTF | LPI2C_MCR_RRF; // clear FIFOs
			port->MTDR = LPI2C_MTDR_CMD_STOP; // try to send a stop
			break;
		}
		// are we done yet?
		if (rxBufferLength >= length && tx_state >= 3) {
			uint32_t tx_fifo = port->MFSR & 0x07;
			if (tx_fifo == 0 && ((status & LPI2C_MSR_SDF) || !sendStop)) {
				break;
			}
		}
		yield();
	}
	uint32_t rx_fifo = (port->MFSR >> 16) & 0x07;
	if (rx_fifo > 0) port->MCR |= LPI2C_MCR_RRF;
	return rxBufferLength;
}

uint8_t TwoWire::requestFrom(uint8_t addr, uint8_t qty, uint32_t iaddr, uint8_t n, uint8_t stop)
{
	if (n > 0) {
		union { uint32_t ul; uint8_t b[4]; } iaddress;
		iaddress.ul = iaddr;
		beginTransmission(addr);
		if (n > 3) n = 3;
		do {
			n = n - 1;
			write(iaddress.b[n]);
		} while (n > 0);
		endTransmission(false);
	}
	if (qty > BUFFER_LENGTH) qty = BUFFER_LENGTH;
	return requestFrom(addr, qty, stop);
}

bool TwoWire::force_clock()
{
	bool ret = false;
	uint32_t sda_pin = hardware.sda_pins[sda_pin_index_].pin;
	uint32_t scl_pin = hardware.scl_pins[scl_pin_index_].pin;
	uint32_t sda_mask = digitalPinToBitMask(sda_pin);
	uint32_t scl_mask = digitalPinToBitMask(scl_pin);
	// take control of pins with GPIO
	*portConfigRegister(sda_pin) = 5 | 0x10;
	*portSetRegister(sda_pin) = sda_mask;
	*portModeRegister(sda_pin) |= sda_mask;
	*portConfigRegister(scl_pin) = 5 | 0x10;
	*portSetRegister(scl_pin) = scl_mask;
	*portModeRegister(scl_pin) |= scl_mask;
	delayMicroseconds(10);
	for (int i=0; i < 9; i++) {
		if ((*portInputRegister(sda_pin) & sda_mask)
		  && (*portInputRegister(scl_pin) & scl_mask)) {
			// success, both pins are high
			ret = true;
			break;
		}
		*portClearRegister(scl_pin) = scl_mask;
		delayMicroseconds(5);
		*portSetRegister(scl_pin) = scl_mask;
		delayMicroseconds(5);
	}
	// return control of pins to I2C
	*(portConfigRegister(sda_pin)) = hardware.sda_pins[sda_pin_index_].mux_val;
	*(portConfigRegister(scl_pin)) = hardware.scl_pins[scl_pin_index_].mux_val;
	return ret;
}



//***************************************************
//  Slave Mode
//***************************************************

// registers start on page 2835

void TwoWire::begin(uint8_t address)
{
	IMXRT_LPI2C_t* port = (IMXRT_LPI2C_t*)portAddr;
	CCM_CSCDR2 = (CCM_CSCDR2 & ~CCM_CSCDR2_LPI2C_CLK_PODF(63)) | CCM_CSCDR2_LPI2C_CLK_SEL;
	hardware.clock_gate_register |= hardware.clock_gate_mask;
	// setSDA() & setSCL() may be called before or after begin()
	configSDApin(sda_pin_index_); // Setup SDA register
	configSCLpin(scl_pin_index_); // setup SCL register
	port->SCR = LPI2C_SCR_RST;
	port->SCR = 0;
	port->SCFGR1 = LPI2C_SCFGR1_TXDSTALL | LPI2C_SCFGR1_RXSTALL; // page 2841
	port->SCFGR2 = LPI2C_SCFGR2_FILTSDA(2) | LPI2C_SCFGR2_FILTSCL(2)
		| LPI2C_SCFGR2_DATAVD(3) | LPI2C_SCFGR2_CLKHOLD(2); // page 2803 and 2844
	port->SAMR = LPI2C_SAMR_ADDR0(address);
	attachInterruptVector(hardware.irq_number, hardware.irq_function);
	NVIC_SET_PRIORITY(hardware.irq_number, 144);
	NVIC_ENABLE_IRQ(hardware.irq_number);
	port->SIER = LPI2C_SIER_TDIE | LPI2C_SIER_RDIE | LPI2C_SIER_SDIE | LPI2C_SIER_RSIE;
	transmitting = 0;
	slave_mode = 1;
	port->SCR = LPI2C_SCR_SEN | LPI2C_SCR_FILTEN;
}


void TwoWire::isr(void)
{
	IMXRT_LPI2C_t* port = (IMXRT_LPI2C_t*)portAddr;
	uint32_t status = port->SSR;
	uint32_t w1c_bits = status & 0xF00;
	if (w1c_bits) port->SSR = w1c_bits;

	//Serial.print("isr ");
	//Serial.println(status, HEX);

	if (status & LPI2C_SSR_RDF) { // Receive Data Flag
		int rx = port->SRDR;
		if (rx & 0x8000) {
			rxBufferIndex = 0;
			rxBufferLength = 0;
		}
		if (rxBufferLength < BUFFER_LENGTH) {
			rxBuffer[rxBufferLength++] = rx & 255;
		}
		//Serial.print("rx = ");
		//Serial.println(rx, HEX);
	}
	if (status & LPI2C_SSR_TDF) { // Transmit Data Flag
		if (!transmitting) {
			if (user_onRequest != nullptr) {
				(*user_onRequest)();
			}
			txBufferIndex = 0;
			transmitting = 1;
		}
		if (txBufferIndex < txBufferLength) {
			port->STDR = txBuffer[txBufferIndex++];
		} else {
			port->STDR = 0;
		}
		//Serial.println("tx");
	}

	if ((status & LPI2C_SSR_SDF) || (status & LPI2C_SSR_RSF)) { // Stop or Repeat Start
		//Serial.println("Stop");
		if (rxBufferLength > 0 && user_onReceive != nullptr) {
			(*user_onReceive)(rxBufferLength);
		}
		rxBufferIndex = 0;
		rxBufferLength = 0;
		txBufferIndex = 0;
		txBufferLength = 0;
		transmitting = 0;
	}
}




//***************************************************
//  Pins Configuration
//***************************************************


FLASHMEM void TwoWire::setSDA(uint8_t pin) {
	if (pin == hardware.sda_pins[sda_pin_index_].pin) return;
	uint32_t newindex=0;
	while (1) {
		uint32_t sda_pin = hardware.sda_pins[newindex].pin;
		if (sda_pin == 255) return;
		if (sda_pin == pin) break;
		if (++newindex >= sizeof(hardware.sda_pins)) return;
	}
	if ((hardware.clock_gate_register & hardware.clock_gate_mask)) {
		// disable old pin, hard to know what to go back to?
		*(portConfigRegister(hardware.sda_pins[sda_pin_index_].pin)) = 5;
		// setup new one...
		configSDApin(newindex);
	}
	sda_pin_index_ = newindex;
}

FLASHMEM void TwoWire::configSDApin(uint8_t i)
{
	*(portControlRegister(hardware.sda_pins[i].pin)) = PINCONFIG;
	*(portConfigRegister(hardware.sda_pins[i].pin)) = hardware.sda_pins[i].mux_val;
	if (hardware.sda_pins[i].select_input_register) {
		*(hardware.sda_pins[i].select_input_register) = hardware.sda_pins[i].select_val;
	}
}

FLASHMEM void TwoWire::setSCL(uint8_t pin) {
	if (pin == hardware.scl_pins[scl_pin_index_].pin) return;
	uint32_t newindex=0;
	while (1) {
		uint32_t scl_pin = hardware.scl_pins[newindex].pin;
		if (scl_pin == 255) return;
		if (scl_pin == pin) break;
		if (++newindex >= sizeof(hardware.scl_pins)) return;
	}
	if ((hardware.clock_gate_register & hardware.clock_gate_mask)) {
		// disable old pin, hard to know what to go back to?
		*(portConfigRegister(hardware.scl_pins[scl_pin_index_].pin)) = 5;
		// setup new one...
		configSCLpin(newindex);
	}
	scl_pin_index_ = newindex;
}

FLASHMEM void TwoWire::configSCLpin(uint8_t i)
{
	*(portControlRegister(hardware.scl_pins[i].pin)) = PINCONFIG;
	*(portConfigRegister(hardware.scl_pins[i].pin)) = hardware.scl_pins[i].mux_val;
	if (hardware.scl_pins[i].select_input_register) {
		*(hardware.scl_pins[i].select_input_register) = hardware.scl_pins[i].select_val;
	}
}



#if defined(ARDUINO_TEENSY_MICROMOD)
void lpi2c1_isr(void) { Wire.isr(); }
void lpi2c3_isr(void) { Wire2.isr(); }
void lpi2c4_isr(void) { Wire1.isr(); }
void lpi2c2_isr(void) { Wire3.isr(); }
#else
void lpi2c1_isr(void) { Wire.isr(); }
void lpi2c3_isr(void) { Wire1.isr(); }
void lpi2c4_isr(void) { Wire2.isr(); }
#endif

PROGMEM
const TwoWire::I2C_Hardware_t TwoWire::i2c1_hardware = {
	CCM_CCGR2, CCM_CCGR2_LPI2C1(CCM_CCGR_ON),
		{{18, 3 | 0x10, &IOMUXC_LPI2C1_SDA_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}},
		{{19, 3 | 0x10, &IOMUXC_LPI2C1_SCL_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}},
	IRQ_LPI2C1, &lpi2c1_isr
};
TwoWire Wire(IMXRT_LPI2C1_ADDRESS, TwoWire::i2c1_hardware);

PROGMEM
const TwoWire::I2C_Hardware_t TwoWire::i2c3_hardware = {
	CCM_CCGR2, CCM_CCGR2_LPI2C3(CCM_CCGR_ON),
#if defined(ARDUINO_TEENSY41)
		{{17, 1 | 0x10, &IOMUXC_LPI2C3_SDA_SELECT_INPUT, 2}, {44, 2 | 0x10, &IOMUXC_LPI2C3_SDA_SELECT_INPUT, 1}},
		{{16, 1 | 0x10, &IOMUXC_LPI2C3_SCL_SELECT_INPUT, 2}, {45, 2 | 0x10, &IOMUXC_LPI2C3_SCL_SELECT_INPUT, 1}},
#else  // T4 and ARDUINO_TEENSY_MICROMOD
		{{17, 1 | 0x10, &IOMUXC_LPI2C3_SDA_SELECT_INPUT, 2}, {36, 2 | 0x10, &IOMUXC_LPI2C3_SDA_SELECT_INPUT, 1}},
		{{16, 1 | 0x10, &IOMUXC_LPI2C3_SCL_SELECT_INPUT, 2}, {37, 2 | 0x10, &IOMUXC_LPI2C3_SCL_SELECT_INPUT, 1}},
#endif
	IRQ_LPI2C3, &lpi2c3_isr
};
//TwoWire Wire1(&IMXRT_LPI2C3, TwoWire::i2c3_hardware);

PROGMEM
const TwoWire::I2C_Hardware_t TwoWire::i2c4_hardware = {
	CCM_CCGR6, CCM_CCGR6_LPI2C4_SERIAL(CCM_CCGR_ON),
		{{25, 0 | 0x10, &IOMUXC_LPI2C4_SDA_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}},
		{{24, 0 | 0x10, &IOMUXC_LPI2C4_SCL_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}},
	IRQ_LPI2C4, &lpi2c4_isr
};
//TwoWire Wire2(&IMXRT_LPI2C4, TwoWire::i2c4_hardware);

#if defined(ARDUINO_TEENSY_MICROMOD)
	TwoWire Wire2(IMXRT_LPI2C3_ADDRESS, TwoWire::i2c3_hardware);
	TwoWire Wire1(IMXRT_LPI2C4_ADDRESS, TwoWire::i2c4_hardware);
#else
	TwoWire Wire1(IMXRT_LPI2C3_ADDRESS, TwoWire::i2c3_hardware);
	TwoWire Wire2(IMXRT_LPI2C4_ADDRESS, TwoWire::i2c4_hardware);
#endif


#if defined(ARDUINO_TEENSY_MICROMOD)
PROGMEM
const TwoWire::I2C_Hardware_t TwoWire::i2c2_hardware = {
	CCM_CCGR2, CCM_CCGR2_LPI2C2(CCM_CCGR_ON),
		{{41, 2 | 0x10, &IOMUXC_LPI2C2_SDA_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}},
		{{40, 2 | 0x10, &IOMUXC_LPI2C2_SCL_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}},
	IRQ_LPI2C2, &lpi2c2_isr
};
TwoWire Wire3(IMXRT_LPI2C2_ADDRESS, TwoWire::i2c2_hardware);
#endif





// Timeout if a device stretches SCL this long, in microseconds
#define CLOCK_STRETCH_TIMEOUT 15000


void TwoWire::setClock(uint32_t frequency)
{
	IMXRT_LPI2C_t* port = (IMXRT_LPI2C_t*)portAddr;
	port->MCR = 0;
	if (frequency < 400000) {
		// 100 kHz
		port->MCCR0 = LPI2C_MCCR0_CLKHI(55) | LPI2C_MCCR0_CLKLO(59) |
			LPI2C_MCCR0_DATAVD(25) | LPI2C_MCCR0_SETHOLD(40);
		port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(1);
		port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(5) | LPI2C_MCFGR2_FILTSCL(5) |
			LPI2C_MCFGR2_BUSIDLE(3000); // idle timeout 250 us
		port->MCFGR3 = LPI2C_MCFGR3_PINLOW(CLOCK_STRETCH_TIMEOUT * 12 / 256 + 1);
	} else if (frequency < 1000000) {
		// 400 kHz
		port->MCCR0 = LPI2C_MCCR0_CLKHI(26) | LPI2C_MCCR0_CLKLO(28) |
			LPI2C_MCCR0_DATAVD(12) | LPI2C_MCCR0_SETHOLD(18);
		port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0);
		port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(2) | LPI2C_MCFGR2_FILTSCL(2) |
			LPI2C_MCFGR2_BUSIDLE(3600); // idle timeout 150 us
		port->MCFGR3 = LPI2C_MCFGR3_PINLOW(CLOCK_STRETCH_TIMEOUT * 24 / 256 + 1);
	} else {
		// 1 MHz
		port->MCCR0 = LPI2C_MCCR0_CLKHI(9) | LPI2C_MCCR0_CLKLO(10) |
			LPI2C_MCCR0_DATAVD(4) | LPI2C_MCCR0_SETHOLD(7);
		port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0);
		port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(1) | LPI2C_MCFGR2_FILTSCL(1) |
			LPI2C_MCFGR2_BUSIDLE(2400); // idle timeout 100 us
		port->MCFGR3 = LPI2C_MCFGR3_PINLOW(CLOCK_STRETCH_TIMEOUT * 24 / 256 + 1);
	}
	port->MCCR1 = port->MCCR0;
	port->MCFGR0 = 0;
	port->MFCR = LPI2C_MFCR_RXWATER(1) | LPI2C_MFCR_TXWATER(1);
	port->MCR = LPI2C_MCR_MEN;
}

#endif
