From 044ad7c3987460ede48ff27afd6bdb0ca05a0432 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Mon, 4 Jul 2011 20:52:54 +0200 Subject: import at91lib from at91lib_20100901_softpack_1_9_v_1_0_svn_v15011 it's sad to see that atmel doesn't publish their svn repo or has a centralized location or even puts proper version/release info into the library itself --- memories/sdmmc/sdspi.c | 711 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 711 insertions(+) create mode 100644 memories/sdmmc/sdspi.c (limited to 'memories/sdmmc/sdspi.c') diff --git a/memories/sdmmc/sdspi.c b/memories/sdmmc/sdspi.c new file mode 100644 index 0000000..93b7629 --- /dev/null +++ b/memories/sdmmc/sdspi.c @@ -0,0 +1,711 @@ +/* ---------------------------------------------------------------------------- + * 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; +} + + -- cgit v1.2.3