/* ---------------------------------------------------------------------------- | |
* SAM Software Package License | |
* ---------------------------------------------------------------------------- | |
* Copyright (c) 2015, Atmel Corporation | |
* | |
* All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions are met: | |
* | |
* - Redistributions of source code must retain the above copyright notice, | |
* this list of conditions and the disclaimer below. | |
* | |
* Atmel's name may not be used to endorse or promote products derived from | |
* this software without specific prior written permission. | |
* | |
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR | |
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE | |
* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, | |
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, | |
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, | |
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
* ---------------------------------------------------------------------------- | |
*/ | |
/** \addtogroup spi_module Working with SPI | |
* \section Purpose | |
* The SPI driver provides the interface to configure and use the SPI | |
* peripheral. | |
* | |
* The Serial Peripheral Interface (SPI) circuit is a synchronous serial | |
* data link that provides communication with external devices in Master | |
* or Slave Mode. | |
* | |
* \section Usage | |
* To use the SPI, the user has to follow these few steps: | |
* -# Enable the SPI pins required by the application (see pio.h). | |
* -# Configure the SPI using the \ref spi_configure(). This enables the | |
* peripheral clock. The mode register is loaded with the given value. | |
* -# Configure all the necessary chip selects with \ref spi_configure_npcs(). | |
* -# Enable the SPI by calling \ref spi_enable(). | |
* -# Send/receive data using \ref spi_write() and \ref | |
* spi_read(). Note that \ref spi_read() | |
* must be called after \ref spi_write() to retrieve the last value read. | |
* -# Disable the SPI by calling \ref spi_disable(). | |
* | |
* For more accurate information, please look at the SPI section of the | |
* Datasheet. | |
* | |
* Related files :\n | |
* \ref spi.c\n | |
* \ref spi.h\n | |
*/ | |
/*@{*/ | |
/*@}*/ | |
/** | |
* \file | |
* | |
* Implementation of Serial Peripheral Interface (SPI) controller. | |
* | |
*/ | |
/*---------------------------------------------------------------------------- | |
* Headers | |
*----------------------------------------------------------------------------*/ | |
#include "chip.h" | |
#include "peripherals/spi.h" | |
#include "peripherals/pmc.h" | |
#include "io.h" | |
#include "trace.h" | |
#include <stdint.h> | |
#include <assert.h> | |
/*--------------------------------------------------------------------------- | |
* Macros | |
*----------------------------------------------------------------------------*/ | |
#define SPI_PCS(npcs) SPI_MR_PCS((~(1 << npcs) & 0xF)) | |
/*---------------------------------------------------------------------------- | |
* local functions | |
*----------------------------------------------------------------------------*/ | |
static inline uint32_t _spi_compute_scbr(uint32_t bitrate, uint32_t periph_id) | |
{ | |
assert(bitrate>0); | |
return SPI_CSR_SCBR( | |
pmc_get_peripheral_clock(periph_id) / (bitrate*1000)); | |
} | |
static inline uint32_t _spi_compute_dlybs(uint32_t delay, uint32_t periph_id) | |
{ | |
uint32_t dlybs = | |
((pmc_get_peripheral_clock(periph_id)/1000000u) * delay) / 100; | |
return SPI_CSR_DLYBS(dlybs); | |
} | |
static inline uint32_t _spi_compute_dlybct(uint32_t delay, uint32_t periph_id) | |
{ | |
uint32_t dlybct = | |
((pmc_get_peripheral_clock(periph_id)/31250u) * delay) / 100; | |
return SPI_CSR_DLYBCT(dlybct); | |
} | |
static inline uint32_t _spi_is_master(Spi* spi) | |
{ | |
return (spi->SPI_MR & SPI_MR_MSTR); | |
} | |
static inline uint32_t _spi_is_variable_ps(Spi* spi) | |
{ | |
return (spi->SPI_MR & SPI_MR_PS); | |
} | |
static void _spi_write_dummy(Spi* spi) | |
{ | |
if (_spi_is_master(spi)) { | |
writehw(&spi->SPI_TDR, 0xFF); | |
} | |
} | |
static void _spi_consume_read(Spi* spi, uint32_t cs) | |
{ | |
if (_spi_is_master(spi)) { | |
while(!(spi->SPI_SR & SPI_SR_RDRF)); | |
uint16_t value; | |
if ((spi->SPI_CSR[cs] & SPI_CSR_BITS_Msk) < SPI_CSR_BITS_9_BIT) { | |
readb(&spi->SPI_RDR, (uint8_t*)&value); | |
} else { | |
readhw(&spi->SPI_RDR, &value); | |
} | |
(void)value; | |
} | |
} | |
#ifdef CONFIG_HAVE_SPI_FIFO | |
static void _spi_fifo_clear(Spi* spi, uint32_t fifos) | |
{ | |
trace_debug("Spi: Clearing FIFOs\r\n"); | |
while (!(spi->SPI_SR & SPI_SR_TXFEF)); | |
spi->SPI_CR = fifos & (SPI_CR_RXFCLR | SPI_CR_TXFCLR); | |
} | |
static inline void _clear_fifo_control_flags(uint32_t* control_reg) | |
{ | |
*control_reg |= SPI_CR_TXFCLR | SPI_CR_RXFCLR | SPI_CR_FIFODIS; | |
} | |
#else | |
#define _clear_fifo_control_flags(dummy) do {} while(0) | |
#endif | |
/*---------------------------------------------------------------------------- | |
* Exported functions | |
*----------------------------------------------------------------------------*/ | |
void spi_enable(Spi * spi) | |
{ | |
spi->SPI_CR = SPI_CR_SPIEN; | |
} | |
void spi_disable(Spi * spi) | |
{ | |
spi->SPI_CR = SPI_CR_SPIDIS; | |
} | |
void spi_enable_it(Spi * spi, uint32_t dwSources) | |
{ | |
spi->SPI_IER = dwSources; | |
} | |
void spi_disable_it(Spi * spi, uint32_t dwSources) | |
{ | |
spi->SPI_IDR = dwSources; | |
} | |
void spi_configure(Spi * spi, uint32_t configuration) | |
{ | |
/* Execute a software reset of the SPI twice */ | |
spi->SPI_CR = SPI_CR_SWRST; | |
spi->SPI_CR = SPI_CR_SWRST; | |
uint32_t control_reg = SPI_CR_SPIDIS; | |
/* Add clear FIFO flags if present */ | |
_clear_fifo_control_flags(&control_reg); | |
/* Apply */ | |
spi->SPI_CR = control_reg; | |
spi->SPI_MR = configuration; | |
} | |
void spi_chip_select(Spi * spi, uint8_t cS) | |
{ | |
spi->SPI_MR &= ~SPI_MR_PCS_Msk; | |
spi->SPI_MR |= SPI_PCS(cS); | |
} | |
void spi_set_mode(Spi * spi, uint32_t dwConfiguration) | |
{ | |
spi->SPI_MR = dwConfiguration; | |
} | |
void spi_release_cs(Spi * spi) | |
{ | |
spi->SPI_CR = SPI_CR_LASTXFER; | |
} | |
void spi_configure_cs(Spi * spi, uint32_t cs, uint32_t bitrate, | |
uint32_t delay_dlybs, uint32_t delay_dlybct, | |
uint32_t spi_mode, uint32_t release_on_last) | |
{ | |
trace_debug("Spi: configuring chip select %u\r\n", (unsigned int)cs); | |
uint32_t id = get_spi_id_from_addr(spi); | |
assert(id < ID_PERIPH_COUNT); | |
bitrate = _spi_compute_scbr(bitrate, id); | |
delay_dlybs = _spi_compute_dlybs(delay_dlybs, id); | |
delay_dlybct = _spi_compute_dlybct(delay_dlybct, id); | |
release_on_last = release_on_last ? SPI_CSR_CSAAT : 0; | |
spi->SPI_CSR[cs] = bitrate | delay_dlybs | delay_dlybct | |
| release_on_last | spi_mode; | |
} | |
void spi_configure_cs_mode(Spi * spi, uint32_t cs, uint32_t release_on_last) | |
{ | |
if (!release_on_last) { | |
spi->SPI_CSR[cs] |= SPI_CSR_CSAAT; | |
} else { | |
spi->SPI_CSR[cs] &= ~SPI_CSR_CSAAT; | |
} | |
} | |
uint32_t spi_get_status(Spi * spi) | |
{ | |
return spi->SPI_SR; | |
} | |
uint16_t spi_read(Spi * spi, uint8_t cs) | |
{ | |
_spi_write_dummy(spi); | |
while ((spi->SPI_SR & SPI_SR_RDRF) == 0) ; | |
uint16_t value; | |
if ((spi->SPI_CSR[cs] & SPI_CSR_BITS_Msk) < SPI_CSR_BITS_9_BIT) { | |
readb(&spi->SPI_RDR, (uint8_t*)&value); | |
} else { | |
readhw(&spi->SPI_RDR, &value); | |
} | |
return value; | |
} | |
/** | |
* \details If the SPI is configured to use a fixed peripheral select, | |
* the npcs value is meaningless. Otherwise, it identifies the | |
* component which shall be addressed. | |
*/ | |
void spi_write(Spi * spi, uint32_t cs, uint16_t data) | |
{ | |
/* Send data */ | |
while ((spi->SPI_SR & SPI_SR_TXEMPTY) == 0); | |
if (_spi_is_variable_ps(spi)) { | |
spi->SPI_TDR = data | SPI_PCS(cs); | |
} else { | |
writehw(&spi->SPI_TDR, data); | |
} | |
/* Consume write */ | |
_spi_consume_read(spi, cs); | |
} | |
/** | |
* \brief Sends last data through a SPI peripheral. | |
* | |
* \details If the SPI is configured to use a fixed peripheral select, | |
* the npcs value is meaningless. Otherwise, it identifies the | |
* component which shall be addressed. | |
* | |
* \param spi Pointer to an Spi instance. | |
* \param dwNpcs Chip select of the component to address (0, 1, 2 or 3). | |
* \param wData Word of data to send. | |
*/ | |
void spi_write_last(Spi * spi, uint32_t cs, uint16_t data) | |
{ | |
/* Send data */ | |
while ((spi->SPI_SR & SPI_SR_TXEMPTY) == 0) ; | |
spi->SPI_TDR = data | SPI_PCS(cs) | SPI_TDR_LASTXFER; | |
/* Consume write to not corrupt FIFO if present (dummy | |
* function if CONFIG_HAV_SPI_FIFO not defined) */ | |
_spi_consume_read(spi, cs); | |
} | |
uint32_t spi_is_finished(Spi * spi) | |
{ | |
return ((spi->SPI_SR & SPI_SR_TXEMPTY) != 0); | |
} | |
#ifdef CONFIG_HAVE_SPI_FIFO | |
static void _spi_fifo_set_rx_threshold(Spi* spi, uint8_t rx_thres) | |
{ | |
spi->SPI_FMR &= ~SPI_FMR_RXFTHRES_Msk; | |
spi->SPI_FMR |= SPI_FMR_RXFTHRES(rx_thres); | |
} | |
static uint32_t _spi_write_stream(Spi *spi, uint32_t chip_select, | |
const void *stream, uint32_t len) | |
{ | |
const uint8_t* buffer = stream; | |
uint32_t left = len; | |
uint8_t is_ps = _spi_is_variable_ps(spi); | |
uint32_t max_size = is_ps ? sizeof(uint8_t) : sizeof(uint16_t); | |
int32_t fifo_size = get_peripheral_fifo_depth(spi); | |
if (fifo_size < 0) | |
return 0; | |
while (left > 0) { | |
if ((spi->SPI_SR & SPI_SR_TDRE) == 0) continue; | |
/* Get FIFO free size (int octet) and clamp it */ | |
uint32_t buf_size = fifo_size - spi_fifo_tx_size(spi); | |
buf_size = (buf_size > left) ? left : buf_size; | |
/* Fill the FIFO as must as possible */ | |
while (buf_size >= max_size) { | |
if (is_ps) { | |
spi->SPI_TDR = *(uint8_t*)buffer | | |
SPI_PCS(chip_select); | |
} else { | |
spi->SPI_TDR = *buffer | | |
(*(buffer+1) << 16); | |
} | |
buffer += max_size; | |
left -= max_size; | |
buf_size -= max_size; | |
} | |
if (buf_size >= sizeof(uint8_t)) { | |
writehw(&spi->SPI_TDR, *(uint8_t*)buffer); | |
buffer += sizeof(uint8_t); | |
left -= sizeof(uint8_t); | |
} | |
} | |
trace_debug("Spi (fifo): %u data writen\r\n", | |
(unsigned int)(len-left)); | |
return len - left; | |
} | |
void spi_fifo_configure(Spi* spi, uint8_t tx_thres, | |
uint8_t rx_thres, | |
uint32_t ready_modes) | |
{ | |
/* Disable SPI and activate FIFO */ | |
spi->SPI_CR = SPI_CR_SPIDIS | SPI_CR_FIFOEN; | |
/* Configure FIFO */ | |
spi->SPI_FMR = SPI_FMR_TXFTHRES(tx_thres) | SPI_FMR_RXFTHRES(rx_thres) | |
| ready_modes; | |
/* Reenable SPI */ | |
spi->SPI_CR = SPI_CR_SPIEN; | |
} | |
void spi_fifo_disable(Spi* spi) | |
{ | |
uint32_t reg = 0; | |
_clear_fifo_control_flags(®); | |
spi->SPI_CR = reg; | |
} | |
uint32_t spi_fifo_rx_size(Spi *spi) | |
{ | |
return (spi->SPI_FLR & SPI_FLR_RXFL_Msk) >> SPI_FLR_RXFL_Pos; | |
} | |
uint32_t spi_fifo_tx_size(Spi *spi) | |
{ | |
return (spi->SPI_FLR & SPI_FLR_TXFL_Msk) >> SPI_FLR_TXFL_Pos; | |
} | |
uint32_t spi_read_stream(Spi *spi, uint32_t chip_select, | |
void *stream, uint32_t len) | |
{ | |
uint8_t* buffer = stream; | |
uint32_t left = len; | |
uint8_t is_master = _spi_is_master(spi); | |
uint32_t max_size = is_master ? sizeof(uint8_t) : sizeof(uint32_t); | |
while (left > 0) { | |
uint32_t lenwrite = (left > SPI_FIFO_DEPTH) ? | |
SPI_FIFO_DEPTH : left; | |
_spi_fifo_set_rx_threshold(spi, lenwrite); | |
_spi_write_stream(spi, chip_select, | |
stream, lenwrite); | |
while (!(spi->SPI_SR & SPI_SR_RXFTHF)); | |
/* Get FIFO size (in octets) and clamp it */ | |
uint32_t buf_size = spi_fifo_rx_size(spi); | |
buf_size = buf_size > left ? left : buf_size; | |
/* Fill the buffer as must as possible with four data reads */ | |
while (buf_size >= max_size) { | |
if (is_master) { | |
readb(&spi->SPI_RDR, (uint8_t*)buffer); | |
} else { | |
*(uint32_t*)buffer = spi->SPI_RDR; | |
} | |
buffer += max_size; | |
left -= max_size; | |
buf_size -= max_size; | |
} | |
/* Add tail data if stream is not 4 octet aligned */ | |
if (buf_size >= sizeof(uint16_t)) { | |
/* two data read */ | |
readhw(&spi->SPI_RDR, (uint16_t*)buffer); | |
left -= sizeof(uint16_t); | |
buffer += sizeof(uint16_t); | |
buf_size -= sizeof(uint16_t); | |
} | |
if (buf_size >= sizeof(uint8_t)) { | |
/* two data read */ | |
readb(&spi->SPI_RDR, (uint8_t*)buffer); | |
left -= sizeof(uint8_t); | |
buffer += sizeof(uint8_t); | |
} | |
} | |
trace_debug("Spi (fifo): %u data read\r\n", | |
(unsigned int)(len-left)); | |
return len - left; | |
} | |
uint32_t spi_write_stream(Spi *spi, uint32_t chip_select, | |
const void *stream, uint32_t len) | |
{ | |
uint32_t consumed = _spi_write_stream(spi, chip_select, stream, len); | |
_spi_fifo_clear(spi, SPI_CR_RXFCLR); | |
return consumed; | |
} | |
#endif |