/* ---------------------------------------------------------------------------- | |
* ATMEL Microcontroller Software Support | |
* ---------------------------------------------------------------------------- | |
* Copyright (c) 2008, 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. | |
* ---------------------------------------------------------------------------- | |
*/ | |
//------------------------------------------------------------------------------ | |
// Headers | |
//------------------------------------------------------------------------------ | |
#include "spid.h" | |
#include "board.h" | |
#include <dma/dma.h> | |
#include <drivers/dmad/dmad.h> | |
#include <irq/irq.h> | |
//------------------------------------------------------------------------------ | |
// Defines | |
//------------------------------------------------------------------------------ | |
/// DMA Link List size | |
#define SIZE_LL 2 | |
/// DMA Width BYTE | |
#define DMA_WIDTH 0 | |
//------------------------------------------------------------------------------ | |
// Macros | |
//------------------------------------------------------------------------------ | |
/// Write PMC register | |
#define WRITE_PMC(pPmc, regName, value) pPmc->regName = (value) | |
/// Write SPI register | |
#define WRITE_SPI(pSpi, regName, value) pSpi->regName = (value) | |
/// Read SPI registers | |
#define READ_SPI(pSpi, regName) (pSpi->regName) | |
/// Enable Peripheral | |
#define PERIPH_ENABLE(id) \ | |
WRITE_PMC(AT91C_BASE_PMC, PMC_PCER, (1 << (id))) | |
/// Disable Peripheral | |
#define PERIPH_DISABLE(id) \ | |
WRITE_PMC(AT91C_BASE_PMC, PMC_PCDR, (1 << (id))) | |
//------------------------------------------------------------------------------ | |
// Local Variables | |
//------------------------------------------------------------------------------ | |
/// Linked lists for multi transfer buffer chaining structure instance. | |
static DmaLinkList dmaTxLinkList[SIZE_LL]; | |
static DmaLinkList dmaRxLinkList[SIZE_LL]; | |
//------------------------------------------------------------------------------ | |
// Local functions | |
//------------------------------------------------------------------------------ | |
//------------------------------------------------------------------------------ | |
/// Configure the DMA Channels: 0 RX, 1 TX. | |
/// Channels are disabled after configure. | |
//------------------------------------------------------------------------------ | |
static void configureDmaChannels(void) | |
{ | |
// Enable DMA Peripheral | |
PERIPH_ENABLE(AT91C_ID_HDMA); | |
// Enable DMA | |
DMA_Enable(); | |
// Free status | |
DMA_DisableIt(0xFFFFFFFF); | |
DMA_GetChannelStatus(); | |
DMA_GetStatus(); | |
DMA_DisableChannels((1 << DMA_CHANNEL_0) | (1 << DMA_CHANNEL_1)); | |
// RX channel 0 | |
DMA_SetConfiguration(DMA_CHANNEL_0, | |
AT91C_HDMA_SRC_PER_2 | |
| AT91C_HDMA_DST_PER_2 | |
| AT91C_HDMA_SRC_H2SEL_HW | |
| AT91C_HDMA_DST_H2SEL_SW | |
| AT91C_HDMA_SOD_ENABLE | |
| AT91C_HDMA_FIFOCFG_LARGESTBURST | |
); | |
// TX channel 1 | |
DMA_SetConfiguration(DMA_CHANNEL_1, | |
AT91C_HDMA_SRC_PER_1 | |
| AT91C_HDMA_DST_PER_1 | |
| AT91C_HDMA_SRC_H2SEL_SW | |
| AT91C_HDMA_DST_H2SEL_HW | |
| AT91C_HDMA_SOD_ENABLE | |
| AT91C_HDMA_FIFOCFG_LARGESTBURST | |
); | |
} | |
//------------------------------------------------------------------------------ | |
/// Configure the DMA source and destination with Linker List mode. | |
/// \param pCommand Pointer to command | |
//------------------------------------------------------------------------------ | |
static void configureLinkList(AT91S_SPI *pSpiHw, | |
SpidCmd *pCommand) | |
{ | |
// Setup RX Link List | |
dmaRxLinkList[0].sourceAddress = (unsigned int)&pSpiHw->SPI_RDR; | |
dmaRxLinkList[0].destAddress = (unsigned int)pCommand->pCmd; | |
dmaRxLinkList[0].controlA = pCommand->cmdSize | |
| AT91C_HDMA_SRC_WIDTH_BYTE | |
| AT91C_HDMA_DST_WIDTH_BYTE | |
; | |
dmaRxLinkList[0].controlB = AT91C_HDMA_SIF_0 | |
| AT91C_HDMA_DIF_0 | |
| AT91C_HDMA_SRC_DSCR_FETCH_FROM_MEM | |
| AT91C_HDMA_DST_DSCR_FETCH_FROM_MEM | |
| AT91C_HDMA_FC_PER2MEM | |
| AT91C_HDMA_SRC_ADDRESS_MODE_FIXED | |
| AT91C_HDMA_DST_ADDRESS_MODE_INCR | |
; | |
dmaTxLinkList[0].sourceAddress = (unsigned int)pCommand->pCmd; | |
dmaTxLinkList[0].destAddress = (unsigned int)&pSpiHw->SPI_TDR; | |
dmaTxLinkList[0].controlA = pCommand->cmdSize | |
| AT91C_HDMA_SRC_WIDTH_BYTE | |
| AT91C_HDMA_DST_WIDTH_BYTE | |
; | |
dmaTxLinkList[0].controlB = AT91C_HDMA_SIF_0 | |
| AT91C_HDMA_DIF_0 | |
| AT91C_HDMA_SRC_DSCR_FETCH_FROM_MEM | |
| AT91C_HDMA_DST_DSCR_FETCH_FROM_MEM | |
| AT91C_HDMA_FC_MEM2PER | |
| AT91C_HDMA_SRC_ADDRESS_MODE_INCR | |
| AT91C_HDMA_DST_ADDRESS_MODE_FIXED | |
; | |
// Only command | |
if (pCommand->pData == 0) { | |
dmaRxLinkList[0].descriptor = 0; | |
dmaTxLinkList[0].descriptor = 0; | |
} | |
// Command & Data | |
else { | |
dmaRxLinkList[0].descriptor = (unsigned int)&dmaRxLinkList[1]; | |
dmaRxLinkList[1].sourceAddress = (unsigned int)&pSpiHw->SPI_RDR; | |
dmaRxLinkList[1].destAddress = (unsigned int)pCommand->pData; | |
dmaRxLinkList[1].controlA = pCommand->dataSize | |
| AT91C_HDMA_SRC_WIDTH_BYTE | |
| AT91C_HDMA_DST_WIDTH_BYTE | |
; | |
dmaRxLinkList[1].controlB = AT91C_HDMA_SIF_0 | |
| AT91C_HDMA_DIF_0 | |
| AT91C_HDMA_SRC_DSCR_FETCH_DISABLE | |
| AT91C_HDMA_DST_DSCR_FETCH_DISABLE | |
| AT91C_HDMA_FC_PER2MEM | |
| AT91C_HDMA_SRC_ADDRESS_MODE_FIXED | |
| AT91C_HDMA_DST_ADDRESS_MODE_INCR | |
; | |
dmaRxLinkList[1].descriptor = 0; | |
dmaTxLinkList[0].descriptor = (unsigned int)&dmaTxLinkList[1]; | |
dmaTxLinkList[1].sourceAddress = (unsigned int)pCommand->pData; | |
dmaTxLinkList[1].destAddress = (unsigned int)&pSpiHw->SPI_TDR; | |
dmaTxLinkList[1].controlA = pCommand->dataSize | |
| AT91C_HDMA_SRC_WIDTH_BYTE | |
| AT91C_HDMA_DST_WIDTH_BYTE | |
; | |
dmaTxLinkList[1].controlB = AT91C_HDMA_SIF_0 | |
| AT91C_HDMA_DIF_0 | |
| AT91C_HDMA_SRC_DSCR_FETCH_DISABLE | |
| AT91C_HDMA_DST_DSCR_FETCH_DISABLE | |
| AT91C_HDMA_FC_MEM2PER | |
| AT91C_HDMA_SRC_ADDRESS_MODE_INCR | |
| AT91C_HDMA_DST_ADDRESS_MODE_FIXED | |
; | |
dmaTxLinkList[1].descriptor = 0; | |
} | |
// Setup registers | |
DMA_SetDescriptorAddr(DMA_CHANNEL_0, (unsigned int)&dmaRxLinkList[0]); | |
DMA_SetDescriptorAddr(DMA_CHANNEL_1, (unsigned int)&dmaTxLinkList[0]); | |
AT91C_BASE_HDMA->HDMA_CH[DMA_CHANNEL_0].HDMA_CTRLB = 0 | |
| AT91C_HDMA_SRC_DSCR_FETCH_FROM_MEM | |
| AT91C_HDMA_DST_DSCR_FETCH_FROM_MEM | |
; | |
AT91C_BASE_HDMA->HDMA_CH[DMA_CHANNEL_1].HDMA_CTRLB = 0 | |
| AT91C_HDMA_SRC_DSCR_FETCH_FROM_MEM | |
| AT91C_HDMA_DST_DSCR_FETCH_FROM_MEM | |
; | |
} | |
//------------------------------------------------------------------------------ | |
// Exported functions | |
//------------------------------------------------------------------------------ | |
//------------------------------------------------------------------------------ | |
/// Initializes the Spid structure and the corresponding SPI & DMA hardware. | |
/// The driver will uses DMA channel 0 for RX and DMA channel 1 for TX. | |
/// The DMA channels are freed automatically when no SPI command processing. | |
/// \param pSpid Pointer to a Spid instance. | |
/// \param pSpiHw Associated SPI peripheral. | |
/// \param spiId SPI peripheral identifier. | |
/// \return Always 0. | |
//------------------------------------------------------------------------------ | |
unsigned char SPID_Configure(Spid *pSpid, AT91S_SPI *pSpiHw, unsigned char spiId) | |
{ | |
// Initialize the SPI structure | |
pSpid->pSpiHw = pSpiHw; | |
pSpid->spiId = spiId; | |
pSpid->semaphore = 1; | |
pSpid->pCurrentCommand = 0; | |
// Enable the SPI Peripheral | |
PERIPH_ENABLE(pSpid->spiId); | |
// Execute a software reset of the SPI twice | |
WRITE_SPI(pSpiHw, SPI_CR, AT91C_SPI_SWRST); | |
WRITE_SPI(pSpiHw, SPI_CR, AT91C_SPI_SWRST); | |
// Configure SPI in Master Mode with No CS selected !!! | |
WRITE_SPI(pSpiHw, SPI_MR, AT91C_SPI_MSTR | AT91C_SPI_MODFDIS | AT91C_SPI_PCS); | |
// Disable the PDC transfer | |
WRITE_SPI(pSpiHw, SPI_PTCR, AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS); | |
// Disable the SPI TX & RX | |
WRITE_SPI(pSpiHw, SPI_CR, AT91C_SPI_SPIDIS); | |
// Disable the SPI Peripheral | |
PERIPH_DISABLE(pSpid->spiId); | |
return 0; | |
} | |
//------------------------------------------------------------------------------ | |
/// Configures the parameters for the device corresponding to the cs. | |
/// \param pSpid Pointer to a Spid instance. | |
/// \param cs number corresponding to the SPI chip select. | |
/// \param csr SPI_CSR value to setup. | |
//------------------------------------------------------------------------------ | |
void SPID_ConfigureCS(Spid *pSpid, unsigned char cs, unsigned int csr) | |
{ | |
AT91S_SPI *pSpiHw = pSpid->pSpiHw; | |
// Enable the SPI Peripheral | |
PERIPH_ENABLE(pSpid->spiId); | |
// Write CS | |
WRITE_SPI(pSpiHw, SPI_CSR[cs], csr); | |
// Disable the SPI Peripheral | |
PERIPH_DISABLE(pSpid->spiId); | |
} | |
//------------------------------------------------------------------------------ | |
/// Starts a SPI master transfer. This is a non blocking function. It will | |
/// return as soon as the transfer is started. | |
/// Returns 0 if the transfer has been started successfully; otherwise returns | |
/// SPID_ERROR_LOCK is the driver is in use, or SPID_ERROR if the command is not | |
/// valid. | |
/// \param pSpid Pointer to a Spid instance. | |
/// \param pCommand Pointer to the SPI command to execute. | |
//------------------------------------------------------------------------------ | |
unsigned char SPID_SendCommand(Spid *pSpid, SpidCmd *pCommand) | |
{ | |
AT91S_SPI *pSpiHw = pSpid->pSpiHw; | |
unsigned int spiMr; | |
// Try to get the dataflash semaphore | |
if (pSpid->semaphore == 0) { | |
return SPID_ERROR_LOCK; | |
} | |
pSpid->semaphore--; | |
// Enable the SPI Peripheral | |
PERIPH_ENABLE(pSpid->spiId); | |
// Disable PDC transmitter and receiver | |
WRITE_SPI(pSpiHw, SPI_PTCR, AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS); | |
// Write to the MR register | |
spiMr = READ_SPI(pSpiHw, SPI_MR); | |
spiMr |= AT91C_SPI_PCS; | |
spiMr &= ~((1 << pCommand->spiCs) << 16); | |
WRITE_SPI(pSpiHw, SPI_MR, spiMr); | |
// Initialize DMA controller using channel 0 for RX, 1 for TX. | |
configureDmaChannels(); | |
configureLinkList(pSpiHw, pCommand); | |
// Initialize the callback | |
pSpid->pCurrentCommand = pCommand; | |
// Enable the SPI TX & RX | |
WRITE_SPI(pSpiHw, SPI_CR, AT91C_SPI_SPIEN); | |
// Start DMA 0(RX) && 1(TX) | |
DMA_EnableChannels((1 << DMA_CHANNEL_0) | (1 << DMA_CHANNEL_1)); | |
// Enable DMA Interrupts | |
DMA_EnableIt( (DMA_CBTC << DMA_CHANNEL_0) | |
| (DMA_CBTC << DMA_CHANNEL_1)); | |
return 0; | |
} | |
//------------------------------------------------------------------------------ | |
/// SPI DMA transfer ISR, Handle RX complete | |
//------------------------------------------------------------------------------ | |
void SPID_Handler(Spid *pSpid) | |
{ | |
unsigned int dmaStatus; | |
SpidCmd *pSpidCmd = pSpid->pCurrentCommand; | |
AT91S_SPI *pSpiHw = pSpid->pSpiHw; | |
dmaStatus = DMA_GetStatus(); | |
if ((dmaStatus & AT91C_CBTC) == 0) | |
return; | |
if ((dmaStatus & (DMA_CBTC << DMA_CHANNEL_0)) == 0) | |
return; | |
// Disable the SPI TX & RX | |
WRITE_SPI(pSpiHw, SPI_CR, AT91C_SPI_SPIDIS); | |
// Disable the SPI Peripheral | |
PERIPH_DISABLE(pSpid->spiId); | |
// Disable DMA | |
DMA_Disable(); | |
// Disable DMA Peripheral | |
PERIPH_DISABLE(AT91C_ID_HDMA); | |
// Release the dataflash semaphore | |
pSpid->semaphore++; | |
// Invoke the callback associated with the current command | |
if (pSpidCmd && pSpidCmd->callback) { | |
pSpidCmd->callback(0, pSpidCmd->pArgument); | |
} | |
} | |
//------------------------------------------------------------------------------ | |
/// Returns 1 if the SPI driver is currently busy executing a command; otherwise | |
/// returns 0. | |
/// \param pSpid Pointer to a SPI driver instance. | |
//------------------------------------------------------------------------------ | |
unsigned char SPID_IsBusy(const Spid *pSpid) | |
{ | |
if (pSpid->semaphore == 0) { | |
return 1; | |
} | |
else { | |
return 0; | |
} | |
} | |