/** 
 * \file sdcard.h
 * \brief sdcard header file
 * \details File contains structures, enumerations and functions declarations for physical layout of SD card communication
 *
 * \author Juraj Golej
 * \version 1.0
 */


#ifndef SDCARD_H_
#define SDCARD_H_

#include "sam.h"
#include <stdbool.h>
#include <stdlib.h>
#include <clocks/clocks/clocks.h>
#include <utils/retcode/retcode.h>
#include <utils/assert/arm_assert.h>

/** 
 * \brief Command response enumeration
 */
enum SDCARD_RESPONSE {
	SDCARD_RESPONSE_R1	= HSMCI_CMDR_RSPTYP_48_BIT,		///< R1 response (card status)
	SDCARD_RESPONSE_R1b	= HSMCI_CMDR_RSPTYP_R1B,		///< R1b response (same as R1, but user must wait for busy flag)
	SDCARD_RESPONSE_R2	= HSMCI_CMDR_RSPTYP_136_BIT,	///< R2 response (CID or CSD register)
	SDCARD_RESPONSE_R3	= HSMCI_CMDR_RSPTYP_48_BIT,		///< R3 response (OCR register)
	SDCARD_RESPONSE_R6	= HSMCI_CMDR_RSPTYP_48_BIT,		///< R6 response (get RCA adress)
	SDCARD_RESPONSE_R7	= HSMCI_CMDR_RSPTYP_48_BIT,		///< R7 response (card interface condition)
}; /* enum SDCARD_RESPONSE */

/** 
 * \brief Commands enumeration
 */
enum SDCARD_CMD {
	/* Basic commands */
	SDCARD_CMD0   = 0ul,							///< Resets all cards to idle state 
	SDCARD_CMD2   = 2ul  | SDCARD_RESPONSE_R2,		///< Asks any card to send the CID numbers on the CMD line
	SDCARD_CMD3	  = 3ul  | SDCARD_RESPONSE_R6,		///< Ask the card to publish a new relative address (RCA)
	SDCARD_CMD7	  = 7ul  | SDCARD_RESPONSE_R1b,		///< Select/deselect card
	SDCARD_CMD8   = 8ul  | SDCARD_RESPONSE_R7,		///< Sends SD Memory Card interface condition
	SDCARD_CMD9   = 9ul  | SDCARD_RESPONSE_R2,		///< Addressed card sends its card-specific data (CSD) on the CMD line.
	SDCARD_CMD10  = 10ul | SDCARD_RESPONSE_R2,		///< Addressed card sends its card identification (CID) on CMD the line. 
	SDCARD_CMD12  = 12ul | SDCARD_RESPONSE_R1b | HSMCI_CMDR_TRCMD_STOP_DATA,	///< Forces the card to stop transmission 
	SDCARD_CMD13  = 13ul | SDCARD_RESPONSE_R1,		///< Addressed card sends its status register.

	/* Block-Oriented Read Commands */
	SDCARD_CMD16  = 16ul | SDCARD_RESPONSE_R1,		///< Set block length
	SDCARD_CMD17  = 17ul | SDCARD_RESPONSE_R1 | HSMCI_CMDR_TRDIR_READ | HSMCI_CMDR_TRCMD_START_DATA | HSMCI_CMDR_TRTYP_SINGLE,		///< Read single block (arg: block address)
	SDCARD_CMD18  = 18ul | SDCARD_RESPONSE_R1 | HSMCI_CMDR_TRDIR_READ | HSMCI_CMDR_TRCMD_START_DATA | HSMCI_CMDR_TRTYP_MULTIPLE, 	///< Read multiple blocks (arg: start block address)

	/* Application-specific Commands */ 
	SDCARD_CMD55  = 55ul | SDCARD_RESPONSE_R1,		///< Next command will be application specific (ACMDx)
	SDCARD_ACMD41 = 41ul | SDCARD_RESPONSE_R3,		///< Sends host capacity support information (HCS) and asks the accessed card to send its OCR register
}; /* enum SDCARD_CMD */

/** 
 * \brief SD card types enumeration
 */
enum SDCARD_TYPE {
	SDCARD_TYPE_UNKNOWN,	///< Unknown card
	SDCARD_TYPE_SDv1,		///< SD card version 1
	SDCARD_TYPE_SDv2,		///< SD card version 2
	SDCARD_TYPE_SDHCv2,		///< SDHC card version 2
}; /* enum SDCARD_TYPE */

/** 
 * \brief SDCARD (HSMCI) module structure
 */
struct SDCARD_module {				
	enum SDCARD_TYPE type;		///< SD card type
	uint32_t adress;			///< Relative adress (RCA)
}; /* struct SDCARD_module */

/**
 * \brief SDCARD module (periphery) initialization
 * 
 * \param[in] clkDiv Main CLK (MCK) clock divider
 *
 * \note Call this function before anything else
 */
void SDCARD_init(const uint8_t clkDiv);

/**
 * \brief Identify SD card and set transfer state
 * 
 * \return \b struct \b SDCARD_module* Pointer on \c SDCARD_module
 *
 * \note Call this function before reading from SD card
 */
struct SDCARD_module* SDCARD_identify();

/**
 * \brief Get response error status
 * 
 * \return \b bool Error status
 * \retval true Error
 * \retval false No error
 */
inline bool SDCARD_responseError()
{
	Try(HSMCI->HSMCI_SR & (HSMCI_SR_RTOE_Msk | HSMCI_SR_RENDE_Msk | HSMCI_SR_RCRCE_Msk | HSMCI_SR_RDIRE_Msk | HSMCI_SR_RINDE_Msk), true);
	return false;
}

/**
 * \brief Send command
 * 
 * \param[in] cmd Command
 * \param[in] arg Command argument (0 for no argument)
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EBusy There is already some transfer on CMD or DATA lines
 * \retval EHardwareProblem Response error
 */
inline enum retCode SDCARD_sendCmd(const enum SDCARD_CMD cmd, const uint32_t arg)
{	
	/* Check, if transfer isn't busy */
	Try(!((HSMCI->HSMCI_SR & HSMCI_SR_XFRDONE_Msk) || (cmd & SDCARD_RESPONSE_R1b)) , EBusy);

	/* Write argument and command */
	HSMCI->HSMCI_ARGR = arg;
	HSMCI->HSMCI_CMDR = cmd | HSMCI_CMDR_MAXLAT_64;

	/* Wait for response */
	while(!(HSMCI->HSMCI_SR & HSMCI_SR_CMDRDY_Msk));

	/* Check response error */
	Try(SDCARD_responseError(), EHardwareProblem);

	return ENone;
}

/**
 * \brief Get short 48 bit response from SD card
 * 
 * \return \b uint32_t Response
 */
inline uint32_t SDCARD_getResponse48()
{
	return HSMCI->HSMCI_RSPR[0];
}

/**
 * \brief Get long 136 bit response from SD card
 * 
 * \param[out] response Pointer on array of 4 double words
 */
inline void SDCARD_getResponse136(uint32_t* response)
{
	Assert(response);

	for(uint8_t i = 0; i < 4; ++i) {
		response[i] = HSMCI->HSMCI_RSPR[0];
	}
}

/**
 * \brief Check, if there is some transfer on CMD or DATA lines
 * 
 * \return \c bool Busy state
 * \retval true There is some transfer
 * \retval false CMD or DATA lines are free
 */
inline bool SDCARD_isBusy()
{
	Try(HSMCI->HSMCI_SR & HSMCI_SR_XFRDONE_Msk, false)
	return true;
}

/**
 * \brief Check, if connected card is in transfer state
 * 
 * \return \b bool 
 * \retval true Card is ready for transfer
 * \retval false Card isn't ready for transfer
 */
bool SDCARD_isReady();

/**
 * \brief Read one 512 byte long block
 * 
 * \param[in] address SD card address
 * \param[out] buffer Output buffer
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval ENullPtr \c buffer is \c NULL 
 * \retval EInvalidParameter Address is not multiply by 512
 * \retval EDenied There is some error from transfer
 * \retval EBusy There is already active transfer
 */
enum retCode SDCARD_read(uint32_t address, uint8_t* buffer);

#endif /* SDCARD_H_ */