
// ****************************************************************************
//
//                         Page I2C - I2C scanner
//
// ****************************************************************************
// Software bit-banged I2C implementation

#include "../include.h"

// fast set mode of SDA and SCL pin (without setting pulls)
#define I2C_SDA_MODE(mode) I2C_SDA_PORT->CFGLR = (I2C_SDA_PORT->CFGLR & ~(0x0f << (I2C_SDA_PIN*4))) | (mode << (I2C_SDA_PIN*4))
#define I2C_SCL_MODE(mode) I2C_SCL_PORT->CFGLR = (I2C_SCL_PORT->CFGLR & ~(0x0f << (I2C_SCL_PIN*4))) | (mode << (I2C_SCL_PIN*4))

// GPIO manipulation
#define I2C_SCL_HIGH()		I2C_SCL_PORT->BSHR = 1<<I2C_SCL_PIN	// set SCL to HIGH
#define I2C_SCL_LOW()		I2C_SCL_PORT->BCR = 1<<I2C_SCL_PIN	// set SCL to LOW
#define I2C_SCL_INPUT()		I2C_SCL_MODE(8);			// set SCL to input mode with pulls (to simulate open-drain)
#define I2C_SCL_OUTPUT()	I2C_SCL_MODE(5);			// set SCL to open-drain ouput mode

#define I2C_SDA_HIGH()		I2C_SDA_PORT->BSHR = 1<<I2C_SDA_PIN	// set SDA to HIGH
#define I2C_SDA_LOW()		I2C_SDA_PORT->BCR = 1<<I2C_SDA_PIN	// set SDA to LOW
#define I2C_SDA_INPUT()		I2C_SDA_MODE(8);			// set SDA to input mode with pulls (to simulate open-drain)
#define I2C_SDA_OUTPUT()	I2C_SDA_MODE(5);			// set SDA to open-drain ouput mode
#define I2C_SDA_INDATA()	GPIOx_In(I2C_SDA_PORT, I2C_SDA_PIN)	// get input data

// delay, speed ~100kHz
NOINLINE void I2C_Delay(void)
{
	volatile int i;
	for (i = 60; i > 0; i--) nop();
}

// drive SDA low
NOINLINE void I2C_SDA_Low(void)
{
	I2C_SDA_LOW();		// set SDA to LOW state
	I2C_SDA_OUTPUT();	// set SDA to open-drain output mode
}

// drive SDA high (release bus)
NOINLINE void I2C_SDA_High(void)
{
	I2C_SDA_INPUT();	// set SDA to input mode with pulls
	I2C_SDA_HIGH();		// set SDA high, to select pull-up
}

// drive SCL low
NOINLINE void I2C_SCL_Low(void)
{
	I2C_SCL_LOW();		// set SCL to LOW state
	I2C_SCL_OUTPUT();	// set SCL to open-drain output mode
}

// drive SCL high (release bus)
NOINLINE void I2C_SCL_High(void)
{
	I2C_SCL_INPUT();	// set SCL to input mode with pulls
	I2C_SCL_HIGH();		// set SCL high, to select pull-up
}

// read SDA data
NOINLINE u8 I2C_SDA_Read(void)
{
	return I2C_SDA_INDATA();
}

// start I2C transfer
void I2C_Start(void)
{
	// release bus
	I2C_SDA_High();		// drive SDA high
	I2C_SCL_High();		// drive SCL high
	I2C_Delay();		// delay

	// start condition: SDA goes low, while SCL is high
	I2C_SDA_Low();		// drive SDA low
	I2C_Delay();		// delay

	// drive SCL low
	I2C_SCL_Low();		// drive SCL low
}

// stop I2C transfer
void I2C_Stop(void)
{
	// set initial state - SDA must be low
	I2C_SDA_Low();		// drive SDA low
	I2C_Delay();		// delay

	// stop condition: SDA goes high while SCL is high
	I2C_SCL_High();		// drive SCL high
	I2C_Delay();		// delay

	// SDA release
	I2C_SDA_High();		// drive SDA high
	I2C_Delay();		// delay
}

// write I2C byte and check ACK (returns 0=ACK, 1=NACK)
u8 I2C_Write(u8 data)
{
	int i;
	s32 d = (s32)data << (32-8);
	for (i = 8; i > 0; i--)
	{
		// set data output
		if (d < 0)
			I2C_SDA_High();	// drive SDA high
		else
			I2C_SDA_Low();	// drive SDA low
		I2C_Delay();	// delay

		// SCL pulse
		I2C_SCL_High();		// drive SCL high
		I2C_Delay();		// delay
		I2C_SCL_Low();		// drive SCL low

		// shift data
		d <<= 1;
	}

	// handle ACK
	I2C_SDA_High();		// drive SDA high
	I2C_Delay();		// delay
	I2C_SCL_High();		// drive SCL high
	I2C_Delay();		// delay
	u8 ack = I2C_SDA_INDATA(); // read SDA bus
	I2C_SCL_Low();		// drive SCL low
	return ack;
}

// I2C Initialize
void I2C_Init()
{
	// release bus
	I2C_SDA_High();		// drive SDA high
	I2C_SCL_High();		// drive SCL high
}

// I2C terminate
void I2C_Term()
{
	// reset pins
	GPIO_PinReset(I2C_SDA_GPIO);
	GPIO_PinReset(I2C_SCL_GPIO);
}

// I2C scan and display
void I2C_Disp()
{
	u8 ch;

	// clear screen
	DrawRectClrFast(0, TITLE_H, WIDTH, HEIGHT-TITLE_H);

	// select font 8x12
	SelFont12();

	// scan I2C (one entry is "0x7F ", 3 entries per row, 4 rows, 12 entries total)
	int x = 0;
	int y = TITLE_H;
	int addr;
	for (addr = 1; addr < 127; addr++)
	{
		// start I2C transfer
		I2C_Start();

		// check one address
		ch = I2C_Write(addr << 1);

		// stop I2C transfer
		I2C_Stop();

		// check result (0=ACK)
		if (ch == 0)
		{
			// print address
			DrawChar('0', x, y); x += 8;
			DrawChar('x', x, y); x += 8;
			DrawChar((addr >> 4) + '0', x, y); x += 8;
			ch = (addr & 0x0f) + '0';
			if (ch > '9') ch += 7;
			DrawChar(ch, x, y); x += 8;
			DrawChar(' ', x, y); x += 8;

			if (x >= 3*5*8)
			{
				x = 0;
				y += 12;
				if (y >= HEIGHT) break;
			}
		}
	}

	// display update
	DispUpdate();
}

// Page I2C (returns key PREV/NEXT)
u8 PageI2C()
{
	u8 key;

	// I2C initialize
	I2C_Init();

	// I2C scan and display
	I2C_Disp();

	while (True)
	{
		// reload watchdog counter
		IWDG_Reload();

		// I2C scan and display
		I2C_Disp();

		// keyboard input
		key = KeyGet();
		switch (key)
		{
		// change page
		case KEY_PREV:
		case KEY_NEXT:
			// I2C terminate
			I2C_Term();
			return key;
		}
	}
}
