/*-----------------------------------------------------------------------*/
/* MMC/SDSC/SDHC (in UASRT MSPI mode) control module  (C)ChaN, 2007      */
/*-----------------------------------------------------------------------*/
/* Only rcvr_spi(), xmit_spi(), disk_timerproc() and some macros         */
/* are platform dependent.                                               */
/*-----------------------------------------------------------------------*/


#include <avr/io.h>
#include "diskio.h"
#include "includes.h"
#include "ffconf.h"
#include "mmc_uspi_conf.h"
//#include "spi.h"
//JIM#include "kernel.h"
//#include "timer.h"

#define _USPITYPE                               // Define which version of hardware we're using.


static void power_on(BYTE drv);
static void power_off(BYTE drv);

// ................................................................................................
static const char*    ferror_str[] = {
                                      "FR_OK",
                                      "FR_DISK_ERR",
                                      "FR_INT_ERR",
                                      "FR_NOT_READY",
                                      "FR_NO_FILE",
                                      "FR_NO_PATH",
                                      "FR_INVALID_NAME",
                                      "FR_DENIED",
                                      "FR_EXIST",
                                      "FR_INVALID_OBJECT",
                                      "FR_WRITE_PROTECTED",
                                      "FR_INVALID_DRIVE",
                                      "FR_NOT_ENABLED",
                                      "FR_NO_FILESYSTEM",
                                      "FR_MKFS_ABORTED",
                                      "FR_TIMEOUT"
                                     };
char * FError2Str(int ferror)
{
  return (char*)ferror_str[ferror];
}

#define USPI_POWER_ON(drv)  USPI##drv##SETTO0nPON
#define USPI_POWER_OFF(drv) USPI##drv##SETTO1nPON

      // Set I/O bits.
      // Set up USART in master SPI mode. Turn off power reduction.
      // Turn off baud rate divider.
      // USPI##drv##SRA - Not required to be initialized.
      // USPI##drv##SRB - Enable both RX and TX components.
      // USPI##drv##SRC - Set to Master (USART) SPI mode. Frame format is: MSB first, mode 0.
      // Unused settings: | _BV(USPI##drv##UCORD) | _BV(USPI##drv##UCPHA) | _BV(USPI##drv##UCPOL)
      // Finish with powering on the card slot.
#define USPI_INIT(drv)                                                  \
      USPI##drv##INITPXCK;                                              \
      USPI##drv##SETTO1nPON;                                            \
      USPI##drv##INITPnPON;                                             \
      USPI##drv##SETTO1nSS;                                             \
      USPI##drv##INITPnSS;                                              \
      USPI##drv##INITPnCD;                                              \
      USPI##drv##INITPnPOK;                                             \
                                                                        \
      PRR1 &= ~_BV(USPI##drv##PRUSART);                                 \
      USPI##drv##BRRH = 0;                                              \
      USPI##drv##BRRL = 0;                                              \
      USPI##drv##SRB = _BV(USPI##drv##RXEN) | _BV(USPI##drv##TXEN);     \
      USPI##drv##SRC = _BV(USPI##drv##UMSEL1) | _BV(USPI##drv##UMSEL0);

      // Turn special function ports of the USART to general outputs.
      // Set outputs to zero.
      // Change special function port to general I/O.
#define USPI_DEINIT(drv)          \
      USPI##drv##INITPioMISO;     \
      USPI##drv##SETTO0XCK;       \
      USPI##drv##SETTO0nSS;       \
      USPI##drv##SETTO0ioMOSI;    \
      USPI##drv##SETTO0ioMISO;    \
      USPI##drv##SRB = 0;


#if _DRIVES == 1
static volatile DSTATUS Stat[_DRIVES] = {STA_NOINIT}; /* Disk status */
#endif // _DRIVES == 1
#if _DRIVES == 2
static volatile DSTATUS Stat[_DRIVES] = {STA_NOINIT, STA_NOINIT};
#endif // _DRIVES == 2
#if _DRIVES == 3
static volatile DSTATUS Stat[_DRIVES] = {STA_NOINIT, STA_NOINIT, STA_NOINIT};
#endif // _DRIVES == 3
static BYTE CardType[_DRIVES];                  /* Card type flags */


/* Definitions for MMC/SDC command */
#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 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 CMD55   (0x40+55) /* APP_CMD */
#define CMD58   (0x40+58) /* READ_OCR */

// these variables provide access to hardware. Note that only one
// instance of the SD flash is possible
#ifdef _USPITYPE
////////////// #define SELECT()          USPISETTO0nSS
static void Select(BYTE drv)
{
  switch (drv)
  {
#if _DRIVES > 0
    case 0:
      USPI0SETTO0nSS
      break;
#if _DRIVES > 1
    case 1:
      USPI1SETTO0nSS
      break;
#if _DRIVES > 2
    case 2:
      USPI2SETTO0nSS
      break;
#endif // _DRIVES > 2
#endif // _DRIVES > 1
#endif // _DRIVES > 0
    default:
      break;
  } // switch
} // Select

/////////////// #define DESELECT()        USPISETTO1nSS
static void Deselect(BYTE drv)
{
  switch (drv)
  {
#if _DRIVES > 0
    case 0:
      USPI0SETTO1nSS
      break;
#if _DRIVES > 1
    case 1:
      USPI1SETTO1nSS
      break;
#if _DRIVES > 2
    case 2:
      USPI2SETTO1nSS
      break;
#endif // _DRIVES > 2
#endif // _DRIVES > 1
#endif // _DRIVES > 0
    default:
      break;
  } // switch
} // Deselect

/////////////////// #define FCLK_SLOW()       { USPIBRRL = 9; }     // Assumes that UBRR#H does not need to change.
static void Fclk_Slow(BYTE drv)
{
  switch (drv)
  {
#if _DRIVES > 0
    case 0:
      USPI0BRRL = 9;
      break;
#if _DRIVES > 1
    case 1:
      USPI1BRRL = 9;
      break;
#if _DRIVES > 2
    case 2:
      USPI2BRRL = 9;
      break;
#endif // _DRIVES > 2
#endif // _DRIVES > 1
#endif // _DRIVES > 0
    default:
      break;
  } // switch
} // Fclk_Slow

////////////////////// #define FCLK_FAST()       { USPIBRRL = 0; }
static void Fclk_Fast(BYTE drv)
{
  switch (drv)
  {
#if _DRIVES > 0
    case 0:
      USPI0BRRL = 0;
      break;
#if _DRIVES > 1
    case 1:
      USPI1BRRL = 0;
      break;
#if _DRIVES > 2
    case 2:
      USPI2BRRL = 0;
      break;
#endif // _DRIVES > 2
#endif // _DRIVES > 1
#endif // _DRIVES > 0
    default:
      break;
  } // switch
} // Fclk_Fast
#endif // _USPITYPE

// ................................................................................................
// Initialize handware and anything else related to USART based SPI port used to access the SD Card.
//  Does not turn on power to the SD Card socket.
//
// NOTE: This routine does NOT check for valid drive number. If it does get here with an invalid
//        drive number, nothing gets done, but no error message is sent back.
//
// Parameters:
//    drv                 - Specifies the drive to be initialized.
// Returns:
//    Nothing.
//
void USPI_Device_Init(BYTE drv)
{
  switch (drv)
  {
#if _DRIVES > 0
    case 0:
      // Set dedicated I/O pins.
      USPI_INIT(0);
      break;
#if _DRIVES > 1
    case 1:
      // Set dedicated I/O pins.
      USPI_INIT(1);
      break;
#if _DRIVES > 2
    case 2:
      // Set dedicated I/O pins.
      USPI_INIT(2);
      break;
#endif // _DRIVES > 2
#endif // _DRIVES > 1
#endif // _DRIVES > 0
    default:
      break;
  } // switch
} // USPI_Device_Init

// ................................................................................................
// Disable handware related to USART based SPI port used to access the SD Card.
//  No nice turn off, checking to see if any transactions are in progress. Just cold, hard shutdown.
//
// NOTE: This routine does NOT check for valid drive number. If it does get here with an invalid
//        drive number, nothing gets done, but no error message is sent back.
//
// Parameters:
//    drv                 - Specifies the drive to be initialized.
// Returns:
//    Nothing.
//
void USPI_Device_Disable(BYTE drv)
{
  Stat[drv] = STA_NOINIT;												// Set status to indicate that the device needs to 
																								//	be remounted on power up.
  USPI_Device_PowerOff(drv);
  switch (drv)
  {
#if _DRIVES > 0
    case 0:
      USPI_DEINIT(0);
      break;
#if _DRIVES > 1
    case 1:
      USPI_DEINIT(1);
      break;
#if _DRIVES > 2
    case 2:
      USPI_DEINIT(2);
      break;
#endif // _DRIVES > 2
#endif // _DRIVES > 1
#endif // _DRIVES > 0
    default:
      break;
  } // switch
} // USPI_Device_Disable

// ................................................................................................
// Turn on power to SD Card slot.
//  Calling this routine without the hardware having been initialized will probably not hurt
//    anything. It will attempt to change the PON I/O bit.
//
// NOTE: This routine does NOT check for valid drive number. If it does get here with an invalid
//        drive number, nothing gets done, but no error message is sent back.
//
// Parameters:
//    drv                 - Specifies the drive to be initialized.
// Returns:
//    Nothing.
//
void USPI_Device_PowerOn(BYTE drv)
{
  power_on(drv);
} // USPI_Device_PowerOn

// ................................................................................................
// Turn on power to SD Card slot.
//  Hardware must have been initialized prior to calling this routine.
//  It will not shut down "nicely" as the internal power_off routine does, so use it sparingly.
//
// NOTE: This routine does NOT check for valid drive number. If it does get here with an invalid
//        drive number, nothing gets done, but no error message is sent back.
//
// Parameters:
//    drv                 - Specifies the drive to be initialized.
// Returns:
//    Nothing.
//
void USPI_Device_PowerOff(BYTE drv)
{
  switch (drv)
  {
#if _DRIVES > 0
    case 0:
      USPI_POWER_OFF(0);
      break;
#if _DRIVES > 1
    case 1:
      USPI_POWER_OFF(1);
      break;
#if _DRIVES > 2
    case 2:
      USPI_POWER_OFF(2);
      break;
#endif // _DRIVES > 2
#endif // _DRIVES > 1
#endif // _DRIVES > 0
    default:
      break;
  } // switch
} // USPI_Device_PowerOff

/*--------------------------------------------------------------------------

   Module Private Functions

---------------------------------------------------------------------------*/


/*-----------------------------------------------------------------------*/
/* Transmit a byte to MMC via SPI  (Platform dependent)                  */
/*-----------------------------------------------------------------------*/

#ifdef _USPITYPE
static void xmit_spi(BYTE drv, uint8_t dat)
{
  uint8_t tmp;                                  // Don't corrupt the input byte in case the inlining
                                                //  code does so when reading character back into
                                                //  'dat'.
  switch (drv)
  {
#if _DRIVES > 0
    case 0:
      USPI0DAT = dat;                           // Output byte to USART SPI data port.
      while (!(USPI0SRA & USPI0RXC)) ;          // Wait until a character has been received.
                                                //  When so, transmission is also complete.
      tmp = USPI0DAT;                           // Throw out received character.
      break;
#if _DRIVES > 1
    case 1:
      USPI1DAT = dat;                           // Ditto.
      while (!(USPI1SRA & USPI1RXC)) ;

      tmp = USPI1DAT;
      break;
#endif // _DRIVES > 1
#if _DRIVES > 2
    case 2:
      USPI2DAT = dat;
      while (!(USPI2SRA & USPI2RXC)) ;

      tmp = USPI2DAT;
      break;
#endif // _DRIVES > 1
#endif // _DRIVES > 0
    default:
      break;
  } // switch
} // xmit_spi
#endif // _USPITYPE

/*-----------------------------------------------------------------------*/
/* Receive a byte from MMC via SPI  (Platform dependent)                 */
/*-----------------------------------------------------------------------*/


#ifdef _USPITYPE
static BYTE rcvr_spi(BYTE drv)
{
  switch (drv)
  {
#if _DRIVES > 0
    case 0:
      USPI0DAT = 0xFF;                          // Output all ones to get the transfer going.
      while (!(USPI0SRA & USPI0RXC)) ;          // Wait until a character has been received.
                                                //  When so, transmission is also complete.
      return USPI0DAT;                          // Return received character.
#if _DRIVES > 1
    case 1:
      USPI1DAT = 0xFF;                          // Ditto.
      while (!(USPI1SRA & USPI1RXC)) ;

      return USPI1DAT;
#if _DRIVES > 2
    case 2:
      USPI2DAT = 0xFF;
      while (!(USPI2SRA & USPI2RXC)) ;

      return USPI2DAT;
#endif // _DRIVES > 2
#endif // _DRIVES > 1
#endif // _DRIVES > 0
    default:
      break;
  } // switch
  return 0;                                     // Will never reach here unless it defaults. And
                                                //  that's no good anyway.
} // rcvr_spi

static void rcvr_spi_m(BYTE drv, uint8_t * dst)
{
  switch (drv)
  {
#if _DRIVES > 0
    case 0:
      USPI0DAT = 0xFF;                          // Output all ones to get the transfer going.
      while (!(USPI0SRA & USPI0RXC)) ;          // Wait until a character has been received.
                                                //  When so, transmission is also complete.
      *(dst) = USPI0DAT;                        // Place data into buffer.
      break;
#if _DRIVES > 1
    case 1:
      USPI1DAT = 0xFF;                          // Ditto.
      while (!(USPI1SRA & USPI1RXC)) ;

      *(dst) = USPI1DAT;
      break;
#if _DRIVES > 2
    case 2:
      USPI2DAT = 0xFF;
      while (!(USPI2SRA & USPI2RXC)) ;

      *(dst) = USPI2DAT;
      break;
#endif // _DRIVES > 2
#endif // _DRIVES > 1
#endif // _DRIVES > 0
    default:
      break;
  } // switch
} // rcvr_spi_m
#endif // _USPITYPE

/*-----------------------------------------------------------------------*/
/* Wait for card ready                                                   */
/*-----------------------------------------------------------------------*/

#ifdef _USPITYPE
// ................................................................................................
// Wait for device ready.
//  Device is deemed ready when it returns a 0xFF data byte. This routine will check for this 0xFF
//    for up to 500 ms. If it does not appear, it will return the value of the last byte returned.
//
// Parameters:
//    None
// Returns:
//    BYTE                - The value last read.
//                          If == 0xFF, the wait completed successfully.
//                          if != 0xFF, it timed out.
//
static BYTE wait_ready(BYTE drv)
{
  BYTE    res;
  uint8_t err;
  OS_TMR  *Timer;

  rcvr_spi(drv);                                // Trigger wait return byte.
  if ((res = rcvr_spi(drv)) == 0xFF) return res;  // Test for immediate gratification.

  // Setup for 500 ms timer.
  Timer = OSTmrCreate(  (500 / (1000 / OS_TMR_CFG_TICKS_PER_SEC) + 1), 0,
              OS_TMR_OPT_ONE_SHOT,
              NULL, NULL,
              (uint8_t *)"Trdb",
              &err
             );
  if ((res = rcvr_spi(drv)) == 0xFF) return res;  // Make one more attempt before using the timer.

  OSTmrStart(Timer, &err);                      // Must manually start the timer.
  do
  {
    // Wait for data packet in timeout of 100ms.
    if (err != OS_ERR_NONE) break;              // In case there's been an error -- just quit.
    OSTimeDly(1);                               // Allow other tasks to be serviced, but this may
                                                // take up to 1/128 seconds to return.
  } while (((res = rcvr_spi(drv)) != 0xFF) && (OSTmrStateGet(Timer, &err) != OS_TMR_STATE_COMPLETED));
  OSTmrDel(Timer, &err);                        // Delete timer. (Improves performance some.)

  return res;
}
#endif // _USPITYPE



/*-----------------------------------------------------------------------*/
/* Deselect the card and release SPI bus                                 */
/*-----------------------------------------------------------------------*/

static void release_spi (BYTE drv)
{
  Deselect(drv);
  rcvr_spi(drv);
}



/*-----------------------------------------------------------------------*/
/* Power Control  (Platform dependent)                                   */
/*-----------------------------------------------------------------------*/
/* When the target system does not support socket power control, there   */
/* is nothing to do in these functions and chk_power always returns 1.   */

static void power_on(BYTE drv)
{
  switch (drv)
  {
#if _DRIVES > 0
    case 0:
      USPI_POWER_ON(0);
      break;
#if _DRIVES > 1
    case 1:
      USPI_POWER_ON(1);
      break;
#if _DRIVES > 2
    case 2:
      USPI_POWER_ON(2);
      break;
#endif // _DRIVES > 2
#endif // _DRIVES > 1
#endif // _DRIVES > 0
    default:
      break;
  } // switch
  OSTimeDly(2);                                 // Give it a bit of time to stabilize.
                                                // About 1ms should do it, but this is the minimum
                                                //  this OS will give us.
} // power_on

static void power_off(BYTE drv)
{
  Select(drv);                                     /* Wait for card ready */
  wait_ready(drv);
  release_spi(drv);
// Do not turn the power off here, because it doesn't know when to turn it on again.
// Apparently there are some (at least one) routine that checks the SD Card without regard to the
//  power condition of the card. This will possibly hang the system; at a minimum it will not help
//  it.
//Don't//  USPI_Device_Disable(drv);
} // power_off

static uint8_t chk_power(BYTE drv)
{
  switch (drv)
  {
#if _DRIVES > 0
    case 0:
      return (USPI0GETnPOK ? 0 : 1);
#if _DRIVES > 1
    case 1:
      return (USPI1GETnPOK ? 0 : 1);
#if _DRIVES > 2
    case 2:
      return (USPI2GETnPOK ? 0 : 1);
#endif // _DRIVES > 2
#endif // _DRIVES > 1
#endif // _DRIVES > 0
    default:
      break;
  } // switch
  return 0;                                     // Will never reach here unless it defaults. And
                                                //  that's no good anyway.
} // chk_power


/*-----------------------------------------------------------------------*/
/* Receive a data packet from MMC                                        */
/*-----------------------------------------------------------------------*/

#ifdef _USPITYPE
static BOOL rcvr_datablock (
  BYTE drv,                                     // Drive select.
  BYTE *buff,                                   // Data buffer to store received data.
  UINT btr                                      // Byte count (must be multiple of 4).
)
{
  BYTE  token;
  uint8_t err;
  OS_TMR  *Timer;

  token = rcvr_spi(drv);                        // Do an early read to see if the token is present.
                                                // If so, it'll save a whole lot of time not having
                                                // to set up the timer.
  if (token == 0xFF)
  {
    // Setup for 100 ms timer.
    Timer = OSTmrCreate(  (100 / (1000 / OS_TMR_CFG_TICKS_PER_SEC) + 1), 0,
                OS_TMR_OPT_ONE_SHOT,
                NULL, NULL,
                (uint8_t *)"Trdb",
                &err
               );
    token = rcvr_spi(drv);                      // Make one more attempt before using the timer.
    if (token == 0xFF)
    {
      OSTmrStart(Timer, &err);                  // Must manually start the timer.
      do
      {
        // Wait for data packet in timeout of 100ms.
        if (err != OS_ERR_NONE) break;          // In case there's been an error -- just quit.
        OSTimeDly(1);                           // Allow other tasks to be serviced, but this may
                                                // take up to 1/128 seconds to return.
      } while (((token = rcvr_spi(drv)) == 0xFF) && (OSTmrStateGet(Timer, &err) != OS_TMR_STATE_COMPLETED));
    }
    OSTmrDel(Timer, &err);                      // Delete timer. (Improves performance some.)
  }

  if(token != 0xFE) return FALSE;               // If not valid data token, return with error.

/* Wait until later to try to optimize this code. This is how it was used in Ceros, and this code
    has slightly different parameters.
    asm(
        "std    Z+6, %3\n"                      // spawn two transactions to start
    "1:\tstd    Z+6, %3\n\t"
        "subi   %A0, 1\n\t"
        "sbc    %B0, r1\n"
    "2:\tldd    r0,Z+0\n\t"
        "sbrs   r0,7\n\t"
        "rjmp   2b\n\t"
        "ldd    r0,Z+6\n\t"
        "st     X+,r0\n\t"
        "brne   1b\n"
    "2:\tldd    r0,Z+0\n\t"                     // do the last character
        "sbrs   r0,7\n\t"
        "rjmp   2b\n\t"
        "ldd    r0,Z+6\n\t"
        "st     X+, r0\n\t"
        :                                       // no outputs
        : "d" (btr-1), "x" (buff), "z" (rxs->scsra), "r" ((uint8_t)(0xFF))
    );
*/
// >>> Equivalent to the above optimization.
  do {                                          // Receive the data block into buffer.
    rcvr_spi_m(drv, buff++);
    rcvr_spi_m(drv, buff++);
    rcvr_spi_m(drv, buff++);
    rcvr_spi_m(drv, buff++);
  } while (btr -= 4);
// <<< Equivalent to the above optimization.
  rcvr_spi(drv);                                // Discard CRC.
  rcvr_spi(drv);

  return TRUE;                                  // Return with success.
}
#endif // _USPITYPE



/*-----------------------------------------------------------------------*/
/* Send a data packet to MMC                                             */
/*-----------------------------------------------------------------------*/

#if _FS_READONLY == 0
#ifdef _USPITYPE
static BOOL xmit_datablock (
  BYTE drv,                                     // Drive select.
  const BYTE *buff,                             /* 512 byte data block to be transmitted */
  BYTE token                                    /* Data/Stop token */
)
{
  BYTE resp, wc;


  if (wait_ready(drv) != 0xFF) return FALSE;

  xmit_spi(drv, token);                         /* transmit data token */
  if (token != 0xFD) {                          /* Is data token */
    wc = 0;
/* Wait until later to try to optimize this code. This is how it was used in Ceros, and this code
    has slightly different parameters.
    asm("; start of asm code...\n"
    "loop1:\n\t"
        "ld     r0, X+\n\t"                     // load the data and write to the data register
        "std    Z+6, r0\n\t"
        "subi   %A0,1\n\t"                      // decrement the count while its transmitting
        "sbc    %B0,r1\n\t"                     // the conditon code will be good later...
    "loop2:\n\t"
        "ld     r0, Z\n\t"                      // wait for RXC (bit 7) high
        "sbrs   r0,7\n\t"
        "rjmp   loop2\n\t"
        "ldd    r0, Z+6\n\t"                    // dummy load of the data register
        "brne   loop1\n\t"
        :
        : "d" ((int) 512), "x" (buff), "z" (txs->scsra));
*/
// >>> Equivalent to the above optimization.
    do {                                        /* transmit the 512 byte data block to MMC */
      xmit_spi(drv, *buff++);
      xmit_spi(drv, *buff++);
    } while (--wc);
// <<< Equivalent to the above optimization.
    xmit_spi(drv, 0xFF);                        /* CRC (Dummy) */
    xmit_spi(drv, 0xFF);
    resp = rcvr_spi(drv);                       /* Receive data response */
    if ((resp & 0x1F) != 0x05)                  /* If not accepted, return with error */
      return FALSE;
  }

  return TRUE;
}
#endif // _USPITYPE
#endif /* _FS_READONLY */



/*-----------------------------------------------------------------------*/
/* Send a command packet to MMC                                          */
/*-----------------------------------------------------------------------*/

static
BYTE send_cmd (
  BYTE drv,                                     // Drive select.
  BYTE cmd,                                     /* Command byte */
  DWORD arg                                     /* Argument */
)
{
  BYTE n, res;

  if (cmd & 0x80) {                             /* ACMD<n> is the command sequence of CMD55-CMD<n> */
    cmd &= 0x7F;
    res = send_cmd(drv, CMD55, 0);
    if (res > 1) return res;
  }

  /* Select the card and wait for ready */
  Deselect(drv);
  Select(drv);
  if (wait_ready(drv) != 0xFF)
    return 0xFF;

  /* Send command packet */
  xmit_spi(drv, cmd);                           /* Start + Command index */
  xmit_spi(drv, (BYTE)(arg >> 24));             /* Argument[31..24] */
  xmit_spi(drv, (BYTE)(arg >> 16));             /* Argument[23..16] */
  xmit_spi(drv, (BYTE)(arg >> 8));              /* Argument[15..8] */
  xmit_spi(drv, (BYTE)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) */
  xmit_spi(drv, n);

  /* Receive command response */
  if (cmd == CMD12) rcvr_spi(drv);              /* Skip a stuff byte when stop reading */

  n = 10;                                       /* Wait for a valid response in timeout of 10 attempts */
  do
    res = rcvr_spi(drv);
  while ((res & 0x80) && --n);
  return res;                                   /* Return with the response value */
}



/*--------------------------------------------------------------------------

   Public Functions

---------------------------------------------------------------------------*/


/*-----------------------------------------------------------------------*/
/* Initialize Disk Drive                                                 */
/*-----------------------------------------------------------------------*/
// the spi port must have been previously initialized
DSTATUS disk_initialize (
  BYTE drv                                      /* Physical drive number */
)
{
  OS_TMR            *Timer;
  volatile uint8_t  timer_state;
  uint8_t           err;
  BYTE              n, cmd, ty, ocr[4];

  if (drv >= _DRIVES) return STA_NOINIT;        // Limit the drives that may be initialized.

//  if (drv) return STA_NOINIT;                   /* Supports only single drive */
  if (Stat[drv] & STA_NODISK) return Stat[drv]; /* No card in the socket */

  // Setup for 1000 ms timer.
  Timer = OSTmrCreate(  (1000 / (1000 / OS_TMR_CFG_TICKS_PER_SEC) + 1), 0,
                        OS_TMR_OPT_ONE_SHOT,
                        NULL, NULL,
                        (uint8_t *)"Tdi",
                        &err
                     );

  power_on(drv);                                /* Force socket power on */
  Fclk_Slow(drv);
  for (n = 10; n; n--) rcvr_spi(drv);           /* 80 dummy clocks */

  ty = 0;
  if (send_cmd(drv, CMD0, 0) == 1) {            /* Enter Idle state */
    OSTmrStart(Timer, &err);                    // Must manually start the timer.
    timer_state = OSTmrStateGet( Timer, &err);
    if (send_cmd(drv, CMD8, 0x1AA) == 1) {           /* SDHC */
      for (n = 0; n < 4; n++) ocr[n] = rcvr_spi(drv);  /* Get trailing return value of R7 response */
      if (ocr[2] == 0x01 && ocr[3] == 0xAA) {     /* The card can work at VDD range of 2.7-3.6V */
        while ((timer_state != OS_TMR_STATE_COMPLETED) && send_cmd(drv, ACMD41, 1UL << 30))
          timer_state = OSTmrStateGet( Timer, &err);
        if ((timer_state != OS_TMR_STATE_COMPLETED) && send_cmd(drv, CMD58, 0) == 0) {
          for (n = 0; n < 4; n++) ocr[n] = rcvr_spi(drv);
          ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;
        }
      }
    } else {                                    /* SDSC or MMC */
      if (send_cmd(drv, ACMD41, 0) <= 1)   {
        ty = CT_SD1; cmd = ACMD41;              /* SDSC */
      } else {
        ty = CT_MMC; cmd = CMD1;                /* MMC */
      }
      while ((timer_state != OS_TMR_STATE_COMPLETED) && send_cmd(drv, cmd, 0))
        timer_state = OSTmrStateGet( Timer, &err);
      if (!(timer_state != OS_TMR_STATE_COMPLETED) || send_cmd(drv, CMD16, 512) != 0)
        // Set R/W block length to 512.
        ty = 0;
    }
  }
  OSTmrDel(Timer, &err);                        // Delete timer. (Improves performance some.)
  CardType[drv] = ty;
  release_spi(drv);

  if (ty) {                                     /* Initialization succeeded */
    Stat[drv] &= ~STA_NOINIT;                   /* Clear STA_NOINIT */
    Fclk_Fast(drv);
  } else {                                      /* Initialization failed */
    power_off(drv);
  }

  return Stat[drv];
} // disk_initialize



/*-----------------------------------------------------------------------*/
/* Get Disk Status                                                       */
/*-----------------------------------------------------------------------*/

DSTATUS disk_status (
  BYTE drv                                      /* Physical drive number */
)
{
  if (drv >= _DRIVES) return STA_NOINIT;
//  if (drv) return STA_NOINIT;                   /* Supports only single drive */
  return Stat[drv];
}



/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read (
  BYTE drv,                                     /* Physical drive number (0) */
  BYTE *buff,                                   /* Pointer to the data buffer to store read data */
  DWORD sector,                                 /* Start sector number (LBA) */
  BYTE count                                    /* Sector count (1..255) */
)
{
  if ((drv >= _DRIVES) || !count) return RES_PARERR;
//  if (drv || !count) return RES_PARERR;
  if (Stat[drv] & STA_NOINIT) return RES_NOTRDY;

  if (!(CardType[drv] & CT_BLOCK)) sector *= 512; /* Convert to byte address if needed */

  if (count == 1) {                             /* Single block read */
    if (send_cmd(drv, CMD17, sector) == 0) {    /* READ_SINGLE_BLOCK */
      if (rcvr_datablock(drv, buff, 512)) {
      count = 0;
  }
    }
  }
  else {                                        /* Multiple block read */
    if (send_cmd(drv, CMD18, sector) == 0) {    /* READ_MULTIPLE_BLOCK */
      do {
        if (!rcvr_datablock(drv, buff, 512)) {
          break;
        }
        buff += 512;
      } while (--count);
      send_cmd(drv, CMD12, 0);                  /* STOP_TRANSMISSION */
    }
  }
  release_spi(drv);

  return count ? RES_ERROR : RES_OK;
}



/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if _FS_READONLY == 0

DRESULT disk_write (
  BYTE drv,                                     /* Physical drive number (0) */
  const BYTE *buff,                             /* Pointer to the data to be written */
  DWORD sector,                                 /* Start sector number (LBA) */
  BYTE count                                    /* Sector count (1..255) */
)
{
  if ((drv >= _DRIVES) || !count) return RES_PARERR;
//  if (drv || !count) return RES_PARERR;
  if (Stat[drv] & STA_NOINIT) return RES_NOTRDY;
  if (Stat[drv] & STA_PROTECT) return RES_WRPRT;

  if (!(CardType[drv] & CT_BLOCK)) sector *= 512; /* Convert to byte address if needed */

  if (count == 1) {                             /* Single block write */
    if ((send_cmd(drv, CMD24, sector) == 0)     /* WRITE_BLOCK */
      && xmit_datablock(drv, buff, 0xFE))
      count = 0;
  }
  else {                                        /* Multiple block write */
    if (CardType[drv] & CT_SDC) send_cmd(drv, ACMD23, count);
    if (send_cmd(drv, CMD25, sector) == 0) {    /* WRITE_MULTIPLE_BLOCK */
      do {
        if (!xmit_datablock(drv, buff, 0xFC)) break;
        buff += 512;
      } while (--count);
      if (!xmit_datablock(drv, 0, 0xFD))        /* STOP_TRAN token */
        count = 1;
    }
  }
  release_spi(drv);

  return count ? RES_ERROR : RES_OK;
}
#endif /* _FS_READONLY == 0 */



/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/
#define _USE_IOCTL 1    // !!! ???
#if _USE_IOCTL != 0
DRESULT disk_ioctl (
  BYTE drv,                                     /* Physical drive number (0) */
  BYTE ctrl,                                    /* Control code */
  void *buff                                    /* Buffer to send/receive control data */
)
{
  DRESULT res;
  BYTE n, csd[16], *ptr = buff;
  WORD csize;

  if (drv >= _DRIVES) return RES_PARERR;        // Limit to drives available.
//  if (drv) return RES_PARERR;

  res = RES_ERROR;

  if (ctrl == CTRL_POWER) {
    switch (*ptr) {
    case 0:                                     /* Sub control code == 0 (POWER_OFF) */
      if (chk_power(drv))
        power_off(drv);                         /* Power off */
      res = RES_OK;
      break;
    case 1:                                     /* Sub control code == 1 (POWER_ON) */
      power_on(drv);                            /* Power on */
      res = RES_OK;
      break;
    case 2:                                     /* Sub control code == 2 (POWER_GET) */
      *(ptr+1) = (BYTE)chk_power(drv);
      res = RES_OK;
      break;
    default :
      res = RES_PARERR;
    }
  }
  else {
    if (Stat[drv] & STA_NOINIT) return RES_NOTRDY;

    switch (ctrl) {
    case CTRL_SYNC :                            /* Make sure that no pending write process */
      Select(drv);
      if (wait_ready(drv) == 0xFF)
        res = RES_OK;
      break;

    case GET_SECTOR_COUNT :                     /* Get number of sectors on the disk (DWORD) */
      if ((send_cmd(drv, CMD9, 0) == 0) && rcvr_datablock(drv, csd, 16)) {
        if ((csd[0] >> 6) == 1) {               /* SDC version 2.00 */
          csize = csd[9] + ((WORD)csd[8] << 8) + 1;
          *(DWORD*)buff = (DWORD)csize << 10;
        } else {                                /* SDC version 1.XX or MMC*/
          n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
          csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;
          *(DWORD*)buff = (DWORD)csize << (n - 9);
        }
        res = RES_OK;
      }
      break;

    case GET_SECTOR_SIZE :                      /* Get R/W sector size (WORD) */
      *(WORD*)buff = 512;
      res = RES_OK;
      break;

    case GET_BLOCK_SIZE :                       /* Get erase block size in unit of sector (DWORD) */
      if (CardType[drv] & CT_SD2) {             /* SDC version 2.00 */
        if (send_cmd(drv, ACMD13, 0) == 0) {    /* Read SD status */
          rcvr_spi(drv);
          if (rcvr_datablock(drv, csd, 16)) {   /* Read partial block */
            for (n = 64 - 16; n; n--) rcvr_spi(drv); /* Purge trailing data */
            *(DWORD*)buff = 16UL << (csd[10] >> 4);
            res = RES_OK;
          }
        }
      } else {                                  /* SDC version 1.XX or MMC */
        if ((send_cmd(drv, CMD9, 0) == 0) && rcvr_datablock(drv, csd, 16)) {  /* Read CSD */
          if (CardType[drv] & CT_SD1) {         /* SDC version 1.XX */
            *(DWORD*)buff = (((csd[10] & 63) << 1) + ((WORD)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);
          } else {                              /* MMC */
            *(DWORD*)buff = ((WORD)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1);
          }
          res = RES_OK;
        }
      }
      break;

    case MMC_GET_TYPE :                         /* Get card type flags (1 byte) */
      *ptr = CardType[drv];
      res = RES_OK;
      break;

    case MMC_GET_CSD :                          /* Receive CSD as a data block (16 bytes) */
      if (send_cmd(drv, CMD9, 0) == 0           /* READ_CSD */
        && rcvr_datablock(drv, ptr, 16))
        res = RES_OK;
      break;

    case MMC_GET_CID :                          /* Receive CID as a data block (16 bytes) */
      if (send_cmd(drv, CMD10, 0) == 0          /* READ_CID */
        && rcvr_datablock(drv, ptr, 16))
        res = RES_OK;
      break;

    case MMC_GET_OCR :                          /* Receive OCR as an R3 resp (4 bytes) */
      if (send_cmd(drv, CMD58, 0) == 0) {       /* READ_OCR */
        for (n = 4; n; n--) *ptr++ = rcvr_spi(drv);
        res = RES_OK;
      }
      break;

    case MMC_GET_SDSTAT :                       /* Receive SD status as a data block (64 bytes) */
      if (send_cmd(drv, ACMD13, 0) == 0) {      /* SD_STATUS */
        rcvr_spi(drv);
        if (rcvr_datablock(drv, ptr, 64))
          res = RES_OK;
      }
      break;

    default:
      res = RES_PARERR;
    }

    release_spi(drv);
  }

  return res;
}
#endif /* _USE_IOCTL != 0 */

#include "timer.h"                // Added for PIB code.
DWORD get_fattime(void)
{
  tm* dateTime;               // Added for PIB code.
  time_t time;                // Ditto.

  DWORD retval = 0x28210000;
  DWORD temp = 0;

/*
  if(timestamp.second != -1)
  {
    temp = ((DWORD)timestamp.year & 0x0000007F) << 25;
    temp |= (((DWORD)timestamp.month & 0x0000000F) << 21);
    temp |= (((DWORD)timestamp.day & 0x0000001F) << 16);
    temp |= (((DWORD)timestamp.hour & 0x0000001F) << 11);
    temp |= (((DWORD)timestamp.minute & 0x0000003F) << 5);
    temp |= ((((DWORD)timestamp.second / 2) & 0x0000001F) );
    retval = temp;
  }
*/
  time = gettime();
  dateTime = gmtime(&time);
  if(dateTime->tm_sec != -1)
  {
    temp  = ((((DWORD)dateTime->tm_year - 100)  & 0x0000007F) << 25);
    temp |= (((DWORD)dateTime->tm_mon         & 0x0000000F) << 21);
    temp |= (((DWORD)dateTime->tm_mday        & 0x0000001F) << 16);
    temp |= (((DWORD)dateTime->tm_hour        & 0x0000001F) << 11);
    temp |= (((DWORD)dateTime->tm_min         & 0x0000003F) << 5);
    temp |= ((((DWORD)dateTime->tm_sec / 2)     & 0x0000001F) );
    retval = temp;
  }

  return retval;
}
