/* flaneth - flash and ethernet - dartboard mod
 * version 0.1 date 2008-11-09
 * Copyright (C) 2007-2008 Stefan Schuermans <stefan@schuermans.info>
 * 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 "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)

// 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_AT_ONCE (32) // number of bytes to read/write at once
#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

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

// compact flash insertion state
enum CfInsertionState
{
  CfInsNone, // no compact flash inserted
  CfInsNew, // new compact flash has been inserted, wait for powerup
  CfInsReset, // resetting new compact flash
  CfInsWait, // waiting for new compact flash to get ready after reset
  CfInsReady, // compact flash inserted and ready to work with
  CfInsError, // compact flash error occured, compact flash deactivated
} CfInsState = CfInsNone;

// compact flash working state
enum CfWorkingState
{
  CfWorkNone, // no compact flash work to do
  CfWorkIdentify, // identification of compact flash active
} CfWorkState = CfWorkNone;

// compact flash working step
enum CfWorkingStep
{
  CfStepNone, // no step in progress
  CfStepCmdStatus, // checking command status
  CfStepDataTransfer, // transferring (reading/writing) data
  CfStepDataStatus, // checking data status
} CfWorkStep = CfStepNone;

// 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;

// state of active command
unsigned long CfSectorNo = 0; // current sector number of compact flash
unsigned short CfNextOffset = 0; // next offset in sector that is processed (read, written, ...)

// wait for ready counter of compact flash
//  - counts down in 20ms steps
//  - zero if inactive
unsigned char CfWaitReady20 = 0;

// sector buffer
unsigned char CfSectorBuffer[CF_SECTOR_SIZE]; // (extern)

// 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 ) // force inlining by using "extern"
{
  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 ) // force inlining by using "extern"
{
  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 ) // force inlining by using "extern"
{
  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 ) // force inlining by using "extern"
{
  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 ) // force inlining by using "extern"
{
  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 ) // force inlining by using "extern"
{
  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 work done
static void CfDone( void )
{
  debug_cf_printf( "CF done" );

  // set working state and step to none
  CfWorkState = CfWorkNone;
  CfWorkStep = CfStepNone;

  // reset state of active command
  CfSectorNo = 0;
  CfNextOffset = 0;

  // disable wait for ready counter
  CfWaitReady20 = 0;
}

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

  // set insertion state to error
  CfInsState = CfInsError;

  // set working state and step to none
  CfWorkState = CfWorkNone;
  CfWorkStep = CfStepNone;

  // reset all information about CF card
  CfSectorCnt = 0;

  // reset state of active command
  CfSectorNo = 0;
  CfNextOffset = 0;

  // disable wait for ready counter
  CfWaitReady20 = 0;
}

// process compact flash insertion
static void CfProcIns( void )
{
  switch( CfInsState )
  {

    // no compact flash inserted
    case CfInsNone:
      // if compact flash is detected, set state to new CF
      if( CF_IS_DETECT( ) ) {
        CfInsState = CfInsNew;
        debug_cf_printf( "new CF" );
        break;
      }
      break;

    // new compact flash has been inserted, wait for powerup
    case CfInsNew:
      // if compact flash has been removed, set state to none
      if( ! CF_IS_DETECT( ) ) {
        CfInsState = CfInsNone;
        break;
      }
      // set reset
      CF_RESET_ACT( );
      CfInsState = CfInsReset;
      break;

    // resetting new compact flash
    case CfInsReset:
      // take back reset
      CF_RESET_IDLE( );
      // if compact flash has been removed, set state to none
      if( ! CF_IS_DETECT( ) ) {
        CfInsState = CfInsNone;
        break;
      }
      break;

    // waiting for new compact flash to get ready after reset
    case CfInsWait:
      // if compact flash has been removed, set state to none
      if( ! CF_IS_DETECT( ) ) {
        CfInsState = CfInsNone;
        break;
      }
      // if compact flash card is ready, set state to ready
      if( CF_IS_READY( ) ) {
        CfInsState = CfInsReady;
        debug_cf_printf( "CF ready" );
      }
      break;

    // compact flash inserted and ready to work with
    case CfInsReady:
      // do nothing here, card work is done by task procedure
      // (task will detect CF removal or timeout and set state to error)
      break;

    // compact flash error occured, compact flash deactivated
    case CfInsError:
      // if compact flash has been removed, set state to none
      if( ! CF_IS_DETECT( ) ) {
        CfInsState = CfInsNone;
        debug_cf_printf( "CF removed" );
      }
      break;

  } // switch( CfInsState )
}

// detect compact flash timeout
//  - called every 20ms
static void CfDetectTimeout20( void )
{
  // only detect CF timeout if insertion state is ready
  // (otherwise, the insertion processing is still doing some work)
  if( CfInsState != CfInsReady )
    return;

  // wait for ready counter not active
  if( CfWaitReady20 <= 0 )
    return;

  // decrement wait for ready counter
  CfWaitReady20--;

  // timeout occured
  if( CfWaitReady20 <= 0 ) {
    debug_cf_printf( "timeout while waiting for CF ready" );
    CfError( );
  }
}

// continue with identifying compact flash
static void CfProcWorkIdentify( void )
{
  unsigned char status = 0;
  unsigned short len, val16;

  switch( CfWorkStep ) {

    // no step
    case CfStepNone:
      debug_cf_printf( "internal error (identify, CfStepNone)" );
      CfError( );
      break;

    // checking command status
    case CfStepCmdStatus:
      // 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( );
      }
      // continue with data transfer
      CfWorkStep = CfStepDataTransfer;
      CfNextOffset = 0;
      break;

    // transferring data
    case CfStepDataTransfer:
      // still bytes to read
      if( CfNextOffset < CF_BYTES_IDENTIFY ) {
        // get number of bytes to read
        len = CF_BYTES_IDENTIFY - CfNextOffset;
        if( len > CF_BYTES_AT_ONCE )
          len = CF_BYTES_AT_ONCE;
        // read bytes
        if( CfReadRegBuf( CF_REG_DATA, &CfSectorBuffer[CfNextOffset], len ) != 0 ) {
          debug_cf_printf( "reading from CF failed (identify, offset 0x%03X, length 0x%02X)", CfNextOffset, len );
          CfError( );
          break;
        }
        CfNextOffset += len;
      }
      // all bytes read
      if( CfNextOffset >= CF_BYTES_IDENTIFY ) {
        // check identifier
        val16 = (unsigned short)CfSectorBuffer[CF_ID_OFS_ID_16 + 0]
              | (unsigned short)CfSectorBuffer[CF_ID_OFS_ID_16 + 1] << 8;
        if( val16 != CF_ID ) {
          debug_cf_printf( "invalid CF identifier: 0x%04X", val16 );
          CfError( );
          break;
        }
        // check if LBA mode is supported
        val16 = (unsigned short)CfSectorBuffer[CF_ID_OFS_CAPA_16 + 0]
              | (unsigned short)CfSectorBuffer[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( );
          break;
        }
        // get number of sectors on CF
        CfSectorCnt = (unsigned long)CfSectorBuffer[CF_ID_OFS_SEC_CNT_32 + 0]
                    | (unsigned long)CfSectorBuffer[CF_ID_OFS_SEC_CNT_32 + 1] << 8
                    | (unsigned long)CfSectorBuffer[CF_ID_OFS_SEC_CNT_32 + 1] << 16
                    | (unsigned long)CfSectorBuffer[CF_ID_OFS_SEC_CNT_32 + 1] << 24;
        if( CfSectorCnt <= 0 ) {
          debug_cf_printf( "CF does not contain any sectors" );
          CfError( );
          break;
        }
        // done
        CfDone( );
      }
      break;

    // checking data status
    case CfStepDataStatus:
      debug_cf_printf( "internal error (identify, CfStepDataStatus)" );
      CfError( );
      break;

  } // switch( CfWorkStep )
}

// do normal compact flash work
static void CfProcWork( void )
{
  // only do CF processing if insertion state is ready
  // (otherwise, the insertion processing is still doing some work)
  if( CfInsState != CfInsReady )
    return;

  // compact flash has been removed -> error
  if( ! CF_IS_DETECT( ) ) {
    debug_cf_printf( "CF no more present" );
    CfError( );
    return;
  }

  // compact flash is not ready
  if( ! CF_IS_READY( ) ) {
    // wait for ready not active --> error
    if( CfWaitReady20 <= 0 ) {
      debug_cf_printf( "CF no more ready" );
      CfError( );
    }
    // do nothing if CF is not ready
    return;
  }

  switch( CfWorkState ) {

    // no compact flash work to do
    case CfWorkNone:
      // nothing to do here
      break;

    // identification of compact flash active
    case CfWorkIdentify:
      CfProcWorkIdentify( );
      break;

  } // switch( CfWorkState )
}

// identify compact flash
//  - returns 0 on success, 1 if not idle and -1 on error
static char CfIdentify( void )
{
  // check that no command is being worked on
  if( CfWorkState != CfWorkNone )
    return 1;

  debug_cf_printf( "CF identify" );

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

  // now identifying compact flash
  CfWorkState = CfWorkIdentify;
  CfWorkStep = CfStepCmdStatus;
  CfNextOffset = 0;
  CfWaitReady20 = CF_WAIT_READY_20_TIMEOUT;
  return 0;
}

// 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_clear( CF_PORT_nRST, CF_BIT_nRST ); // reset pin to LOW
  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 enable to LOW
  bit_set( CF_DDR_nCE, CF_BIT_nCE ); // card enable pin to output
}

// tick procedure - call every 20ms
void CfTick20( void ) // (extern)
{
  // process compact flash insertion
  CfProcIns( );

  // detect compact flash timeout
  CfDetectTimeout20( );
}

// task function to do the work - call from main loop
void CfTask( void ) // (extern)
{
  // do nothing if no CF is inserted
  if( CfInsState != CfInsReady )
    return;

  // compact flash is ready but not yet identified
  if( CfSectorCnt == 0 ) {
    // identify compact flash
    CfIdentify( );
  }

  // do normal compact flash work
  CfProcWork( );

  // update status
  StatusInfoCfPresent = CfInsState == CfInsReady; // report working CF if insertion state is ready
}

