blob: db46baf03ccfe2c23edc973f3770fa863488c945 [file] [log] [blame]
/*
Copyright (c) 2011, Dmitry Grinberg (as published on http://dmitrygr.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following condition is met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
*/
#define ADK_INTERNAL
#include "fwk.h"
#include "SD.h"
static const uint8_t pinCD = PORTA(5);
static const uint8_t pinsSD[] = {PORTA(7), PORTA(19), PORTA(20), PORTA(21)};
#define CS 0
#define SCLK 1
#define MOSI 2
#define MISO 3
#define FLAG_TIMEOUT 0x80
#define FLAG_PARAM_ERR 0x40
#define FLAG_ADDR_ERR 0x20
#define FLAG_ERZ_SEQ_ERR 0x10
#define FLAG_CMD_CRC_ERR 0x08
#define FLAG_ILLEGAL_CMD 0x04
#define FLAG_ERZ_RST 0x02
#define FLAG_IN_IDLE_MODE 0x01
char gSdSpiFast = 1;
static void sdClockSpeed(char fast){
gSdSpiFast = fast;
}
static uint8_t sdSpiByte(uint32_t v){
uint32_t r = 0;
#define BIT(m, d) \
do{ \
gpioSetVal(pinsSD[MOSI], !!(v & (m))); \
gpioSetVal(pinsSD[SCLK], 1); \
d; \
if(gpioGetVal(pinsSD[MISO])) \
r |= (m); \
gpioSetVal(pinsSD[SCLK], 0); \
}while(0)
#define ASMBIT \
"lsls %1, #1 \n\t" \
"ite cs \n\t" \
"strcs r4, [r2, #0x30] \n\t" \
"strcc r4, [r2, #0x34] \n\t" \
"str r1, [r2, #0x30] \n\t" \
"ldr r3, [r2, #0x3C] \n\t" \
"str r1, [r2, #0x34] \n\t" \
"lsrs r3, #22 \n\t" \
"adcs %0, %0 \n\t"
if(gSdSpiFast){
asm(
"mov r4, #0x100000 \n\t" //mosi
"mov r1, #0x080000 \n\t" //sclk
"ldr r2, =0x400E0E00 \n\t" //PIOA
"lsl %1, #24 \n\t" //cleverness
ASMBIT
ASMBIT
ASMBIT
ASMBIT
ASMBIT
ASMBIT
ASMBIT
ASMBIT
:"=l"(r)
:"l"(v)
:"cc", "r4", "r1", "r2", "r3"
);
}
else{
volatile uint32_t x;
uint8_t t;
for(t = 0; t < 8; t++){
BIT(1 << (7 - t), for(x = 0; x < 0x10; x += 2) x--);
for(x = 0; x < 0x10; x += 2) x--;
}
}
return r;
}
uint8_t* __attribute__((naked)) sdStreamSec(uint8_t* buf){
#define RDBIT \
"str r1, [r2, #0x30] \n\t" \
"ldr r3, [r2, #0x3C] \n\t" \
"str r1, [r2, #0x34] \n\t" \
"lsrs r3, #22 \n\t" \
"adcs r4, r4 \n\t"
asm(
" push {r4,r5,lr} \n\t"
" ldr r2, =0x400E0E00 \n\t"
" mov r1, #0x100000 \n\t" //mosi
" str r1, [r2, #0x30] \n\t"
" mov r1, #0x080000 \n\t"
" mov r5, #128 \n\t"
"loop: \n\t"
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
RDBIT
" rev r4, r4 \n\t"
" str r4, [r0], #4 \n\t"
" subs r5, #1 \n\t"
" bne loop \n\t"
" pop {r4,r5,pc} \n\t"
);
}
static void sdChipSelect(char on){
gpioSetVal(pinsSD[CS], !on);
}
static uint8_t sdCrc7(uint8_t* chr,uint8_t cnt,uint8_t crc){
uint8_t i, a;
uint8_t Data;
for(a = 0; a < cnt; a++){
Data = chr[a];
for(i = 0; i < 8; i++){
crc <<= 1;
if( (Data & 0x80) ^ (crc & 0x80) ) crc ^= 0x09;
Data <<= 1;
}
}
return crc & 0x7F;
}
static inline void sdPrvSendCmd(uint8_t cmd, uint32_t param, char crc){
uint8_t send[6];
uint8_t L = crc ? 6 : 5;
send[0] = cmd | 0x40;
send[1] = param >> 24;
send[2] = param >> 16;
send[3] = param >> 8;
send[4] = param;
send[5] = crc ? (sdCrc7(send, 5, 0) << 1) | 1 : 0;
for(cmd = 0; cmd < L; cmd++) sdSpiByte(send[cmd]);
}
static uint8_t sdPrvSimpleCommand(uint8_t cmd, uint32_t param, char crc, char cmdDone){ //do a command, return R1 reply
uint8_t ret;
uint8_t i = 0;
sdChipSelect(1);
sdPrvSendCmd(cmd, param, crc);
do{ //our max wait time is 128 byte clocks (1024 clock ticks)
ret = sdSpiByte(0xFF);
}while(i++ < 128 && (ret == 0xFF));
if(cmdDone){
sdChipSelect(0);
sdSpiByte(0xFF);
}
return ret;
}
static uint8_t sdPrvReadData(uint8_t* data, uint32_t sz, char cmdDone){
uint8_t ret;
uint16_t tries = 20000;
do{
ret = sdSpiByte(0xFF);
if((ret & 0xF0) == 0x00) return ret; //fail
if(ret == 0xFE) break;
tries--;
}while(tries);
if(!tries) return 0xFF;
*data = ret;
ret = 0;
while(sz--) *data++ = sdSpiByte(0xFF);
if(cmdDone){
sdChipSelect(0);
sdSpiByte(0xFF);
}
return ret;
}
static uint8_t sdPrvACMD(uint8_t cmd, uint32_t param, char crc){
uint8_t ret;
ret = sdPrvSimpleCommand(55, 0, crc, 1);
if(ret & FLAG_TIMEOUT) return ret;
if(ret & FLAG_ILLEGAL_CMD) return ret;
return sdPrvSimpleCommand(cmd, param, crc, 1);
}
static char sdPrvCardInit(char sd, char hc){
uint32_t time = 0;
uint32_t resp;
uint32_t param;
param = hc ? (1UL<< 30) : 0;
while(time++ < 10000UL){ //retry 10..0 times
resp = sd ? sdPrvACMD(41, param, 1) : sdPrvSimpleCommand(1, param, 1, 1);
if(resp & FLAG_TIMEOUT) break;
if(!(resp & FLAG_IN_IDLE_MODE)) return 1;
}
return 0;
}
static uint32_t sdPrvGetBits(uint8_t* data,uint32_t numBytesInArray,uint32_t startBit,uint32_t len){//for CID and CSD data..
uint32_t bitWrite = 0;
uint32_t numBitsInArray = numBytesInArray * 8;
uint32_t ret = 0;
do{
uint32_t bit,byte;
bit = numBitsInArray - startBit - 1;
byte = bit / 8;
bit = 7 - (bit % 8);
ret |= ((data[byte] >> bit) & 1) << (bitWrite++);
startBit++;
}while(--len);
return ret;
}
static uint32_t sdPrvGetCardNumBlocks(char mmc,uint8_t* csd){
uint32_t ver = sdPrvGetBits(csd,16,126,2);
uint32_t cardSz = 0;
if(ver == 0 || (mmc && ver <= 2)){
uint32_t cSize = sdPrvGetBits(csd,16,62,12);
uint32_t cSizeMult = sdPrvGetBits(csd,16,47,3);
uint32_t readBlLen = sdPrvGetBits(csd,16,80,4);
uint32_t blockLen,blockNr;
uint32_t divTimes = 9; //from bytes to blocks division
blockLen = 1UL << readBlLen;
blockNr = (cSize + 1) * (1UL << (cSizeMult + 2));
/*
multiplying those two produces result in bytes, we need it in blocks
so we shift right 9 times. doing it after multiplication might fuck up
the 4GB card, so we do it before, but to avoid killing significant bits
we only cut the zero-valued bits, if at the end we end up with non-zero
"divTimes", divide after multiplication, and thus underuse the card a bit.
This will never happen in reality since 512 is 2^9, and we are
multiplying two numbers whose product is a multiple of 2^9, so they
togethr should have at least 9 lower zero bits.
*/
while(divTimes && !(blockLen & 1)){
blockLen = blockLen >> 1;
divTimes--;
}
while(divTimes && !(blockNr & 1)){
blockNr = blockNr >> 1;
divTimes--;
}
cardSz = (blockNr * blockLen) >> divTimes;
}
else if(ver == 1){
cardSz = sdPrvGetBits(csd,16,48,22)/*num 512K blocks*/ << 10;
}
return cardSz;
}
char sdInit(SD* sd){
uint8_t v, t;
uint16_t retries = 1050;
uint8_t respBuf[16];
dbgPrintf("SD: initing\n");
gpioSetFun(pinCD, GPIO_FUNC_GPIO);
gpioSetDir(pinCD, 1);
for(v = 0; v < sizeof(pinsSD); v++){
gpioSetFun(pinsSD[v], GPIO_FUNC_GPIO);
gpioSetDir(pinsSD[v], 0);
gpioSetVal(pinsSD[v], 0);
}
gpioSetDir(pinsSD[MISO], 1);
gpioSetVal(pinsSD[CS], 1);
sd->inited = 0;
sd->SD = 0;
sd->HC = 0;
sdClockSpeed(0);
sdChipSelect(0);
for(v = 0; v < 10; v++) sdSpiByte(0xFF); //80 clocks with CS not asserted to give card time to init
while(0x01 != sdPrvSimpleCommand(0, 0, 1, 1)) if(!--retries) return 0; //always a good idea
v = sdPrvSimpleCommand(8, 0x000001AAUL, 1, 0); //try CMD8 to init SDHC cards
if(v & FLAG_TIMEOUT) return 0;
sdSpiByte(0xFF); //ignore the reply
sdSpiByte(0xFF);
sdSpiByte(0xFF);
sdSpiByte(0xFF);
sdChipSelect(0);
sdSpiByte(0xFF);
v = sdPrvSimpleCommand(55, 0, 1, 1); //see if this is SD or MMC
if(v & FLAG_TIMEOUT) return 0;
sd->SD = !(v & FLAG_ILLEGAL_CMD);
dbgPrintf("SD: card is likely %s\n", sd->SD ? "SD" : "MMC");
if(sd->SD){
if(!sdPrvCardInit(1, 1) && !sdPrvCardInit(1, 0)){
return 0;
}
v = sdPrvSimpleCommand(58,0, 1, 0); //see if it is HC
t = sdSpiByte(0xFF); //get the reply
sdSpiByte(0xFF);
sdSpiByte(0xFF);
sdSpiByte(0xFF);
sdChipSelect(0);
sdSpiByte(0xFF);
if(v) t = 0;
if(t & 0x40) sd->HC = 1;
}
else{
if(!sdPrvCardInit(0, 1) && !sdPrvCardInit(0, 0)){
return 0;
}
}
v = sdPrvSimpleCommand(59, 0, 1, 1); //crc off
if(v & FLAG_TIMEOUT) return 0;
v = sdPrvSimpleCommand(9, 0, 0, 0); //read CSD
if(v & FLAG_TIMEOUT) return 0;
v = sdPrvReadData(respBuf, 16, 1);
if(v) return 0;
sd->numSec = sdPrvGetCardNumBlocks(!sd->SD, respBuf);
sd->inited = 1;
sdClockSpeed(1);
return 1;
}
uint32_t sdGetNumSec(SD* sd){
return sd->inited ? sd->numSec : 0;
}
char sdSecRead(SD* sd, uint32_t sec, void* buf){ //CMD17
uint8_t v, retry = 0;
char ret = 0;
if(!sd->inited) return 0;
do{
v = sdPrvSimpleCommand(17, sd->HC ? sec : sec << 9, 0, 0);
if(v & FLAG_TIMEOUT) return 0;
v = sdPrvReadData(buf, 0, 0);
sdStreamSec(buf);
sdSpiByte(0xFF); //skip crc
sdSpiByte(0xFF);
sdChipSelect(0);
if(!v){
ret = 1;
break;
}
}while(++retry < 5); //retry up to 5 times
return ret;
}
char sdSecWrite(SD* sd, uint32_t sec, const void* buf){ //CMD24
uint32_t v16;
uint8_t v, retry = 0;
uint8_t* buf_ = buf;
char ret = 0;
if(!sd->inited) return 0;
do{
v = sdPrvSimpleCommand(24, sd->HC ? sec : sec << 9, 0, 0);
if(v & FLAG_TIMEOUT) return 0;
sdSpiByte(0xFF); //as per SD-spi spec, we give it 8 clocks to consider the ramifications of the command we just sent
sdSpiByte(0xFE); //start of data block
for(v16 = 0; v16 < SD_BLOCK_SIZE; v16++) sdSpiByte(*buf_++); //data
while((v = sdSpiByte(0xFF)) == 0xFF); //wait while card isnt answering
while(sdSpiByte(0xFF) != 0xFF); //wait while card is busy
if((v & 0x1F) == 5){
ret = 1;
break;
}
}while(++retry < 5); //retry up to 5 times
return ret;
}
//stream mode
char sdReadStart(SD* sd, uint32_t sec){
uint8_t v;
v = sdPrvSimpleCommand(18, sd->HC ? sec : sec << 9, 0, 0);
if(v & FLAG_TIMEOUT) return 0;
do{
v = sdSpiByte(0xFF);
}while(v == 0xFF);
if(v != 0xFE){
sdChipSelect(0);
return 0;
}
return 1;
}
void sdNextSec(SD* sd){
uint8_t v;
sdSpiByte(0xFF); //skip crc
sdSpiByte(0xFF);
do{
v = sdSpiByte(0xFF);
}while(v == 0xFF);
}
void sdSecReadStop(SD* sd){
sdChipSelect(0);
sdSpiByte(0xFF); //skip crc
sdSpiByte(0xFF);
//cancel read
sdPrvSimpleCommand(12, 0, 0, 1);
}