/*
 * i2c_irq.c
 */
#include <string.h>
// NXP LPC register definitions header
#include "LPC17xx.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"
#include "i2c.h"


#define I2C_USE_IRQ

#define I2C_IDLE              0
#define I2C_STARTED           1
#define I2C_RESTARTED         2
#define I2C_REPEATED_START    3
#define DATA_ACK              4
#define DATA_NACK             5
#define I2C_BUSY              6
#define I2C_NO_DATA           7
#define I2C_NACK_ON_ADDRESS   8
#define I2C_NACK_ON_DATA      9
#define I2C_ARBITRATION_LOST  10
#define I2C_TIME_OUT          11
#define I2C_OK                12

#define I2CONSET_I2EN       (0x1<<6)  /* I2C Control Set Register */
#define I2CONSET_AA         (0x1<<2)
#define I2CONSET_SI         (0x1<<3)
#define I2CONSET_STO        (0x1<<4)
#define I2CONSET_STA        (0x1<<5)

#define I2CONCLR_AAC        (0x1<<2)  /* I2C Control clear Register */
#define I2CONCLR_SIC        (0x1<<3)
#define I2CONCLR_STAC       (0x1<<5)
#define I2CONCLR_I2ENC      (0x1<<6)

#define I2C_BUFSIZE 64
/* i2c transfer descriptor uses shared buffer for
 * read and write. If both read and write are to
 * be performed write is done first. When write is
 * complete the buffer is used to store data from
 * the device. Thus the written data is not preserved
 * on combined write/read operation.
 */

/*
 * From device to device, the I2C communication protocol may vary,
 * in the example below, the protocol uses repeated start
 * to read data from the device:
 * For master read: the sequence is: STA,Addr(W),offset,RE-STA,Addr(r),data...STO
 * for master write: the sequence is: STA,Addr(W),offset,data...STO
 * Thus, in state 8, the address is always WRITE.
 * In state 10, the address is always READ. The address is
 * taken from the first byte of buffer since the device address
 * must always be the first byte transmitted byte in i2c transfer.
 */

typedef struct i2c_descriptor_ {
	int state;
	int rlength;
	int wlength;
	int pos;
	struct i2c_descriptor_ *next;
	void (*confirm) (struct i2c_descriptor_ *);
	uint8_t buf[I2C_BUFSIZE];
} I2C_DESC;

#ifdef I2C_USE_IRQ

static int i2c_send(void);

static I2C_DESC *I2C_IRQHandler(I2C_DESC *desc, LPC_I2C_TypeDef *LPC_I2C, xSemaphoreHandle binHandle, portBASE_TYPE *xHPW);

I2C_DESC *i2c1_q;
xSemaphoreHandle i2c1_binary;
xSemaphoreHandle i2c1_mutex;

void I2C1_IRQHandler(void) {
	portBASE_TYPE xHigherPriorityWoken = pdFALSE;
	i2c1_q = I2C_IRQHandler(i2c1_q, LPC_I2C1, i2c1_binary, &xHigherPriorityWoken);
	portEND_SWITCHING_ISR(xHigherPriorityWoken);
}


/* This general handler can serve all i2c interrupts */
static I2C_DESC *I2C_IRQHandler(I2C_DESC *desc, LPC_I2C_TypeDef *LPC_I2C, xSemaphoreHandle binHandle, portBASE_TYPE *xHPW)
{
	uint8_t StatValue;

	/* this handler deals with master read and master write only */
	StatValue = LPC_I2C->I2STAT;
	switch (StatValue) {
	case 0x08: /* A start condition was issued. */
		if (desc->wlength > 0) {
			/* Write first byte and enforce write mode */
			LPC_I2C->I2DAT = desc->buf[0] & 0xFE; /* write mode */
			LPC_I2C->I2CONCLR = I2CONCLR_STAC;
			desc->pos = 1;
		} else {
			/* In the (unlikely) case that this is read only
			 * operation go to master receiver mode */
			LPC_I2C->I2DAT = desc->buf[0] | 0x01; /* read mode */
			LPC_I2C->I2CONCLR = I2CONCLR_STAC;
			desc->pos = 1;
		}
		break;

	case 0x10: /* A repeated started was issued, for us it means switch to master receive mode */
		if (desc->rlength > 0) {
			/* Send SLA with R bit set, */
			LPC_I2C->I2DAT = desc->buf[0] | 0x01; /* read mode */
			LPC_I2C->I2CONCLR = I2CONCLR_STAC;
			desc->pos = 0;
		} else {
			/* should never happen, can we just send STOP? */
			LPC_I2C->I2CONSET = I2CONSET_STO; /* Set Stop flag */
		}
		break;

	case 0x18: /* Master Transmit, SLA_W has been sent, ACK received */
		if (desc->pos >= desc->wlength) {
			/* write is completed */
			if (desc->rlength > 0) {
				/* read not done, issue new start */
				LPC_I2C->I2CONSET = I2CONSET_STA; /* Set Repeated-start flag */
			} else {
				LPC_I2C->I2CONSET = I2CONSET_STO; /* Set Stop flag */
				desc->state = I2C_NO_DATA;
			}
		} else {
			LPC_I2C->I2DAT = desc->buf[desc->pos++];
		}
		break;

	case 0x28: /* Data byte has been transmitted, ACK received */
		if (desc->pos < desc->wlength) {
			LPC_I2C->I2DAT = desc->buf[desc->pos++];
		} else {
			if (desc->rlength > 0) {
				LPC_I2C->I2CONSET = I2CONSET_STA; /* Set Repeated-start flag */
			} else {
				LPC_I2C->I2CONSET = I2CONSET_STO; /* Set Stop flag */
				desc->state = I2C_OK;
			}
		}
		break;

	case 0x30: /* Data byte has been transmitted, NACK received */
		LPC_I2C->I2CONSET = I2CONSET_STO; /* Set Stop flag */
		desc->state = I2C_NACK_ON_DATA;
		break;

	case 0x40: /* Master Receive, SLA_R has been sent and acknowledged */
		if ((desc->pos + 1) < desc->rlength) {
			/* More than one byte to read */
			LPC_I2C->I2CONSET = I2CONSET_AA; /* assert ACK after data is received */
		} else {
			/* Only one byte to read */
			LPC_I2C->I2CONCLR = I2CONCLR_AAC; /* assert NACK after data is received */
		}
		break;

	case 0x50: /* Data byte has been received, ACK has been sent */
		desc->buf[desc->pos++] = LPC_I2C->I2DAT;
		if ((desc->pos + 1) < desc->rlength) {
			LPC_I2C->I2CONSET = I2CONSET_AA; /* assert ACK after data is received */
		} else {
			LPC_I2C->I2CONCLR = I2CONCLR_AAC; /* assert NACK on last byte */
		}
		break;

	case 0x58: /* Data byte has been received, NACK has been sent */
		/* this implies that we have received all bytes */
		desc->buf[desc->pos++] = LPC_I2C->I2DAT;
		desc->state = I2C_OK;
		LPC_I2C->I2CONSET = I2CONSET_STO; /* Set Stop flag */
		break;

	case 0x20: /* Master Transmit, SLA_W has been sent, NACK received */
	case 0x48: /* Master Receive, SLA_R has been sent, NACK received */
		LPC_I2C->I2CONSET = I2CONSET_STO; /* Set Stop flag */
		desc->state = I2C_NACK_ON_ADDRESS;
		break;

	case 0x38: /* Arbitration lost */
		/* In this handler, we don't deal with multiple master situation */
	default:
		if(LPC_I2C->I2CONSET & I2CONSET_STA) {
			LPC_I2C->I2CONCLR = I2CONCLR_STAC;
		}
		desc->state = I2C_ARBITRATION_LOST;
		break;
	}
	LPC_I2C->I2CONCLR = I2CONCLR_SIC;

	if(desc->state != I2C_BUSY) {
#if 0
		/* immediate retry */
		if(desc->state == I2C_NACK_ON_ADDRESS) {
			desc->state = I2C_BUSY;
			LPC_I2C->I2CONSET = I2CONSET_STA;
			return desc;
		}
#endif
		/* confirm completion of operation */
		if(desc->confirm != NULL) desc->confirm(desc);
		/* go to next operation */
		desc = desc->next;
		/* check if the list is empty */
		if(desc != NULL) {
			LPC_I2C->I2CONSET = I2CONSET_STA;
		}
		else {
			xSemaphoreGiveFromISR(binHandle, xHPW);
		}
	}
	return desc;
}


// global variable is allocated at compile time
// which saves stack space
I2C_DESC tdesc;



// init function must be called from a running task since it needs to take the newly created semaphore
void i2c_init(void)
{
	LPC_PINCON->PINSEL1 |= (1 << 7) | (1 << 6) | (1 << 8) | (1 << 9); // enable SDA & SCL at P0.19 & P0.20

	tdesc.confirm = NULL;
	tdesc.next = NULL;
	/*--- Clear flags ---*/
	LPC_I2C1->I2CONCLR = I2CONSET_AA | I2CONSET_SI | I2CONSET_STA | I2CONSET_I2EN;

	/*--- Reset registers ---*/
	LPC_I2C1->I2SCLL   = 0x20; // orig 0x20
	LPC_I2C1->I2SCLH   = 0x20; // orig 0x20

	vSemaphoreCreateBinary(i2c1_binary);
	xSemaphoreTake(i2c1_binary, 0); // binary semaphore created "given" so we need to take if first

	i2c1_mutex = xSemaphoreCreateMutex();

	portENTER_CRITICAL();

	NVIC_SetPriority( I2C1_IRQn, configI2C_INTERRUPT_PRIORITY );
	/* Install interrupt handler */
	NVIC_EnableIRQ(I2C1_IRQn);

	/*--- Enable I2C controller ---*/
	LPC_I2C1->I2CONSET = I2CONSET_I2EN;

	portEXIT_CRITICAL();

}


int i2c_write(int device, int address, int length, const char* data_ptr)
{
	if((length + 3) > I2C_BUFSIZE) return -1;

	xSemaphoreTake(i2c1_mutex, portMAX_DELAY);

	tdesc.buf[0] = device;
	tdesc.buf[1] = address >> 8;
	tdesc.buf[2] = address & 0xFF;
	memcpy(tdesc.buf+3, data_ptr, length);
	tdesc.wlength = length + 3;
	tdesc.rlength = 0;
	tdesc.state = I2C_BUSY;

	i2c_send();

	/* little delay to allow write to complete (spec. say max 5 ms) */
	vTaskDelay(configTICK_RATE_HZ / 200);

	xSemaphoreGive(i2c1_mutex);

	return 0;
}

int i2c_read(int device, int address, int length, char* data_ptr)
{
	if(length > I2C_BUFSIZE) return -1;

	xSemaphoreTake(i2c1_mutex, portMAX_DELAY);

	tdesc.buf[0] = device;
	tdesc.buf[1] = address >> 8;
	tdesc.buf[2] = address & 0xFF;
	tdesc.wlength = 3;
	tdesc.rlength = length;
	tdesc.state = I2C_BUSY;

	i2c_send();

	memcpy(data_ptr, tdesc.buf, length);

	xSemaphoreGive(i2c1_mutex);

	return 0;
}

int i2c_readb(int device, int address)
{
	xSemaphoreTake(i2c1_mutex, portMAX_DELAY);

	tdesc.buf[0] = device;
	tdesc.buf[1] = address >> 8;
	tdesc.buf[2] = address & 0xFF;
	tdesc.wlength = 3;
	tdesc.rlength = 1;
	tdesc.state = I2C_BUSY;

	i2c_send();

	xSemaphoreGive(i2c1_mutex);

	return tdesc.buf[0];
}

int i2c_writeb(int device, int address, int data)
{
	xSemaphoreTake(i2c1_mutex, portMAX_DELAY);

	tdesc.buf[0] = device;
	tdesc.buf[1] = address >> 8;
	tdesc.buf[2] = address & 0xFF;
	tdesc.buf[3] = data;
	tdesc.wlength = 4;
	tdesc.rlength = 0;
	tdesc.state = I2C_BUSY;

	i2c_send();

	/* little delay to allow write to complete (spec. say max 5 ms) */
	vTaskDelay(configTICK_RATE_HZ / 200);

	xSemaphoreGive(i2c1_mutex);

	return 0;
}

static int i2c_send(void)
{

	tdesc.confirm = NULL;
	tdesc.next = NULL; // we could set multiple operations back to back but in this case we do one at a time
	i2c1_q = &tdesc; // set queue head
	LPC_I2C1->I2CONSET = I2CONSET_STA; /* Set Start flag */

	// wait until ISR signals that it is ready
	xSemaphoreTake(i2c1_binary, portMAX_DELAY);

	return 0;
}

#endif
