/*
 * spiport.c
 *
 * Created: 2/7/2018 10:51:42 AM
 *  Author: Jake
 */ 

#include "spiport.h"

spiport_t spi_new(Sercom *com, PortGroup *port, uint32_t miso_pin, uint32_t mosi_pin, uint32_t sck_pin, uint32_t csn_pin, uint32_t apbx, uint32_t peripheral){
	spiport_t spi;
	
	spi.com = com;
	spi.port = port;
	
	spi.miso_pin = miso_pin;
	spi.miso_bm = (uint32_t)(1 << miso_pin);
	spi.mosi_pin = mosi_pin;
	spi.mosi_bm = (uint32_t)(1 << mosi_pin);
	spi.sck_pin = sck_pin;
	spi.sck_bm = (uint32_t)(1 << sck_pin);
	spi.csn_pin = csn_pin;
	spi.csn_bm = (uint32_t)(1 << csn_pin);
	
	spi.apbx = apbx;
	spi.peripheral = peripheral;
	
	return spi;
}

void spi_init(spiport_t *spi, uint32_t gclknum, uint32_t gclkidcore, uint8_t baud, uint8_t dipo, uint8_t dopo, uint8_t csnhardware, uint8_t cpha, uint8_t cpol, uint8_t lsbfirst){
	// to add to this lib: doc, cleaning, options properly enumerated: do when doing AS5147
	// clk is unmasked (external to this lib)
	// do pin configs
	spi->port->DIRCLR.reg = spi->miso_bm;
	spi->port->PINCFG[spi->miso_pin].bit.PMUXEN = 1;
	spi->port->DIRSET.reg = spi->mosi_bm | spi->sck_bm | spi->csn_bm;
	spi->port->PINCFG[spi->mosi_pin].bit.PMUXEN = 1;
	spi->port->PINCFG[spi->sck_pin].bit.PMUXEN = 1;
	
	if(csnhardware){
		spi->port->PINCFG[spi->csn_pin].bit.PMUXEN = 1;
		spi->com->SPI.CTRLB.reg |= SERCOM_SPI_CTRLB_MSSEN;
		if(spi->csn_pin % 2){ // yes if odd
			spi->port->PMUX[spi->csn_pin >> 1].reg |= PORT_PMUX_PMUXO(spi->peripheral);
			} else {
			spi->port->PMUX[spi->csn_pin >> 1].reg |= PORT_PMUX_PMUXE(spi->peripheral);
		}
	}else{
		spi->port->OUTSET.reg = spi->csn_bm; // set hi to start! - this should properly depend on our CPHA !
	}
	
	if(spi->miso_pin % 2){ // yes if odd
		spi->port->PMUX[spi->miso_pin >> 1].reg |= PORT_PMUX_PMUXO(spi->peripheral);
	} else {
		spi->port->PMUX[spi->miso_pin >> 1].reg |= PORT_PMUX_PMUXE(spi->peripheral);
	}
	
	if(spi->mosi_pin % 2){ // yes if odd
		spi->port->PMUX[spi->mosi_pin >> 1].reg |= PORT_PMUX_PMUXO(spi->peripheral);
		} else {
		spi->port->PMUX[spi->mosi_pin >> 1].reg |= PORT_PMUX_PMUXE(spi->peripheral);
	}
	
	if(spi->sck_pin % 2){ // yes if odd
		spi->port->PMUX[spi->sck_pin >> 1].reg |= PORT_PMUX_PMUXO(spi->peripheral);
		} else {
		spi->port->PMUX[spi->sck_pin >> 1].reg |= PORT_PMUX_PMUXE(spi->peripheral);
	}
	
	// build a clock for
	GCLK->GENCTRL[gclknum].reg = GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_DFLL) | GCLK_GENCTRL_GENEN;
	while(GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_GENCTRL(gclknum));
	GCLK->PCHCTRL[gclkidcore].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(gclknum);
	
	// now some SERCOM
	// tmc spi is clock inactive hi
	// tmc spi latches data on the rising edge of sck and drives data out on the next falling edge
	spi->com->SPI.CTRLA.bit.ENABLE = 0;
	// master, data in pinout, data out pinout
	spi->com->SPI.CTRLA.reg |= SERCOM_SPI_CTRLA_MODE(0x03);
	if(cpol){
		spi->com->SPI.CTRLA.reg |= SERCOM_SPI_CTRLA_CPOL;
	}
	if(cpha){
		spi->com->SPI.CTRLA.reg	|= SERCOM_SPI_CTRLA_CPHA;
	}
	if(lsbfirst){
		SERCOM0->SPI.CTRLA.reg |= SERCOM_SPI_CTRLA_DORD; // 0 MSB, 1 LSB
	}
	spi->com->SPI.CTRLA.reg |= SERCOM_SPI_CTRLA_DIPO(dipo) | SERCOM_SPI_CTRLA_DOPO(dopo);
	// these to defaults, but here for show
	//SERCOM0->SPI.CTRLB.reg |= SERCOM_SPI_CTRLB_CHSIZE(0x0); // 8 bits character - 0x0, so no need to set
	// BAUD
	// f_baud = f_ref / (2 * (BAUD +1)) so BAUD = f_ref / (2 * f_baud) - 1
	spi->com->SPI.BAUD.reg |= SERCOM_SPI_BAUD_BAUD(baud);
	// use hardware slave select, enable receiver
	spi->com->SPI.CTRLB.reg |= SERCOM_SPI_CTRLB_RXEN; //SERCOM_SPI_CTRLB_MSSEN | 
	// 8 or 32 bits
	// do if(bits)
	//spi->com->SPI.CTRLC.reg |= SERCOM_SPI_CTRLC_DATA32B;
	//spi->com->SPI.LENGTH.reg |= SERCOM_SPI_LENGTH_LENEN | SERCOM_SPI_LENGTH_LEN(20);
	
	// turnt it up
	spi->com->SPI.CTRLA.bit.ENABLE = 1;
}

void spi_txchar_polled(spiport_t *spi, uint8_t data){
	while(!(spi->com->SPI.INTFLAG.bit.DRE));
	spi->com->SPI.DATA.reg = SERCOM_SPI_DATA_DATA(data);
}

void spi_txchars_polled(spiport_t *spi, uint8_t *data, uint8_t length){
	spi->port->OUTCLR.reg = spi->csn_bm;
	for(int i = 0; i < length; i ++){
		spi_txchar_polled(spi, data[i]);
	}
	while(!spi->com->SPI.INTFLAG.bit.TXC); // wait for complete of last byte before
	spi->port->OUTSET.reg = spi->csn_bm;
}

void spi_txrxchars_polled(spiport_t *spi, uint8_t *data_tx, uint8_t length, uint8_t *data_rx){
	spi->port->OUTCLR.reg = spi->csn_bm;
	for(int i = 0; i < length; i ++){
		while(!(spi->com->SPI.INTFLAG.bit.DRE));
		spi->com->SPI.DATA.reg = SERCOM_SPI_DATA_DATA(data_tx[i]);
		while(!(spi->com->SPI.INTFLAG.bit.RXC)); // wait for data to come out of rx buffer
		data_rx[i] = spi->com->SPI.DATA.reg;
	}
	while(!spi->com->SPI.INTFLAG.bit.TXC); // wait for complete of last byte before
	spi->port->OUTSET.reg = spi->csn_bm;
}

void spi_txrxchar_polled(spiport_t *spi, uint8_t data, uint8_t *rxdata){
	// not yet bc so far have not needed single receive
}