/**
  ******************************************************************************
  * @file microsd.c
  * @brief This file contains basic functions to control the MicroSD card of
  *  STM8L1x-EVAL board.
  * @author STMicroelectronics - MCD Application Team
  * @version V1.1.0
  * @date    09/14/2009
  ******************************************************************************
  *
  */
  
// Includes ------------------------------------------------------------------
#include "microsd.h"

// Private typedef -----------------------------------------------------------
// Private define ------------------------------------------------------------
// Private macro -------------------------------------------------------------
// Select MSD Card: ChipSelect pin low  
#define MSD_CS_LOW()     GPIO_ResetBits(MSD_CS_PORT, MSD_CS_PIN)
// Deselect MSD Card: ChipSelect pin high 
#define MSD_CS_HIGH()    GPIO_SetBits(MSD_CS_PORT, MSD_CS_PIN)
// Private function prototypes -----------------------------------------------
// Private functions ---------------------------------------------------------

/**
  * @brief Write a byte in the MicroSD card the SPI peripheral.
  * @param[in] Data Data to be sent.
  * @retval uint8_t Data received from Card
  */
uint8_t MSD_WriteByte(uint8_t Data)
{
  // Wait until the transmit buffer is empty 
  while (SPI_GetFlagStatus(SPI_FLAG_TXE) == RESET);
  // Send the byte 
  SPI_SendData(Data);
  // Wait to receive a byte
  while(SPI_GetFlagStatus(SPI_FLAG_RXNE) == RESET);
  // Return the byte read from the SPI bus  
  return SPI_ReceiveData();
}

/**
  * @brief Read a byte from the MicroSD card the SPI peripheral.
  * @par Parameters:
  * None
  * @retval uint8_t Data read
  */
uint8_t MSD_ReadByte(void)
{
  uint8_t Data = 0;

  // Wait until the transmit buffer is empty 
  while (SPI_GetFlagStatus(SPI_FLAG_TXE) == RESET);
  // Send the byte 
  SPI_SendData(DUMMY);

  while (SPI_GetFlagStatus(SPI_FLAG_RXNE) == RESET);
  // Get the received data 
  Data = SPI_ReceiveData();

  // Return the shifted data 
  return Data;
}

/**
  * @brief Initialize the  MicroSD card the SPI peripheral.
  * The MSD Response:
  * - MSD_RESPONSE_FAILURE: Sequence failed.
  * - MSD_RESPONSE_NO_ERROR: Sequence succeed .
  * @par Parameters:
  * None
  * @retval uint8_t MSD_GoIdleState State of the MSD.
  */
uint8_t MSD_Init(void)
{
  uint32_t i;

  // Set MSD ChipSelect pin in Output push-pull high level 
  GPIO_Init(MSD_CS_PORT, MSD_CS_PIN, GPIO_Mode_Out_PP_High_Fast);

  // MSD chip select high 
  MSD_ChipSelect(ENABLE);
  // Send dummy byte 0xFF, 10 times with CS high
  // rise CS and MOSI for 80 clocks cycles 
  for (i = 0; i <= 9; i++)
  {
    // Send dummy byte 0xFF 
    MSD_WriteByte(DUMMY);
  }
  // ------------Put MSD in SPI mode--------------
  // MSD initialized and set to SPI mode properly 
  return (MSD_GoIdleState());
}

/**
  * @brief Enable or Disable the MicroSD card.
  * @param[in] NewState CS pin state
  * @retval void None
  */
void MSD_ChipSelect(FunctionalState NewState)
{
  if (NewState != ENABLE)
  {
    GPIO_ResetBits(MSD_CS_PORT, MSD_CS_PIN); /* CS pin low: MSD disabled */
  }
  else
  {
		GPIO_SetBits(MSD_CS_PORT, MSD_CS_PIN); /* CS pin high: MSD enabled */
  }
}

/**
  * @brief Return the State of the MSD.
  * The MSD Response:
  * - MSD_RESPONSE_FAILURE: Sequence failed.
  * - MSD_RESPONSE_NO_ERROR: Sequence succeed.
  * @par Parameters:
  * None
  * @retval uint8_t State of MSD
  */
uint8_t MSD_GoIdleState(void)
{
  // MSD chip select low 
  MSD_ChipSelect(ENABLE);
  // Send CMD0 (GO_IDLE_STATE) to put MSD in SPI mode 
  MSD_SendCmd(MSD_GO_IDLE_STATE, 0, 0x95);

  // Wait for In Idle State Response (R1 Format) equal to 0x01 
  if (MSD_GetResponse(MSD_IN_IDLE_STATE))
  {
    // No Idle State Response: return response failue 
    return MSD_RESPONSE_FAILURE;
  }
  // ----------Activates the card initialization process-----------
  do
  {
    // MSD chip select high 
    MSD_ChipSelect(DISABLE);
    // Send Dummy byte 0xFF 
    MSD_WriteByte(DUMMY);

    // MSD chip select low 
    MSD_ChipSelect(ENABLE);

    // Send CMD1 (Activates the card process) until response equal to 0x0 
    MSD_SendCmd(MSD_SEND_OP_COND, 0, 0xFF);
    // Wait for no error Response (R1 Format) equal to 0x00 
  }
  while (MSD_GetResponse(MSD_RESPONSE_NO_ERROR));

  // MSD chip select high 
  MSD_ChipSelect(DISABLE);
  // Send dummy byte 0xFF 
  MSD_WriteByte(DUMMY);

  return MSD_RESPONSE_NO_ERROR;
}

/**
  * @brief Send 5 bytes command to the MSD.
  * @param[in] Cmd The user expected command to send to MSD card.
  * @param[in] Arg The command argument.
  * @param[in] Crc The CRC.
  * @retval void None
  */
void MSD_SendCmd(uint8_t Cmd, uint32_t Arg, uint8_t Crc)
{
  uint32_t i = 0x00;
  uint8_t Frame[6];

  // Construct byte1 
  Frame[0] = (uint8_t)(Cmd | 0x40);
  // Construct byte2 
  Frame[1] = (uint8_t)(Arg >> 24);
  // Construct byte3 
  Frame[2] = (uint8_t)(Arg >> 16);
  // Construct byte4 
  Frame[3] = (uint8_t)(Arg >> 8);
  // Construct byte5 
  Frame[4] = (uint8_t)(Arg);
  // Construct CRC: byte6 
  Frame[5] = (Crc);

  // Send the Cmd bytes 
  for (i = 0; i < 6; i++)
  {
    MSD_WriteByte(Frame[i]);
  }
}

/**
  * @brief Get response from the MSD.
  * The MSD Response:
  * - MSD_RESPONSE_FAILURE: Sequence failed.
  * - MSD_RESPONSE_NO_ERROR: Sequence succeed 
  * @param[in] Response Response to get
  * @retval uint8_t Response value
  */
uint8_t MSD_GetResponse(uint8_t Response)
{
  uint32_t Count = 0xFFF;

  // Check if response is got or a timeout is happen 
  while ((MSD_ReadByte() != Response) && Count)
  {
    Count--;
  }

  if (Count == 0)
  {
    // After time out 
    return MSD_RESPONSE_FAILURE;
  }
  else
  {
    // Right response got 
    return MSD_RESPONSE_NO_ERROR;
  }
}
/**
  * @brief Write a block in the MSD card.
  * If the amount of data to write is less than MSD card BLOCK size (512 Byte), DUMMY bytes
  * will be send to fill the block size.
  * @param[in] pBuffer pointer to the buffer containing the data to be written on the MSD.
  * @param[in] WriteAddr address to write on.
  * @param[in] NumByteToWrite number of data to write
  * @retval uint8_t MSD response
  */
uint8_t MSD_WriteBlock(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  uint32_t i = 0;
  uint8_t rvalue = MSD_RESPONSE_FAILURE;

  // MSD chip select low 
  MSD_ChipSelect(ENABLE);
  // Send CMD24 (MSD_WRITE_BLOCK) to write multiple block 
  MSD_SendCmd(MSD_WRITE_BLOCK, WriteAddr, 0xFF);

  // Check if the MSD acknowledged the write block command: R1 response (0x00: no errors) 
  if (!MSD_GetResponse(MSD_RESPONSE_NO_ERROR))
  {
    // Send a dummy byte 
    MSD_WriteByte(DUMMY);
    // Send the data token to signify the start of the data 
    MSD_WriteByte(0xFE);
    // Write the block data to MSD : write count data by block 
    for (i = 0; i < NumByteToWrite; i++)
    {
      // Send the pointed byte 
      MSD_WriteByte(*pBuffer);
      // Point to the next location where the byte read will be saved 
      pBuffer++;
    }
		
		/* Send DUMMY bytes when the number of data to be written are lower
		   than the MSD card BLOCK size (512 Byte) */
		for (; i != BLOCK_SIZE; i++)
    {
      // Send the pointed byte 
      MSD_WriteByte(DUMMY);
	  }
	
    // Put CRC bytes (not really needed by us, but required by MSD) 
    MSD_ReadByte();
    MSD_ReadByte();
    // Read data response 
    if (MSD_GetDataResponse() == MSD_DATA_OK)
    {
      rvalue = MSD_RESPONSE_NO_ERROR;
    }
  }

  // MSD chip select high 
  MSD_ChipSelect(DISABLE);
  // Send dummy byte: 8 Clock pulses of delay 
  MSD_WriteByte(DUMMY);
  // Returns the reponse 
  return rvalue;
}

/**
  * @brief Get the data MSD card reponse status.
  * @par Parameters:
  * None
  * @retval uint8_t The MSD response status
  * Read data response xxx0<status>1.
  * - status 010: Data accepted.
  * - status 101: Data rejected due to a crc error.
  * - status 110: Data rejected due to a Write error.
  * - status 111: Data rejected due to other error.
  */
uint8_t MSD_GetDataResponse(void)
{
  uint32_t i = 0;
  uint8_t response, rvalue;

  while (i <= 64)
  {
    // Read resonse 
    response = MSD_ReadByte();
    // Mask unused bits 
    response &= 0x1F;

    switch (response)
    {
      case MSD_DATA_OK:
      {
        rvalue = MSD_DATA_OK;
        break;
      }

      case MSD_DATA_CRC_ERROR:
        return MSD_DATA_CRC_ERROR;

      case MSD_DATA_WRITE_ERROR:
        return MSD_DATA_WRITE_ERROR;

      default:
      {
        rvalue = MSD_DATA_OTHER_ERROR;
        break;
      }
    }
    // Exit loop in case of data ok 
    if (rvalue == MSD_DATA_OK)
      break;
    // Increment loop counter 
    i++;
  }
  // Wait null data 
  while (MSD_ReadByte() == 0);
  // Return response 
  return response;
}

/**
  * @brief Read a block from the MSD card.
  * @param[in] pBuffer pointer to the buffer that receives the data read from the MSD.
  * @param[in] ReadAddr MSD's internal address to read from.
  * @param[in] NumByteToRead number of bytes to read from the MSD.
  * @retval uint8_t The MSD response
  * - MSD_RESPONSE_FAILURE: Sequence failed.
  * - MSD_RESPONSE_NO_ERROR: Sequence succeed.
  */
uint8_t MSD_ReadBlock(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
  uint32_t i = 0;
  uint8_t rvalue = MSD_RESPONSE_FAILURE;

  // MSD chip select low 
  MSD_ChipSelect(ENABLE);
  // Send CMD17 (MSD_READ_SINGLE_BLOCK) to read one block 
  MSD_SendCmd(MSD_READ_SINGLE_BLOCK, ReadAddr, 0xFF);

  // Check if the MSD acknowledged the read block command: R1 response (0x00: no errors) 
  if (!MSD_GetResponse(MSD_RESPONSE_NO_ERROR))
  {
    // Now look for the data token to signify the start of the data 
    if (!MSD_GetResponse(MSD_START_DATA_SINGLE_BLOCK_READ))
    {
      // Read the MSD block data : read NumByteToRead data 
      for (i = 0; i < NumByteToRead; i++)
      {
        // Save the received data 
        *pBuffer = MSD_ReadByte();
        // Point to the next location where the byte read will be saved 
        pBuffer++;
      }
      // Get CRC bytes (not really needed by us, but required by MSD) 
      MSD_ReadByte();
      MSD_ReadByte();
      // Set response value to success 
      rvalue = MSD_RESPONSE_NO_ERROR;
    }
  }

  // MSD chip select high 
  MSD_ChipSelect(DISABLE);
  // Send dummy byte: 8 Clock pulses of delay 
  MSD_WriteByte(DUMMY);
  // Returns the reponse 
  return rvalue;
}

/**
  * @brief Write a buffer (many blocks) in the MSD card.
	* The amount of data to write should be a multiple of MSD card BLOCK size (512 Byte).  
  * @param[in] pBuffer pointer to the buffer containing the data to be written on the MSD.
  * @param[in] WriteAddr address to write on.
  * @param[in] NumByteToWrite number of data to write.
  * @retval uint8_t The MSD response
  * - MSD_RESPONSE_FAILURE: Sequence failed
  * - MSD_RESPONSE_NO_ERROR: Sequence succeed
  */
uint8_t MSD_WriteBuffer(uint8_t *pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
{
  uint32_t i = 0, NbrOfBlock = 0, Offset = 0;
  uint8_t rvalue = MSD_RESPONSE_FAILURE;

  // Calculate number of blocks to write 
  NbrOfBlock = NumByteToWrite / BLOCK_SIZE;
  // MSD chip select low 
  MSD_ChipSelect(ENABLE);

  // Data transfer 
  while (NbrOfBlock--)
  {
    // Send CMD24 (MSD_WRITE_BLOCK) to write blocks 
    MSD_SendCmd(MSD_WRITE_BLOCK, WriteAddr + Offset, 0xFF);

    // Check if the MSD acknowledged the write block command: R1 response (0x00: no errors) 
    if (MSD_GetResponse(MSD_RESPONSE_NO_ERROR))
    {
      return MSD_RESPONSE_FAILURE;
    }
    // Send dummy byte 
    MSD_WriteByte(DUMMY);
    // Send the data token to signify the start of the data 
    MSD_WriteByte(MSD_START_DATA_SINGLE_BLOCK_WRITE);
    // Write the block data to MSD : write count data by block 
    for (i = 0; i < BLOCK_SIZE; i++)
    {
      // Send the pointed byte 
      MSD_WriteByte(*pBuffer);
      // Point to the next location where the byte read will be saved 
      pBuffer++;
    }
    // Set next write address 
    Offset += 512;
    // Put CRC bytes (not really needed by us, but required by MSD) 
    MSD_ReadByte();
    MSD_ReadByte();
    // Read data response 
    if (MSD_GetDataResponse() == MSD_DATA_OK)
    {
      // Set response value to success 
      rvalue = MSD_RESPONSE_NO_ERROR;
    }
    else
    {
      // Set response value to failure 
      rvalue = MSD_RESPONSE_FAILURE;
    }
  }

  // MSD chip select high 
  MSD_ChipSelect(DISABLE);
  // Send dummy byte: 8 Clock pulses of delay 
  MSD_WriteByte(DUMMY);
  // Returns the reponse 
  return rvalue;
}

/**
  * @brief Read a buffer (many blocks) from the MSD card.
  * @param[in] pBuffer pointer to the buffer that receives the data read from the MSD.
  * @param[in] ReadAddr MSD's internal address to read from.
  * @param[in] NumByteToRead number of bytes to read from the MSD.
  * @retval uint8_t The MSD response
  * - MSD_RESPONSE_FAILURE: Sequence failed
  * - MSD_RESPONSE_NO_ERROR: Sequence succeed
  */
uint8_t MSD_ReadBuffer(uint8_t *pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)
{
  uint32_t i = 0, NbrOfBlock = 0, Offset = 0;
  uint8_t rvalue = MSD_RESPONSE_FAILURE;

  // Calculate number of blocks to read 
  NbrOfBlock = NumByteToRead / BLOCK_SIZE;
  // MSD chip select low 
  MSD_ChipSelect(ENABLE);

  // Data transfer 
  while (NbrOfBlock --)
  {
    // Send CMD17 (MSD_READ_SINGLE_BLOCK) to read one block 
    MSD_SendCmd (MSD_READ_SINGLE_BLOCK, ReadAddr + Offset, 0xFF);
    // Check if the MSD acknowledged the read block command: R1 response (0x00: no errors) 
    if (MSD_GetResponse(MSD_RESPONSE_NO_ERROR))
    {
      return  MSD_RESPONSE_FAILURE;
    }
    // Now look for the data token to signify the start of the data 
    if (!MSD_GetResponse(MSD_START_DATA_SINGLE_BLOCK_READ))
    {
      // Read the MSD block data : read NumByteToRead data 
      for (i = 0; i < BLOCK_SIZE; i++)
      {
        // Read the pointed data 
        *pBuffer = MSD_ReadByte();
        // Point to the next location where the byte read will be saved 
        pBuffer++;
      }
      // Set next read address
      Offset += 512;
      // get CRC bytes (not really needed by us, but required by MSD) 
      MSD_ReadByte();
      MSD_ReadByte();
      // Set response value to success 
      rvalue = MSD_RESPONSE_NO_ERROR;
    }
    else
    {
      // Set response value to failure 
      rvalue = MSD_RESPONSE_FAILURE;
    }
  }

  // MSD chip select high 
  MSD_ChipSelect(DISABLE);
  // Send dummy byte: 8 Clock pulses of delay 
  MSD_WriteByte(DUMMY);
  // Returns the reponse 
  return rvalue;
}
/**
  * @brief Read the CSD card register.
  * Reading the contents of the CSD register in SPI mode is a simple
  * read-block transaction.
  * @param[in] MSD_csd  pointer on an SCD register structure.
  * @retval
  * The MSD Response: - MSD_RESPONSE_FAILURE: Sequence failed
  *                   - MSD_RESPONSE_NO_ERROR: Sequence succeed
  */
uint8_t MSD_GetCSDRegister(MSD_CSD* MSD_csd)
{
  uint32_t i = 0;
  uint8_t rvalue = MSD_RESPONSE_FAILURE;
  uint8_t CSD_Tab[16];

  // MSD chip select low 
  MSD_CS_HIGH();
  // Send CMD9 (CSD register) or CMD10(CSD register) 
  MSD_SendCmd(MSD_SEND_CSD, 0, 0xFF);

  // Wait for response in the R1 format (0x00 is no errors) 
  if (!MSD_GetResponse(MSD_RESPONSE_NO_ERROR))
  {
    if (!MSD_GetResponse(MSD_START_DATA_SINGLE_BLOCK_READ))
    {
      for (i = 0; i < 16; i++)
      {
        // Store CSD register value on CSD_Tab 
        CSD_Tab[i] = MSD_ReadByte();
      }
    }
    // Get CRC bytes (not really needed by us, but required by MSD) 
    MSD_WriteByte(DUMMY);
    MSD_WriteByte(DUMMY);
    // Set response value to success 
    rvalue = MSD_RESPONSE_NO_ERROR;
  }

  // MSD chip select high 
  MSD_CS_HIGH();
  // Send dummy byte: 8 Clock pulses of delay 
  MSD_WriteByte(DUMMY);

  // Byte 0 
  MSD_csd->CSDStruct = (CSD_Tab[0] & 0xC0) >> 6;
  MSD_csd->SysSpecVersion = (CSD_Tab[0] & 0x3C) >> 2;
  MSD_csd->Reserved1 = CSD_Tab[0] & 0x03;
  // Byte 1 
  MSD_csd->TAAC = CSD_Tab[1] ;
  // Byte 2 
  MSD_csd->NSAC = CSD_Tab[2];
  // Byte 3 
  MSD_csd->MaxBusClkFrec = CSD_Tab[3];
  // Byte 4 
  MSD_csd->CardComdClasses = CSD_Tab[4] << 4;
  // Byte 5 
  MSD_csd->CardComdClasses |= (CSD_Tab[5] & 0xF0) >> 4;
  MSD_csd->RdBlockLen = CSD_Tab[5] & 0x0F;
  // Byte 6 
  MSD_csd->PartBlockRead = (CSD_Tab[6] & 0x80) >> 7;
  MSD_csd->WrBlockMisalign = (CSD_Tab[6] & 0x40) >> 6;
  MSD_csd->RdBlockMisalign = (CSD_Tab[6] & 0x20) >> 5;
  MSD_csd->DSRImpl = (CSD_Tab[6] & 0x10) >> 4;
  MSD_csd->Reserved2 = 0; /* Reserved */
  MSD_csd->DeviceSize = (CSD_Tab[6] & 0x03) << 10;
  // Byte 7 
  MSD_csd->DeviceSize |= (CSD_Tab[7]) << 2;
  // Byte 8 
  MSD_csd->DeviceSize |= (CSD_Tab[8] & 0xC0) >> 6;
  MSD_csd->MaxRdCurrentVDDMin = (CSD_Tab[8] & 0x38) >> 3;
  MSD_csd->MaxRdCurrentVDDMax = (CSD_Tab[8] & 0x07);
  // Byte 9 
  MSD_csd->MaxWrCurrentVDDMin = (CSD_Tab[9] & 0xE0) >> 5;
  MSD_csd->MaxWrCurrentVDDMax = (CSD_Tab[9] & 0x1C) >> 2;
  MSD_csd->DeviceSizeMul = (CSD_Tab[9] & 0x03) << 1;
  // Byte 10 
  MSD_csd->DeviceSizeMul |= (CSD_Tab[10] & 0x80) >> 7;
  MSD_csd->EraseGrSize = (CSD_Tab[10] & 0x7C) >> 2;
  MSD_csd->EraseGrMul = (CSD_Tab[10] & 0x03) << 3;
  // Byte 11 
  MSD_csd->EraseGrMul |= (CSD_Tab[11] & 0xE0) >> 5;
  MSD_csd->WrProtectGrSize = (CSD_Tab[11] & 0x1F);
  // Byte 12 
  MSD_csd->WrProtectGrEnable = (CSD_Tab[12] & 0x80) >> 7;
  MSD_csd->ManDeflECC = (CSD_Tab[12] & 0x60) >> 5;
  MSD_csd->WrSpeedFact = (CSD_Tab[12] & 0x1C) >> 2;
  MSD_csd->MaxWrBlockLen = (CSD_Tab[12] & 0x03) << 2;
  // Byte 13 
  MSD_csd->MaxWrBlockLen |= (CSD_Tab[13] & 0xc0) >> 6;
  MSD_csd->WriteBlockPaPartial = (CSD_Tab[13] & 0x20) >> 5;
  MSD_csd->Reserved3 = 0;
  MSD_csd->ContentProtectAppli = (CSD_Tab[13] & 0x01);
  // Byte 14 
  MSD_csd->FileFormatGrouop = (CSD_Tab[14] & 0x80) >> 7;
  MSD_csd->CopyFlag = (CSD_Tab[14] & 0x40) >> 6;
  MSD_csd->PermWrProtect = (CSD_Tab[14] & 0x20) >> 5;
  MSD_csd->TempWrProtect = (CSD_Tab[14] & 0x10) >> 4;
  MSD_csd->FileFormat = (CSD_Tab[14] & 0x0C) >> 2;
  MSD_csd->ECC = (CSD_Tab[14] & 0x03);
  // Byte 15 
  MSD_csd->CRC = (CSD_Tab[15] & 0xFE) >> 1;
  MSD_csd->Reserved4 = 1;

  // Return the reponse 
  return rvalue;
}

