/*
    ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010,
                 2011,2012,2013 Giovanni Di Sirio.
    This file is part of ChibiOS/RT.
    ChibiOS/RT is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    ChibiOS/RT is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see .
*/
#include 
#include "ch.h"
#include "hal.h"
#include "shell.h"
#include "chprintf.h"
#include "ff.h"
#define SDC_DATA_DESTRUCTIVE_TEST   FALSE
#define SDC_BURST_SIZE      8 /* how many sectors reads at once */
static uint8_t outbuf[MMCSD_BLOCK_SIZE * SDC_BURST_SIZE + 1];
static uint8_t  inbuf[MMCSD_BLOCK_SIZE * SDC_BURST_SIZE + 1];
/* FS object.*/
static FATFS SDC_FS;
/* FS mounted and ready.*/
static bool_t fs_ready = FALSE;
/**
 * @brief   Parody of UNIX badblocks program.
 *
 * @param[in] start       first block to check
 * @param[in] end         last block to check
 * @param[in] blockatonce number of blocks to check at once
 * @param[in] pattern     check pattern
 *
 * @return              The operation status.
 * @retval SDC_SUCCESS  operation succeeded, the requested blocks have been
 *                      read.
 * @retval SDC_FAILED   operation failed, the state of the buffer is uncertain.
 */
bool_t badblocks(uint32_t start, uint32_t end, uint32_t blockatonce, uint8_t pattern){
  uint32_t position = 0;
  uint32_t i = 0;
  chDbgCheck(blockatonce <= SDC_BURST_SIZE, "badblocks");
  /* fill control buffer */
  for (i=0; i < MMCSD_BLOCK_SIZE * blockatonce; i++)
    outbuf[i] = pattern;
  /* fill SD card with pattern. */
  position = start;
  while (position < end){
    if (sdcWrite(&SDCD1, position, outbuf, blockatonce))
      goto ERROR;
    position += blockatonce;
  }
  /* read and compare. */
  position = start;
  while (position < end){
    if (sdcRead(&SDCD1, position, inbuf, blockatonce))
      goto ERROR;
    if (memcmp(inbuf, outbuf, blockatonce * MMCSD_BLOCK_SIZE) != 0)
      goto ERROR;
    position += blockatonce;
  }
  return FALSE;
ERROR:
  return TRUE;
}
/**
 *
 */
void fillbuffer(uint8_t pattern, uint8_t *b){
  uint32_t i = 0;
  for (i=0; i < MMCSD_BLOCK_SIZE * SDC_BURST_SIZE; i++)
    b[i] = pattern;
}
/**
 *
 */
void fillbuffers(uint8_t pattern){
  fillbuffer(pattern, inbuf);
  fillbuffer(pattern, outbuf);
}
/**
 *
 */
bool_t sdc_lld_is_write_protected(SDCDriver *sdcp) {
  (void)sdcp;
  return FALSE;
}
/**
 *
 */
bool_t sdc_lld_is_card_inserted(SDCDriver *sdcp) {
  (void)sdcp;
  return !palReadPad(GPIOE, GPIOE_SDIO_DETECT);
}
/**
 *
 */
void cmd_sdiotest(BaseSequentialStream *chp, int argc, char *argv[]){
  (void)argc;
  (void)argv;
  uint32_t i = 0;
  chprintf(chp, "Trying to connect SDIO... ");
  chThdSleepMilliseconds(100);
  if (!sdcConnect(&SDCD1)) {
    chprintf(chp, "OK\r\n");
    chprintf(chp, "*** Card CSD content is: ");
    chprintf(chp, "%X %X %X %X \r\n", (&SDCD1)->csd[3], (&SDCD1)->csd[2],
                                      (&SDCD1)->csd[1], (&SDCD1)->csd[0]);
    chprintf(chp, "Single aligned read...");
    chThdSleepMilliseconds(100);
    if (sdcRead(&SDCD1, 0, inbuf, 1))
      chSysHalt();
    chprintf(chp, " OK\r\n");
    chThdSleepMilliseconds(100);
    chprintf(chp, "Single unaligned read...");
    chThdSleepMilliseconds(100);
    if (sdcRead(&SDCD1, 0, inbuf + 1, 1))
      chSysHalt();
    if (sdcRead(&SDCD1, 0, inbuf + 2, 1))
      chSysHalt();
    if (sdcRead(&SDCD1, 0, inbuf + 3, 1))
      chSysHalt();
    chprintf(chp, " OK\r\n");
    chThdSleepMilliseconds(100);
    chprintf(chp, "Multiple aligned reads...");
    chThdSleepMilliseconds(100);
    fillbuffers(0x55);
    /* fill reference buffer from SD card */
    if (sdcRead(&SDCD1, 0, inbuf, SDC_BURST_SIZE))
      chSysHalt();
    for (i=0; i<1000; i++){
      if (sdcRead(&SDCD1, 0, outbuf, SDC_BURST_SIZE))
        chSysHalt();
      if (memcmp(inbuf, outbuf, SDC_BURST_SIZE * MMCSD_BLOCK_SIZE) != 0)
        chSysHalt();
    }
    chprintf(chp, " OK\r\n");
    chThdSleepMilliseconds(100);
    chprintf(chp, "Multiple unaligned reads...");
    chThdSleepMilliseconds(100);
    fillbuffers(0x55);
    /* fill reference buffer from SD card */
    if (sdcRead(&SDCD1, 0, inbuf + 1, SDC_BURST_SIZE))
      chSysHalt();
    for (i=0; i<1000; i++){
      if (sdcRead(&SDCD1, 0, outbuf + 1, SDC_BURST_SIZE))
        chSysHalt();
      if (memcmp(inbuf, outbuf, SDC_BURST_SIZE * MMCSD_BLOCK_SIZE) != 0)
        chSysHalt();
    }
    chprintf(chp, " OK\r\n");
    chThdSleepMilliseconds(100);
#if SDC_DATA_DESTRUCTIVE_TEST
    chprintf(chp, "Single aligned write...");
    chThdSleepMilliseconds(100);
    fillbuffer(0xAA, inbuf);
    if (sdcWrite(&SDCD1, 0, inbuf, 1))
      chSysHalt();
    fillbuffer(0, outbuf);
    if (sdcRead(&SDCD1, 0, outbuf, 1))
      chSysHalt();
    if (memcmp(inbuf, outbuf, MMCSD_BLOCK_SIZE) != 0)
      chSysHalt();
    chprintf(chp, " OK\r\n");
    chprintf(chp, "Single unaligned write...");
    chThdSleepMilliseconds(100);
    fillbuffer(0xFF, inbuf);
    if (sdcWrite(&SDCD1, 0, inbuf+1, 1))
      chSysHalt();
    fillbuffer(0, outbuf);
    if (sdcRead(&SDCD1, 0, outbuf+1, 1))
      chSysHalt();
    if (memcmp(inbuf+1, outbuf+1, MMCSD_BLOCK_SIZE) != 0)
      chSysHalt();
    chprintf(chp, " OK\r\n");
    chprintf(chp, "Running badblocks at 0x10000 offset...");
    chThdSleepMilliseconds(100);
    if(badblocks(0x10000, 0x11000, SDC_BURST_SIZE, 0xAA))
      chSysHalt();
    chprintf(chp, " OK\r\n");
#endif /* !SDC_DATA_DESTRUCTIVE_TEST */
    /**
     * Now perform some FS tests.
     */
    FRESULT err;
    uint32_t clusters;
    FATFS *fsp;
    FIL FileObject;
    uint32_t bytes_written;
    uint32_t bytes_read;
    FILINFO filinfo;
    uint8_t teststring[] = {"This is test file\r\n"};
    chprintf(chp, "Register working area for filesystem... ");
    chThdSleepMilliseconds(100);
    err = f_mount(0, &SDC_FS);
    if (err != FR_OK){
      chSysHalt();
    }
    else{
      fs_ready = TRUE;
      chprintf(chp, "OK\r\n");
    }
#if SDC_DATA_DESTRUCTIVE_TEST
    chprintf(chp, "Formatting... ");
    chThdSleepMilliseconds(100);
    err = f_mkfs (0,0,0);
    if (err != FR_OK){
      chSysHalt();
    }
    else{
      chprintf(chp, "OK\r\n");
    }
#endif /* SDC_DATA_DESTRUCTIVE_TEST */
    chprintf(chp, "Mount filesystem... ");
    chThdSleepMilliseconds(100);
    err = f_getfree("/", &clusters, &fsp);
    if (err != FR_OK) {
      chSysHalt();
    }
    chprintf(chp, "OK\r\n");
    chprintf(chp,
             "FS: %lu free clusters, %lu sectors per cluster, %lu bytes free\r\n",
             clusters, (uint32_t)SDC_FS.csize,
             clusters * (uint32_t)SDC_FS.csize * (uint32_t)MMCSD_BLOCK_SIZE);
    chprintf(chp, "Create file \"chtest.txt\"... ");
    chThdSleepMilliseconds(100);
    err = f_open(&FileObject, "0:chtest.txt", FA_WRITE | FA_OPEN_ALWAYS);
    if (err != FR_OK) {
      chSysHalt();
    }
    chprintf(chp, "OK\r\n");
    chprintf(chp, "Write some data in it... ");
    chThdSleepMilliseconds(100);
    err = f_write(&FileObject, teststring, sizeof(teststring), (void *)&bytes_written);
    if (err != FR_OK) {
      chSysHalt();
    }
    else
      chprintf(chp, "OK\r\n");
    chprintf(chp, "Close file \"chtest.txt\"... ");
    err = f_close(&FileObject);
    if (err != FR_OK) {
      chSysHalt();
    }
    else
      chprintf(chp, "OK\r\n");
    chprintf(chp, "Check file size \"chtest.txt\"... ");
    err = f_stat("0:chtest.txt", &filinfo);
    chThdSleepMilliseconds(100);
    if (err != FR_OK) {
      chSysHalt();
    }
    else{
      if (filinfo.fsize == sizeof(teststring))
        chprintf(chp, "OK\r\n");
      else
        chSysHalt();
    }
    chprintf(chp, "Check file content \"chtest.txt\"... ");
    err = f_open(&FileObject, "0:chtest.txt", FA_READ | FA_OPEN_EXISTING);
    chThdSleepMilliseconds(100);
    if (err != FR_OK) {
      chSysHalt();
    }
    uint8_t buf[sizeof(teststring)];
    err = f_read(&FileObject, buf, sizeof(teststring), (void *)&bytes_read);
    if (err != FR_OK) {
      chSysHalt();
    }
    else{
      if (memcmp(teststring, buf, sizeof(teststring)) != 0){
        chSysHalt();
      }
      else{
        chprintf(chp, "OK\r\n");
      }
    }
    chprintf(chp, "Umount filesystem... ");
    f_mount(0, NULL);
    chprintf(chp, "OK\r\n");
    chprintf(chp, "Disconnecting from SDIO...");
    chThdSleepMilliseconds(100);
    if (sdcDisconnect(&SDCD1))
      chSysHalt();
    chprintf(chp, " OK\r\n");
    chprintf(chp, "------------------------------------------------------\r\n");
    chprintf(chp, "All tests passed successfully.\r\n");
    chThdSleepMilliseconds(100);
  }
  else{
    chSysHalt();
  }
}
/*
 * SDIO configuration.
 */
static const SDCConfig sdccfg = {
  0
};
/**
 *
 */
static SerialConfig ser_cfg = {
    115200,
    0,
    0,
    0,
};
static const ShellCommand commands[] = {
  {"sdiotest", cmd_sdiotest},
  {NULL, NULL}
};
static const ShellConfig shell_cfg1 = {
  (BaseSequentialStream *)&SD2,
  commands
};
/*
 * Application entry point.
 */
int main(void) {
  halInit();
  chSysInit();
  /* start debugging serial link */
  sdStart(&SD2, &ser_cfg);
  shellInit();
  static WORKING_AREA(waShell, 2048);
  shellCreateStatic(&shell_cfg1, waShell, sizeof(waShell), NORMALPRIO);
  /*
   * Initializes the SDIO drivers.
   */
  sdcStart(&SDCD1, &sdccfg);
  /*
   * Normal main() thread activity.
   * Blinking signaling about successful passing.
   */
  while (TRUE) {
    palTogglePad(GPIOB, GPIOB_LED_R);
    chThdSleepMilliseconds(100);
  }
}