#include <ioc.h>
#include <defs.h>

#define in_reset        __attribute__((section (".reset")))
#define in_reset_data   __attribute__((section (".reset_data")))
#define in_unckdata     __attribute__((section (".unckdata")))

in_unckdata   int volatile _ioc_lock    = 0;
in_reset_data int volatile _ioc_done    = 0;
in_reset_data int volatile _ioc_status;

#ifndef SOCLIB_IOC
/*in_reset_data volatile unsigned int _ioc_init_ok = 0;*/

static in_reset_data struct sdcard_dev  _sdcard_device;
static in_reset_data struct spi_dev   * _spi_device   = ( struct spi_dev * )IOC_BASE;
#endif

//////////////////////////////////////////////////////////////////////////////////////////
//  I/O BLOCK_DEVICE
// The three functions below use the three variables _ioc_lock _ioc_done, 
// and _ioc_status for synchronsation.
// - As the IOC component can be used by several programs running in parallel,
// the _ioc_lock variable guaranties exclusive access to the device.
// The _ioc_read() and _ioc_write() functions use atomic LL/SC to get the lock.
// and set _ioc_lock to a non zero value. 
// The _ioc_write() and _ioc_read() functions are blocking, polling the _ioc_lock
// variable until the device is available.
// - When the tranfer is completed, the ISR routine activated by the IOC IRQ
// set the _ioc_done variable to a non-zero value. Possible address errors detected
// by the IOC peripheral are reported by the ISR in the _ioc_status variable.
// The _ioc_completed() function is polling the _ioc_done variable, waiting for
// tranfer conpletion. When the completion is signaled, the _ioc_completed() function
// reset the _ioc_done variable to zero, and releases the _ioc_lock variable.
// 
// In a multi-tasks environment, this polling policy must be replaced by a
// descheduling policy for the requesting process. 
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
//  _dcache_buf_invalidate()
// Invalidate all cache lines corresponding to a memory buffer.
// This is used by the block_device driver.
/////////////////////////////////////////////////////////////////////////
//in_reset static void _dcache_buf_invalidate(const void * buffer, size_t size)
//{
//    size_t i;
//    size_t dcache_line_size;
//
//    // retrieve dcache line size from config register (bits 12:10)
//    asm volatile("mfc0 %0, $16, 1" : "=r" (dcache_line_size));
//
//    dcache_line_size = 2 << ((dcache_line_size>>10) & 0x7);
//
//    // iterate on lines to invalidate each one of them
//    for ( i=0; i<size; i+=dcache_line_size )
//        asm volatile
//            (" mtc2 %0,     $7\n"
//             :
//             : "r" (*((char*)buffer+i))
//             );
//}

///////////////////////////////////////////////////////////////////////////////////////
//  _ioc_get_lock()
// This blocking function is used by the _ioc_read() and _ioc_write() functions 
// to get _ioc_lock using LL/SC.
///////////////////////////////////////////////////////////////////////////////////////
in_reset void _ioc_get_lock()
{
    register unsigned int   proctime;
    register unsigned int   delay;
    register unsigned int*  plock = (unsigned int*)&_ioc_lock;          

    asm volatile(
        "mfc0 %0, $9    \n"
        : "=r"(proctime)
        : /* No inputs */
    );

    delay = (proctime & 0xF) << 4;

    asm volatile ("_ioc_llsc:                   \n"
                  "ll   $2,    0(%0)            \n" // $2 <= _ioc_lock
                  "bnez $2,    _ioc_delay       \n" // random delay if busy
                  "li   $3,    1                \n" // prepare argument for sc  
                  "sc   $3,    0(%0)            \n" // try to set _ioc_busy
                  "bnez $3,    _ioc_ok          \n" // exit if atomic 
                  "_ioc_delay:                  \n"
                  "move $4,    %1               \n" // $4 <= delay
                  "_ioc_loop:                   \n"
                  "addi $4,    $4,    -1        \n" // $4 <= $4 - 1
                  "beqz $4,    _ioc_loop        \n" // test end delay
                  "j           _ioc_llsc        \n" // retry
                  "_ioc_ok:                     \n"
                  ::"r"(plock),"r"(delay):"$2","$3","$4");
}

in_reset int _ioc_init()
{
#ifndef SOCLIB_IOC
    in_reset_data static char init_begin[] = "Initializing block device\n\r";
    in_reset_data static char init_end[]   = "Finish block device initialization\n\r";

    unsigned char sdcard_rsp;

    boot_puts(init_begin);

#ifndef SYSCLK_FREQ
#warning "Using default value for SYSCLK_FREQ = 50000000"
#define SYSCLK_FREQ 50000000U
#endif

    /**
     * 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
     */
    if ( (sdcard_rsp = sdcard_dev_open(&_sdcard_device, _spi_device, 0)) )
        return sdcard_rsp;

    if ( (sdcard_rsp = sdcard_dev_set_blocklen(&_sdcard_device, 512)) )
        return sdcard_rsp;

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

    boot_puts(init_end);
#endif

    return 0;
}

///////////////////////////////////////////////////////////////////////////////////////
//  _ioc_completed()
// This blocking function cheks 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.
///////////////////////////////////////////////////////////////////////////////////////
in_reset int _ioc_completed()
{
    unsigned int status = 0;

#ifdef SOCLIB_IOC

    unsigned int * ioc_address = ( unsigned int * )VCIBD_BASE;
  
    while ( 1 )
    { 
        status = ioread32(&ioc_address[BLOCK_DEVICE_STATUS]);

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

    return status;
}

//////////////////////////////////////////////////////////////////////////////////////
//  _ioc_write()
// Transfer data from a memory buffer to a file on the block_device.
// - lba    : first block index on the disk
// - buffer : base address of the memory buffer
// - count  : number of blocks to be transfered
// The source buffer must be in user address space.
///////////////////////////////////////////////////////////////////////////////////////
in_reset int _ioc_write(size_t lba, void* buffer, size_t count)
{
    // get the lock
    // _ioc_get_lock();

#ifdef SOCLIB_IOC

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

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

    _ioc_completed();

#else   
/*
in_reset_data static char end_line[] =
    "\n\r";
in_reset_data static char error_init[] =
    "ERROR during initialization of block device. Code: ";

unsigned int sdcard_rsp;

if ( _ioc_init_ok == 0 )
{
    if (( sdcard_rsp = _ioc_init(&_sdcard_device, 0) ))
    {
        boot_puts(error_init);
        boot_putx(sdcard_rsp);
        boot_puts(end_line);
    }    

    _ioc_init_ok = 1;
}
*/
        
    sdcard_dev_lseek(&_sdcard_device, lba);
    sdcard_dev_write(&_sdcard_device, buffer, count*512);

#endif

// _ioc_lock = 0;

    return 0;
}

/**
 *  _ioc_read()
 * 
 * Transfer data from a file on the block device to a memory buffer.
 *
 * \param lba    : first block index on the disk
 * \param buffer : base address of the memory buffer
 * \param count  : number of blocks to be transfered
 *
 * \note This is a blocking function. The function returns once the transfer
 *       has finished
 */
in_reset int _ioc_read(size_t lba, void* buffer, size_t count)
{
    // get the lock
    // _ioc_get_lock();

#ifdef SOCLIB_IOC

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

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

    _ioc_completed();

#else
    in_reset_data static char error_read[] =
        "ERROR during read on the SDCARD device. Code: ";
    in_reset_data static char end_line[] =
        "\n\r";
/*
in_reset_data static char error_init[] =
    "ERROR during initialization of block device. Code: ";
*/

    unsigned int sdcard_rsp;

/*
if ( _ioc_init_ok == 0 )
{
    if (( sdcard_rsp = _ioc_init(&_sdcard_device, 0) ))
    {
        boot_puts(error_init);
        boot_putx(sdcard_rsp);
        boot_puts(end_line);
    }    
    
    _ioc_init_ok = 1;
}
*/

    sdcard_dev_lseek(&_sdcard_device, lba);

    unsigned int i;
    for(i = 0; i < count; i++)
    {
        if (( sdcard_rsp = sdcard_dev_read (
                        &_sdcard_device,
                        (unsigned char *) buffer + (512 * i),
                        512
                        ) 
            ))
        {
            boot_puts(error_read); 
            boot_putx(sdcard_rsp);
            boot_puts(end_line);

            return 1;
        }   
    }
    
#endif

    //_ioc_lock = 0;

    return 0;
}
#undef in_reset
#undef in_reset_data
#undef in_unckdata

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