///  MMC/SD Library. CopyLefter from Procyon AVRlib
///	      _________________
///	     /  1 2 3 4 5 6 78 |	<- view of MMC/SD card looking at contacts
///	    / 9                |	Pins 8 and 9 are present only on SD cards
///	    |    MMC/SD Card   |
///	    |                  |
///	    /\/\/\/\/\/\/\/\/\/\
///	    1 - CS   (chip select)          - wire to any available I/O pin(*)
///	    2 - DIN  (data in, card<-host)  - wire to SPI MOSI pin
///	    3 - VSS  (ground)               - wire to ground
///	    4 - VDD  (power, 3.3V only?)    - wire to power (MIGHT BE 3.3V ONLY!)
///	    5 - SCLK (data clock)           - wire to SPI SCK pin
///	    6 - VSS  (ground)               - wire to ground
///	    7 - DOUT (data out, card->host) - wire to SPI MISO pin
///         8,9 (SD card only) - PullUP to VDD

#define MMC_CS_PORT			PORTB
#define MMC_CS_DDR			DDRB
#define MMC_CS_PIN			0

// function prototypes

// SPI interface initializer
void spiInit(void);

// spiSendByte(unsigned char data) waits until the SPI interface is ready
// and then sends a single byte over the SPI port.  This command
// does not receive anything.
void spiSendByte(unsigned char data);

// spiTransferByte(unsigned char data) waits until the SPI interface is ready
// and then sends a single byte over the SPI port.  The function also
// returns the byte that was received during transmission.
unsigned char spiTransferByte(unsigned char data);

// spiTransferWord(unsigned char data) works just like spiTransferByte but
// operates on a whole word (16-bits of data).
unsigned int spiTransferWord(unsigned int data);

// global variables
volatile unsigned char spiTransferComplete;

// access routines
void spiInit()
{
unsigned char zzz;
    // setup SPI I/O pins
    PORTB.5=1;  // set SCK hi
    DDRB.5=1;   // set SCK as output
    DDRB.4=0;   // set MISO as input
    DDRB.3=1;   // set MOSI as output
    DDRB.2=1;   // SS must be output for Master mode to work

	SPCR.4=1;
	// clock = f/16
	SPCR.0=0;
	SPCR.1=0;
	SPSR.0=0;
	// select clock phase positive-going in middle of data
	SPCR.3=0;
	// Data order MSB first
	SPCR.5=0;
	// enable SPI
	SPCR.6=1;
		
	// clear status
	zzz=SPSR;
	spiTransferComplete = 255;

}

void spiSendByte(unsigned char data)
{
	while(!((SPSR) & (1<< SPSR.7)));
	spiTransferComplete = 0;
	SPDR=data;
}

unsigned char spiTransferByte(unsigned char data)
{
	// send the given data
	spiTransferComplete = 0;
 
SPDR=data;
while ((SPSR & (1<<7))==0);
spiTransferComplete = 255;
return SPDR;

}



// constants/macros/typdefs
// MMC commands (taken from sandisk MMC reference)
#define MMC_GO_IDLE_STATE			0		///< initialize card to SPI-type access
#define MMC_SEND_OP_COND			1		///< set card operational mode
#define MMC_SEND_CSD				9		///< get card's CSD
#define MMC_SEND_CID				10		///< get card's CID
#define MMC_SEND_STATUS				13
#define MMC_SET_BLOCKLEN			16		///< Set number of bytes to transfer per block
#define MMC_READ_SINGLE_BLOCK			17		///< read a block
#define MMC_WRITE_BLOCK				24		///< write a block
#define MMC_PROGRAM_CSD				27
#define MMC_SET_WRITE_PROT			28
#define MMC_CLR_WRITE_PROT			29
#define MMC_SEND_WRITE_PROT			30
#define MMC_TAG_SECTOR_START			32
#define MMC_TAG_SECTOR_END			33
#define MMC_UNTAG_SECTOR			34
#define MMC_TAG_ERASE_GROUP_START 		35		///< Sets beginning of erase group (mass erase)
#define MMC_TAG_ERARE_GROUP_END			36		///< Sets end of erase group (mass erase)
#define MMC_UNTAG_ERASE_GROUP			37		///< Untag (unset) erase group (mass erase)
#define MMC_ERASE				38		///< Perform block/mass erase
#define MMC_CRC_ON_OFF				59		///< Turns CRC check on/off
// R1 Response bit-defines
#define MMC_R1_BUSY				0x80	///< R1 response: bit indicates card is busy
#define MMC_R1_PARAMETER			0x40
#define MMC_R1_ADDRESS				0x20
#define MMC_R1_ERASE_SEQ			0x10
#define MMC_R1_COM_CRC				0x08
#define MMC_R1_ILLEGAL_COM			0x04
#define MMC_R1_ERASE_RESET			0x02
#define MMC_R1_IDLE_STATE			0x01
// Data Start tokens
#define MMC_STARTBLOCK_READ			0xFE	///< when received from card, indicates that a block of data will follow
#define MMC_STARTBLOCK_WRITE		0xFE	///< when sent to card, indicates that a block of data will follow
#define MMC_STARTBLOCK_MWRITE		0xFC
// Data Stop tokens
#define MMC_STOPTRAN_WRITE			0xFD
// Data Error Token values
#define MMC_DE_MASK					0x1F
#define MMC_DE_ERROR				0x01
#define MMC_DE_CC_ERROR				0x02
#define MMC_DE_ECC_FAIL				0x04
#define MMC_DE_OUT_OF_RANGE			0x04
#define MMC_DE_CARD_LOCKED			0x04
// Data Response Token values
#define MMC_DR_MASK					0x1F
#define MMC_DR_ACCEPT				0x05
#define MMC_DR_REJECT_CRC			0x0B
#define MMC_DR_REJECT_WRITE_ERROR	0x0D

// functions

//! Initialize AVR<->MMC hardware interface.
/// Prepares hardware for MMC access.
void mmcInit(void);

//! Initialize the card and prepare it for use.
/// Returns zero if successful.
unsigned char mmcReset(void);

//! Send card an MMC command.
/// Returns R1 result code.
unsigned char mmcSendCommand(unsigned char cmd, unsigned long arg);

//! Read 512-byte sector from card to buffer
/// Returns zero if successful.
unsigned char mmcRead(unsigned long sector, unsigned char* buffer);

//! Write 512-byte sector from buffer to card
/// Returns zero if successful.
unsigned char mmcWrite(unsigned long sector, unsigned char* buffer);

//! Internal command function.
/// Issues a generic MMC command as specified by cmd and arg.
unsigned char mmcCommand(unsigned char cmd, unsigned long arg);


void mmcInit(void)
{
	// initialize SPI interface
	spiInit();
	// release chip select
	DDRB.0=1;
	PORTB.0=1;
}

unsigned char mmcReset(void)
{
	unsigned char retry,rn;
	unsigned char r1=0;


	retry = 0;
	do
	{
		// send dummy bytes with CS high before accessing
		for (rn=0; rn<74;rn++) spiTransferByte(0xFF);
		delay_ms(1);
		// resetting card, go to SPI mode
		r1 = mmcSendCommand(MMC_GO_IDLE_STATE, 0);
		// do retry counter
		retry++;
		if(retry>10) return 255;
	} while(r1 != 0x01);

	retry = 0;
	do
	{
		// initializing card for operation
		r1 = mmcSendCommand(MMC_SEND_OP_COND, 0);
		// do retry counter
		retry++;
		if(retry>250) return 254;
	} while(!(r1==0));
		
	// turn off CRC checking to simplify communication
  //r1 = mmcSendCommand(MMC_CRC_ON_OFF, 0);

	// set block length to 512 bytes
	r1 = mmcSendCommand(MMC_SET_BLOCKLEN, 512);

	// return success
	return 0;
}

unsigned char mmcSendCommand(unsigned char cmd, unsigned long arg)
{
	unsigned char r1;

	// assert chip select
	PORTB.0=0;
	// issue the command
	r1 = mmcCommand(cmd, arg);
	// release chip select
	PORTB.0=1;

	return r1;
}

unsigned char mmcRead(unsigned long sector, unsigned char* buffer)
{
	unsigned char r1;
	unsigned int i;

	// assert chip select
	PORTB.0=0;
	// issue command
	r1 = mmcCommand(MMC_READ_SINGLE_BLOCK, sector<<9);
	// check for valid response
	if(r1 != 0x00)
		return r1;
	// wait for block start
	while(spiTransferByte(0xFF) != MMC_STARTBLOCK_READ);
	// read in data
	for(i=0; i<0x200; i++)
	{
		*buffer++ = spiTransferByte(0xFF);
	}
	// read 16-bit CRC
	spiTransferByte(0xFF);
	spiTransferByte(0xFF);
	// release chip select
	PORTB.0=1;
	// return success
	return 0;
}

unsigned char mmcWrite(unsigned long sector, unsigned char* buffer)
{
	unsigned char r1;
	unsigned int i;

	// assert chip select
	PORTB.0=0;
	// issue command
	r1 = mmcCommand(MMC_WRITE_BLOCK, sector<<9);
	// check for valid response
	if(r1 != 0x00)
		return r1;
	// send dummy
	spiTransferByte(0xFF);
	// send data start token
	spiTransferByte(MMC_STARTBLOCK_WRITE);
	// write data
	for(i=0; i<0x200; i++)
	{
		spiTransferByte(*buffer++);
	}
	// write 16-bit CRC (dummy values)
	spiTransferByte(0xFF);
	spiTransferByte(0xFF);
	// read data response token
	r1 = spiTransferByte(0xFF);
	if( (r1&MMC_DR_MASK) != MMC_DR_ACCEPT)
		return r1;
	// wait until card not busy
	while(!spiTransferByte(0xFF));
	// release chip select
	PORTB.0=1;
	// return success
	return 0;
}

unsigned char mmcCommand(unsigned char cmd, unsigned long arg)
{
	unsigned char r1;
	unsigned char retry=0;
	// send command
	spiTransferByte(cmd | 0x40);
	spiTransferByte(arg>>24);
	spiTransferByte(arg>>16);
	spiTransferByte(arg>>8);
	spiTransferByte(arg);
	spiTransferByte(0x95);	// crc valid only for MMC_GO_IDLE_STATE
	// end command
	// wait for response
	// if more than 8 retries, card has timed-out
	// return the received 0xFF
	while((r1 = spiTransferByte(0xFF)) == 0xFF)
		if(retry++ > 8) break;
	// return response
	return r1;
}
