/******************************************************************************* | |
* (c) Copyright 2008 Actel Corporation. All rights reserved. | |
* | |
* SmartFusion microcontroller subsystem SPI bare metal software driver | |
* implementation. | |
* | |
* SVN $Revision: 2176 $ | |
* SVN $Date: 2010-02-15 21:04:22 +0000 (Mon, 15 Feb 2010) $ | |
*/ | |
#include "mss_spi.h" | |
#include "../../CMSIS/mss_assert.h" | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
/***************************************************************************//** | |
MSS SPI can operate as master or slave. | |
*/ | |
#define MSS_SPI_MODE_SLAVE (uint32_t)0 | |
#define MSS_SPI_MODE_MASTER (uint32_t)1 | |
/***************************************************************************//** | |
* Mask of transfer protocol and SPO, SPH bits within control register. | |
*/ | |
#define PROTOCOL_MODE_MASK (uint32_t)0x030000C0 | |
/***************************************************************************//** | |
* Mask of theframe count bits within the SPI control register. | |
*/ | |
#define TXRXDFCOUNT_MASK (uint32_t)0x00FFFF00 | |
#define TXRXDFCOUNT_SHIFT (uint32_t)8 | |
/***************************************************************************//** | |
* SPI hardware FIFO depth. | |
*/ | |
#define RX_FIFO_SIZE 4u | |
/***************************************************************************//** | |
Marker used to detect that the configuration has not been selected for a | |
specific slave when operating as a master. | |
*/ | |
#define NOT_CONFIGURED 0xFFFFFFFF | |
/***************************************************************************//** | |
* SPI instance data structures for SPI0 and SPI1. A pointer to these data | |
* structures must be used as first parameter to any of the SPI driver functions | |
* to identify the SPI hardware block that will perform the requested operation. | |
*/ | |
mss_spi_instance_t g_mss_spi0; | |
mss_spi_instance_t g_mss_spi1; | |
/***************************************************************************//** | |
SPI0 interrupt service routine | |
*/ | |
#if defined(__GNUC__) | |
__attribute__((__interrupt__)) void SPI0_IRQHandler( void ); | |
#else | |
void SPI0_IRQHandler( void ); | |
#endif | |
/***************************************************************************//** | |
SPI1 interrupt service routine | |
*/ | |
#if defined(__GNUC__) | |
__attribute__((__interrupt__)) void SPI1_IRQHandler( void ); | |
#else | |
void SPI1_IRQHandler( void ); | |
#endif | |
/***************************************************************************//** | |
* MSS_SPI_init() | |
* See "mss_spi.h" for details of how to use this function. | |
*/ | |
void MSS_SPI_init | |
( | |
mss_spi_instance_t * this_spi | |
) | |
{ | |
uint16_t i; | |
ASSERT( (this_spi == &g_mss_spi0) || (this_spi == &g_mss_spi1) ); | |
if (this_spi == &g_mss_spi0) | |
{ | |
this_spi->hw_reg = SPI0; | |
this_spi->hw_reg_bit = SPI0_BITBAND; | |
this_spi->irqn = SPI0_IRQn; | |
/* reset SPI0 */ | |
SYSREG->SOFT_RST_CR |= SYSREG_SPI0_SOFTRESET_MASK; | |
/* Clear any previously pended SPI0 interrupt */ | |
NVIC_ClearPendingIRQ( SPI0_IRQn ); | |
/* Take SPI0 out of reset. */ | |
SYSREG->SOFT_RST_CR &= ~SYSREG_SPI0_SOFTRESET_MASK; | |
} | |
else | |
{ | |
this_spi->hw_reg = SPI1; | |
this_spi->hw_reg_bit = SPI1_BITBAND; | |
this_spi->irqn = SPI1_IRQn; | |
/* reset SPI1 */ | |
SYSREG->SOFT_RST_CR |= SYSREG_SPI1_SOFTRESET_MASK; | |
/* Clear any previously pended SPI1 interrupt */ | |
NVIC_ClearPendingIRQ( SPI1_IRQn ); | |
/* Take SPI1 out of reset. */ | |
SYSREG->SOFT_RST_CR &= ~SYSREG_SPI1_SOFTRESET_MASK; | |
} | |
this_spi->frame_rx_handler = 0U; | |
this_spi->slave_tx_frame = 0U; | |
this_spi->block_rx_handler = 0U; | |
this_spi->slave_tx_buffer = 0U; | |
this_spi->slave_tx_size = 0U; | |
this_spi->slave_tx_idx = 0U; | |
for ( i = 0u; i < (uint16_t)MSS_SPI_MAX_NB_OF_SLAVES; ++i ) | |
{ | |
this_spi->slaves_cfg[i].ctrl_reg = NOT_CONFIGURED; | |
} | |
} | |
/***************************************************************************//** | |
* MSS_SPI_configure_slave_mode() | |
* See "mss_spi.h" for details of how to use this function. | |
*/ | |
void MSS_SPI_configure_slave_mode | |
( | |
mss_spi_instance_t * this_spi, | |
mss_spi_protocol_mode_t protocol_mode, | |
mss_spi_pclk_div_t clk_rate, | |
uint8_t frame_bit_length | |
) | |
{ | |
ASSERT( (this_spi == &g_mss_spi0) || (this_spi == &g_mss_spi1) ); | |
ASSERT( frame_bit_length <= 32 ); | |
/* Set the mode. */ | |
this_spi->hw_reg_bit->CTRL_MASTER = MSS_SPI_MODE_SLAVE; | |
/* Set the clock rate. */ | |
this_spi->hw_reg_bit->CTRL_ENABLE = 0U; | |
this_spi->hw_reg->CONTROL = (this_spi->hw_reg->CONTROL & ~PROTOCOL_MODE_MASK) | (uint32_t)protocol_mode; | |
this_spi->hw_reg->CLK_GEN = (uint32_t)clk_rate; | |
/* Set default frame size to byte size and number of data frames to 1. */ | |
this_spi->hw_reg->CONTROL = (this_spi->hw_reg->CONTROL & ~TXRXDFCOUNT_MASK) | ((uint32_t)1 << TXRXDFCOUNT_SHIFT); | |
this_spi->hw_reg->TXRXDF_SIZE = frame_bit_length; | |
this_spi->hw_reg_bit->CTRL_ENABLE = 1U; | |
} | |
/***************************************************************************//** | |
* MSS_SPI_configure_master_mode() | |
* See "mss_spi.h" for details of how to use this function. | |
*/ | |
void MSS_SPI_configure_master_mode | |
( | |
mss_spi_instance_t * this_spi, | |
mss_spi_slave_t slave, | |
mss_spi_protocol_mode_t protocol_mode, | |
mss_spi_pclk_div_t clk_rate, | |
uint8_t frame_bit_length | |
) | |
{ | |
ASSERT( (this_spi == &g_mss_spi0) || (this_spi == &g_mss_spi1) ); | |
ASSERT( slave < MSS_SPI_MAX_NB_OF_SLAVES ); | |
ASSERT( frame_bit_length <= 32 ); | |
/* Set the mode. */ | |
this_spi->hw_reg_bit->CTRL_ENABLE = 0U; | |
this_spi->hw_reg_bit->CTRL_MASTER = MSS_SPI_MODE_MASTER; | |
this_spi->hw_reg_bit->CTRL_ENABLE = 1U; | |
/* | |
* Keep track of the required register configuration for this slave. These | |
* values will be used by the MSS_SPI_set_slave_select() function to configure | |
* the master to match the slave being selected. | |
*/ | |
if ( slave < MSS_SPI_MAX_NB_OF_SLAVES ) | |
{ | |
this_spi->slaves_cfg[slave].ctrl_reg = 0x00000002uL | (uint32_t)protocol_mode | ((uint32_t)1 << TXRXDFCOUNT_SHIFT); | |
this_spi->slaves_cfg[slave].txrxdf_size_reg = frame_bit_length; | |
this_spi->slaves_cfg[slave].clk_gen = (uint8_t)clk_rate; | |
} | |
} | |
/***************************************************************************//** | |
* MSS_SPI_set_slave_select() | |
* See "mss_spi.h" for details of how to use this function. | |
*/ | |
void MSS_SPI_set_slave_select | |
( | |
mss_spi_instance_t * this_spi, | |
mss_spi_slave_t slave | |
) | |
{ | |
ASSERT( (this_spi == &g_mss_spi0) || (this_spi == &g_mss_spi1) ); | |
/* This function is only intended to be used with an SPI master. */ | |
ASSERT( this_spi->hw_reg_bit->CTRL_MASTER == MSS_SPI_MODE_MASTER ); | |
ASSERT( this_spi->slaves_cfg[slave].ctrl_reg != NOT_CONFIGURED ); | |
/* Set the clock rate. */ | |
this_spi->hw_reg_bit->CTRL_ENABLE = 0U; | |
this_spi->hw_reg->CONTROL = this_spi->slaves_cfg[slave].ctrl_reg; | |
this_spi->hw_reg->CLK_GEN = this_spi->slaves_cfg[slave].clk_gen; | |
this_spi->hw_reg->TXRXDF_SIZE = this_spi->slaves_cfg[slave].txrxdf_size_reg; | |
this_spi->hw_reg_bit->CTRL_ENABLE = 1U; | |
/* Set slave select */ | |
this_spi->hw_reg->SLAVE_SELECT |= ((uint32_t)1 << (uint32_t)slave); | |
} | |
/***************************************************************************//** | |
* MSS_SPI_clear_slave_select() | |
* See "mss_spi.h" for details of how to use this function. | |
*/ | |
void MSS_SPI_clear_slave_select | |
( | |
mss_spi_instance_t * this_spi, | |
mss_spi_slave_t slave | |
) | |
{ | |
ASSERT( (this_spi == &g_mss_spi0) || (this_spi == &g_mss_spi1) ); | |
/* This function is only intended to be used with an SPI master. */ | |
ASSERT( this_spi->hw_reg_bit->CTRL_MASTER == MSS_SPI_MODE_MASTER ); | |
this_spi->hw_reg->SLAVE_SELECT &= ~((uint32_t)1 << (uint32_t)slave); | |
} | |
/***************************************************************************//** | |
* MSS_SPI_transfer_frame() | |
* See "mss_spi.h" for details of how to use this function. | |
*/ | |
uint32_t MSS_SPI_transfer_frame | |
( | |
mss_spi_instance_t * this_spi, | |
uint32_t tx_bits | |
) | |
{ | |
volatile uint32_t dummy; | |
ASSERT( (this_spi == &g_mss_spi0) || (this_spi == &g_mss_spi1) ); | |
/* This function is only intended to be used with an SPI master. */ | |
ASSERT( this_spi->hw_reg_bit->CTRL_MASTER == MSS_SPI_MODE_MASTER ); | |
/* Flush Rx FIFO. */ | |
while ( this_spi->hw_reg_bit->STATUS_RX_RDY == 1U ) | |
{ | |
dummy = this_spi->hw_reg->RX_DATA; | |
dummy = dummy; /* Prevent Lint warning. */ | |
} | |
/* Send frame. */ | |
this_spi->hw_reg->TX_DATA = tx_bits; | |
/* Wait for frame Tx to complete. */ | |
while ( this_spi->hw_reg_bit->STATUS_TX_DONE == 0U ) | |
{ | |
; | |
} | |
/* Read received frame. */ | |
/* Wait for Rx complete. */ | |
while ( this_spi->hw_reg_bit->STATUS_RX_RDY == 0U ) | |
{ | |
; | |
} | |
/* Return Rx data. */ | |
return( this_spi->hw_reg->RX_DATA ); | |
} | |
/***************************************************************************//** | |
* MSS_SPI_transfer_block() | |
* See "mss_spi.h" for details of how to use this function. | |
*/ | |
void MSS_SPI_transfer_block | |
( | |
mss_spi_instance_t * this_spi, | |
const uint8_t * cmd_buffer, | |
uint16_t cmd_byte_size, | |
uint8_t * rd_buffer, | |
uint16_t rd_byte_size | |
) | |
{ | |
uint16_t transfer_idx = 0U; | |
uint16_t tx_idx; | |
uint16_t rx_idx; | |
uint32_t frame_count; | |
volatile uint32_t rx_raw; | |
uint16_t transit = 0U; | |
uint16_t transfer_size; /* Total number of bytes transfered. */ | |
ASSERT( (this_spi == &g_mss_spi0) || (this_spi == &g_mss_spi1) ); | |
/* This function is only intended to be used with an SPI master. */ | |
ASSERT( this_spi->hw_reg_bit->CTRL_MASTER == MSS_SPI_MODE_MASTER ); | |
/* Compute number of bytes to transfer. */ | |
transfer_size = cmd_byte_size + rd_byte_size; | |
/* Adjust to 1 byte transfer to cater for DMA transfers. */ | |
if ( transfer_size == 0U ) | |
{ | |
frame_count = 1U; | |
} | |
else | |
{ | |
frame_count = transfer_size; | |
} | |
/* Set frame size to 8 bits and the frame count to the tansfer size. */ | |
this_spi->hw_reg_bit->CTRL_ENABLE = 0U; | |
this_spi->hw_reg->CONTROL = (this_spi->hw_reg->CONTROL & ~TXRXDFCOUNT_MASK) | ( (frame_count << TXRXDFCOUNT_SHIFT) & TXRXDFCOUNT_MASK); | |
this_spi->hw_reg->TXRXDF_SIZE = 8U; | |
this_spi->hw_reg_bit->CTRL_ENABLE = 1U; | |
/* Flush the receive FIFO. */ | |
while ( !this_spi->hw_reg_bit->STATUS_RX_FIFO_EMPTY ) | |
{ | |
rx_raw = this_spi->hw_reg->RX_DATA; | |
} | |
tx_idx = 0u; | |
rx_idx = 0u; | |
if ( tx_idx < cmd_byte_size ) | |
{ | |
this_spi->hw_reg->TX_DATA = cmd_buffer[tx_idx]; | |
++tx_idx; | |
++transit; | |
} | |
else | |
{ | |
if ( tx_idx < transfer_size ) | |
{ | |
this_spi->hw_reg->TX_DATA = 0x00U; | |
++tx_idx; | |
++transit; | |
} | |
} | |
/* Perform the remainder of the transfer by sending a byte every time a byte | |
* has been received. This should ensure that no Rx overflow can happen in | |
* case of an interrupt occurs during this function. */ | |
while ( transfer_idx < transfer_size ) | |
{ | |
if ( !this_spi->hw_reg_bit->STATUS_RX_FIFO_EMPTY ) | |
{ | |
/* Process received byte. */ | |
rx_raw = this_spi->hw_reg->RX_DATA; | |
if ( transfer_idx >= cmd_byte_size ) | |
{ | |
if ( rx_idx < rd_byte_size ) | |
{ | |
rd_buffer[rx_idx] = (uint8_t)rx_raw; | |
} | |
++rx_idx; | |
} | |
++transfer_idx; | |
--transit; | |
} | |
if ( !this_spi->hw_reg_bit->STATUS_TX_FIFO_FULL ) | |
{ | |
if (transit < RX_FIFO_SIZE) | |
{ | |
/* Send another byte. */ | |
if ( tx_idx < cmd_byte_size ) | |
{ | |
this_spi->hw_reg->TX_DATA = cmd_buffer[tx_idx]; | |
++tx_idx; | |
++transit; | |
} | |
else | |
{ | |
if ( tx_idx < transfer_size ) | |
{ | |
this_spi->hw_reg->TX_DATA = 0x00U; | |
++tx_idx; | |
++transit; | |
} | |
} | |
} | |
} | |
} | |
} | |
/***************************************************************************//** | |
* MSS_SPI_set_frame_rx_handler() | |
* See "mss_spi.h" for details of how to use this function. | |
*/ | |
void MSS_SPI_set_frame_rx_handler | |
( | |
mss_spi_instance_t * this_spi, | |
mss_spi_frame_rx_handler_t rx_handler | |
) | |
{ | |
ASSERT( (this_spi == &g_mss_spi0) || (this_spi == &g_mss_spi1) ); | |
/* This function is only intended to be used with an SPI slave. */ | |
ASSERT( this_spi->hw_reg_bit->CTRL_MASTER == MSS_SPI_MODE_SLAVE ); | |
/* Disable block Rx handler as they are mutually exclusive. */ | |
this_spi->block_rx_handler = 0U; | |
/* Keep a copy of the pointer to the rx hnadler function. */ | |
this_spi->frame_rx_handler = rx_handler; | |
/* Enable Rx interrupt. */ | |
this_spi->hw_reg_bit->CTRL_RX_INT_EN = 1U; | |
NVIC_EnableIRQ( this_spi->irqn ); | |
} | |
/***************************************************************************//** | |
* MSS_SPI_set_slave_tx_frame() | |
* See "mss_spi.h" for details of how to use this function. | |
*/ | |
void MSS_SPI_set_slave_tx_frame | |
( | |
mss_spi_instance_t * this_spi, | |
uint32_t frame_value | |
) | |
{ | |
ASSERT( (this_spi == &g_mss_spi0) || (this_spi == &g_mss_spi1) ); | |
/* This function is only intended to be used with an SPI slave. */ | |
ASSERT( this_spi->hw_reg_bit->CTRL_MASTER == MSS_SPI_MODE_SLAVE ); | |
/* Disable slave block tx buffer as it is mutually exclusive with frame | |
* level handling. */ | |
this_spi->slave_tx_buffer = 0U; | |
this_spi->slave_tx_size = 0U; | |
this_spi->slave_tx_idx = 0U; | |
/* Keep a copy of the slave tx frame value. */ | |
this_spi->slave_tx_frame = frame_value; | |
/* Load frame into Tx data register. */ | |
this_spi->hw_reg->TX_DATA = this_spi->slave_tx_frame; | |
/* Enable Tx Done interrupt in order to reload the slave Tx frame after each | |
* time it has been sent. */ | |
this_spi->hw_reg_bit->CTRL_TX_INT_EN = 1U; | |
NVIC_EnableIRQ( this_spi->irqn ); | |
} | |
/***************************************************************************//** | |
* MSS_SPI_set_slave_block_buffers() | |
* See "mss_spi.h" for details of how to use this function. | |
*/ | |
void MSS_SPI_set_slave_block_buffers | |
( | |
mss_spi_instance_t * this_spi, | |
const uint8_t * tx_buffer, | |
uint32_t tx_buff_size, | |
uint8_t * rx_buffer, | |
uint32_t rx_buff_size, | |
mss_spi_block_rx_handler_t block_rx_handler | |
) | |
{ | |
uint32_t frame_count; | |
ASSERT( (this_spi == &g_mss_spi0) || (this_spi == &g_mss_spi1) ); | |
/* This function is only intended to be used with an SPI slave. */ | |
ASSERT( this_spi->hw_reg_bit->CTRL_MASTER == MSS_SPI_MODE_SLAVE ); | |
/* Disable Rx frame handler as it is mutually exclusive with block rx handler. */ | |
this_spi->frame_rx_handler = 0U; | |
/* Keep a copy of the pointer to the block rx handler function. */ | |
this_spi->block_rx_handler = block_rx_handler; | |
this_spi->slave_rx_buffer = rx_buffer; | |
this_spi->slave_rx_size = rx_buff_size; | |
this_spi->slave_rx_idx = 0U; | |
/**/ | |
this_spi->slave_tx_buffer = tx_buffer; | |
this_spi->slave_tx_size = tx_buff_size; | |
this_spi->slave_tx_idx = 0U; | |
frame_count = rx_buff_size; | |
/**/ | |
this_spi->hw_reg_bit->CTRL_ENABLE = 0U; | |
this_spi->hw_reg->CONTROL = (this_spi->hw_reg->CONTROL & ~TXRXDFCOUNT_MASK) | (frame_count << TXRXDFCOUNT_SHIFT); | |
this_spi->hw_reg->TXRXDF_SIZE = 8U; | |
this_spi->hw_reg_bit->CTRL_ENABLE = 1U; | |
/* Load the transmit FIFO. */ | |
while ( !(this_spi->hw_reg_bit->STATUS_TX_FIFO_FULL) && ( this_spi->slave_tx_idx < this_spi->slave_tx_size ) ) | |
{ | |
this_spi->hw_reg->TX_DATA = this_spi->slave_tx_buffer[this_spi->slave_tx_idx]; | |
++this_spi->slave_tx_idx; | |
} | |
/* Enable Rx interrupt. */ | |
this_spi->hw_reg_bit->CTRL_RX_INT_EN = 1U; | |
NVIC_EnableIRQ( this_spi->irqn ); | |
} | |
/***************************************************************************//** | |
* SPI interrupt service routine. | |
*/ | |
static void mss_spi_isr | |
( | |
mss_spi_instance_t * this_spi | |
) | |
{ | |
uint32_t rx_frame; | |
ASSERT( (this_spi == &g_mss_spi0) || (this_spi == &g_mss_spi1) ); | |
if ( this_spi->hw_reg_bit->MIS_RX_RDY ) | |
{ | |
while( !this_spi->hw_reg_bit->STATUS_RX_FIFO_EMPTY ) | |
{ | |
rx_frame = this_spi->hw_reg->RX_DATA; | |
if ( this_spi->frame_rx_handler != 0U ) | |
{ | |
/* Single frame handling mode. */ | |
this_spi->frame_rx_handler( rx_frame ); | |
} | |
else | |
{ | |
if ( this_spi->block_rx_handler != 0U ) | |
{ | |
/* Block handling mode. */ | |
if ( this_spi->slave_rx_idx < this_spi->slave_rx_size ) | |
{ | |
this_spi->slave_rx_buffer[this_spi->slave_rx_idx] = (uint8_t)rx_frame; | |
++this_spi->slave_rx_idx; | |
if ( this_spi->slave_rx_idx == this_spi->slave_rx_size ) | |
{ | |
(*this_spi->block_rx_handler)( this_spi->slave_rx_buffer, this_spi->slave_rx_size ); | |
} | |
} | |
} | |
} | |
/* Feed transmit FIFO. */ | |
if ( !(this_spi->hw_reg_bit->STATUS_TX_FIFO_FULL) && ( this_spi->slave_tx_idx < this_spi->slave_tx_size ) ) | |
{ | |
this_spi->hw_reg->TX_DATA = this_spi->slave_tx_buffer[this_spi->slave_tx_idx]; | |
++this_spi->slave_tx_idx; | |
} | |
} | |
this_spi->hw_reg_bit->INT_CLEAR_RX_RDY = 1U; | |
} | |
if ( this_spi->hw_reg_bit->MIS_TX_DONE ) | |
{ | |
if ( this_spi->slave_tx_buffer != 0U ) | |
{ | |
this_spi->hw_reg->TX_DATA = this_spi->slave_tx_buffer[this_spi->slave_tx_idx]; | |
++this_spi->slave_tx_idx; | |
if ( this_spi->slave_tx_idx >= this_spi->slave_tx_size ) | |
{ | |
this_spi->slave_tx_idx = 0U; | |
} | |
} | |
else | |
{ | |
/* Reload slave tx frame into Tx data register. */ | |
this_spi->hw_reg->TX_DATA = this_spi->slave_tx_frame; | |
} | |
} | |
} | |
/***************************************************************************//** | |
* SPIO interrupt service routine. | |
* Please note that the name of this ISR is defined as part of the SmartFusion | |
* CMSIS startup code. | |
*/ | |
#if defined(__GNUC__) | |
__attribute__((__interrupt__)) void SPI0_IRQHandler( void ) | |
#else | |
void SPI0_IRQHandler( void ) | |
#endif | |
{ | |
mss_spi_isr( &g_mss_spi0 ); | |
NVIC_ClearPendingIRQ( SPI0_IRQn ); | |
} | |
/***************************************************************************//** | |
* SPI1 interrupt service routine. | |
* Please note that the name of this ISR is defined as part of the SmartFusion | |
* CMSIS startup code. | |
*/ | |
#if defined(__GNUC__) | |
__attribute__((__interrupt__)) void SPI1_IRQHandler( void ) | |
#else | |
void SPI1_IRQHandler( void ) | |
#endif | |
{ | |
mss_spi_isr( &g_mss_spi1 ); | |
NVIC_ClearPendingIRQ( SPI1_IRQn ); | |
} | |
#ifdef __cplusplus | |
} | |
#endif | |