/* ---------------------------------------------------------------------------- * 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 "sdspi.h" #include #include #include #include #include #include #include #include //------------------------------------------------------------------------------ // Macros //------------------------------------------------------------------------------ /// Transfer is pending. #define SDSPI_STATUS_PENDING 1 /// Transfer has been aborted because an error occured. #define SDSPI_STATUS_ERROR 2 /// SPI driver is currently in use. #define SDSPI_ERROR_LOCK 1 // Data Tokens #define SDSPI_START_BLOCK_1 0xFE // Single/Multiple read, single write #define SDSPI_START_BLOCK_2 0xFC // Multiple block write #define SDSPI_STOP_TRAN 0xFD // Cmd12 //------------------------------------------------------------------------------ // Exported functions //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ /// Initializes the SD Spi structure and the corresponding SPI hardware. /// \param pSpid Pointer to a Spid instance. /// \param pSpiHw Associated SPI peripheral. /// \param spiId SPI peripheral identifier. //------------------------------------------------------------------------------ void SDSPI_Configure(SdSpi *pSdSpi, AT91PS_SPI pSpiHw, unsigned char spiId) { // Initialize the SPI structure pSdSpi->pSpiHw = pSpiHw; pSdSpi->spiId = spiId; pSdSpi->semaphore = 1; // Enable the SPI clock AT91C_BASE_PMC->PMC_PCER = (1 << pSdSpi->spiId); // Execute a software reset of the SPI twice pSpiHw->SPI_CR = AT91C_SPI_SWRST; pSpiHw->SPI_CR = AT91C_SPI_SWRST; // Configure SPI in Master Mode with No CS selected !!! pSpiHw->SPI_MR = AT91C_SPI_MSTR | AT91C_SPI_MODFDIS | AT91C_SPI_PCS; // Disables the receiver PDC transfer requests // Disables the transmitter PDC transfer requests. pSpiHw->SPI_PTCR = AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS; // Enable the SPI pSpiHw->SPI_CR = AT91C_SPI_SPIEN; // Disable the SPI clock AT91C_BASE_PMC->PMC_PCDR = (1 << pSdSpi->spiId); } //------------------------------------------------------------------------------ /// Configures the parameters for the device corresponding to the cs. /// \param pSdSpi Pointer to a SdSpi instance. /// \param cs number corresponding to the SPI chip select. /// \param csr SPI_CSR value to setup. //------------------------------------------------------------------------------ void SDSPI_ConfigureCS(SdSpi *pSdSpi, unsigned char cs, unsigned int csr) { unsigned int spiMr; AT91S_SPI *pSpiHw = pSdSpi->pSpiHw; // Enable the SPI clock AT91C_BASE_PMC->PMC_PCER = (1 << pSdSpi->spiId); //TRACE_DEBUG("CSR[%d]=0x%8X\n\r", cs, csr); pSpiHw->SPI_CSR[cs] = csr; //jcb to put in sendcommand // Write to the MR register spiMr = pSpiHw->SPI_MR; spiMr |= AT91C_SPI_PCS; spiMr &= ~((1 << cs) << 16); pSpiHw->SPI_MR = spiMr; // Disable the SPI clock AT91C_BASE_PMC->PMC_PCDR = (1 << pSdSpi->spiId); } //------------------------------------------------------------------------------ /// Use PDC for SPI data transfer. /// Return 0 if no error, otherwise return error status. /// \param pSdSpi Pointer to a SdSpi instance. /// \param pData Data pointer. /// \param size Data transfer byte count. //------------------------------------------------------------------------------ unsigned char SDSPI_PDC(SdSpi *pSdSpi, unsigned char *pData, unsigned int size) { AT91PS_SPI pSpiHw = pSdSpi->pSpiHw; unsigned int spiIer; if (pSdSpi->semaphore == 0) { TRACE_DEBUG("No semaphore\n\r"); return SDSPI_ERROR_LOCK; } pSdSpi->semaphore--; // Enable the SPI clock AT91C_BASE_PMC->PMC_PCER = (1 << pSdSpi->spiId); // Disable transmitter and receiver pSpiHw->SPI_PTCR = AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS; // Receive Pointer Register pSpiHw->SPI_RPR = (int)pData; // Receive Counter Register pSpiHw->SPI_RCR = size; // Transmit Pointer Register pSpiHw->SPI_TPR = (int) pData; // Transmit Counter Register pSpiHw->SPI_TCR = size; spiIer = AT91C_SPI_RXBUFF; // Enable transmitter and receiver pSpiHw->SPI_PTCR = AT91C_PDC_RXTEN | AT91C_PDC_TXTEN; // Interrupt enable shall be done after PDC TXTEN and RXTEN pSpiHw->SPI_IER = spiIer; return 0; } //! Should be moved to a new file //------------------------------------------------------------------------------ /// Read data on SPI data bus; /// Returns 1 if read fails, returns 0 if no error. /// \param pSdSpi Pointer to a SD SPI driver instance. /// \param pData Data pointer. /// \param size Data size. //------------------------------------------------------------------------------ unsigned char SDSPI_Read(SdSpi *pSdSpi, unsigned char *pData, unsigned int size) { unsigned char error; // MOSI should hold high during read, or there will be wrong data in received data. memset(pData, 0xff, size); error = SDSPI_PDC(pSdSpi, pData, size); while(SDSPI_IsBusy(pSdSpi) == 1); if( error == 0 ) { return 0; } else { TRACE_DEBUG("PB SDSPI_Read\n\r"); return 1; } } //------------------------------------------------------------------------------ /// Write data on SPI data bus; /// Returns 1 if write fails, returns 0 if no error. /// \param pSdSpi Pointer to a SD SPI driver instance. /// \param pData Data pointer. /// \param size Data size. //------------------------------------------------------------------------------ unsigned char SDSPI_Write(SdSpi *pSdSpi, unsigned char *pData, unsigned int size) { unsigned char error; error = SDSPI_PDC(pSdSpi, pData, size); while(SDSPI_IsBusy(pSdSpi) == 1); if( error == 0 ) { return 0; } else { TRACE_DEBUG("PB SDSPI_Write\n\r"); return 1; } } //------------------------------------------------------------------------------ /// Return 1 if data busy. //------------------------------------------------------------------------------ unsigned char SDSPI_WaitDataBusy(SdSpi *pSdSpi) { unsigned char busyData; SDSPI_Read(pSdSpi, &busyData, 1); if (busyData != 0xff) { return 1; } else { return 0; } } //------------------------------------------------------------------------------ /// Convert SD MCI command to a SPI mode command token. /// \param pCmdToken Pointer to the SD command token. /// \param arg SD command argument //------------------------------------------------------------------------------ void SDSPI_MakeCmd(unsigned char *pCmdToken, unsigned int arg) { unsigned char sdCmdNum; unsigned char crc = 0; unsigned char crcPrev = 0; sdCmdNum = 0x3f & *pCmdToken; *pCmdToken = sdCmdNum | 0x40; *(pCmdToken+1) = (arg >> 24) & 0xff; *(pCmdToken+2) = (arg >> 16) & 0xff; *(pCmdToken+3) = (arg >> 8) & 0xff; *(pCmdToken+4) = arg & 0xff; crc = crc7(crcPrev, (unsigned char *)(pCmdToken), 5); *(pCmdToken+5) = (crc << 1) | 1; } //------------------------------------------------------------------------------ /// Get response after send SD command. /// Return 0 if no error, otherwise indicate an error. /// \param pSdSpi Pointer to the SD SPI instance. /// \param pCommand Pointer to the SD command //------------------------------------------------------------------------------ unsigned char SDSPI_GetCmdResp(SdSpi *pSdSpi, SdSpiCmd *pCommand) { unsigned char resp[8]; // response unsigned char error; unsigned int respRetry = 8; //NCR max 8, refer to card datasheet memset(resp, 0, 8); // NCR: 1 ~ 8 * (8) // Wait for response start bit. do { error = SDSPI_Read(pSdSpi, &resp[0], 1); if (error) { TRACE_INFO("_GetCmdResp Err: 0x%X\n\r", error); return error; } if ((resp[0]&0x80) == 0) { break; } respRetry--; } while(respRetry > 0); if (respRetry == 0) { TRACE_WARNING("Cmd %d No Resp\n\r", pCommand->cmd & 0x3F); return SDSPI_NO_RESPONSE; } switch (pCommand->resType) { case 1: *(pCommand->pResp) = resp[0]; break; case 2: error = SDSPI_Read(pSdSpi, &resp[1], 1); if (error) { return SDSPI_ERROR; } *(pCommand->pResp) = resp[0] | (resp[1] << 8); break; // Response 3, get OCR case 3: error = SDSPI_Read(pSdSpi, &resp[1], 4); if (error) { return SDSPI_ERROR; } *(pCommand->pResp) = resp[0] | (resp[1] << 8) | (resp[2] << 16) | (resp[3] << 24); *(pCommand->pResp+1) = resp[4]; break; case 7: TRACE_DEBUG("case 7\n\r"); error = SDSPI_Read(pSdSpi, &resp[1], 4); if (error) { return SDSPI_ERROR; } *(pCommand->pResp) = resp[0] | (resp[1] << 8) | (resp[2] << 16) | (resp[3] << 24); *(pCommand->pResp+1) = resp[4]; break; default: TRACE_DEBUG("No Resp Type?\n\r"); break; } return SDSPI_SUCCESS; } //------------------------------------------------------------------------------ /// Get response after send data. /// Return SDSPI_DATA_NO_RESP or data response token. /// \param pSdSpi Pointer to the SD SPI instance. /// \param pCommand Pointer to the SD command //------------------------------------------------------------------------------ unsigned char SDSPI_GetDataResp(SdSpi *pSdSpi, SdSpiCmd *pCommand) { unsigned char resp = 0; // response unsigned char error; unsigned int respRetry = 100; //NCR max 8, refer to card datasheet // Wait for response start bit. do { error = SDSPI_Read(pSdSpi, &resp, 1); if (error) { return SDSPI_ERROR; } // Data Response Token if ((resp & 0x11) == 0x1) return (resp & 0x1F); //if ((resp & 0xF0) == 0) // break; respRetry--; } while(respRetry > 0); return SDSPI_DATA_NO_RESP; } //------------------------------------------------------------------------------ /// 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 /// error. /// \param pSdSpi Pointer to a SdSpi instance. /// \param pCommand Pointer to the SPI command to execute. //------------------------------------------------------------------------------ unsigned char SDSPI_SendCommand(SdSpi *pSdSpi, SdSpiCmd *pCommand) { AT91S_SPI *pSpiHw = pSdSpi->pSpiHw; unsigned char CmdToken[6]; unsigned char *pData; unsigned int blockSize; unsigned int i; unsigned char error; unsigned char dataHeader; unsigned int dataRetry1 = 200000; unsigned int dataRetry2 = 200000; unsigned char crc[2]; unsigned char crcPrev = 0; unsigned char crcPrev2 = 0; SANITY_CHECK(pSdSpi); SANITY_CHECK(pSpiHw); SANITY_CHECK(pCommand); CmdToken[0] = pCommand->cmd & 0x3F; pData = pCommand->pData; blockSize = pCommand->blockSize; SDSPI_MakeCmd((unsigned char *)&CmdToken, pCommand->arg); // Command is now being executed pSdSpi->pCommand = pCommand; pCommand->status = SDSPI_STATUS_PENDING; // Send the command if((pCommand->conTrans == SPI_NEW_TRANSFER) || (blockSize == 0)) { TRACE_DEBUG("SendCmd%d\n\r", pCommand->cmd & 0x3F); for(i = 0; i < 6; i++) { error = SDSPI_Write(pSdSpi, &CmdToken[i], 1); if (error) { TRACE_DEBUG("Error WrCmd[i]: %d\n\r", i, error); return error; } } error = SDSPI_GetCmdResp(pSdSpi, pCommand); if (error) { TRACE_DEBUG("Error GetResp: %d\n\r", error); return error; } } if( (blockSize > 0) && (pCommand->nbBlock == 0) ) { pCommand->nbBlock = 1; } // For data block operations while (pCommand->nbBlock > 0) { // If data block size is invalid, return error if (blockSize == 0) { TRACE_DEBUG("Block Size = 0\n\r"); return 1; } // DATA transfer from card to host if (pCommand->isRead) { TRACE_DEBUG("RD\n\r"); do { SDSPI_Read(pSdSpi, &dataHeader, 1); TRACE_DEBUG("R Hdr %x\n\r", dataHeader); if (dataHeader == SDSPI_START_BLOCK_1) { break; } else if((dataHeader & 0xf0) == 0x00) { pCommand->status = SDSPI_STATUS_ERROR; TRACE_ERROR("R Data Hdr 0x%X!\n\r", dataHeader); return SDSPI_ERROR; } dataRetry1 --; } while(dataRetry1 > 0); if (dataRetry1 == 0) { TRACE_DEBUG("Timeout data RD retry\n\r"); return SDSPI_ERROR; } SDSPI_Read(pSdSpi, pData, blockSize); // CRC is not for R3, CSD, CID if ((pCommand->cmd & 0x3f) != 9 && (pCommand->cmd & 0x3f) != 10 && pCommand->resType != 3) { SDSPI_Read(pSdSpi, crc, 2); #ifdef SDSPI_CRC_ON // Check data CRC TRACE_DEBUG("Check Data CRC\n\r"); crcPrev = 0; crcPrev2 = 0; if (crc[0] != ((crc_itu_t(crcPrev, pData, blockSize) & 0xff00) >> 8 ) || crc[1] != (crc_itu_t(crcPrev2, pData, blockSize) & 0xff)) { TRACE_ERROR("CRC error 0x%X 0x%X 0x%X\n\r", \ crc[0], crc[1], crc_itu_t(pData, blockSize)); return SDSPI_ERROR; } #endif } } // DATA transfer from host to card else { TRACE_DEBUG("WR\n\r"); SANITY_CHECK(pCommand->pResp); //SDSPI_NCS(pSdSpi); if ( (pCommand->conTrans == SPI_CONTINUE_TRANSFER) || ((pCommand->cmd & 0x3f) == 25)) { dataHeader = SDSPI_START_BLOCK_2; } else { dataHeader = SDSPI_START_BLOCK_1; } crcPrev = 0; crc[0] = (crc_itu_t(crcPrev, pData, blockSize) & 0xff00) >> 8; crcPrev2 = 0; crc[1] = (crc_itu_t(crcPrev2, pData, blockSize) & 0xff); SDSPI_Write(pSdSpi, &dataHeader, 1); SDSPI_Write(pSdSpi, pData, blockSize); SDSPI_Write(pSdSpi, crc, 2); // Check response status error = SDSPI_GetDataResp(pSdSpi, pCommand); switch(error) { case SDSPI_DATA_ACCEPTED: break; case SDSPI_DATA_CRC_ERR: TRACE_ERROR("WR CRC\n\r"); return SDSPI_ERROR; case SDSPI_DATA_WR_ERR: TRACE_ERROR("WR ERR\n\r"); return SDSPI_ERROR; default: TRACE_ERROR("WR RESP %x\n\r", error); return SDSPI_ERROR; } do { if (SDSPI_WaitDataBusy(pSdSpi) == 0) { break; } dataRetry2--; } while(dataRetry2 > 0); if (dataRetry2 == 0) { TRACE_ERROR("WR Busy timeout\n\r"); return SDSPI_BUSY; } } pData += blockSize; pCommand->nbBlock--; } if (pCommand->status == SDSPI_STATUS_PENDING) { pCommand->status = 0; } //TRACE_DEBUG("end SDSPI_SendCommand\n\r"); return 0; } //! //------------------------------------------------------------------------------ /// The SPI_Handler must be called by the SPI Interrupt Service Routine with the /// corresponding Spi instance. /// The SPI_Handler will unlock the Spi semaphore and invoke the upper application /// callback. /// \param pSdSpi Pointer to a SdSpi instance. //------------------------------------------------------------------------------ void SDSPI_Handler(SdSpi *pSdSpi) { SdSpiCmd *pCommand = pSdSpi->pCommand; AT91S_SPI *pSpiHw = pSdSpi->pSpiHw; volatile unsigned int spiSr; // Read the status register spiSr = pSpiHw->SPI_SR; if(spiSr & AT91C_SPI_RXBUFF) { if (pCommand->status == SDSPI_STATUS_PENDING) { pCommand->status = 0; } // Disable transmitter and receiver pSpiHw->SPI_PTCR = AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS; // Disable the SPI clock AT91C_BASE_PMC->PMC_PCDR = (1 << pSdSpi->spiId); // Disable buffer complete interrupt pSpiHw->SPI_IDR = AT91C_SPI_RXBUFF | AT91C_SPI_ENDTX; // Release the SPI semaphore pSdSpi->semaphore++; } // Invoke the callback associated with the current command if (pCommand && pCommand->callback) { pCommand->callback(0, pCommand); } } //------------------------------------------------------------------------------ /// Returns 1 if the given SPI transfer is complete; otherwise returns 0. /// \param pCommand Pointer to a SdSpiCmd instance. //------------------------------------------------------------------------------ unsigned char SDSPI_IsTxComplete(SdSpiCmd *pCommand) { if (pCommand->status != SDSPI_STATUS_PENDING) { if (pCommand->status != 0){ TRACE_DEBUG("SPI_IsTxComplete %d\n\r", pCommand->status); } return 1; } else { return 0; } } //------------------------------------------------------------------------------ /// Close a SPI driver instance and the underlying peripheral. /// \param pSdSpi Pointer to a SD SPI driver instance. //------------------------------------------------------------------------------ void SDSPI_Close(SdSpi *pSdSpi) { AT91S_SPI *pSpiHw = pSdSpi->pSpiHw; SANITY_CHECK(pSdSpi); SANITY_CHECK(pSpiHw); // Enable the SPI clock AT91C_BASE_PMC->PMC_PCER = (1 << pSdSpi->spiId); // Disable the PDC transfer pSpiHw->SPI_PTCR = AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS; // Disable the SPI pSpiHw->SPI_CR = AT91C_SPI_SPIDIS; // Disable the SPI clock AT91C_BASE_PMC->PMC_PCDR = (1 << pSdSpi->spiId); // Disable all the interrupts pSpiHw->SPI_IDR = 0xFFFFFFFF; } //------------------------------------------------------------------------------ /// Returns 1 if the SPI driver is currently busy programming; /// otherwise returns 0. /// \param pSdSpi Pointer to a SD SPI driver instance. //------------------------------------------------------------------------------ unsigned char SDSPI_IsBusy(SdSpi *pSdSpi) { if (pSdSpi->semaphore == 0) { return 1; } else { return 0; } } //------------------------------------------------------------------------------ /// Wait several cycles on SPI bus; /// Returns 0 to indicates no error, otherwise return 1. /// \param pSdSpi Pointer to a SD SPI driver instance. /// \param cycles Wait data cycles. //------------------------------------------------------------------------------ unsigned char SDSPI_Wait(SdSpi *pSdSpi, unsigned int cycles) { unsigned int i = cycles; unsigned char data = 0xff; for (; i > 0; i--) { if (SDSPI_Read(pSdSpi, &data, 1)) { return 1; } } return 0; } //------------------------------------------------------------------------------ /// Send stop transfer data token; /// Returns 0 to indicates no error, otherwise return 1. /// \param pSdSpi Pointer to a SD SPI driver instance. //------------------------------------------------------------------------------ unsigned char SDSPI_StopTranToken(SdSpi *pSdSpi) { unsigned char stopToken = SDSPI_STOP_TRAN; TRACE_DEBUG("SDSPI_StopTranToken\n\r"); return SDSPI_Write(pSdSpi, &stopToken, 1); } //------------------------------------------------------------------------------ /// Wait, SD card Ncs cycles; /// Returns 0 to indicates no error, otherwise return 1. /// \param pSdSpi Pointer to a SD SPI driver instance. //------------------------------------------------------------------------------ unsigned char SDSPI_NCS(SdSpi *pSdSpi) { unsigned int i; unsigned char ncs; for(i = 0; i < 15; i++) { ncs = 0xff; if (SDSPI_Write(pSdSpi, &ncs, 1)) { return 1; } } return 0; }