/**
 ******************************************************************************
 * @addtogroup PIOS PIOS Core hardware abstraction layer
 * @{
 * @addtogroup   PIOS_SPI SPI Functions
 * @brief PIOS interface to read and write from SPI ports
 * @{
 *
 * @file       pios_spi.c
 * @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
 * @brief      Hardware Abstraction Layer for SPI ports of STM32
 * @see        The GNU Public License (GPL) Version 3
 * @notes
 *
 * Note that additional chip select lines can be easily added by using
 * the remaining free GPIOs of the core module. Shared SPI ports should be
 * arbitrated with (FreeRTOS based) Mutexes to avoid collisions!
 *
 *****************************************************************************/
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
/*
 * @todo	Clocking is wrong (interface is badly defined, should be speed not prescaler magic numbers)
 * @todo	DMA doesn't work.  Fix it.
 */
#include <pios.h>

#if defined(PIOS_INCLUDE_SPI)

#include <pios_spi_priv.h>

#define SPI_MAX_BLOCK_PIO	128

static bool PIOS_SPI_validate(struct pios_spi_dev * com_dev)
{
	/* Should check device magic here */
	return(true);
}

#if defined(PIOS_INCLUDE_FREERTOS) && 0
static struct pios_spi_dev * PIOS_SPI_alloc(void)
{
	return (malloc(sizeof(struct pios_spi_dev)));
}
#else
static struct pios_spi_dev pios_spi_devs[PIOS_SPI_MAX_DEVS];
static uint8_t pios_spi_num_devs;
static struct pios_spi_dev * PIOS_SPI_alloc(void)
{
	if (pios_spi_num_devs >= PIOS_SPI_MAX_DEVS) {
		return (NULL);
	}

	return (&pios_spi_devs[pios_spi_num_devs++]);
}
#endif

/**
* Initialises SPI pins
* \param[in] mode currently only mode 0 supported
* \return < 0 if initialisation failed
*/
int32_t PIOS_SPI_Init(uint32_t * spi_id, const struct pios_spi_cfg * cfg)
{
	uint32_t	init_ssel = 0;

	PIOS_Assert(spi_id);
	PIOS_Assert(cfg);

	struct pios_spi_dev * spi_dev;

	spi_dev = (struct pios_spi_dev *) PIOS_SPI_alloc();
	if (!spi_dev) goto out_fail;

	/* Bind the configuration to the device instance */
	spi_dev->cfg = cfg;

#if defined(PIOS_INCLUDE_FREERTOS)
	vSemaphoreCreateBinary(spi_dev->busy);
	xSemaphoreGive(spi_dev->busy);
#endif

	/* Disable callback function */
	spi_dev->callback = NULL;

	/* Set rx/tx dummy bytes to a known value */
	spi_dev->rx_dummy_byte = 0xFF;
	spi_dev->tx_dummy_byte = 0xFF;

	switch (spi_dev->cfg->init.SPI_NSS) {
	case SPI_NSS_Soft:
		if (spi_dev->cfg->init.SPI_Mode == SPI_Mode_Master) {
			/* We're a master in soft NSS mode, make sure we see NSS high at all times. */
			SPI_NSSInternalSoftwareConfig(spi_dev->cfg->regs, SPI_NSSInternalSoft_Set);
			/* Init as many slave selects as the config advertises. */
			init_ssel = spi_dev->cfg->slave_count;
		} else {
			/* We're a slave in soft NSS mode, make sure we see NSS low at all times. */
			SPI_NSSInternalSoftwareConfig(spi_dev->cfg->regs, SPI_NSSInternalSoft_Reset);
		}
		break;

	case SPI_NSS_Hard:
		/* only legal for single-slave config */
		PIOS_Assert(spi_dev->cfg->slave_count == 1);
		init_ssel = 1;
		/* FIXME: Should this also call SPI_SSOutputCmd()? */
		break;

	default:
		PIOS_Assert(0);
	}

	/* Initialize the GPIO pins */
	/* note __builtin_ctz() due to the difference between GPIO_PinX and GPIO_PinSourceX */
	if (spi_dev->cfg->remap) {
		GPIO_PinAFConfig(spi_dev->cfg->sclk.gpio,
				__builtin_ctz(spi_dev->cfg->sclk.init.GPIO_Pin),
				spi_dev->cfg->remap);
		GPIO_PinAFConfig(spi_dev->cfg->mosi.gpio,
				__builtin_ctz(spi_dev->cfg->mosi.init.GPIO_Pin),
				spi_dev->cfg->remap);
		GPIO_PinAFConfig(spi_dev->cfg->miso.gpio,
				__builtin_ctz(spi_dev->cfg->miso.init.GPIO_Pin),
				spi_dev->cfg->remap);
		for (uint32_t i = 0; i < init_ssel; i++) {
			GPIO_PinAFConfig(spi_dev->cfg->ssel[i].gpio,
					__builtin_ctz(spi_dev->cfg->ssel[i].init.GPIO_Pin),
					spi_dev->cfg->remap);
		}
	}
	GPIO_Init(spi_dev->cfg->sclk.gpio, (GPIO_InitTypeDef*)&(spi_dev->cfg->sclk.init));
	GPIO_Init(spi_dev->cfg->mosi.gpio, (GPIO_InitTypeDef*)&(spi_dev->cfg->mosi.init));
	GPIO_Init(spi_dev->cfg->miso.gpio, (GPIO_InitTypeDef*)&(spi_dev->cfg->miso.init));
	for (uint32_t i = 0; i < init_ssel; i++) {
		/* Since we're driving the SSEL pin in software, ensure that the slave is deselected */
		/* XXX multi-slave support - maybe have another SPI_NSS_ mode? */
		GPIO_SetBits(spi_dev->cfg->ssel[i].gpio, spi_dev->cfg->ssel[i].init.GPIO_Pin);
		GPIO_Init(spi_dev->cfg->ssel[i].gpio, (GPIO_InitTypeDef*)&(spi_dev->cfg->ssel[i].init));
	}

	/* Configure DMA for SPI Rx */
	DMA_Cmd(spi_dev->cfg->dma.rx.channel, DISABLE);
	DMA_Init(spi_dev->cfg->dma.rx.channel, (DMA_InitTypeDef*)&(spi_dev->cfg->dma.rx.init));

	/* Configure DMA for SPI Tx */
	DMA_Cmd(spi_dev->cfg->dma.tx.channel, DISABLE);
	DMA_Init(spi_dev->cfg->dma.tx.channel, (DMA_InitTypeDef*)&(spi_dev->cfg->dma.tx.init));

	/* Initialize the SPI block */
	SPI_Init(spi_dev->cfg->regs, (SPI_InitTypeDef*)&(spi_dev->cfg->init));

	/* Configure CRC calculation */
	if (spi_dev->cfg->use_crc) {
		SPI_CalculateCRC(spi_dev->cfg->regs, ENABLE);
	} else {
		SPI_CalculateCRC(spi_dev->cfg->regs, DISABLE);
	}

	/* Enable SPI */
	SPI_Cmd(spi_dev->cfg->regs, ENABLE);

	/* Enable SPI interrupts to DMA */
	SPI_I2S_DMACmd(spi_dev->cfg->regs, SPI_I2S_DMAReq_Tx | SPI_I2S_DMAReq_Rx, ENABLE);

	/* Must store this before enabling interrupt */
	*spi_id = (uint32_t)spi_dev;

	/* Configure DMA interrupt */
	NVIC_Init((NVIC_InitTypeDef*)&(spi_dev->cfg->dma.irq.init));
	DMA_ITConfig(spi_dev->cfg->dma.tx.channel, spi_dev->cfg->dma.irq.flags, ENABLE);	/* XXX is this correct? */

	return(0);

out_fail:
	return(-1);
}

/**
* (Re-)initialises SPI peripheral clock rate
*
* \param[in] spi SPI number (0 or 1)
* \param[in] spi_prescaler configures the SPI speed
* \return 0 if no error
* \return -1 if disabled SPI port selected
* \return -3 if invalid spi_prescaler selected
*/
int32_t PIOS_SPI_SetClockSpeed(uint32_t spi_id, SPIPrescalerTypeDef spi_prescaler)
{
	struct pios_spi_dev * spi_dev = (struct pios_spi_dev *)spi_id;

	bool valid = PIOS_SPI_validate(spi_dev);
	PIOS_Assert(valid)

	SPI_InitTypeDef       SPI_InitStructure;

	if (spi_prescaler >= 8) {
		/* Invalid prescaler selected */
		return -3;
	}

	/* Start with a copy of the default configuration for the peripheral */
	SPI_InitStructure = spi_dev->cfg->init;

	/* Adjust the prescaler for the peripheral's clock */
	/* XXX is this correct? */
	SPI_InitStructure.SPI_BaudRatePrescaler = ((uint16_t) spi_prescaler & 7) << 3;

	/* Write back the new configuration */
	SPI_Init(spi_dev->cfg->regs, &SPI_InitStructure);

	/* XXX what gets selected by this? */
	PIOS_SPI_TransferByte(spi_id, 0xFF);
	return 0;
}

/**
 * Claim the SPI bus semaphore.  Calling the SPI functions does not require this
 * \param[in] spi SPI number (0 or 1)
 * \return 0 if no error
 * \return -1 if timeout before claiming semaphore
 */
int32_t PIOS_SPI_ClaimBus(uint32_t spi_id)
{
#if defined(PIOS_INCLUDE_FREERTOS)
	struct pios_spi_dev * spi_dev = (struct pios_spi_dev *)spi_id;

	bool valid = PIOS_SPI_validate(spi_dev);
	PIOS_Assert(valid)

	if (xSemaphoreTake(spi_dev->busy, 0xffff) != pdTRUE)
		return -1;
#endif
	return 0;
}

/**
 * Release the SPI bus semaphore.  Calling the SPI functions does not require this
 * \param[in] spi SPI number (0 or 1)
 * \return 0 if no error
 */
int32_t PIOS_SPI_ReleaseBus(uint32_t spi_id)
{
#if defined(PIOS_INCLUDE_FREERTOS)
	struct pios_spi_dev * spi_dev = (struct pios_spi_dev *)spi_id;

	bool valid = PIOS_SPI_validate(spi_dev);
	PIOS_Assert(valid)

	xSemaphoreGive(spi_dev->busy);
#endif
	return 0;
}

/**
* Controls the RC (Register Clock alias Chip Select) pin of a SPI port
* \param[in] spi SPI number (0 or 1)
* \param[in] pin_value 0 or 1
* \return 0 if no error
*/
int32_t PIOS_SPI_RC_PinSet(uint32_t spi_id, uint32_t slave_id, uint8_t pin_value)
{
	struct pios_spi_dev * spi_dev = (struct pios_spi_dev *)spi_id;

	bool valid = PIOS_SPI_validate(spi_dev);
	PIOS_Assert(valid)
	PIOS_Assert(slave_id <= spi_dev->cfg->slave_count)

	/* XXX multi-slave support? */
	if (pin_value) {
		GPIO_SetBits(spi_dev->cfg->ssel[slave_id].gpio, spi_dev->cfg->ssel[slave_id].init.GPIO_Pin);
	} else {
		GPIO_ResetBits(spi_dev->cfg->ssel[slave_id].gpio, spi_dev->cfg->ssel[slave_id].init.GPIO_Pin);
	}

	return 0;
}

/**
* Transfers a byte to SPI output and reads back the return value from SPI input
* \param[in] spi SPI number (0 or 1)
* \param[in] b the byte which should be transfered
*/
int32_t PIOS_SPI_TransferByte(uint32_t spi_id, uint8_t b)
{
	struct pios_spi_dev * spi_dev = (struct pios_spi_dev *)spi_id;

	bool valid = PIOS_SPI_validate(spi_dev);
	PIOS_Assert(valid)

//	uint8_t dummy;
	uint8_t rx_byte;

	/* 
	 * Procedure taken from STM32F10xxx Reference Manual section 23.3.5
	 */

	/* Make sure the RXNE flag is cleared by reading the DR register */
	/*dummy =*/(void)spi_dev->cfg->regs->DR;

	/* Start the transfer */
	spi_dev->cfg->regs->DR = b;

	/* Wait until there is a byte to read */
	while (!(spi_dev->cfg->regs->SR & SPI_I2S_FLAG_RXNE)) ;

	/* Read the rx'd byte */
	rx_byte = spi_dev->cfg->regs->DR;

	/* Wait until the TXE goes high */
	while (!(spi_dev->cfg->regs->SR & SPI_I2S_FLAG_TXE)) ;

	/* Wait for SPI transfer to have fully completed */
	while (spi_dev->cfg->regs->SR & SPI_I2S_FLAG_BSY) ;

	/* Return received byte */
	return rx_byte;
}

/**
* Transfers a block of bytes via DMA.
* \param[in] spi SPI number (0 or 1)
* \param[in] send_buffer pointer to buffer which should be sent.<BR>
* If NULL, 0xff (all-one) will be sent.
* \param[in] receive_buffer pointer to buffer which should get the received values.<BR>
* If NULL, received bytes will be discarded.
* \param[in] len number of bytes which should be transfered
* \param[in] callback pointer to callback function which will be executed
* from DMA channel interrupt once the transfer is finished.
* If NULL, no callback function will be used, and PIOS_SPI_TransferBlock() will
* block until the transfer is finished.
* \return >= 0 if no error during transfer
* \return -1 if disabled SPI port selected
* \return -3 if function has been called during an ongoing DMA transfer
*/
static int32_t SPI_DMA_TransferBlock(uint32_t spi_id, const uint8_t *send_buffer, uint8_t *receive_buffer, uint16_t len, void *callback)
{
	struct pios_spi_dev * spi_dev = (struct pios_spi_dev *)spi_id;

	bool valid = PIOS_SPI_validate(spi_dev);
	PIOS_Assert(valid)

	DMA_InitTypeDef       dma_init;

	/* Exit if ongoing transfer */
	if (DMA_GetCurrDataCounter(spi_dev->cfg->dma.rx.channel)) {
		return -3;
	}

	/* Disable the DMA channels */
	DMA_Cmd(spi_dev->cfg->dma.rx.channel, DISABLE);
	DMA_Cmd(spi_dev->cfg->dma.tx.channel, DISABLE);

	while(DMA_GetCmdStatus(spi_dev->cfg->dma.rx.channel) == ENABLE);
	while(DMA_GetCmdStatus(spi_dev->cfg->dma.tx.channel) == ENABLE);

	/* Disable the SPI peripheral */
	SPI_Cmd(spi_dev->cfg->regs, DISABLE);

	/* Set callback function */
	spi_dev->callback = callback;

	/*
	 * Configure Rx channel
	 */

	/* Start with the default configuration for this peripheral */
	dma_init = spi_dev->cfg->dma.rx.init;

	if (receive_buffer != NULL) {
		/* Enable memory addr. increment - bytes written into receive buffer */
		dma_init.DMA_Memory0BaseAddr = (uint32_t) receive_buffer;
		dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable;
	} else {
		/* Disable memory addr. increment - bytes written into dummy buffer */
		spi_dev->rx_dummy_byte = 0xFF;
		dma_init.DMA_Memory0BaseAddr = (uint32_t) &spi_dev->rx_dummy_byte;
		dma_init.DMA_MemoryInc = DMA_MemoryInc_Disable;
	}
	if (spi_dev->cfg->use_crc) {
		/* Make sure the CRC error flag is cleared before we start */
		SPI_I2S_ClearFlag(spi_dev->cfg->regs, SPI_FLAG_CRCERR);
	}

	dma_init.DMA_BufferSize = len;
	DMA_Init(spi_dev->cfg->dma.rx.channel, &(dma_init));

	/*
	 * Configure Tx channel
	 */

	/* Start with the default configuration for this peripheral */
	dma_init = spi_dev->cfg->dma.tx.init;

	if (send_buffer != NULL) {
		/* Enable memory addr. increment - bytes written into receive buffer */
		dma_init.DMA_Memory0BaseAddr = (uint32_t) send_buffer;
		dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable;
	} else {
		/* Disable memory addr. increment - bytes written into dummy buffer */
		spi_dev->tx_dummy_byte = 0xFF;
		dma_init.DMA_Memory0BaseAddr = (uint32_t) &spi_dev->tx_dummy_byte;
		dma_init.DMA_MemoryInc = DMA_MemoryInc_Disable;
	}

	if (spi_dev->cfg->use_crc) {
		/* The last byte of the payload will be replaced with the CRC8 */
		dma_init.DMA_BufferSize = len - 1;
	} else {
		dma_init.DMA_BufferSize = len;
	}

	DMA_Init(spi_dev->cfg->dma.tx.channel, &(dma_init));

	/* Enable DMA interrupt if callback function active */
	DMA_ITConfig(spi_dev->cfg->dma.rx.channel, DMA_IT_TC, (callback != NULL) ? ENABLE : DISABLE);

	/* Flush out the CRC registers */
	SPI_CalculateCRC(spi_dev->cfg->regs, DISABLE);
	(void)SPI_GetCRC(spi_dev->cfg->regs, SPI_CRC_Rx);
	SPI_I2S_ClearFlag(spi_dev->cfg->regs, SPI_FLAG_CRCERR);

	/* Make sure to flush out the receive buffer */
	(void)SPI_I2S_ReceiveData(spi_dev->cfg->regs);

	if (spi_dev->cfg->use_crc) {
		/* Need a 0->1 transition to reset the CRC logic */
		SPI_CalculateCRC(spi_dev->cfg->regs, ENABLE);
	}

	/* Start DMA transfers */
	DMA_Cmd(spi_dev->cfg->dma.rx.channel, ENABLE);
	DMA_Cmd(spi_dev->cfg->dma.tx.channel, ENABLE);

	/* Reenable the SPI device */
	SPI_Cmd(spi_dev->cfg->regs, ENABLE);

	if (callback) {
		/* User has requested a callback, don't wait for the transfer to complete. */
		return 0;
	}

	/* Wait until all bytes have been transmitted/received */
	while (DMA_GetCurrDataCounter(spi_dev->cfg->dma.rx.channel));

	/* Wait for the final bytes of the transfer to complete, including CRC byte(s). */
	while (!(SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_I2S_FLAG_TXE)));

	/* Wait for the final bytes of the transfer to complete, including CRC byte(s). */
	while (SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_I2S_FLAG_BSY));

	/* Check the CRC on the transfer if enabled. */
	if (spi_dev->cfg->use_crc) {
		/* Check the SPI CRC error flag */
		if (SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_FLAG_CRCERR)) {
			return -4;
		}
	}

	/* No error */
	return 0;
}

/**
* Transfers a block of bytes via PIO.
*
* \param[in] spi_id SPI device handle
* \param[in] send_buffer pointer to buffer which should be sent.<BR>
* If NULL, 0xff (all-one) will be sent.
* \param[in] receive_buffer pointer to buffer which should get the received values.<BR>
* If NULL, received bytes will be discarded.
* \param[in] len number of bytes which should be transfered
* \return >= 0 if no error during transfer
* \return -1 if disabled SPI port selected
* \return -3 if function has been called during an ongoing DMA transfer
*/
static int32_t SPI_PIO_TransferBlock(uint32_t spi_id, const uint8_t *send_buffer, uint8_t *receive_buffer, uint16_t len)
{
	struct pios_spi_dev * spi_dev = (struct pios_spi_dev *)spi_id;
	uint8_t b;

	bool valid = PIOS_SPI_validate(spi_dev);
	PIOS_Assert(valid)

	/* Exit if ongoing transfer */
	if (DMA_GetCurrDataCounter(spi_dev->cfg->dma.rx.channel)) {
		return -3;
	}

	/* Make sure the RXNE flag is cleared by reading the DR register */
	b = spi_dev->cfg->regs->DR;

	while (len--) {
		/* get the byte to send */
		b = send_buffer ? *(send_buffer++) : 0xff;

		/* Start the transfer */
		spi_dev->cfg->regs->DR = b;

		/* Wait until there is a byte to read */
		while (!(spi_dev->cfg->regs->SR & SPI_I2S_FLAG_RXNE)) ;

		/* Read the rx'd byte */
		b = spi_dev->cfg->regs->DR;

		/* save the received byte */
		if (receive_buffer)
			*(receive_buffer++) = b;

		/* Wait until the TXE goes high */
		while (!(spi_dev->cfg->regs->SR & SPI_I2S_FLAG_TXE)) ;
	}

	/* Wait for SPI transfer to have fully completed */
	while (spi_dev->cfg->regs->SR & SPI_I2S_FLAG_BSY) ;

	return 0;
}


/**
* Transfers a block of bytes via PIO or DMA.
* \param[in] spi_id SPI device handle
* \param[in] send_buffer pointer to buffer which should be sent.<BR>
* If NULL, 0xff (all-one) will be sent.
* \param[in] receive_buffer pointer to buffer which should get the received values.<BR>
* If NULL, received bytes will be discarded.
* \param[in] len number of bytes which should be transfered
* \param[in] callback pointer to callback function which will be executed
* from DMA channel interrupt once the transfer is finished.
* If NULL, no callback function will be used, and PIOS_SPI_TransferBlock() will
* block until the transfer is finished.
* \return >= 0 if no error during transfer
* \return -1 if disabled SPI port selected
* \return -3 if function has been called during an ongoing DMA transfer
*/
int32_t PIOS_SPI_TransferBlock(uint32_t spi_id, const uint8_t *send_buffer, uint8_t *receive_buffer, uint16_t len, void *callback)
{
	if (callback || len > SPI_MAX_BLOCK_PIO) {
		return SPI_DMA_TransferBlock(spi_id, send_buffer, receive_buffer, len, callback);
	}
	return SPI_PIO_TransferBlock(spi_id, send_buffer, receive_buffer, len);
}

/**
* Check if a transfer is in progress
* \param[in] spi SPI number (0 or 1)
* \return >= 0 if no transfer is in progress
* \return -1 if disabled SPI port selected
* \return -2 if unsupported SPI port selected
* \return -3 if function has been called during an ongoing DMA transfer
*/
int32_t PIOS_SPI_Busy(uint32_t spi_id)
{
	struct pios_spi_dev * spi_dev = (struct pios_spi_dev *)spi_id;

	bool valid = PIOS_SPI_validate(spi_dev);
	PIOS_Assert(valid)

	/* DMA buffer has data or SPI transmit register not empty or SPI is busy*/
	if (DMA_GetCurrDataCounter(spi_dev->cfg->dma.rx.channel) ||
		!SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_I2S_FLAG_TXE) ||
		SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_I2S_FLAG_BSY))
	{
		return -3;
	}

	return(0);
}

void PIOS_SPI_SetPrescalar(uint32_t spi_id, uint32_t prescaler)
{
	struct pios_spi_dev * spi_dev = (struct pios_spi_dev *)spi_id;
	
	bool valid = PIOS_SPI_validate(spi_dev);
	PIOS_Assert(valid);
	PIOS_Assert(IS_SPI_BAUDRATE_PRESCALER(prescaler));
	
	spi_dev->cfg->regs->CR1 = (spi_dev->cfg->regs->CR1 & ~0x0038) | prescaler;
}


void PIOS_SPI_IRQ_Handler(uint32_t spi_id)
{
	struct pios_spi_dev * spi_dev = (struct pios_spi_dev *)spi_id;

	bool valid = PIOS_SPI_validate(spi_dev);
	PIOS_Assert(valid)

	// FIXME XXX Only RX channel or better clear flags for both channels?
	DMA_ClearFlag(spi_dev->cfg->dma.rx.channel, spi_dev->cfg->dma.irq.flags);

	/* Wait for the final bytes of the transfer to complete, including CRC byte(s). */
	while (!(SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_I2S_FLAG_TXE))) ;

	/* Wait for the final bytes of the transfer to complete, including CRC byte(s). */
	while (SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_I2S_FLAG_BSY)) ;

	if (spi_dev->callback != NULL) {
		bool crc_ok = true;
		uint8_t crc_val;

		if (SPI_I2S_GetFlagStatus(spi_dev->cfg->regs, SPI_FLAG_CRCERR)) {
			crc_ok = false;
			SPI_I2S_ClearFlag(spi_dev->cfg->regs, SPI_FLAG_CRCERR);
		}
		crc_val = SPI_GetCRC(spi_dev->cfg->regs, SPI_CRC_Rx);
		spi_dev->callback(crc_ok, crc_val);
	}
}

#endif

/**
  * @}
  * @}
  */