/*
 * $Id: efm8_smb1.c 464 2019-08-01 02:15:54Z true $
 * begin 20190505 true
 *
 * TODO: make work with smb0 as well with little code overhead
 */

#include "driver/efm8_smb1.h"


/*
 * copied from the interwebs and modified significantly as needed
 * this is a master-only implementation
 */

volatile uint8_t smb1_state = SMB1_IDLE;

static uint8_t idata smb1_slave_addr;		// address of currently addressed slave; we are master only

static uint8_t *smb1_buf;
static uint16_t smb1_buf_len;
static uint16_t smb1_buf_idx;

static void (*smb1_cb)(uint8_t);


void smb1_mwrite(uint8_t slave, const char *buf, uint16_t bytes, void(*completed_callback)(uint8_t))
{
	uint8_t sfrsave;

	smb1_slave_addr = slave;
	smb1_buf = buf;
	smb1_buf_len = bytes;
	smb1_cb = completed_callback;

	smb1_state = SMB1_MW_STA_SENT;

	sfrsave = SFRPAGE;
	SFRPAGE = SMB1_PAGE;

	SMB1CN0_STA = 1;	//initiate the transaction by setting the start-condition bit

	SFRPAGE = sfrsave;
}

void smb1_mread(uint8_t slave, char *buf, uint16_t bytes, void(*completed_callback)(uint8_t))
{
	uint8_t sfrsave;

	smb1_slave_addr = slave;
	smb1_buf = buf;
	smb1_buf_len = bytes;
	smb1_cb = completed_callback;

	smb1_state = SMB1_MR_STA_SENT;

	sfrsave = SFRPAGE;
	SFRPAGE = SMB1_PAGE;

	SMB1CN0_STA = 1;	//initiate the transaction by setting the start-condition bit

	SFRPAGE = sfrsave;
}

/*
 * is it wise to send this when starting up, just in case a device is hung on the bus?
 */
void smb1_stop()
{
	uint8_t sfrsave;

	sfrsave = SFRPAGE;
	SFRPAGE = SMB1_PAGE;

	SMB1CN0_STO = 1;

	SFRPAGE = sfrsave;
}

uint8_t smb1_get_state()
{
	return smb1_state;
}

/* interrupt */
void smb1_interrupt()
{
	uint8_t sfrsave;

	sfrsave = SFRPAGE;
	SFRPAGE = SMB1_PAGE;

	switch(smb1_state)
	{
		// Master Write ====
		  // master transmit byte (step 3+)
		case SMB1_MW_BYTE_SENT: {
			if (!SMB0CN0_ACK) {
				// cancel transmission and release bus
				SMB1CN0_STO = 1;	// transmit stop condition
				smb1_state = SMB1_IDLE;

				if (smb1_cb != 0) {
					smb1_cb(1);
				}

				break;
			}

			if (smb1_buf_idx >= smb1_buf_len) {
				// we sent all the data
				SMB1CN0_STO = 1;		// transmit stop condition
				smb1_state = SMB1_IDLE;	// set current state as IDLE

				if (smb1_cb != 0) {
					smb1_cb(1);
				}

				break;
			}

			SMB1DAT = smb1_buf[smb1_buf_idx++];

			break;
		}
		  // write start (step 1)
		case SMB1_MW_STA_SENT: {
			SMB1CN0_STA = 0;	// clear start-condition bit
			SMB1CN0_STO = 0;	// make sure that stop-condition bit is cleared

			SMB1DAT = (smb1_slave_addr << 1);
			smb1_state = SMB1_MW_ADDR_SENT;	// set state variable to next state
			break;
		}

		  // write address transmit (step 2)
		case SMB1_MW_ADDR_SENT: {
			if (!SMB0CN0_ACK) {
				// cancel transmission and release bus
				SMB1CN0_STO = 1;	// transmit stop condition
				smb1_state = SMB1_IDLE;
				break;
			}

			SMB1DAT = smb1_buf[0];
			smb1_buf_idx = 1;

			smb1_state = SMB1_MW_BYTE_SENT;

			break;
		}

		// Master Read ====
		  // read start
		case SMB1_MR_STA_SENT: {
			SMB1CN0_STA = 0;	// clear start-condition bit
			SMB1CN0_STO = 0;	// make sure that stop-condition bit is cleared
			SMB1DAT = (smb1_slave_addr << 1) | 0x01;	//combine slave address with R/nW = 1

			smb1_state = SMB1_MR_ADDR_SENT;		//set state variable to next state
			SMB0CN0_SI = 0;	//clear interrupt flag

			break;
		}

		  // read address transmit
		case SMB1_MR_ADDR_SENT: {
			if (!SMB1CN0_ACK) {
				// cancel transmission and release bus
				SMB0CN0_STO = 1;			// transmit stop condition
				smb1_state = SMB1_IDLE;		// set current state as IDLE

				break;
			}

			if (smb1_buf_len == 1) {
				// single byte; master NACKs to say "stop transmitting"
				SMB1CN0_ACK = 0;
			} else {
				// multi byte; master ACKs to say "continue transmitting"
				SMB0CN0_ACK = 1;
			}

			smb1_buf_idx = 0;
			smb1_state = SMB1_MR_READ_BYTE;

			break;
		}

		  // master receive byte
		case SMB1_MR_READ_BYTE: {
			smb1_buf[smb1_buf_idx++] = SMB1DAT;

			if (smb1_buf_idx >= smb1_buf_len) {
				// data done; release bus
				SMB1CN0_STO = 1;	// transmit stop condition
				SMB1CN0_SI = 0;		// clear interrupt flag
				smb1_state = SMB1_IDLE;

				if (smb1_cb != 0) {
					smb1_cb(1);
				}

				break;
			}

			if (smb1_buf_idx == (smb1_buf_len - 1)) {
				SMB1CN0_ACK = 0;	// master NACKs to say "stop transmitting"
			} else {
				SMB0CN0_ACK = 1;	// master ACKs to say "continue transmitting"
			}

			break;
		}
	}

	// clear interrupt
	SMB1CN0_SI = 0;

	SFRPAGE = sfrsave;
}
