/*
 * spi.c
 *
 *  Created on: Jun 19, 2023
 *      Author: true
 */


#include "hk32f030m.h"
#include "spi.h"



uint8_t *spi_txbuf;
uint8_t *spi_rxbuf;
uint16_t spi_xfer_len;

uint16_t spi_txpos;
uint16_t spi_rxpos;

void (*spi_done_cb)(void);



/*
 * sets up SPI peripheral.
 */
void spi_init()
{
	// NVIC_InitTypeDef nvic;

	// enable SPI clock
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

	// run at 4MHz, master mode 3
	SPI->CR1 = SPI_BaudRatePrescaler_8 | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_MSTR | SPI_CR1_CPOL | SPI_CR1_CPHA;
	// default to 8-bit, interrupts disabled, motorola mode
	SPI->CR2 = SPI_CR2_FRXTH | SPI_DataSize_8b;

	/*
	 * originally I wanted to use interrupts, but it ended up being WAY too slow.
	 * all of our transfers are pretty quick anyway, so using the hardware peripheral
	 * just to make sure timings are good. we aren't wasting too much energy I guess...
	 *
    // configure and enable IRQ
	nvic.NVIC_IRQChannel = SPI1_IRQn;
	nvic.NVIC_IRQChannelPriority = 0;
	nvic.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&nvic);
	 */

	// enable SPI
	SPI->CR1 |= SPI_CR1_SPE;
}

/*
 * starts an SPI transfer. assumes you have set slave select before calling.
 * all transfers are BLOCKING.
 * if callback is set to a function, will be executed when xfer is done.
 */
__attribute__ ((long_call, section(".ramfunc"))) void spi_xfer(uint8_t *tx, uint8_t *rx, uint16_t len, void (*cb)(void))
{
	uint16_t flags;
	uint16_t val;
	uint8_t txb = 0;
	uint8_t rxb = 0;

	volatile uint8_t *spi_dr = (volatile uint8_t *)((uint32_t)SPI + 0x0c);

	// wait until the SPI is not busy
	while (SPI->SR & SPI_SR_BSY);

	// transfer all of our data
	while (txb != len) {
		flags = SPI->SR;

		// load next byte to send
		val = tx ? tx[txb] : 0;

		// transmit FIFO has space? if so, send byte
		if ((flags & SPI_SR_FTLVL) != SPI_SR_FTLVL) {
			*spi_dr = val;
			txb++;
		}

		// receive FIFO has data for us? if so, receive byte
		if (flags & SPI_SR_RXNE) {
			val = *spi_dr;
			if (rx) rx[rxb] = val;
			rxb++;
		}
	}

	// we shouldn't hit this code, but if there are bytes left over in FIFO...
	while (rxb != len) {
		flags = SPI->SR;

		// receive FIFO has data for us? if so, receive byte
		if (flags & SPI_SR_RXNE) {
			val = *spi_dr;
			if (rx) rx[rxb] = val;
			rxb++;
		}
	}

	if (cb) {
		cb();
	}

	/*

	// load first byte to send
	val = tx ? tx[0] : 0;
	// and send it
	SPI_SendData8(SPI, tx[0]);

	// set values for this transfer
	spi_txbuf = tx;
	spi_rxbuf = rx;
	spi_xfer_len = len;
	spi_done_cb = cb;
	// set initial buffer positions
	spi_txpos = 1;
	spi_rxpos = 0;

	// enable SPI
	// SPI->CR1 |= SPI_CR1_SPE;

	// enable tx/rx interrupt.
	// interrupt handler will deal with the transmission
	// SPI->CR2 |= SPI_CR2_TXEIE | SPI_CR2_RXNEIE;

	*/
}

/*
 * returns whether the SPI peripheral is busy with a transfer.
 */
/*
uint8_t spi_isbusy()
{
	return spi_xfer_len ? 1 : 0;
}
*/

/*
 * selects which mosi to activate.
 * note: when going from NONE to LED, must wait at least 1ms before
 *       sending data. so just architect the system to never do this.
 */
void spi_mosi_sel(uint8_t which)
{
	uint16_t moder;

	moder = SPI_MOSI_SPI_PORT->MODER & \
			~((0x3 << (SPI_MOSI_SPI_PIN * 2)) | \
			  (0x3 << (SPI_CLK_PIN * 2)) | \
			  (0x3 << (SPI_MOSI_LED_PIN * 2)));

	switch (which) {
		case SPI_MOSI_NONE: {
			SPI_MOSI_LED_PORT->BSRR = (1 << SPI_MOSI_LED_PIN);
			moder |= ( \
					(GPIO_Mode_OUT << (SPI_MOSI_SPI_PIN * 2)) | \
					(GPIO_Mode_OUT << (SPI_CLK_PIN * 2)) | \
					(GPIO_Mode_IN  << (SPI_MOSI_LED_PIN * 2)));
			break;
		}
		case SPI_MOSI_SPI: {
			moder |= ( \
					(GPIO_Mode_AF  << (SPI_MOSI_SPI_PIN * 2)) | \
					(GPIO_Mode_AF  << (SPI_CLK_PIN * 2)) | \
					(GPIO_Mode_OUT << (SPI_MOSI_LED_PIN * 2)));
			break;
		}
		case SPI_MOSI_LED: {
			SPI_MOSI_LED_PORT->BRR = (1 << SPI_MOSI_LED_PIN);
			moder |= ( \
					(GPIO_Mode_OUT << (SPI_MOSI_SPI_PIN * 2)) | \
					(GPIO_Mode_OUT << (SPI_CLK_PIN * 2)) | \
					(GPIO_Mode_AF  << (SPI_MOSI_LED_PIN * 2)));
			break;
		}
	}

	SPI_MOSI_SPI_PORT->MODER = moder;
}

// interrupt handler
/*
__attribute__ ((long_call, section(".ramfunc"))) void SPI1_IRQHandler()
{
	uint8_t data;

	// transmit buffer
	if (SPI->SR & SPI_SR_TXE) {
		if (spi_txpos >= spi_xfer_len) {
			// we are done
			SPI->CR2 &= (uint16_t)(~(uint16_t)SPI_CR2_TXEIE);
		} else {
			// load next byte
			SPI_SendData8(SPI, spi_txbuf[spi_txpos++]);
		}
	}

	if (SPI->SR & SPI_SR_RXNE) {
		data = SPI_ReceiveData8(SPI);
		if (spi_rxbuf) {
			 spi_rxbuf[spi_rxpos++] = data;
		}

		// are we done?
		if (spi_rxpos >= spi_xfer_len) {
			// we are done
			SPI->CR2 &= (uint16_t)(~(uint16_t)SPI_CR2_RXNEIE);
			spi_xfer_len = 0;

			// disable SPI
			// SPI->CR1 &= (uint16_t)(~(uint16_t)SPI_CR1_SPE);

			// do callback
			if (spi_done_cb) {
				spi_done_cb();
			}
		}
	}
}
*/
