/* flaneth - flash and ethernet
   Copyright (C) 2007-2012 Stefan Schuermans <stefan@blinkenarea.org>
   Copyleft: GNU public license V2 - http://www.gnu.org/copyleft/gpl.html
   a BlinkenArea project - http://www.blinkenarea.org/ */

#include <avr/io.h>

#include "bus.h"
#include "cf.h"
#include "config.h"
#include "debug.h"
#include "macros.h"
#include "status.h"
#include "tasks.h"
#include "timing.h"

// IO pins of compact flash
#define CF_DDR_nCD (DDRB)
#define CF_PORT_nCD (PORTB)
#define CF_PIN_nCD (PINB)
#define CF_BIT_nCD (4)
#define CF_DDR_nRST (DDRB)
#define CF_PORT_nRST (PORTB)
#define CF_BIT_nRST (5)
#define CF_DDR_RDY (DDRB)
#define CF_PORT_RDY (PORTB)
#define CF_PIN_RDY (PINB)
#define CF_BIT_RDY (7)
#define CF_DDR_nCE (DDRB)
#define CF_PORT_nCE (PORTB)
#define CF_BIT_nCE (6)
// special pin commands
#define CF_IS_DETECT() (is_bit_clear(CF_PIN_nCD, CF_BIT_nCD))
#define CF_RESET_ACT() (bit_clear(CF_PORT_nRST, CF_BIT_nRST))
#define CF_RESET_IDLE() (bit_set(CF_PORT_nRST, CF_BIT_nRST))
#define CF_IS_READY() (is_bit_set(CF_PIN_RDY, CF_BIT_RDY))
#define CF_CE_ACT() (bit_clear(CF_PORT_nCE, CF_BIT_nCE))
#define CF_CE_IDLE() (bit_set(CF_PORT_nCE, CF_BIT_nCE))

// compact flash registers
#define CF_REG_DATA (0x00)
#define CF_REG_ERR (0x01)
#define CF_REG_SEC_CNT (0x02)
#define CF_REG_SEC_NO (0x03)
#define CF_REG_CYL_L (0x04)
#define CF_REG_CYL_H (0x05)
#define CF_REG_HEAD (0x06)
#define CF_REG_STATUS (0x07)
#define CF_REG_CMD (0x07)

// compact flash status bits
#define CF_SB_BUSY (7)
#define CF_SB_RDY (6)
#define CF_SB_DWF (5)
#define CF_SB_DSC (4)
#define CF_SB_DRQ (3)
#define CF_SB_CORR (2)
#define CF_SB_IDX (1)
#define CF_SB_ERR (0)

// time to give CF to power up (in 20ms steps)
#define CF_POWERUP_TIME (5) // 100ms

// time to give CF to set outputs after reset (in 20ms steps)
#define CF_POST_RESET_TIME (2) // 40ms

// timeout value for wait for ready after reset counter (in 20ms steps)
#define CF_RESET_READY_TIMEOUT (50)    // 1s

// timeout value for wait for ready counter (in 20ms steps)
#define CF_READY_TIMEOUT (4)    // 80ms

// compact flash commands
#define CF_CMD_IDENTIFY (0xEC)
#define CF_CMD_READ_SEC (0x20)
#define CF_CMD_WRITE_SEC (0x30)

// some number of bytes
#define CF_BYTES_IDENTIFY (124) // number of bytes to read for identify

// interesting locations in identify data
#define CF_ID_OFS_ID_16 (0)     // CF identifier, 16 bit
#define CF_ID_OFS_CAPA_16 (98)  // CF capabilities, 16 bit
#define CF_ID_OFS_SEC_CNT_32 (120)      // number of sectors on CF (in LBA
                                        // mode), 32 bit

// various CF constants
#define CF_ID (0x848A)  // identifier of compact flash cards
#define CF_CAPA_BIT_LBA (9)     // bit number of the LBA bit in the CF
                                // capabilities

// compact flash error state (0 for no error, 1 for error)
char CfErrorState = 1;

// tick/timeout counter of compact flash
// - counts down in 20ms steps, stops at zero
unsigned char CfTickCnt = 0;

// number of sectors on compact flash card
// - zero if no CF card is present, CF card has not yet been identified or CF 
// card error occured
unsigned long CfSectorCnt = 0;

// write a compact flash register (returns 1 if successful or 0 on error)
// - returns 0 on success and -1 on error
extern inline char CfWriteReg(unsigned char reg, unsigned char val)
{
  if (!CF_IS_DETECT() || !CF_IS_READY())        // check that card is present 
                                                // and ready
    return -1;  // error
  BUS_DATA = val;       // output value
  BUS_DATA_DDR = 0xFF;  // data port to output
  BUS_ADDR = reg;       // output address
  CF_CE_ACT();  // set card enable
  BUS_WRITE_ACT();      // activate write signal
  nop();
  nop();
  nop();
  nop();
  nop();
  nop();
  BUS_WRITE_IDLE();     // take back write signal
  CF_CE_IDLE(); // take back card enable
  BUS_DATA_DDR = 0x00;  // data back port to input
  BUS_DATA = 0x00;      // turn off pullups
  return 0;     // success
}

// write buffer to compact flash register
// (returns 1 if successful or 0 on error)
// - returns 0 on success and -1 on error
extern inline char CfWriteRegBuf(unsigned char reg, unsigned char *p_buf,
                                 unsigned short len)
{
  if (!CF_IS_DETECT() || !CF_IS_READY())        // check that card is present 
                                                // and ready
    return -1;  // error
  BUS_DATA = *p_buf;    // output first value to initialize port status
                        // before switching to output
  BUS_DATA_DDR = 0xFF;  // data port to output
  BUS_ADDR = reg;       // output address
  CF_CE_ACT();  // set card enable
  for (; len > 0; p_buf++, len--) {
    if (!CF_IS_DETECT() || !CF_IS_READY())      // check that card is present 
                                                // and ready
      break;
    BUS_DATA = *p_buf;  // output value
    BUS_WRITE_ACT();    // activate write signal
    nop();
    nop();
    nop();
    nop();
    nop();
    nop();
    BUS_WRITE_IDLE();   // take back write signal
  }
  CF_CE_IDLE(); // take back card enable
  BUS_DATA_DDR = 0x00;  // data back port to input
  BUS_DATA = 0x00;      // turn off pullups
  return len <= 0 ? 0 : -1;     // success if everything has been written
}

// write constant value to compact flash register multiple times (returns 1
// if successful or 0 on error)
// - returns 0 on success and -1 on error
extern inline char CfWriteRegConst(unsigned char reg, unsigned char val, unsigned short cnt)
{
  if (!CF_IS_DETECT() || !CF_IS_READY())        // check that card is present 
                                                // and ready
    return -1;  // error
  BUS_DATA = val;       // output value
  BUS_DATA_DDR = 0xFF;  // data port to output
  BUS_ADDR = reg;       // output address
  CF_CE_ACT();  // set card enable
  for (; cnt > 0; cnt--) {
    if (!CF_IS_DETECT() || !CF_IS_READY())      // check that card is present 
                                                // and ready
      break;
    BUS_WRITE_ACT();    // activate write signal
    nop();
    nop();
    nop();
    nop();
    nop();
    nop();
    BUS_WRITE_IDLE();   // take back write signal
  }
  CF_CE_IDLE(); // take back card enable
  BUS_DATA_DDR = 0x00;  // data back port to input
  BUS_DATA = 0x00;      // turn off pullups
  return cnt <= 0 ? 0 : -1;     // success if everything has been written
}

// read a compact flash register (returns 1 if successful or 0 on error)
// - returns 0 on success and -1 on error
extern inline char CfReadReg(unsigned char reg, unsigned char *p_var)
{
  if (!CF_IS_DETECT() || !CF_IS_READY())        // check that card is present 
                                                // and ready
    return -1;  // error
  BUS_ADDR = reg;       // output address
  CF_CE_ACT();  // set card enable
  BUS_READ_ACT();       // activate read signal
  nop();
  nop();
  nop();
  nop();
  nop();
  nop();
  *p_var = BUS_DATA_IN; // read data
  BUS_READ_IDLE();      // take back read signal
  CF_CE_IDLE(); // take back card enable
  return 0;     // success
}

// read buffer from a compact flash register (returns 1 if successful or 0 on 
// error)
// - returns 0 on success and -1 on error
extern inline char CfReadRegBuf(unsigned char reg, unsigned char *p_buf, unsigned short len)
{
  if (!CF_IS_DETECT() || !CF_IS_READY())        // check that card is present 
                                                // and ready
    return -1;  // error
  BUS_ADDR = reg;       // output address
  CF_CE_ACT();  // set card enable
  for (; len > 0; p_buf++, len--) {
    if (!CF_IS_DETECT() || !CF_IS_READY())      // check that card is present 
                                                // and ready
      break;
    BUS_READ_ACT();     // activate read signal
    nop();
    nop();
    nop();
    nop();
    nop();
    nop();
    *p_buf = BUS_DATA_IN;       // read data
    BUS_READ_IDLE();    // take back read signal
  }
  CF_CE_IDLE(); // take back card enable
  return len <= 0 ? 0 : -1;     // success if everything has been read
}

// read a compact flash register multiple times and throw away data (returns
// 1 if successful or 0 on error)
// - returns 0 on success and -1 on error
extern inline char CfReadRegMulti(unsigned char reg, unsigned short cnt)
{
  if (!CF_IS_DETECT() || !CF_IS_READY())        // check that card is present 
                                                // and ready
    return -1;  // error
  BUS_ADDR = reg;       // output address
  CF_CE_ACT();  // set card enable
  for (; cnt > 0; cnt--) {
    if (!CF_IS_DETECT() || !CF_IS_READY())      // check that card is present 
                                                // and ready
      break;
    BUS_READ_ACT();     // activate read signal
    nop();
    nop();
    nop();
    nop();
    nop();
    nop();
    BUS_READ_IDLE();    // take back read signal
  }
  CF_CE_IDLE(); // take back card enable
  return cnt <= 0 ? 0 : -1;     // success if everything has been read
}

// compact flash error occured
static void CfError(void)
{
  debug_cf_printf("CF error");

  CfErrorState = 1;
  CfTickCnt = 0;

  CfSectorCnt = 0;

  // report no working CF present
  StatusInfoCfPresent = 0;
}

// compact flash is gone
static void CfGone(void)
{
  debug_cf_printf("CF gone");

  CfError();
}

// compact flash timeout
static void CfTimeout(void)
{
  debug_cf_printf("CF timeout");

  CfError();
}

// initialize
void CfInit(void)       // (extern)
{
  // setup ports
  bit_clear(CF_PORT_nCD, CF_BIT_nCD);   // pull-up of card detect pin off
                                        // (external pull-up is present)
  bit_clear(CF_DDR_nCD, CF_BIT_nCD);    // card detect pin to input
  bit_set(CF_PORT_nRST, CF_BIT_nRST);   // reset not active
  bit_set(CF_DDR_nRST, CF_BIT_nRST);    // reset pin to output
  bit_clear(CF_PORT_RDY, CF_BIT_RDY);   // pull-up of ready pin pin off
                                        // (external pull-up is present)
  bit_clear(CF_DDR_RDY, CF_BIT_RDY);    // ready pin to input
  bit_set(CF_PORT_nCE, CF_BIT_nCE);     // card not enabled
  bit_set(CF_DDR_nCE, CF_BIT_nCE);      // card enable pin to output
}

// tick procedure - call every 20ms
void CfTick20(void)     // (extern)
{
  // count down ticks
  if (CfTickCnt > 0)
    --CfTickCnt;
}

/**
 * @brief wait for CF to become ready
 * @param[in] timeout timeout in 20ms steps
 * @return 0 if CF ready, -1 if CF gone or timeout
 */
static int CfWaitReady(unsigned char timeout)
{
  CfTickCnt = timeout;

  while (!CF_IS_READY()) {

    // card gone -> error
    if (!CF_IS_DETECT()) {
      CfGone();
      return -1;
    }

    // timeout -> error
    if (CfTickCnt <= 0) {
      CfTimeout();
      return -1;
    }

    // execute other tasks while waiting
    Tasks();

  } // while (!CF_IS_READY())

  return 0;
}

/**
 * @brief read bytes from CF card
 * @param[in] ptr pointer to data buffer
 * @param[in] sz size of data to read
 * @return 0 if reading was successful, -1 if CF gon, timeout or error
 */
static int CfReadBytes(unsigned char *ptr, unsigned int sz)
{
  // read bytes
  if (CfReadRegBuf(CF_REG_DATA, ptr, sz) != 0) {
    debug_cf_printf("reading from CF failed (offset length 0x%03X)", sz);
    CfError();
    return -1;
  }

  return 0;
}

/**
 * @brief check if CF card is present
 * @return 1 if CF card is present, 0 if not
 */
char CfIsPresent(void) // (extern)
{
  if (CF_IS_DETECT())
    return 1;
  else
    return 0;
}

/**
 * @brief reset CF card
 * @return 0 if CF card is present and could be reset, -1 if not
 */
char CfReset(void) // (extern)
{
  // wait a little bit
  if (!CF_IS_DETECT()) {
    CfGone();
    return -1;
  }
  CfTickCnt = CF_POWERUP_TIME;
  while (CfTickCnt > 0)
    Tasks();

  // set reset, wait, take back reset
  if (!CF_IS_DETECT()) {
    CfGone();
    return -1;
  }
  CF_RESET_ACT();
  nop();
  nop();
  nop();
  CF_RESET_IDLE();
  debug_cf_printf("CF reset");

  // wait a little bit
  if (!CF_IS_DETECT()) {
    CfGone();
    return -1;
  }
  CfTickCnt = CF_POST_RESET_TIME;
  while (CfTickCnt > 0)
    Tasks();

  // wait for CF to become ready
  if (CfWaitReady(CF_RESET_READY_TIMEOUT) != 0)
    return -1;

  // report working CF present
  CfErrorState = 0;
  StatusInfoCfPresent = 1;
  debug_cf_printf("CF ready");

  return 0;
}

/**
 * @brief identify CF card
 * @param[out] sectors number of sectors on CF card
 * @return 0 if CF card could be identified, -1 if not
 */
char CfIdentify(unsigned long *sectors) // (extern)
{
  unsigned char status, identifyBuf[CF_BYTES_IDENTIFY];
  unsigned short val16;

  debug_cf_printf("CF identify");
  if (CfErrorState) {
    debug_cf_printf("CF in error state");
    return -1;
  }

  // issue identify drive command
  if (CfWriteReg(CF_REG_CMD, CF_CMD_IDENTIFY) != 0) {
    CfError();
    return -1;
  }
  if (CfWaitReady(CF_READY_TIMEOUT) != 0)
    return -1;

  // read status register
  CfReadReg(CF_REG_STATUS, &status);
  // check that BUSY=0, RDY=1, DWF=0, DSC=1, IDX=0, ERR=0
  if ((status &
       (1 << CF_SB_BUSY | 1 << CF_SB_RDY | 1 << CF_SB_DWF | 1 << CF_SB_DSC |
        1 << CF_SB_IDX | 1 << CF_SB_ERR)) !=
      (1 << CF_SB_RDY | 1 << CF_SB_DSC)) {
    debug_cf_printf("unexpected status 0x%02X (identify)", status);
    CfError();
    return -1;
  }

  // read identify bytes
  if (CfReadBytes(identifyBuf, sizeof(identifyBuf)) != 0)
    return -1;

  // check identifier
  val16 = (unsigned short)identifyBuf[CF_ID_OFS_ID_16 + 0] |
          (unsigned short)identifyBuf[CF_ID_OFS_ID_16 + 1] << 8;
  if (val16 != CF_ID) {
    debug_cf_printf("invalid CF identifier: 0x%04X", val16);
    CfError();
    return -1;
  }

  // check if LBA mode is supported
  val16 = (unsigned short)identifyBuf[CF_ID_OFS_CAPA_16 + 0]
      | (unsigned short)identifyBuf[CF_ID_OFS_CAPA_16 + 1] << 8;
  if ((val16 & 1 << CF_CAPA_BIT_LBA) == 0) {
    debug_cf_printf("CF does not support LBA mode");
    CfError();
    return -1;
  }
  // get number of sectors on CF
  CfSectorCnt = (unsigned long)identifyBuf[CF_ID_OFS_SEC_CNT_32 + 0]
      | (unsigned long)identifyBuf[CF_ID_OFS_SEC_CNT_32 + 1] << 8
      | (unsigned long)identifyBuf[CF_ID_OFS_SEC_CNT_32 + 2] << 16
      | (unsigned long)identifyBuf[CF_ID_OFS_SEC_CNT_32 + 3] << 24;
  if (CfSectorCnt <= 0) {
    debug_cf_printf("CF does not contain any sectors");
    CfError();
    return -1;
  }

  debug_cf_printf("CF done (%lu sectors)", CfSectorCnt);
  *sectors = CfSectorCnt;

  return 0;
}

/**
 * @brief read from CF card
 * @param[in] sector number of sector to read
 * @param[out] data data read from sector
 * @return 0 if sector could be read, -1 if not
 */
char CfRead(unsigned long sector, unsigned char data[CF_SECTOR_SIZE]) // (extern)
{
  unsigned char status;

  debug_cf_printf("CF read (sector %lu)", sector);
  if (CfErrorState) {
    debug_cf_printf("CF in error state");
    return -1;
  }
  if (sector >= CfSectorCnt) {
    debug_cf_printf("sector does not exist");
    return -1;
  }

  // set sector number (LBA mode) and sector count (1)
  if (CfWriteReg(CF_REG_HEAD, 0xE0 | (sector >> 24 & 0x0F)) != 0 ||
      CfWriteReg(CF_REG_CYL_H, sector >> 16) != 0 ||
      CfWriteReg(CF_REG_CYL_L, sector >> 8) != 0 ||
      CfWriteReg(CF_REG_SEC_NO, sector) != 0 ||
      CfWriteReg(CF_REG_SEC_CNT, 1) != 0) {
    CfError();
    return -1;
  }

  // issue read sector command
  if (CfWriteReg(CF_REG_CMD, CF_CMD_READ_SEC) != 0) {
    CfError();
    return -1;
  }
  if (CfWaitReady(CF_READY_TIMEOUT) != 0)
    return -1;

  // read status register
  CfReadReg(CF_REG_STATUS, &status);
  // check that BUSY=0, RDY=1, DWF=0, DSC=1, IDX=0, ERR=0
  if ((status &
       (1 << CF_SB_BUSY | 1 << CF_SB_RDY | 1 << CF_SB_DWF | 1 << CF_SB_DSC |
        1 << CF_SB_IDX | 1 << CF_SB_ERR)) !=
      (1 << CF_SB_RDY | 1 << CF_SB_DSC)) {
    debug_cf_printf("unexpected status 0x%02X (identify)", status);
    CfError();
    return -1;
  }

  // read sector bytes
  if (CfReadBytes(data, CF_SECTOR_SIZE) != 0)
    return -1;

  return 0;
}

