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

#include <boot_config.h>
#include <hard_config.h>
#include <boot_hba_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 Y_IO
# error "The Y_IO 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_WIDTH
# error "The Y_WIDTH value should be defined in the 'hard_config.h' file"
#endif


/****************************************************************************
 *                              Global variables.                           *
 ****************************************************************************/

uint32_t    hba_allocated_cmd[32];  /* State of each command slot (0 if
                                           slot is available for use).      */

hba_cmd_desc_t  hba_cmd_list[32]        /* Command List (up to 32 
                                           commands).                       */
__attribute__((aligned(0x40)));

hba_cmd_table_t hba_cmd_table[32]       /* Command Tables array (one 
                                           Command Table for each Command 
                                           List entry).                     */
__attribute__((aligned(0x40)));

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

/****************************************************************************
 * This function returns the value of an  HBA register.                     *
 * @ reg    : HBA register to be read.                                      *
 * @ returns the value stored in reg.                                     *
 ****************************************************************************/
static uint32_t boot_hba_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_hba_get_register()

/****************************************************************************
 * This function sets a new value to an HBA register.                       *
 * @ reg    : HBA register to be configured.                                *
 * @ val    : new value to be written to 'reg'.                             *
 ****************************************************************************/
static void boot_hba_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_hba_set_register()

/****************************************************************************
 * This function allocates an unused command index.                         *
 * @ returns command index (0 to 31) / returns -1 if not found              *
 ****************************************************************************/
static int boot_hba_cmd_alloc( void )
{
    uint32_t i;
    uint32_t cmd_id;

    // loop on the state array to find an unused slot
    cmd_id = -1;
    for (i = 0; i < 32; i++)
    {
        if (hba_allocated_cmd[i] == 0)
        {
            cmd_id = i;
            break;
        }
    }

    return cmd_id;

} // boot_hba_cmd_alloc()

/****************************************************************************
 * This function releases the 'cmd_id' command index by resetting its       *
 * corresponding entry in the state array.                                  *
 * @ returns 0 on success, -1 on error.                                     *
 ****************************************************************************/
static int boot_hba_cmd_release(uint32_t cmd_id)
{
    // check slot allocated
    if (hba_allocated_cmd[cmd_id] == 0)
    {
        boot_printf("\n[BOOT ERROR] boot_hba_cmd_release(): "
                    "Command %d to be released is not allocated\n",
                    cmd_id
                   );
        return -1;
    }

    // Reset entry in state array
    hba_allocated_cmd[cmd_id] = 0;

    return 0;
}

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

///////////////////
int boot_hba_init( void )
{
    uint64_t cmd_table_addr;  // Command Table physical base address
    uint64_t cmd_list_addr;   // Command List physical base address
    uint64_t paddr;           // Command Table physical address
    uint32_t i;               // Iterator for the initialization loop

    /* Getting the Command List and Command Table base addresses. */
    cmd_table_addr = (uint64_t)(intptr_t)&hba_cmd_table[0];
    cmd_list_addr  = (uint64_t)(intptr_t)&hba_cmd_list[0];

    /* Initializing the Command List. */
    for (i = 0; i < 32; i++)
    {
        paddr = cmd_table_addr + i * sizeof(hba_cmd_table_t);
        hba_cmd_list[i].ctba  = (uint32_t)paddr;
        hba_cmd_list[i].ctbau = (uint32_t)(paddr >> 32);
        hba_allocated_cmd[i]  = 0;
    }

    /* Initializing the HBA registers. */
    boot_hba_set_register( HBA_PXCLB , (uint32_t)cmd_list_addr );
    boot_hba_set_register( HBA_PXCLBU, (uint32_t)(cmd_list_addr >> 32) );
    boot_hba_set_register( HBA_PXIS  , 0 );
    boot_hba_set_register( HBA_PXIE  , 0 );
    boot_hba_set_register( HBA_PXCI  , 0 );
    boot_hba_set_register( HBA_PXCMD , 1 );

    return 0;

} // boot_hba_init()

///////////////////////////////////
int boot_hba_access( uint32_t  lba,
                     xptr_t    buf_paddr,
                     uint32_t  count )
{
    uint32_t          cmd_id;     
    uint32_t          pxci; 
    uint32_t          pxis;
    hba_cmd_desc_t  * cmd_desc;
    hba_cmd_table_t * cmd_table;  

    // 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_hba_access(): "
                  "Buffer address is not cache-line-size-aligned\n");
        return -1;
    }

    // Get a free slot in the Command List
    cmd_id = boot_hba_cmd_alloc();

    // Initialize  pointers
    cmd_desc  = &hba_cmd_list[cmd_id];
    cmd_table = &hba_cmd_table[cmd_id];

    // Set the buffer descriptor of the Command Table
    cmd_table->buffer.dba  = buf_ptr;
    cmd_table->buffer.dbau = buf_cxy;
    cmd_table->buffer.dbc  = count * 512;

    // Initialize the Command Table header
    cmd_table->header.lba0 = (char)lba;
    cmd_table->header.lba1 = (char)(lba >> 8);
    cmd_table->header.lba2 = (char)(lba >> 16);
    cmd_table->header.lba3 = (char)(lba >> 24);
    cmd_table->header.lba4 = 0;
    cmd_table->header.lba5 = 0;
    
    // Initialize the Command Descriptor
    cmd_desc->prdtl[0] = 1;
    cmd_desc->prdtl[1] = 0;
    cmd_desc->flag[0] = 0x00;

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

    // Launch  data transfer
    boot_hba_set_register(HBA_PXCI, (1 << cmd_id));

#if DEBUG_BOOT_IOC
boot_printf("\n[BOOT] boot_hba_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
    {
        pxci = boot_hba_get_register(HBA_PXCI);

    } while (pxci & (1 << cmd_id));

    // Get error status then reset it
    pxis = boot_hba_get_register(HBA_PXIS);
    boot_hba_set_register(HBA_PXIS, 0);

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

    if (boot_hba_cmd_release(cmd_id))
        return -1;
    else if (pxis & 0x40000000)
        return pxis;
    else
        return 0;

} // boot_hba_access()
