///////////////////////////////////////////////////////////////////////////////////
// File     : boot_bdv_driver.c
// Date     : 18/01/2017
// Author   : Alain Greiner / Vu Son
// Copyright (c) UPMC-LIP6
///////////////////////////////////////////////////////////////////////////////////

#include <boot_config.h>
#include <boot_bdv_driver.h>
#include <boot_mmc_driver.h>
#include <boot_utils.h>

#ifndef SEG_IOC_BASE
# error "The SEG_IOC_BASE value should be defined in the 'hard_config.h' file"
#endif

#ifndef X_IO
# error "The X_IO value should be defined in the 'hard_config.h' file"
#endif

#ifndef Y_IO
# error "The Y_IO value should be defined in the 'hard_config.h' file"
#endif

#ifndef Y_WIDTH
# error "The Y_WIDTH value should be defined in the 'hard_config.h' file"
#endif


/****************************************************************************
 *                           Internal functions.                            *
 ****************************************************************************/

/****************************************************************************
 * This function returns the value of a BDV register.                       *
 * @ reg    : BDV register to be read.                                      *
 * @ returns the value stored in 'reg'.                                     *
 ****************************************************************************/
static uint32_t boot_bdv_get_register( uint32_t reg )
{
    cxy_t      cxy = (X_IO << Y_WIDTH) + Y_IO;
    uint32_t * ptr = (uint32_t *)SEG_IOC_BASE + reg;
    
    return boot_remote_lw( XPTR( cxy , ptr ) ); 

} // boot_bdv_get_register()

/****************************************************************************
 * This function set a new value to a BDV register.                         *
 * @ reg    : BDV register to be configured.                                *
 * @ val    : new value to be written.                                      *
 ****************************************************************************/
static void boot_bdv_set_register( uint32_t reg,
                                   uint32_t val )
{
    cxy_t      cxy = (X_IO << Y_WIDTH) + Y_IO;
    uint32_t * ptr = (uint32_t *)SEG_IOC_BASE + reg;

    boot_remote_sw( XPTR( cxy , ptr ) , val ); 

} // boot_bdv_set_register()

/****************************************************************************
 *                              Driver API functions.                       *
 ****************************************************************************/

///////////////////
int boot_bdv_init( void )
{
    // Check block size 
    if (boot_bdv_get_register(BDV_BLOCK_SIZE) != 512)
    {
        boot_puts("\n[BOOT ERROR] boot_bdv_init(): "
                  "Block size must be 512 bytes\n");
        return -1;
    }
        
    // Disable  IRQq
    boot_bdv_set_register(BDV_IRQ_ENABLE, 0);

    return 0;

} // boot_bdv_init()

////////////////////////////////////
int boot_bdv_access( uint32_t  lba,
                     xptr_t    buf_paddr,
                     uint32_t  count)
{
    uint32_t error;
    uint32_t status;

    // get target buffer cluster and pointer
    cxy_t      buf_cxy = GET_CXY( buf_paddr );
    uint32_t   buf_ptr = (uint32_t)GET_PTR( buf_paddr );

    // Check buffer address alignment
    if (buf_ptr & 0x3F)
    {
        boot_puts("\n[BOOT ERROR] boot_bdv_access(): "
                  "Buffer address is not cache-line-size-aligned\n");
        return -1;
    }

    // Set BDV device registers
    boot_bdv_set_register(BDV_BUFFER    , buf_ptr );
    boot_bdv_set_register(BDV_BUFFER_EXT, buf_cxy );
    boot_bdv_set_register(BDV_COUNT     , count );
    boot_bdv_set_register(BDV_LBA       , lba );

#if USE_IOB    // software L2/L3 cache coherence
    if( boot_mmc_inval( buf_paddr, count<<9 ) ) return -1;
#endif

    // Launch data transfer
    boot_bdv_set_register(BDV_OPERATION, BDV_READ);

#if DEBUG_BOOT_IOC
boot_printf("\n[BOOT] boot_bdv_access(): Transfer launched at cycle %d\n"
            "       lba = %d / buf = %l / nblocks = %d\n",
            boot_get_proctime() , lba , buf_paddr , count );
#endif

    // Wait transfer completion
    do
    {
        status = boot_bdv_get_register(BDV_STATUS);

    } while ((status != BDV_READ_SUCC ) &&
             (status != BDV_READ_ERR  ) &&
             (status != BDV_WRITE_SUCC) &&
             (status != BDV_WRITE_ERR ));

#if DEBUG_BOOT_IOC
boot_printf("\n[BOOT] boot_bdv_access(): Transfer terminated at cycle %d\n",
            boot_get_proctime() );

// uint32_t * data = (uint32_t *)(uint32_t)buf_paddr;
// uint32_t   line;
// uint32_t   word;
// boot_printf("\n");
// for( line = 0 ; line < 16 ; line++ )
// {
//    for( word = 0 ; word < 8 ; word++ )
//    {
//        boot_printf(" | %x", data[line*8+word] );
//    }
//    boot_printf(" |\n");
// }

#endif

    // Check error
    error = ((status == BDV_READ_ERR) ||
             (status == BDV_WRITE_ERR));

    return error;

} // boot_bdv_access()
