|
|
|
/****************************************************************************
|
|
|
|
* SPI flash driver for M25P64
|
|
|
|
****************************************************************************/
|
|
|
|
#include <common.h>
|
|
|
|
#include <linux/ctype.h>
|
|
|
|
#include <asm/io.h>
|
|
|
|
|
|
|
|
#if defined(CONFIG_SPI)
|
|
|
|
|
|
|
|
/* Application definitions */
|
|
|
|
|
|
|
|
#define NUM_SECTORS 128 /* number of sectors */
|
|
|
|
#define SECTOR_SIZE 0x10000
|
|
|
|
#define NOP_NUM 1000
|
|
|
|
|
|
|
|
#define COMMON_SPI_SETTINGS (SPE|MSTR|CPHA|CPOL) /* Settings to the SPI_CTL */
|
|
|
|
#define TIMOD01 (0x01) /* stes the SPI to work with core instructions */
|
|
|
|
|
|
|
|
/* Flash commands */
|
|
|
|
#define SPI_WREN (0x06) /*Set Write Enable Latch */
|
|
|
|
#define SPI_WRDI (0x04) /*Reset Write Enable Latch */
|
|
|
|
#define SPI_RDSR (0x05) /*Read Status Register */
|
|
|
|
#define SPI_WRSR (0x01) /*Write Status Register */
|
|
|
|
#define SPI_READ (0x03) /*Read data from memory */
|
|
|
|
#define SPI_FAST_READ (0x0B) /*Read data from memory */
|
|
|
|
#define SPI_PP (0x02) /*Program Data into memory */
|
|
|
|
#define SPI_SE (0xD8) /*Erase one sector in memory */
|
|
|
|
#define SPI_BE (0xC7) /*Erase all memory */
|
|
|
|
#define WIP (0x1) /*Check the write in progress bit of the SPI status register */
|
|
|
|
#define WEL (0x2) /*Check the write enable bit of the SPI status register */
|
|
|
|
|
|
|
|
#define TIMEOUT 350000000
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
NO_ERR,
|
|
|
|
POLL_TIMEOUT,
|
|
|
|
INVALID_SECTOR,
|
|
|
|
INVALID_BLOCK,
|
|
|
|
} ERROR_CODE;
|
|
|
|
|
|
|
|
void spi_init_f(void);
|
|
|
|
void spi_init_r(void);
|
|
|
|
ssize_t spi_read(uchar *, int, uchar *, int);
|
|
|
|
ssize_t spi_write(uchar *, int, uchar *, int);
|
|
|
|
|
|
|
|
char ReadStatusRegister(void);
|
|
|
|
void Wait_For_SPIF(void);
|
|
|
|
void SetupSPI(const int spi_setting);
|
|
|
|
void SPI_OFF(void);
|
|
|
|
void SendSingleCommand(const int iCommand);
|
|
|
|
|
|
|
|
ERROR_CODE GetSectorNumber(unsigned long ulOffset, int *pnSector);
|
|
|
|
ERROR_CODE EraseBlock(int nBlock);
|
|
|
|
ERROR_CODE ReadData(unsigned long ulStart, long lCount, int *pnData);
|
|
|
|
ERROR_CODE WriteData(unsigned long ulStart, long lCount, int *pnData);
|
|
|
|
ERROR_CODE Wait_For_Status(char Statusbit);
|
|
|
|
ERROR_CODE Wait_For_WEL(void);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Function: spi_init_f
|
|
|
|
* Description: Init SPI-Controller (ROM part)
|
|
|
|
* return: ---
|
|
|
|
*/
|
|
|
|
void spi_init_f(void)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Function: spi_init_r
|
|
|
|
* Description: Init SPI-Controller (RAM part) -
|
|
|
|
* The malloc engine is ready and we can move our buffers to
|
|
|
|
* normal RAM
|
|
|
|
* return: ---
|
|
|
|
*/
|
|
|
|
void spi_init_r(void)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Function: spi_write
|
|
|
|
*/
|
|
|
|
ssize_t spi_write(uchar * addr, int alen, uchar * buffer, int len)
|
|
|
|
{
|
|
|
|
unsigned long offset;
|
|
|
|
int start_block, end_block;
|
|
|
|
int start_byte, end_byte;
|
|
|
|
ERROR_CODE result = NO_ERR;
|
|
|
|
uchar temp[SECTOR_SIZE];
|
|
|
|
int i, num;
|
|
|
|
|
|
|
|
offset = addr[0] << 16 | addr[1] << 8 | addr[2];
|
|
|
|
/* Get the start block number */
|
|
|
|
result = GetSectorNumber(offset, &start_block);
|
|
|
|
if (result == INVALID_SECTOR) {
|
|
|
|
printf("Invalid sector! ");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* Get the end block number */
|
|
|
|
result = GetSectorNumber(offset + len - 1, &end_block);
|
|
|
|
if (result == INVALID_SECTOR) {
|
|
|
|
printf("Invalid sector! ");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (num = start_block; num <= end_block; num++) {
|
|
|
|
ReadData(num * SECTOR_SIZE, SECTOR_SIZE, (int *)temp);
|
|
|
|
start_byte = num * SECTOR_SIZE;
|
|
|
|
end_byte = (num + 1) * SECTOR_SIZE - 1;
|
|
|
|
if (start_byte < offset)
|
|
|
|
start_byte = offset;
|
|
|
|
if (end_byte > (offset + len))
|
|
|
|
end_byte = (offset + len - 1);
|
|
|
|
for (i = start_byte; i <= end_byte; i++)
|
|
|
|
temp[i - num * SECTOR_SIZE] = buffer[i - offset];
|
|
|
|
EraseBlock(num);
|
|
|
|
result = WriteData(num * SECTOR_SIZE, SECTOR_SIZE, (int *)temp);
|
|
|
|
if (result != NO_ERR)
|
|
|
|
return 0;
|
|
|
|
printf(".");
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Function: spi_read
|
|
|
|
*/
|
|
|
|
ssize_t spi_read(uchar * addr, int alen, uchar * buffer, int len)
|
|
|
|
{
|
|
|
|
unsigned long offset;
|
|
|
|
offset = addr[0] << 16 | addr[1] << 8 | addr[2];
|
|
|
|
ReadData(offset, len, (int *)buffer);
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SendSingleCommand(const int iCommand)
|
|
|
|
{
|
|
|
|
unsigned short dummy;
|
|
|
|
|
|
|
|
/* turns on the SPI in single write mode */
|
|
|
|
SetupSPI((COMMON_SPI_SETTINGS | TIMOD01));
|
|
|
|
|
|
|
|
/* sends the actual command to the SPI TX register */
|
|
|
|
*pSPI_TDBR = iCommand;
|
|
|
|
sync();
|
|
|
|
|
|
|
|
/* The SPI status register will be polled to check the SPIF bit */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
|
|
|
|
dummy = *pSPI_RDBR;
|
|
|
|
|
|
|
|
/* The SPI will be turned off */
|
|
|
|
SPI_OFF();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetupSPI(const int spi_setting)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (icache_status() || dcache_status())
|
|
|
|
udelay(CONFIG_CCLK_HZ / 50000000);
|
|
|
|
/*sets up the PF10 to be the slave select of the SPI */
|
|
|
|
*pPORTF_FER |= (PF10 | PF11 | PF12 | PF13);
|
|
|
|
*pSPI_FLG = 0xFF02;
|
|
|
|
*pSPI_BAUD = CONFIG_SPI_BAUD;
|
|
|
|
*pSPI_CTL = spi_setting;
|
|
|
|
sync();
|
|
|
|
|
|
|
|
*pSPI_FLG = 0xFD02;
|
|
|
|
sync();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SPI_OFF(void)
|
|
|
|
{
|
|
|
|
|
|
|
|
*pSPI_CTL = 0x0400; /* disable SPI */
|
|
|
|
*pSPI_FLG = 0;
|
|
|
|
*pSPI_BAUD = 0;
|
|
|
|
sync();
|
|
|
|
udelay(CONFIG_CCLK_HZ / 50000000);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Wait_For_SPIF(void)
|
|
|
|
{
|
|
|
|
unsigned short dummyread;
|
|
|
|
while ((*pSPI_STAT & TXS)) ;
|
|
|
|
while (!(*pSPI_STAT & SPIF)) ;
|
|
|
|
while (!(*pSPI_STAT & RXS)) ;
|
|
|
|
/* Read dummy to empty the receive register */
|
|
|
|
dummyread = *pSPI_RDBR;
|
|
|
|
}
|
|
|
|
|
|
|
|
ERROR_CODE Wait_For_WEL(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
char status_register = 0;
|
|
|
|
ERROR_CODE ErrorCode = NO_ERR;
|
|
|
|
|
|
|
|
for (i = 0; i < TIMEOUT; i++) {
|
|
|
|
status_register = ReadStatusRegister();
|
|
|
|
if ((status_register & WEL)) {
|
|
|
|
ErrorCode = NO_ERR;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ErrorCode = POLL_TIMEOUT; /* Time out error */
|
|
|
|
};
|
|
|
|
|
|
|
|
return ErrorCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
ERROR_CODE Wait_For_Status(char Statusbit)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
char status_register = 0xFF;
|
|
|
|
ERROR_CODE ErrorCode = NO_ERR;
|
|
|
|
|
|
|
|
for (i = 0; i < TIMEOUT; i++) {
|
|
|
|
status_register = ReadStatusRegister();
|
|
|
|
if (!(status_register & Statusbit)) {
|
|
|
|
ErrorCode = NO_ERR;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ErrorCode = POLL_TIMEOUT; /* Time out error */
|
|
|
|
};
|
|
|
|
|
|
|
|
return ErrorCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
char ReadStatusRegister(void)
|
|
|
|
{
|
|
|
|
char status_register = 0;
|
|
|
|
|
|
|
|
SetupSPI((COMMON_SPI_SETTINGS | TIMOD01)); /* Turn on the SPI */
|
|
|
|
|
|
|
|
*pSPI_TDBR = SPI_RDSR; /* send instruction to read status register */
|
|
|
|
sync();
|
|
|
|
Wait_For_SPIF(); /*wait until the instruction has been sent */
|
|
|
|
*pSPI_TDBR = 0; /*send dummy to receive the status register */
|
|
|
|
sync();
|
|
|
|
Wait_For_SPIF(); /*wait until the data has been sent */
|
|
|
|
status_register = *pSPI_RDBR; /*read the status register */
|
|
|
|
|
|
|
|
SPI_OFF(); /* Turn off the SPI */
|
|
|
|
|
|
|
|
return status_register;
|
|
|
|
}
|
|
|
|
|
|
|
|
ERROR_CODE GetSectorNumber(unsigned long ulOffset, int *pnSector)
|
|
|
|
{
|
|
|
|
int nSector = 0;
|
|
|
|
ERROR_CODE ErrorCode = NO_ERR;
|
|
|
|
|
|
|
|
if (ulOffset > (NUM_SECTORS * 0x10000 - 1)) {
|
|
|
|
ErrorCode = INVALID_SECTOR;
|
|
|
|
return ErrorCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
nSector = (int)ulOffset / 0x10000;
|
|
|
|
*pnSector = nSector;
|
|
|
|
|
|
|
|
return ErrorCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
ERROR_CODE EraseBlock(int nBlock)
|
|
|
|
{
|
|
|
|
unsigned long ulSectorOff = 0x0, ShiftValue;
|
|
|
|
ERROR_CODE ErrorCode = NO_ERR;
|
|
|
|
|
|
|
|
/* if the block is invalid just return */
|
|
|
|
if ((nBlock < 0) || (nBlock > NUM_SECTORS)) {
|
|
|
|
ErrorCode = INVALID_BLOCK;
|
|
|
|
return ErrorCode;
|
|
|
|
}
|
|
|
|
/* figure out the offset of the block in flash */
|
|
|
|
if ((nBlock >= 0) && (nBlock < NUM_SECTORS)) {
|
|
|
|
ulSectorOff = (nBlock * SECTOR_SIZE);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
ErrorCode = INVALID_BLOCK;
|
|
|
|
return ErrorCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A write enable instruction must previously have been executed */
|
|
|
|
SendSingleCommand(SPI_WREN);
|
|
|
|
|
|
|
|
/* The status register will be polled to check the write enable latch "WREN" */
|
|
|
|
ErrorCode = Wait_For_WEL();
|
|
|
|
|
|
|
|
if (POLL_TIMEOUT == ErrorCode) {
|
|
|
|
printf("SPI Erase block error\n");
|
|
|
|
return ErrorCode;
|
|
|
|
} else
|
|
|
|
|
|
|
|
/* Turn on the SPI to send single commands */
|
|
|
|
SetupSPI((COMMON_SPI_SETTINGS | TIMOD01));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Send the erase block command to the flash followed by the 24 address
|
|
|
|
* to point to the start of a sector
|
|
|
|
*/
|
|
|
|
*pSPI_TDBR = SPI_SE;
|
|
|
|
sync();
|
|
|
|
Wait_For_SPIF();
|
|
|
|
/* Send the highest byte of the 24 bit address at first */
|
|
|
|
ShiftValue = (ulSectorOff >> 16);
|
|
|
|
*pSPI_TDBR = ShiftValue;
|
|
|
|
sync();
|
|
|
|
/* Wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
/* Send the middle byte of the 24 bit address at second */
|
|
|
|
ShiftValue = (ulSectorOff >> 8);
|
|
|
|
*pSPI_TDBR = ShiftValue;
|
|
|
|
sync();
|
|
|
|
/* Wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
/* Send the lowest byte of the 24 bit address finally */
|
|
|
|
*pSPI_TDBR = ulSectorOff;
|
|
|
|
sync();
|
|
|
|
/* Wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
|
|
|
|
/* Turns off the SPI */
|
|
|
|
SPI_OFF();
|
|
|
|
|
|
|
|
/* Poll the status register to check the Write in Progress bit */
|
|
|
|
/* Sector erase takes time */
|
|
|
|
ErrorCode = Wait_For_Status(WIP);
|
|
|
|
|
|
|
|
/* block erase should be complete */
|
|
|
|
return ErrorCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ERROR_CODE ReadData()
|
|
|
|
* Read a value from flash for verify purpose
|
|
|
|
* Inputs: unsigned long ulStart - holds the SPI start address
|
|
|
|
* int pnData - pointer to store value read from flash
|
|
|
|
* long lCount - number of elements to read
|
|
|
|
*/
|
|
|
|
ERROR_CODE ReadData(unsigned long ulStart, long lCount, int *pnData)
|
|
|
|
{
|
|
|
|
unsigned long ShiftValue;
|
|
|
|
char *cnData;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Pointer cast to be able to increment byte wise */
|
|
|
|
|
|
|
|
cnData = (char *)pnData;
|
|
|
|
/* Start SPI interface */
|
|
|
|
SetupSPI((COMMON_SPI_SETTINGS | TIMOD01));
|
|
|
|
|
|
|
|
#ifdef CONFIG_SPI_FLASH_FAST_READ
|
|
|
|
/* Send the read command to SPI device */
|
|
|
|
*pSPI_TDBR = SPI_FAST_READ;
|
|
|
|
#else
|
|
|
|
/* Send the read command to SPI device */
|
|
|
|
*pSPI_TDBR = SPI_READ;
|
|
|
|
#endif
|
|
|
|
sync();
|
|
|
|
/* Wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
/* Send the highest byte of the 24 bit address at first */
|
|
|
|
ShiftValue = (ulStart >> 16);
|
|
|
|
/* Send the byte to the SPI device */
|
|
|
|
*pSPI_TDBR = ShiftValue;
|
|
|
|
sync();
|
|
|
|
/* Wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
/* Send the middle byte of the 24 bit address at second */
|
|
|
|
ShiftValue = (ulStart >> 8);
|
|
|
|
/* Send the byte to the SPI device */
|
|
|
|
*pSPI_TDBR = ShiftValue;
|
|
|
|
sync();
|
|
|
|
/* Wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
/* Send the lowest byte of the 24 bit address finally */
|
|
|
|
*pSPI_TDBR = ulStart;
|
|
|
|
sync();
|
|
|
|
/* Wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
|
|
|
|
#ifdef CONFIG_SPI_FLASH_FAST_READ
|
|
|
|
/* Send dummy for FAST_READ */
|
|
|
|
*pSPI_TDBR = 0;
|
|
|
|
sync();
|
|
|
|
/* Wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* After the SPI device address has been placed on the MOSI pin the data can be */
|
|
|
|
/* received on the MISO pin. */
|
|
|
|
for (i = 0; i < lCount; i++) {
|
|
|
|
*pSPI_TDBR = 0;
|
|
|
|
sync();
|
|
|
|
while (!(*pSPI_STAT & RXS)) ;
|
|
|
|
*cnData++ = *pSPI_RDBR;
|
|
|
|
|
|
|
|
if ((i >= SECTOR_SIZE) && (i % SECTOR_SIZE == 0))
|
|
|
|
printf(".");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Turn off the SPI */
|
|
|
|
SPI_OFF();
|
|
|
|
|
|
|
|
return NO_ERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
ERROR_CODE WriteFlash(unsigned long ulStartAddr, long lTransferCount,
|
|
|
|
int *iDataSource, long *lWriteCount)
|
|
|
|
{
|
|
|
|
|
|
|
|
unsigned long ulWAddr;
|
|
|
|
long lWTransferCount = 0;
|
|
|
|
int i;
|
|
|
|
char iData;
|
|
|
|
char *temp = (char *)iDataSource;
|
|
|
|
ERROR_CODE ErrorCode = NO_ERR;
|
|
|
|
|
|
|
|
/* First, a Write Enable Command must be sent to the SPI. */
|
|
|
|
SendSingleCommand(SPI_WREN);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Second, the SPI Status Register will be tested whether the
|
|
|
|
* Write Enable Bit has been set
|
|
|
|
*/
|
|
|
|
ErrorCode = Wait_For_WEL();
|
|
|
|
if (POLL_TIMEOUT == ErrorCode) {
|
|
|
|
printf("SPI Write Time Out\n");
|
|
|
|
return ErrorCode;
|
|
|
|
} else
|
|
|
|
/* Third, the 24 bit address will be shifted out
|
|
|
|
* the SPI MOSI bytewise.
|
|
|
|
* Turns the SPI on
|
|
|
|
*/
|
|
|
|
SetupSPI((COMMON_SPI_SETTINGS | TIMOD01));
|
|
|
|
*pSPI_TDBR = SPI_PP;
|
|
|
|
sync();
|
|
|
|
/*wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
ulWAddr = (ulStartAddr >> 16);
|
|
|
|
*pSPI_TDBR = ulWAddr;
|
|
|
|
sync();
|
|
|
|
/*wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
ulWAddr = (ulStartAddr >> 8);
|
|
|
|
*pSPI_TDBR = ulWAddr;
|
|
|
|
sync();
|
|
|
|
/*wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
ulWAddr = ulStartAddr;
|
|
|
|
*pSPI_TDBR = ulWAddr;
|
|
|
|
sync();
|
|
|
|
/*wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
/*
|
|
|
|
* Fourth, maximum number of 256 bytes will be taken from the Buffer
|
|
|
|
* and sent to the SPI device.
|
|
|
|
*/
|
|
|
|
for (i = 0; (i < lTransferCount) && (i < 256); i++, lWTransferCount++) {
|
|
|
|
iData = *temp;
|
|
|
|
*pSPI_TDBR = iData;
|
|
|
|
sync();
|
|
|
|
/*wait until the instruction has been sent */
|
|
|
|
Wait_For_SPIF();
|
|
|
|
temp++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Turns the SPI off */
|
|
|
|
SPI_OFF();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Sixth, the SPI Write in Progress Bit must be toggled to ensure the
|
|
|
|
* programming is done before start of next transfer
|
|
|
|
*/
|
|
|
|
ErrorCode = Wait_For_Status(WIP);
|
|
|
|
|
|
|
|
if (POLL_TIMEOUT == ErrorCode) {
|
|
|
|
printf("SPI Program Time out!\n");
|
|
|
|
return ErrorCode;
|
|
|
|
} else
|
|
|
|
|
|
|
|
*lWriteCount = lWTransferCount;
|
|
|
|
|
|
|
|
return ErrorCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
ERROR_CODE WriteData(unsigned long ulStart, long lCount, int *pnData)
|
|
|
|
{
|
|
|
|
|
|
|
|
unsigned long ulWStart = ulStart;
|
|
|
|
long lWCount = lCount, lWriteCount;
|
|
|
|
long *pnWriteCount = &lWriteCount;
|
|
|
|
|
|
|
|
ERROR_CODE ErrorCode = NO_ERR;
|
|
|
|
|
|
|
|
while (lWCount != 0) {
|
|
|
|
ErrorCode = WriteFlash(ulWStart, lWCount, pnData, pnWriteCount);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* After each function call of WriteFlash the counter
|
|
|
|
* must be adjusted
|
|
|
|
*/
|
|
|
|
lWCount -= *pnWriteCount;
|
|
|
|
|
|
|
|
/* Also, both address pointers must be recalculated. */
|
|
|
|
ulWStart += *pnWriteCount;
|
|
|
|
pnData += *pnWriteCount / 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* return the appropriate error code */
|
|
|
|
return ErrorCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* CONFIG_SPI */
|