/* ---------------------------------------------------------------------------- * 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 #include #include #include //------------------------------------------------------------------------------ // 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 = 0 //| 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 = 0 //| 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 = 0 //| 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 = 0 //| 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 #if !defined(at91sam3u) WRITE_SPI(pSpiHw, SPI_PTCR, AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS); #endif // 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 #if !defined(at91sam3u) WRITE_SPI(pSpiHw, SPI_PTCR, AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS); #endif // 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; } }