588 lines
16 KiB
C++
588 lines
16 KiB
C++
/*
|
|
* Integration in Miosix by Terraneo Federico.
|
|
* Based on code by Martin Thomas to initialize SD cards from LPC2000
|
|
*/
|
|
|
|
#include "sd_lpc2000.h"
|
|
#include "interfaces/bsp.h"
|
|
#include "LPC213x.h"
|
|
#include "board_settings.h" //For sdVoltage
|
|
#include <cstdio>
|
|
#include <errno.h>
|
|
|
|
//Note: enabling debugging might cause deadlock when using sleep() or reboot()
|
|
//The bug won't be fixed because debugging is only useful for driver development
|
|
///\internal Debug macro, for normal conditions
|
|
//#define DBG iprintf
|
|
#define DBG(x,...) do {} while(0)
|
|
///\internal Debug macro, for errors only
|
|
//#define DBGERR iprintf
|
|
#define DBGERR(x,...) do {} while(0)
|
|
|
|
namespace miosix {
|
|
|
|
///\internal Type of card (1<<0)=MMC (1<<1)=SDv1 (1<<2)=SDv2 (1<<2)|(1<<3)=SDHC
|
|
static unsigned char cardType=0;
|
|
|
|
/*
|
|
* Definitions for MMC/SDC command.
|
|
* A command has the following format:
|
|
* - 1 bit @ 0 (start bit)
|
|
* - 1 bit @ 1 (transmission bit)
|
|
* - 6 bit which identify command index (CMD0..CMD63)
|
|
* - 32 bit command argument
|
|
* - 7 bit CRC
|
|
* - 1 bit @ 1 (end bit)
|
|
* In addition, ACMDxx are the sequence of two commands, CMD55 and CMDxx
|
|
* These constants have the following meaninig:
|
|
* - bit #7 @ 1 indicates that it is an ACMD. send_cmd() will send CMD55, then
|
|
* clear this bit and send the command with this bit @ 0 (which is start bit)
|
|
* - bit #6 always @ 1, because it is the transmission bit
|
|
* - remaining 6 bit represent command index
|
|
*/
|
|
#define CMD0 (0x40+0) /* GO_IDLE_STATE */
|
|
#define CMD1 (0x40+1) /* SEND_OP_COND (MMC) */
|
|
#define ACMD41 (0xC0+41) /* SEND_OP_COND (SDC) */
|
|
#define CMD8 (0x40+8) /* SEND_IF_COND */
|
|
#define CMD9 (0x40+9) /* SEND_CSD */
|
|
#define CMD10 (0x40+10) /* SEND_CID */
|
|
#define CMD12 (0x40+12) /* STOP_TRANSMISSION */
|
|
#define CMD13 (0x40+13) /* SEND_STATUS */
|
|
#define ACMD13 (0xC0+13) /* SD_STATUS (SDC) */
|
|
#define CMD16 (0x40+16) /* SET_BLOCKLEN */
|
|
#define CMD17 (0x40+17) /* READ_SINGLE_BLOCK */
|
|
#define CMD18 (0x40+18) /* READ_MULTIPLE_BLOCK */
|
|
#define CMD23 (0x40+23) /* SET_BLOCK_COUNT (MMC) */
|
|
#define ACMD23 (0xC0+23) /* SET_WR_BLK_ERASE_COUNT (SDC) */
|
|
#define CMD24 (0x40+24) /* WRITE_BLOCK */
|
|
#define CMD25 (0x40+25) /* WRITE_MULTIPLE_BLOCK */
|
|
#define CMD42 (0x40+42) /* LOCK_UNLOCK */
|
|
#define CMD55 (0x40+55) /* APP_CMD */
|
|
#define CMD58 (0x40+58) /* READ_OCR */
|
|
|
|
// SSPCR0 Bit-Definitions
|
|
#define CPOL 6
|
|
#define CPHA 7
|
|
// SSPCR1 Bit-Defintions
|
|
#define SSE 1
|
|
#define MS 2
|
|
#define SCR 8
|
|
// SSPSR Bit-Definitions
|
|
#define TNF 1
|
|
#define RNE 2
|
|
#define BSY 4
|
|
|
|
#define SPI_SCK_PIN 17 // SCK P0.17 out
|
|
#define SPI_MISO_PIN 18 // MISO P0.18 in
|
|
#define SPI_MOSI_PIN 19 // MOSI P0.19 out
|
|
#define SPI_SS_PIN 20 // CS p0.20 out
|
|
|
|
#define SPI_SCK_FUNCBIT 2
|
|
#define SPI_MISO_FUNCBIT 4
|
|
#define SPI_MOSI_FUNCBIT 6
|
|
#define SPI_SS_FUNCBIT 8
|
|
|
|
///\internal Maximum speed 14745600/2=7372800
|
|
#define SPI_PRESCALE_MIN 2
|
|
|
|
///\internal Select/unselect card
|
|
#define CS_LOW() IOCLR0 = (1<<SPI_SS_PIN)
|
|
#define CS_HIGH() IOSET0 = (1<<SPI_SS_PIN)
|
|
|
|
//Function prototypes
|
|
static unsigned char send_cmd(unsigned char cmd, unsigned int arg);
|
|
|
|
/**
|
|
* \internal
|
|
* Initialize SPI
|
|
*/
|
|
static void spi_1_init()
|
|
{
|
|
unsigned char incoming;
|
|
PCONP|=(1<<10);//Enable SPI1
|
|
// setup GPIO
|
|
IODIR0 |= (1<<SPI_SCK_PIN)|(1<<SPI_MOSI_PIN)|(1<<SPI_SS_PIN);
|
|
IODIR0 &= ~(1<<SPI_MISO_PIN);
|
|
// Unselect card
|
|
CS_HIGH();
|
|
// Set GPIO mode
|
|
PINSEL1 &= ~( (3<<SPI_SCK_FUNCBIT) | (3<<SPI_MISO_FUNCBIT) |
|
|
(3<<SPI_MOSI_FUNCBIT) | (3<<SPI_SS_FUNCBIT) );
|
|
|
|
// setup Pin-Functions - keep automatic CS disabled during init
|
|
PINSEL1 |= ( (2<<SPI_SCK_FUNCBIT) | (2<<SPI_MISO_FUNCBIT) |
|
|
(2<<SPI_MOSI_FUNCBIT) );
|
|
// enable SPI-Master - slowest speed
|
|
SSPCR0 = ((8-1)<<0) | (0<<CPOL) | (0x20<<SCR);
|
|
SSPCR1 = (1<<SSE);
|
|
// low speed during init
|
|
SSPCPSR=254;
|
|
|
|
// Send 20 spi commands with card not selected
|
|
for(int i=0;i<20;i++)
|
|
{
|
|
while( !(SSPSR & (1<<TNF)) ) ; //Wait
|
|
SSPDR=0xff;
|
|
while( !(SSPSR & (1<<RNE)) ) ; //Wait
|
|
incoming=SSPDR;
|
|
(void)incoming;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* Send and receive one byte through SPI
|
|
*/
|
|
static unsigned char spi_1_send(unsigned char outgoing)
|
|
{
|
|
while(!(SSPSR & (1<<TNF))) ;
|
|
SSPDR=outgoing;
|
|
while(!(SSPSR & (1<<RNE))) ;
|
|
return SSPDR;
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* Used for debugging, print 8 bit error code from SD card
|
|
*/
|
|
static void print_error_code(unsigned char value)
|
|
{
|
|
switch(value)
|
|
{
|
|
case 0x40:
|
|
DBGERR("Parameter error\n");
|
|
break;
|
|
case 0x20:
|
|
DBGERR("Address error\n");
|
|
break;
|
|
case 0x10:
|
|
DBGERR("Erase sequence error\n");
|
|
break;
|
|
case 0x08:
|
|
DBGERR("CRC error\n");
|
|
break;
|
|
case 0x04:
|
|
DBGERR("Illegal command\n");
|
|
break;
|
|
case 0x02:
|
|
DBGERR("Erase reset\n");
|
|
break;
|
|
case 0x01:
|
|
DBGERR("Card is initializing\n");
|
|
break;
|
|
default:
|
|
DBGERR("Unknown error 0x%x\n",value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* Return 1 if card is OK, otherwise print 16 bit error code from SD card
|
|
*/
|
|
static char sd_status()
|
|
{
|
|
short value=send_cmd(CMD13,0);
|
|
value<<=8;
|
|
value|=spi_1_send(0xff);
|
|
|
|
switch(value)
|
|
{
|
|
case 0x0000:
|
|
return 1;
|
|
case 0x0001:
|
|
DBGERR("Card is Locked\n");
|
|
/*//Try to fix the problem by erasing all the SD card.
|
|
char e=send_cmd(CMD16,1);
|
|
if(e!=0) print_error_code(e);
|
|
e=send_cmd(CMD42,0);
|
|
if(e!=0) print_error_code(e);
|
|
spi_1_send(0xfe); // Start block
|
|
spi_1_send(1<<3); //Erase all disk command
|
|
spi_1_send(0xff); // Checksum part 1
|
|
spi_1_send(0xff); // Checksum part 2
|
|
e=spi_1_send(0xff);
|
|
iprintf("Reached here 0x%x\n",e);//Should return 0xe5
|
|
while(spi_1_send(0xff)!=0xff);*/
|
|
break;
|
|
case 0x0002:
|
|
DBGERR("WP erase skip or lock/unlock cmd failed\n");
|
|
break;
|
|
case 0x0004:
|
|
DBGERR("General error\n");
|
|
break;
|
|
case 0x0008:
|
|
DBGERR("Internal card controller error\n");
|
|
break;
|
|
case 0x0010:
|
|
DBGERR("Card ECC failed\n");
|
|
break;
|
|
case 0x0020:
|
|
DBGERR("Write protect violation\n");
|
|
break;
|
|
case 0x0040:
|
|
DBGERR("Invalid selection for erase\n");
|
|
break;
|
|
case 0x0080:
|
|
DBGERR("Out of range or CSD overwrite\n");
|
|
break;
|
|
default:
|
|
if(value>0x00FF)
|
|
print_error_code((unsigned char)(value>>8));
|
|
else
|
|
DBGERR("Unknown error 0x%x\n",value);
|
|
break;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* Wait until card is ready
|
|
*/
|
|
static unsigned char wait_ready()
|
|
{
|
|
unsigned char result;
|
|
spi_1_send(0xff);
|
|
for(int i=0;i<460800;i++)//Timeout ~500ms
|
|
{
|
|
result=spi_1_send(0xff);
|
|
if(result==0xff) return 0xff;
|
|
if(i%500==0) DBG("*\n");
|
|
}
|
|
DBGERR("Error: wait_ready()\n");
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* Send a command to the SD card
|
|
* \param cmd one among the #define'd commands
|
|
* \param arg command's argument
|
|
* \return command's r1 response. If command returns a longer response, the user
|
|
* can continue reading the response with spi_1_send(0xff)
|
|
*/
|
|
static unsigned char send_cmd(unsigned char cmd, unsigned int arg)
|
|
{
|
|
unsigned char n, res;
|
|
if(cmd & 0x80)
|
|
{ // ACMD<n> is the command sequence of CMD55-CMD<n>
|
|
cmd&=0x7f;
|
|
res=send_cmd(CMD55,0);
|
|
if(res>1) return res;
|
|
}
|
|
|
|
// Select the card and wait for ready
|
|
CS_HIGH();
|
|
CS_LOW();
|
|
|
|
if(cmd==CMD0)
|
|
{
|
|
//wait_ready on CMD0 seems to cause infinite loop
|
|
spi_1_send(0xff);
|
|
} else {
|
|
if(wait_ready()!=0xff) return 0xff;
|
|
}
|
|
// Send command
|
|
spi_1_send(cmd); // Start + Command index
|
|
spi_1_send((unsigned char)(arg >> 24)); // Argument[31..24]
|
|
spi_1_send((unsigned char)(arg >> 16)); // Argument[23..16]
|
|
spi_1_send((unsigned char)(arg >> 8)); // Argument[15..8]
|
|
spi_1_send((unsigned char)arg); // Argument[7..0]
|
|
n=0x01; // Dummy CRC + Stop
|
|
if (cmd==CMD0) n=0x95; // Valid CRC for CMD0(0)
|
|
if (cmd==CMD8) n=0x87; // Valid CRC for CMD8(0x1AA)
|
|
spi_1_send(n);
|
|
// Receive response
|
|
if (cmd==CMD12) spi_1_send(0xff); // Skip a stuff byte when stop reading
|
|
n=10; // Wait response, try 10 times
|
|
do
|
|
res=spi_1_send(0xff);
|
|
while ((res & 0x80) && --n);
|
|
return res; // Return with the response value
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* Receive a data packet from the SD card
|
|
* \param buf data buffer to store received data
|
|
* \param byte count (must be multiple of 4)
|
|
* \return true on success, false on failure
|
|
*/
|
|
static bool rx_datablock(unsigned char *buf, unsigned int btr)
|
|
{
|
|
unsigned char token;
|
|
for(int i=0;i<0xffff;i++)
|
|
{
|
|
token=spi_1_send(0xff);
|
|
if(token!=0xff) break;
|
|
}
|
|
if(token!=0xfe) return false; // If not valid data token, retutn error
|
|
|
|
do { // Receive the data block into buffer
|
|
*buf=spi_1_send(0xff); buf++;
|
|
*buf=spi_1_send(0xff); buf++;
|
|
*buf=spi_1_send(0xff); buf++;
|
|
*buf=spi_1_send(0xff); buf++;
|
|
} while(btr-=4);
|
|
spi_1_send(0xff); // Discard CRC
|
|
spi_1_send(0xff);
|
|
|
|
return true; // Return success
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* Send a data packet to the SD card
|
|
* \param buf 512 byte data block to be transmitted
|
|
* \param token data start/stop token
|
|
* \return true on success, false on failure
|
|
*/
|
|
static bool tx_datablock (const unsigned char *buf, unsigned char token)
|
|
{
|
|
unsigned char resp;
|
|
if(wait_ready()!=0xff) return false;
|
|
|
|
spi_1_send(token); // Xmit data token
|
|
if (token!=0xfd)
|
|
{ // Is data token
|
|
for(int i=0;i<256;i++)
|
|
{ // Xmit the 512 byte data block
|
|
spi_1_send(*buf); buf++;
|
|
spi_1_send(*buf); buf++;
|
|
}
|
|
spi_1_send(0xff); // CRC (Dummy)
|
|
spi_1_send(0xff);
|
|
resp=spi_1_send(0xff); // Receive data response
|
|
if((resp & 0x1f)!=0x05) // If not accepted, return error
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// class SPISDDriver
|
|
//
|
|
|
|
intrusive_ref_ptr<SPISDDriver> SPISDDriver::instance()
|
|
{
|
|
static FastMutex m;
|
|
static intrusive_ref_ptr<SPISDDriver> instance;
|
|
Lock<FastMutex> l(m);
|
|
if(!instance) instance=new SPISDDriver();
|
|
return instance;
|
|
}
|
|
|
|
ssize_t SPISDDriver::readBlock(void* buffer, size_t size, off_t where)
|
|
{
|
|
if(where % 512 || size % 512) return -EFAULT;
|
|
unsigned int lba=where/512;
|
|
unsigned int nSectors=size/512;
|
|
unsigned char *buf=reinterpret_cast<unsigned char*>(buffer);
|
|
Lock<FastMutex> l(mutex);
|
|
DBG("SPISDDriver::readBlock(): nSectors=%d\n",nSectors);
|
|
if(!(cardType & 8)) lba*=512; // Convert to byte address if needed
|
|
unsigned char result;
|
|
if(nSectors==1)
|
|
{ // Single block read
|
|
result=send_cmd(CMD17,lba); // READ_SINGLE_BLOCK
|
|
if(result!=0)
|
|
{
|
|
print_error_code(result);
|
|
CS_HIGH();
|
|
return -EBADF;
|
|
}
|
|
if(rx_datablock(buf,512)==false)
|
|
{
|
|
DBGERR("Block read error\n");
|
|
CS_HIGH();
|
|
return -EBADF;
|
|
}
|
|
} else { // Multiple block read
|
|
//DBGERR("Mbr\n");//debug only
|
|
result=send_cmd(CMD18,lba); // READ_MULTIPLE_BLOCK
|
|
if(result!=0)
|
|
{
|
|
print_error_code(result);
|
|
CS_HIGH();
|
|
return -EBADF;
|
|
}
|
|
do {
|
|
if(!rx_datablock(buf,512)) break;
|
|
buf+=512;
|
|
} while(--nSectors);
|
|
send_cmd(CMD12,0); // STOP_TRANSMISSION
|
|
if(nSectors!=0)
|
|
{
|
|
DBGERR("Multiple block read error\n");
|
|
CS_HIGH();
|
|
return -EBADF;
|
|
}
|
|
}
|
|
CS_HIGH();
|
|
return size;
|
|
}
|
|
|
|
ssize_t SPISDDriver::writeBlock(const void* buffer, size_t size, off_t where)
|
|
{
|
|
if(where % 512 || size % 512) return -EFAULT;
|
|
unsigned int lba=where/512;
|
|
unsigned int nSectors=size/512;
|
|
const unsigned char *buf=reinterpret_cast<const unsigned char*>(buffer);
|
|
Lock<FastMutex> l(mutex);
|
|
DBG("SPISDDriver::writeBlock(): nSectors=%d\n",nSectors);
|
|
if(!(cardType & 8)) lba*=512; // Convert to byte address if needed
|
|
unsigned char result;
|
|
if(nSectors==1)
|
|
{ // Single block write
|
|
result=send_cmd(CMD24,lba); // WRITE_BLOCK
|
|
if(result!=0)
|
|
{
|
|
print_error_code(result);
|
|
CS_HIGH();
|
|
return -EBADF;
|
|
}
|
|
if(tx_datablock(buf,0xfe)==false) // WRITE_BLOCK
|
|
{
|
|
DBGERR("Block write error\n");
|
|
CS_HIGH();
|
|
return -EBADF;
|
|
}
|
|
} else { // Multiple block write
|
|
//DBGERR("Mbw\n");//debug only
|
|
if(cardType & 6) send_cmd(ACMD23,nSectors);//Only if it is SD card
|
|
result=send_cmd(CMD25,lba); // WRITE_MULTIPLE_BLOCK
|
|
if(result!=0)
|
|
{
|
|
print_error_code(result);
|
|
CS_HIGH();
|
|
return -EBADF;
|
|
}
|
|
do {
|
|
if(!tx_datablock(buf,0xfc)) break;
|
|
buf+=512;
|
|
} while(--nSectors);
|
|
if(!tx_datablock(0,0xfd)) // STOP_TRAN token
|
|
{
|
|
DBGERR("Multiple block write error\n");
|
|
CS_HIGH();
|
|
return -EBADF;
|
|
}
|
|
}
|
|
CS_HIGH();
|
|
return size;
|
|
}
|
|
|
|
int SPISDDriver::ioctl(int cmd, void* arg)
|
|
{
|
|
DBG("SPISDDriver::ioctl()\n");
|
|
if(cmd!=IOCTL_SYNC) return -ENOTTY;
|
|
Lock<FastMutex> l(mutex);
|
|
CS_LOW();
|
|
unsigned char result=wait_ready();
|
|
CS_HIGH();
|
|
if(result==0xff) return 0;
|
|
else return -EFAULT;
|
|
}
|
|
|
|
SPISDDriver::SPISDDriver() : Device(Device::BLOCK)
|
|
{
|
|
const int MAX_RETRY=20;//Maximum command retry before failing
|
|
spi_1_init(); /* init at low speed */
|
|
unsigned char resp;
|
|
int i;
|
|
for(i=0;i<MAX_RETRY;i++)
|
|
{
|
|
resp=send_cmd(CMD0,0);
|
|
if(resp==1) break;
|
|
}
|
|
DBG("CMD0 required %d commands\n",i+1);
|
|
|
|
if(resp!=1)
|
|
{
|
|
print_error_code(resp);
|
|
DBGERR("Init failed\n");
|
|
CS_HIGH();
|
|
return; //Error
|
|
}
|
|
unsigned char n, cmd, ty=0, ocr[4];
|
|
// Enter Idle state
|
|
if(send_cmd(CMD8,0x1aa)==1)
|
|
{ /* SDHC */
|
|
for(n=0;n<4;n++) ocr[n]=spi_1_send(0xff);// Get return value of R7 resp
|
|
if((ocr[2]==0x01)&&(ocr[3]==0xaa))
|
|
{ // The card can work at vdd range of 2.7-3.6V
|
|
for(i=0;i<MAX_RETRY;i++)
|
|
{
|
|
resp=send_cmd(ACMD41, 1UL << 30);
|
|
if(resp==0)
|
|
{
|
|
if(send_cmd(CMD58,0)==0)
|
|
{ // Check CCS bit in the OCR
|
|
for(n=0;n<4;n++) ocr[n]=spi_1_send(0xff);
|
|
if(ocr[0] & 0x40)
|
|
{
|
|
ty=12;
|
|
DBG("SDHC, block addressing supported\n");
|
|
} else {
|
|
ty=4;
|
|
DBG("SDHC, no block addressing\n");
|
|
}
|
|
} else DBGERR("CMD58 failed\n");
|
|
|
|
break; //Exit from for
|
|
} else print_error_code(resp);
|
|
}
|
|
DBG("ACMD41 required %d commands\n",i+1);
|
|
} else DBGERR("CMD8 failed\n");
|
|
} else { /* SDSC or MMC */
|
|
if(send_cmd(ACMD41,0)<=1)
|
|
{
|
|
ty=2;
|
|
cmd=ACMD41; /* SDSC */
|
|
DBG("SD card\n");
|
|
} else {
|
|
ty=1;
|
|
cmd=CMD1; /* MMC */
|
|
DBG("MMC card\n");
|
|
}
|
|
for(i=0;i<MAX_RETRY;i++)
|
|
{
|
|
resp=send_cmd(cmd,0);
|
|
if(resp==0)
|
|
{
|
|
if(send_cmd(CMD16,512)!=0)
|
|
{
|
|
ty=0;
|
|
DBGERR("CMD16 failed\n");
|
|
}
|
|
break; //Exit from for
|
|
} else print_error_code(resp);
|
|
}
|
|
DBG("CMD required %d commands\n",i+1);
|
|
}
|
|
|
|
if(ty==0)
|
|
{
|
|
CS_HIGH();
|
|
return; //Error
|
|
}
|
|
cardType=ty;
|
|
|
|
if(sd_status()<0)
|
|
{
|
|
DBGERR("Status error\n");
|
|
CS_HIGH();
|
|
return; //Error
|
|
}
|
|
|
|
CS_HIGH();
|
|
//Configure the SPI interface to use the 7.4MHz high speed mode
|
|
SSPCR0=((8-1)<<0) | (0<<CPOL);
|
|
SSPCPSR=SPI_PRESCALE_MIN;
|
|
|
|
DBG("Init done...\n");
|
|
}
|
|
|
|
} //namespace miosix
|