#include <reset_ioc.h>

#ifndef SOCLIB_IOC

static struct sdcard_dev        _sdcard_device;
static struct spi_dev   *const  _spi_device   = ( struct spi_dev * )IOC_PADDR_BASE;

#endif

#define SDCARD_RESET_ITER_MAX   4

///////////////////////////////////
inline void reset_sleep(int cycles)
{
    int i;
    for (i = 0; i < cycles; i++);
}

#if RESET_DEBUG 
////////////////////////////////////
inline unsigned int reset_proctime()
{
    unsigned int ret;
    asm volatile ("mfc0 %0, $9":"=r" (ret));
    return ret;
}
#endif

#ifndef SOCLIB_IOC
/////////////////////////////////////////////////////////////////////////////////
//     reset_ioc_init
// This function initializes the SDCARD / required for FPGA.
/////////////////////////////////////////////////////////////////////////////////
int reset_ioc_init()
{
    unsigned char sdcard_rsp;

    reset_puts("Initializing block device\n\r");

    /**
     * Initializing the SPI controller
     */
    spi_dev_config (
      _spi_device   ,
      200000        , /**< SPI_clk: 200 Khz */
      SYSCLK_FREQ   , /**< Sys_clk          */
      8             , /**< Charlen: 8       */
      SPI_TX_NEGEDGE,
      SPI_RX_POSEDGE
    );

    /**
     * Initializing the SD Card
     */
    unsigned int iter = 0;
    while(1)
    {
        reset_puts("Trying to initialize SD card... ");

        sdcard_rsp = sdcard_dev_open(&_sdcard_device, _spi_device, 0);
        if (sdcard_rsp == 0)
        {
            reset_puts("OK\n");
            break;
        }

        reset_puts("KO\n");
        reset_sleep(1000);
        if (++iter >= SDCARD_RESET_ITER_MAX)
        {
            reset_puts("\nERROR: During SD card reset to IDLE state\n"
                      "/ card response = ");
            reset_putx(sdcard_rsp);
            reset_puts("\n");
            reset_exit();
        }
    }

    /**
     * Set the block length of the SD Card
     */
    sdcard_rsp = sdcard_dev_set_blocklen(&_sdcard_device, 512);
    if (sdcard_rsp)
    {
        reset_puts("ERROR: During SD card blocklen initialization\n");
        reset_exit();
    }

    /**
     * Incrementing SDCARD clock frequency for normal function
     */
    spi_dev_config (
        _spi_device ,
        10000000    , /**< SPI_clk 10 Mhz */
        SYSCLK_FREQ , /**< Sys_clk        */
        -1          , /**< Charlen: 8     */
        -1          ,
        -1
    );

    reset_puts("Finish block device initialization\n\r");

    return 0;
} // end reset_ioc_init()
#endif 

#ifdef SOCLIB_IOC
/////////////////////////////////////////////////////////////////////////////////////
//      reset_ioc_completed()
// This blocking function checks completion of an I/O transfer and reports errors.
// It returns 0 if the transfer is successfully completed.
// It returns -1 if an error has been reported.
/////////////////////////////////////////////////////////////////////////////////////
int reset_ioc_completed()
{
    unsigned int status = 0;

    unsigned int * ioc_address = ( unsigned int * )IOC_PADDR_BASE;

    while ( 1 )
    {
        status = ioread32(&ioc_address[BLOCK_DEVICE_STATUS]);

        if (( status == BLOCK_DEVICE_READ_SUCCESS ) ||
            ( status == BLOCK_DEVICE_READ_ERROR  ))
        break;
    }

    return status;
} // end reset_ioc_completed()
#endif

#ifdef SOCLIB_IOC
/////////////////////////////////////////////////////////////////////////////////////
//      reset_ioc_read()
// Transfer data the block device to a memory buffer: SOCLIB version
// - param lba    : first block index on the disk
// - param buffer : base address of the memory buffer
// - param count  : number of blocks to be transfered
// This is a blocking function. The function returns once the transfer is completed.
/////////////////////////////////////////////////////////////////////////////////////
int reset_ioc_read( unsigned int lba, 
                    void*        buffer, 
                    unsigned int count )
{

    unsigned int * ioc_address  = (unsigned int*)IOC_PADDR_BASE;

#if RESET_DEBUG 
    unsigned int start_time;
    unsigned int end_time;
    reset_puts("[RESET DEBUG] Reading blocks ");
    reset_putd(lba);
    reset_puts(" to ");
    reset_putd(lba + count - 1);

    start_time = reset_proctime();
#endif

    // block_device configuration
    iowrite32( &ioc_address[BLOCK_DEVICE_BUFFER],
            ( unsigned int ) buffer );

    iowrite32( &ioc_address[BLOCK_DEVICE_COUNT],
            ( unsigned int ) count );

    iowrite32( &ioc_address[BLOCK_DEVICE_LBA],
            ( unsigned int ) lba );

    iowrite32( &ioc_address[BLOCK_DEVICE_IRQ_ENABLE],
            ( unsigned int ) 0 );

    iowrite32( &ioc_address[BLOCK_DEVICE_OP],
            ( unsigned int ) BLOCK_DEVICE_READ );

    reset_ioc_completed();

#if (CACHE_COHERENCE == 0) || (USE_IOB == 1)
    reset_buf_invalidate(buffer, CACHE_LINE_SIZE, count * 512);
#endif

#if USE_IOB 
    reset_mcc_invalidate(buffer, count * 512);
#endif

#if RESET_DEBUG 
    end_time = reset_proctime();
    reset_puts(" / cycles for transfert: ");
    reset_putd(end_time - start_time);
    reset_puts("\n");
#endif

    return 0;
} // end reset_ioc_read()

#else

/////////////////////////////////////////////////////////////////////////////////////
//      reset_ioc_read()
// Transfer data the block device to a memory buffer: FPGA version
// - param lba    : first block index on the disk
// - param buffer : base address of the memory buffer
// - param count  : number of blocks to be transfered
// This is a blocking function. The function returns once the transfer is completed.
/////////////////////////////////////////////////////////////////////////////////////
int reset_ioc_read( unsigned int lba, 
                    void*        buffer, 
                    unsigned int count )
{
    unsigned int sdcard_rsp;
    unsigned int i;

    sdcard_dev_lseek(&_sdcard_device, lba);

#if RESET_DEBUG 
    unsigned int start_time;
    unsigned int end_time;
    reset_puts("[RESET DEBUG] Reading blocks ");
    reset_putd(lba);
    reset_puts(" to ");
    reset_putd(lba + count - 1);
    start_time = reset_proctime();
#endif

    for(i = 0; i < count; i++)
    {
        if (( sdcard_rsp = sdcard_dev_read (
                        &_sdcard_device,
                        (unsigned char *) buffer + (512 * i),
                        512
                        )
            ))
        {
            reset_puts("ERROR during read on the SDCARD device. Code: ");
            reset_putx(sdcard_rsp);
            reset_puts("\n\r");

            return 1;
        }
    }

#if RESET_DEBUG
    end_time = reset_proctime();
    reset_puts(" / cycles for transfert: ");
    reset_putd(end_time - start_time);
    reset_puts("\n");
#endif

    return 0;
} // end reset_ioc_read()
#endif

//////////////////////////////////////////////////////////////////////////////
// reset_dcache_buf_invalidate()
// Invalidate all data cache lines corresponding to a memory buffer
// (identified by an address and a size) in L1 cache.
/////////////////////////////////////////////////////////////////////////////
#if (CACHE_COHERENCE == 0) || (USE_IOB == 1)
void reset_buf_invalidate ( const void * buffer,
                            unsigned int line_size,
                            unsigned int size)
{
    unsigned int i;

    // iterate on cache lines
    for (i = 0; i <= size; i += line_size) 
    {
        asm volatile(
            " cache %0, %1"
            :// no outputs
            :"i" (0x11), "R" (*((unsigned char *) buffer + i))
            );
    }
}
#endif

//////////////////////////////////////////////////////////////////////////////
// reset_mcc_inval()
// Invalidate all data cache lines corresponding to a memory buffer
// (identified by an address and a size) in L2 cache.
/////////////////////////////////////////////////////////////////////////////
#if USE_IOB 
void reset_mcc_invalidate ( const void * buffer,
                            unsigned int size)
{
    unsigned int * mcc_address = (unsigned int *)MCC_PADDR_BASE;

    // get the hard lock assuring exclusive access to MEMC
    while (ioread32(&mcc_address[MCC_LOCK]));

    // write invalidate paremeters on the memory cache
    // this preloader use only the cluster 0 and then the HI bits are not used
    
    iowrite32(&mcc_address[MCC_ADDR_LO], (unsigned int) buffer);
    iowrite32(&mcc_address[MCC_ADDR_HI], (unsigned int) 0);
    iowrite32(&mcc_address[MCC_LENGTH] , (unsigned int) size);
    iowrite32(&mcc_address[MCC_CMD]    , (unsigned int) MCC_CMD_INVAL);

    // release the lock protecting MEMC
    iowrite32(&mcc_address[MCC_LOCK], (unsigned int) 0);
}
#endif

/*
 * vim: tabstop=4 : shiftwidth=4 : expandtab
 */
