#include "driver/hspi.h"

/*
Pinout:
MISO GPIO12
MOSI GPIO13
CLK GPIO14
CS GPIO15
DC GPIO2
*/

#define __min(a,b) ((a > b) ? (b):(a))
uint32_t *spi_fifo;
uint32_t current_spi_port;

spi_config spi_init(uint32_t spi_port, uint32_t prescaler, spi_mode mode)
{
	spi_config config;

	WRITE_PERI_REG(PERIPHS_IO_MUX, 0x105); //clear bit9

	if (spi_port == SPI)
	{
		PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_DATA0_U, 2); // SD_D0 MISO
		PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_DATA1_U, 2); // SD_D1 MOSI
		PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_CLK_U, 2); // CLK
	} else if (spi_port == HSPI)
	{
		PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, 2); // HSPIQ MISO GPIO12
		PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 2); // HSPID MOSI GPIO13
		PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, 2); // CLK GPIO14
		PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 2); // CS GPIO15
	}


	config.mode = mode;
	config.spi_port = spi_port;
	// SPI clock = CPU clock / 10 / 4
	// time length HIGHT level = (CPU clock / 10 / 2) ^ -1,
	// time length LOW level = (CPU clock / 10 / 2) ^ -1
	config.clock_reg_val = (((prescaler - 1) & SPI_CLKDIV_PRE) << SPI_CLKDIV_PRE_S) |
			   ((1 & SPI_CLKCNT_N) << SPI_CLKCNT_N_S) |
			   ((0 & SPI_CLKCNT_H) << SPI_CLKCNT_H_S) |
			   ((1 & SPI_CLKCNT_L) << SPI_CLKCNT_L_S);

	spi_reinit(&config);
	return config;
}

void spi_reinit(spi_config *config)
{
	current_spi_port = config->spi_port;
	spi_fifo = (uint32_t*)SPI_FLASH_C0(current_spi_port);

	uint32_t regvalue = SPI_FLASH_DOUT;
	WRITE_PERI_REG(SPI_FLASH_CLOCK(current_spi_port), config->clock_reg_val);
	WRITE_PERI_REG(SPI_FLASH_CTRL1(current_spi_port), 0);

	switch(config->mode)
	{
		case spi_mode_tx:
		    regvalue &= ~(BIT2 | SPI_FLASH_USR_ADDR | SPI_FLASH_USR_DUMMY | SPI_FLASH_USR_DIN | SPI_USR_COMMAND | SPI_DOUTDIN);
			break;
		case spi_mode_txrx:
			regvalue |= SPI_DOUTDIN | SPI_CK_I_EDGE;
		    regvalue &= ~(BIT2 | SPI_FLASH_USR_ADDR | SPI_FLASH_USR_DUMMY | SPI_FLASH_USR_DIN | SPI_USR_COMMAND);
			break;
	}

	WRITE_PERI_REG(SPI_FLASH_USER(current_spi_port), regvalue);
}

void spi_send_uint16_r(uint16_t data, int32_t repeats)
{
	uint32_t i;
	uint32_t word = data << 16 | data;

	while(repeats > 0)
	{
		uint16_t bytes_to_transfer = __min(repeats * sizeof(uint16_t) , SPIFIFOSIZE * sizeof(uint32_t));
		spi_wait_ready();
		spi_prepare_tx(bytes_to_transfer);
		for(i = 0; i < (bytes_to_transfer + 3) / 4;i++)
			spi_fifo[i] = word;
		spi_start_tx();
		repeats -= bytes_to_transfer / 2;
	}
}

void spi_send_data(const uint8_t * data, uint8_t datasize)
{
	uint32_t *_data = (uint32_t*)data;
	uint8_t i;

	uint8_t words_to_send = __min((datasize + 3) / 4, SPIFIFOSIZE);
	spi_prepare_tx(datasize);
	for(i = 0; i < words_to_send;i++)
		spi_fifo[i] = _data[i];
	spi_start_tx();
}

uint16_t spi_send_uint8_receive_13bits(uint8_t to_send)
{
	spi_prepare_txrx_bits(8, 13);
	*spi_fifo = to_send;
	spi_start_tx();
	spi_wait_ready();
	return (*spi_fifo) & 0x1FFF;
}