/*******************************************************************************
* (c) Copyright 2009-2011 Actel Corporation.  All rights reserved.
*
* PMBus bare metal software driver implementation.
*
* SVN $Revision: 3958 $
* SVN $Date: 2011-11-15 18:45:55 +0530 (Tue, 15 Nov 2011) $
*/
#include "pmbus.h"
#include <stdlib.h>

#ifdef __cplusplus
extern "C" {
#endif

/*------------------------------------------------------------------------------
*  Local Definitions
*/
#ifndef NULL
#define NULL   (void *)0
#endif

/* Data format related constants */
#define MAX_MANTISSA    (1023 * 1000)
#define MIN_MANTISSA    (511 * 1000)

#define DIV_ROUND_CLOSEST(x, divisor)(                 \
{                                                      \
        typeof(divisor) __divisor = divisor;           \
        (((x) + ((__divisor) / 2)) / (__divisor));     \
}                                                      \
)
#define BITS_SHIFT               8u

/* Process call message size */
#define MIN_PRCOESS_CALL_SIZE    1u
#define MAX_PRCOESS_CALL_SIZE   32u

/* Command message size */
#define WORD_SIZE                2u
#define BYTE_SIZE                1u
#define PEC_SIZE          BYTE_SIZE

/* SMB Alert IRQ handler related constants */
#define PMBUS_ALERT_RESPONSE_ADDR  0x0Cu
#define PMBUS_ARA_ERROR            0xFFu

#define PAGE_CMD                   0x00u
/*-------------------------------------------------------------------------*//**
  PMBus Command extension code
 */
#define PMBUS_COMMAND_EXTENSION_CODE 0xFEu

#define SLAVE_ADDR_OFFSET   0u
#define COMMAND_OFFSET      1u
#define DATA_OFFSET         COMMAND_OFFSET

#define CRC_TABLE_SIZE    256u

/*
* CRC-8 Constant table generated from polynomial
*  x^8 + x^2 + x + 1
*/
static const uint8_t crcTable[CRC_TABLE_SIZE] =
{
    0x00,0x07,0x0e,0x09,0x1c,0x1b,0x12,0x15,
    0x38,0x3f,0x36,0x31,0x24,0x23,0x2a,0x2d,
    0x70,0x77,0x7e,0x79,0x6c,0x6b,0x62,0x65,
    0x48,0x4f,0x46,0x41,0x54,0x53,0x5a,0x5d,
    0xe0,0xe7,0xee,0xe9,0xfc,0xfb,0xf2,0xf5,
    0xd8,0xdf,0xd6,0xd1,0xc4,0xc3,0xca,0xcd,
    0x90,0x97,0x9e,0x99,0x8c,0x8b,0x82,0x85,
    0xa8,0xaf,0xa6,0xa1,0xb4,0xb3,0xba,0xbd,
    0xc7,0xc0,0xc9,0xce,0xdb,0xdc,0xd5,0xd2,
    0xff,0xf8,0xf1,0xf6,0xe3,0xe4,0xed,0xea,
    0xb7,0xb0,0xb9,0xbe,0xab,0xac,0xa5,0xa2,
    0x8f,0x88,0x81,0x86,0x93,0x94,0x9d,0x9a,
    0x27,0x20,0x29,0x2e,0x3b,0x3c,0x35,0x32,
    0x1f,0x18,0x11,0x16,0x03,0x04,0x0d,0x0a,
    0x57,0x50,0x59,0x5e,0x4b,0x4c,0x45,0x42,
    0x6f,0x68,0x61,0x66,0x73,0x74,0x7d,0x7a,
    0x89,0x8e,0x87,0x80,0x95,0x92,0x9b,0x9c,
    0xb1,0xb6,0xbf,0xb8,0xad,0xaa,0xa3,0xa4,
    0xf9,0xfe,0xf7,0xf0,0xe5,0xe2,0xeb,0xec,
    0xc1,0xc6,0xcf,0xc8,0xdd,0xda,0xd3,0xd4,
    0x69,0x6e,0x67,0x60,0x75,0x72,0x7b,0x7c,
    0x51,0x56,0x5f,0x58,0x4d,0x4a,0x43,0x44,
    0x19,0x1e,0x17,0x10,0x05,0x02,0x0b,0x0c,
    0x21,0x26,0x2f,0x28,0x3d,0x3a,0x33,0x34,
    0x4e,0x49,0x40,0x47,0x52,0x55,0x5c,0x5b,
    0x76,0x71,0x78,0x7f,0x6a,0x6d,0x64,0x63,
    0x3e,0x39,0x30,0x37,0x22,0x25,0x2c,0x2b,
    0x06,0x01,0x08,0x0f,0x1a,0x1d,0x14,0x13,
    0xae,0xa9,0xa0,0xa7,0xb2,0xb5,0xbc,0xbb,
    0x96,0x91,0x98,0x9f,0x8a,0x8d,0x84,0x83,
    0xde,0xd9,0xd0,0xd7,0xc2,0xc5,0xcc,0xcb,
    0xe6,0xe1,0xe8,0xef,0xfa,0xfd,0xf4,0xf3
};

/*The node for a linked list */
struct llnode
{
    pmbus_slave_info_t *pmbus_slave_instance;
    struct llnode *next;
};

typedef struct llnode pmbus_node_t ;

/* global static data */
static uint8_t number_of_slave_in_grp_cmd = 0u;
static pmbus_node_t *pmbus_channel_list = NULL;
static uint8_t listlen = 0u;

/*------------------------------------------------------------------------------
*  Local Prototype functions
*/
static pmbus_error_code_t
pmbus_read_data
(
    pmbus_slave_info_t *pmbus_slave_instance,
    pmbus_command_info_t *cmd_info,
    uint8_t *read_buffer
);

static pmbus_error_code_t
pmbus_write_data
(
    pmbus_slave_info_t *pmbus_slave_instance,
    pmbus_command_info_t *cmd_info,
    const uint8_t *write_buffer
);

static uint8_t
pmbus_get_crc8
(
    uint8_t *message, uint8_t nBytes
);

/* Linked list related local functions*/
static pmbus_error_code_t
pmbus_list_addfront
(
    pmbus_slave_info_t *pmbus_slave_instance
);

static pmbus_error_code_t
pmbus_list_addend
(
    pmbus_slave_info_t *pmbus_slave_instance
);

static
uint8_t pmbus_list_find_position
(
    uint8_t slave_address
);

static pmbus_slave_info_t *
pmbus_list_get_slave_info
(
    uint8_t slave_address,
    ui2c_channel_t *i2c_channel
);

static pmbus_error_code_t
pmbus_list_addNode
(
    pmbus_slave_info_t *pmbus_slave_instance
);

/*------------------------------------------------------------------------------
* PMBUS_add_slave()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_error_code_t
PMBUS_add_slave
(
    ui2c_channel_t *i2c_channel,
    pmbus_slave_info_t *pmbus_slave_instance,
    void *user_data,
    uint8_t total_supported_page,
    uint8_t slave_addr,
    uint8_t pec_support
)
{
    pmbus_error_code_t error_code ;

    pmbus_slave_instance->i2c_channel = i2c_channel;
    pmbus_slave_instance->slave_address = slave_addr;
    pmbus_slave_instance->is_pec_supported = pec_support;
    pmbus_slave_instance->p_user_data = user_data ;

    /*
    * User needs to make sure that total supported page value is set
    * only when PAGE command is provided for the device
    */
    pmbus_slave_instance->total_supported_pages = total_supported_page ;

    /* Build PMBus device list */
    error_code = pmbus_list_addNode(pmbus_slave_instance);

    return(error_code);
}

/*------------------------------------------------------------------------------
* PMBUS_set_page()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_error_code_t
PMBUS_set_page
(
    pmbus_slave_info_t *pmbus_slave_instance,
    uint8_t page_number
)
{
    uint8_t current_page ;
    pmbus_error_code_t page_setup_error = PMBUS_FAILURE ;
    pmbus_command_info_t cmd_info ;

    if( page_number < (pmbus_slave_instance->total_supported_pages))
    {
        cmd_info.cmd_type =  PMBUS_BYTE_TYPE;
        cmd_info.command_code = PAGE_CMD;
        cmd_info.is_extended_command = 0u ;
        cmd_info.command_size = BYTE_SIZE ;

        page_setup_error = pmbus_write_data
                           (
                                pmbus_slave_instance,
                                &cmd_info,
                                &page_number
                           );

        if( page_setup_error == PMBUS_WRITE_SUCCESS )
        {
            page_setup_error = pmbus_read_data
                               (
                                   pmbus_slave_instance,
                                   &cmd_info,
                                   &current_page
                               );
            if(
                (page_setup_error == PMBUS_READ_SUCCESS)
                && (current_page == page_number)
            )
            {
                page_setup_error = PMBUS_SUCCESS;
            }
            else
            {
                page_setup_error = PMBUS_FAILURE ;
            }
        }
    }
    return(page_setup_error);
}

/*------------------------------------------------------------------------------
* PMBUS_send_byte()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_error_code_t
PMBUS_send_byte
(
    pmbus_slave_info_t *pmbus_slave_instance,
    uint8_t command_code
)
{
    ui2c_status_t i2c_dev_status = UI2C_FAILED ;
    pmbus_error_code_t send_error = PMBUS_WRITE_FAILURE ;

    if( pmbus_slave_instance->is_pec_supported)
    {
        uint8_t data_buffer[WORD_SIZE+PEC_SIZE];
        data_buffer[SLAVE_ADDR_OFFSET] = pmbus_slave_instance->slave_address ;
        data_buffer[COMMAND_OFFSET] = command_code ;
        data_buffer[DATA_OFFSET+1u] = pmbus_get_crc8( data_buffer, WORD_SIZE );
        UI2C_write
        (
            pmbus_slave_instance->i2c_channel,
            pmbus_slave_instance->slave_address,
            (data_buffer+DATA_OFFSET),
            WORD_SIZE,
            UI2C_RELEASE_BUS
        );
    }
    else
    {
        UI2C_write
        (
            pmbus_slave_instance->i2c_channel,
            pmbus_slave_instance->slave_address,
            &(command_code),
            BYTE_SIZE,
            UI2C_RELEASE_BUS
        );
    }

    i2c_dev_status = UI2C_wait_complete(pmbus_slave_instance->i2c_channel);
    if ( i2c_dev_status == UI2C_SUCCESS)
    {
        send_error = PMBUS_WRITE_SUCCESS ;
    }
    return(send_error);
}

/*------------------------------------------------------------------------------
* PMBUS_read_byte()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_error_code_t
PMBUS_read_byte
(
    pmbus_slave_info_t *pmbus_slave_instance,
    uint8_t command_code,
    uint8_t is_extended_command,
    uint8_t *read_buffer
)
{
    pmbus_error_code_t error_code ;
    pmbus_command_info_t cmd_info ;

    cmd_info.cmd_type = PMBUS_BYTE_TYPE ;
    cmd_info.command_code = command_code;
    cmd_info.is_extended_command = is_extended_command ;
    cmd_info.command_size =( BYTE_SIZE +
                        ((pmbus_slave_instance->is_pec_supported) ? 1u :0u));

    error_code = pmbus_read_data
                 (
                    pmbus_slave_instance,
                    &cmd_info,
                    read_buffer
                 );
    return(error_code);
}

/*------------------------------------------------------------------------------
* PMBUS_write_byte()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_error_code_t
PMBUS_write_byte
(
    pmbus_slave_info_t *pmbus_slave_instance,
    uint8_t command_code,
    uint8_t is_extended_command,
    const uint8_t *write_buffer
)
{
    pmbus_error_code_t error_code ;
    pmbus_command_info_t cmd_info ;

    cmd_info.cmd_type = PMBUS_BYTE_TYPE ;
    cmd_info.command_code = command_code;
    cmd_info.is_extended_command = is_extended_command ;
    cmd_info.command_size = BYTE_SIZE ;

    error_code = pmbus_write_data
                 (
                    pmbus_slave_instance,
                    &cmd_info,
                    write_buffer
                 );
    return(error_code);
}

/*------------------------------------------------------------------------------
* PMBUS_read_word()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_error_code_t
PMBUS_read_word
(
    pmbus_slave_info_t *pmbus_slave_instance,
    uint8_t command_code,
    uint8_t is_extended_command,
    uint8_t *read_buffer
)
{
    pmbus_error_code_t error_code ;
    pmbus_command_info_t cmd_info ;

    cmd_info.cmd_type = PMBUS_WORD_TYPE ;
    cmd_info.command_code = command_code;
    cmd_info.is_extended_command = is_extended_command ;
    cmd_info.command_size =( WORD_SIZE +
                        ((pmbus_slave_instance->is_pec_supported) ? 1u :0u));

    error_code = pmbus_read_data
                (
                    pmbus_slave_instance,
                    &cmd_info,
                    read_buffer
                );
    return(error_code);
}

/*------------------------------------------------------------------------------
* PMBUS_write_word()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_error_code_t
PMBUS_write_word
(
    pmbus_slave_info_t *pmbus_slave_instance,
    uint8_t command_code,
    uint8_t is_extended_command,
    const uint8_t *write_buffer
)
{
    pmbus_error_code_t error_code ;
    pmbus_command_info_t cmd_info ;

    cmd_info.cmd_type = PMBUS_WORD_TYPE ;
    cmd_info.command_code = command_code;
    cmd_info.is_extended_command = is_extended_command ;
    cmd_info.command_size = WORD_SIZE ;

    error_code = pmbus_write_data
                (
                    pmbus_slave_instance,
                    &cmd_info,
                    write_buffer
                );
    return(error_code);
}

/*------------------------------------------------------------------------------
* PMBUS_read_block()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_error_code_t
PMBUS_read_block
(
    pmbus_slave_info_t *pmbus_slave_instance,
    uint8_t command_code,
    uint8_t is_extended_command,
    uint8_t *read_buffer,
    uint8_t size
)
{
    pmbus_error_code_t error_code = PMBUS_READ_FAILURE ;
    pmbus_command_info_t cmd_info ;

    if( size >0u)
    {
        cmd_info.cmd_type = PMBUS_BLOCK_TYPE ;
        cmd_info.command_code = command_code;
        cmd_info.is_extended_command = is_extended_command ;
        cmd_info.command_size = size +
                ((pmbus_slave_instance->is_pec_supported) ? 2u :1u);

        error_code = pmbus_read_data
                    (
                        pmbus_slave_instance,
                        &cmd_info,
                        read_buffer
                    );
    }
    return(error_code);
}

/*------------------------------------------------------------------------------
* PMBUS_write_block()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_error_code_t
PMBUS_write_block
(
    pmbus_slave_info_t *pmbus_slave_instance,
    uint8_t command_code,
    uint8_t is_extended_command,
    const uint8_t *write_buffer,
    uint8_t size
)
{
    pmbus_error_code_t error_code = PMBUS_WRITE_FAILURE ;
    pmbus_command_info_t cmd_info ;

    if( size > 0u )
    {
        cmd_info.cmd_type = PMBUS_BLOCK_TYPE ;
        cmd_info.command_code = command_code;
        cmd_info.is_extended_command = is_extended_command ;
        cmd_info.command_size = size ;

        error_code = pmbus_write_data
                    (
                        pmbus_slave_instance,
                        &cmd_info,
                        write_buffer
                    );
    }
    return(error_code);
}

/*------------------------------------------------------------------------------
* pmbus_read_data()
* Static function.
*/
static pmbus_error_code_t
pmbus_read_data
(
    pmbus_slave_info_t *pmbus_slave_instance,
    pmbus_command_info_t *cmd_info,
    uint8_t *read_buffer
)
{
    ui2c_status_t i2c_dev_status = UI2C_FAILED ;

    if( cmd_info->is_extended_command )
    {
        uint8_t extended_command[WORD_SIZE] ;
        extended_command[0] = PMBUS_COMMAND_EXTENSION_CODE ;
        extended_command[1] = cmd_info->command_code ;

        /* Send command code to the device */
        UI2C_write
        (
            pmbus_slave_instance->i2c_channel,
            pmbus_slave_instance->slave_address,
            extended_command,
            WORD_SIZE,
            UI2C_HOLD_BUS
        );
    }
    else
    {
        /* Send command code to the device */
        UI2C_write
        (
            pmbus_slave_instance->i2c_channel,
            pmbus_slave_instance->slave_address,
            &(cmd_info->command_code),
            BYTE_SIZE,
            UI2C_HOLD_BUS
        );
    }

    i2c_dev_status = UI2C_wait_complete(pmbus_slave_instance->i2c_channel);
    if ( i2c_dev_status !=UI2C_SUCCESS)
    {
        return(PMBUS_READ_FAILURE);
    }

    /* Now repeated start to capture data */
    UI2C_read
    (
        pmbus_slave_instance->i2c_channel,
        pmbus_slave_instance->slave_address,
        read_buffer,
        cmd_info->command_size,
        UI2C_RELEASE_BUS
    );

    i2c_dev_status = UI2C_wait_complete(pmbus_slave_instance->i2c_channel);
    if ( i2c_dev_status !=UI2C_SUCCESS)
    {
        return(PMBUS_READ_FAILURE);
    }

    return(PMBUS_READ_SUCCESS);
}

/*------------------------------------------------------------------------------
* pmbus_write_data()
* Static function.
*/
static pmbus_error_code_t
pmbus_write_data
(
    pmbus_slave_info_t *pmbus_slave_instance,
    pmbus_command_info_t *cmd_info,
    const uint8_t *write_buffer
)
{
    pmbus_error_code_t write_error = PMBUS_WRITE_FAILURE ;
    ui2c_status_t i2c_dev_status = UI2C_FAILED ;
    uint8_t *data_buffer;
    uint8_t inc;

    if( cmd_info->cmd_type != PMBUS_SEND_TYPE)
    {
        uint8_t data_offset = 1u;
        uint8_t buffer_size;

        buffer_size = (cmd_info->command_size)+
                            ((cmd_info->is_extended_command)?1u:0u) ;
        buffer_size += ((cmd_info->cmd_type == PMBUS_BLOCK_TYPE) ? 1u :0u);
        buffer_size += ((pmbus_slave_instance->is_pec_supported)? 1u : 0u);
        buffer_size += WORD_SIZE ; /* Storing slave address and Command code */

        data_buffer = (uint8_t *)malloc(buffer_size*sizeof(uint8_t));

        if( data_buffer != NULL)
        {
            data_buffer[SLAVE_ADDR_OFFSET]= pmbus_slave_instance->slave_address;

            if( cmd_info->is_extended_command )
            {
                data_buffer[COMMAND_OFFSET] = PMBUS_COMMAND_EXTENSION_CODE;
                data_buffer[COMMAND_OFFSET+1u] = cmd_info->command_code;
                data_offset = 3u;
            }
            else
            {
                data_buffer[COMMAND_OFFSET] = cmd_info->command_code;
                data_offset = 2u;
            }

            if(cmd_info->cmd_type == PMBUS_BLOCK_TYPE )
            {
                data_buffer[data_offset] = cmd_info->command_size;
                ++data_offset;
            }

            for( inc = 0u ; inc < (cmd_info->command_size) ; inc++ )
            {
                data_buffer[inc+data_offset] = write_buffer[inc];
            }

            if( pmbus_slave_instance->is_pec_supported)
            {
                data_buffer[inc+data_offset] =
                        pmbus_get_crc8
                        (
                            data_buffer,
                            ((cmd_info->command_size)+ data_offset)
                        );
            }

            UI2C_write
            (
                pmbus_slave_instance->i2c_channel,
                pmbus_slave_instance->slave_address,
                (data_buffer+DATA_OFFSET),
                ((cmd_info->command_size)+ (data_offset -1u)),
                UI2C_RELEASE_BUS
            );

            i2c_dev_status = UI2C_wait_complete(pmbus_slave_instance->i2c_channel);
            if ( i2c_dev_status ==UI2C_SUCCESS)
            {
                write_error = PMBUS_WRITE_SUCCESS ;
            }
            free(data_buffer);
        }
    }
    return( write_error);
}

/*------------------------------------------------------------------------------
* PMBUS_write_read_block()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_error_code_t
PMBUS_write_read_block
(
    pmbus_slave_info_t *pmbus_slave_instance,
    uint8_t command_code,
    uint8_t is_extended_command,
    uint8_t *write_buffer,
    uint8_t write_size,
    uint8_t *read_buffer,
    uint8_t read_size
)
{

    ui2c_status_t i2c_dev_status = UI2C_FAILED ;
    uint8_t *data_buffer;
    uint8_t inc;
    uint8_t actual_rcvd_msg_size ;

    uint8_t data_offset = 1u;

    if(
        (read_size >= MIN_PRCOESS_CALL_SIZE)
                    &&
        (write_size >= MIN_PRCOESS_CALL_SIZE)
                    &&
        ((read_size +write_size) <= MAX_PRCOESS_CALL_SIZE)
    )
    {
        uint8_t buffer_size;

        buffer_size = write_size+ ((is_extended_command)?1u:0u) ;
        buffer_size += ((pmbus_slave_instance->is_pec_supported)? 1u : 0u);
        buffer_size += 1; /* Include size of block command*/
        buffer_size += WORD_SIZE ; /* Storing slave address and Command code */

        data_buffer = (uint8_t *)malloc(buffer_size*sizeof(uint8_t));

        if( data_buffer != NULL)
        {
            data_buffer[SLAVE_ADDR_OFFSET] = pmbus_slave_instance->slave_address ;

            if( is_extended_command )
            {
                data_buffer[1] = PMBUS_COMMAND_EXTENSION_CODE;
                data_buffer[2] = command_code;
                data_offset = 3u;
            }
            else
            {
                data_buffer[1] = command_code;
                data_offset = 2u;
            }

            data_buffer[data_offset] = write_size;
            ++data_offset;

            for( inc = 0u ; inc < write_size ; inc++ )
            {
                data_buffer[inc+data_offset] = write_buffer[inc];
            }

            UI2C_write
            (
                pmbus_slave_instance->i2c_channel,
                pmbus_slave_instance->slave_address,
                (data_buffer+DATA_OFFSET),
                (write_size+(data_offset-1u)),
                UI2C_HOLD_BUS
            );

            i2c_dev_status = UI2C_wait_complete(pmbus_slave_instance->i2c_channel);
            if ( i2c_dev_status !=UI2C_SUCCESS)
            {
                free(data_buffer);
                return( PMBUS_WRITE_FAILURE) ;
            }

            /* Read part */
            actual_rcvd_msg_size = read_size +
                        ((pmbus_slave_instance->is_pec_supported) ? 2u :1u) ;

            /* Now repeated start to capture data */
            UI2C_read
            (
                pmbus_slave_instance->i2c_channel,
                pmbus_slave_instance->slave_address,
                read_buffer,
                actual_rcvd_msg_size,
                UI2C_RELEASE_BUS
            );

            i2c_dev_status = UI2C_wait_complete(pmbus_slave_instance->i2c_channel);
            if ( i2c_dev_status !=UI2C_SUCCESS)
            {
                free(data_buffer);
                return(PMBUS_READ_FAILURE);
            }
            free(data_buffer);
        }
    }
    return(PMBUS_SUCCESS);
}

/*------------------------------------------------------------------------------
* PMBUS_add_slave_to_group_tbl()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_error_code_t
PMBUS_add_slave_to_group_tbl
(
    ui2c_channel_t *i2c_channel,
    pmbus_slave_info_t *pmbus_slave_instance,
    pmbus_command_info_t *cmd_info,
    uint8_t *write_buffer,
    pmbus_group_cmd_info_t *group_command_table
)
{
    pmbus_error_code_t error_code = PMBUS_FAILURE ;
    uint8_t inc;

    if(group_command_table != NULL )
    {
        if(( (number_of_slave_in_grp_cmd !=0u)
                &&
            group_command_table[0].pmbus_slave_instance.i2c_channel
                                                        != i2c_channel)
                             ||
            ( pmbus_slave_instance->i2c_channel != i2c_channel)
          )
        {
            return( error_code);
        }
        else
        {
            for( inc = 0u; inc < number_of_slave_in_grp_cmd; inc++)
            {
                if ((group_command_table[inc].pmbus_slave_instance.slave_address )
                                    == (pmbus_slave_instance->slave_address ))
                {
                    break ;
                }
            }
        }
        if( inc == number_of_slave_in_grp_cmd )
        {
            group_command_table[inc].pmbus_slave_instance.i2c_channel = i2c_channel ;
            group_command_table[inc].pmbus_slave_instance.is_pec_supported =
                                            pmbus_slave_instance->is_pec_supported;
            group_command_table[inc].pmbus_slave_instance.slave_address =
                                            pmbus_slave_instance->slave_address;

            /* Updating User data and PAGE number is not mandatory. However still doing it */
            group_command_table[inc].pmbus_slave_instance.p_user_data =
                                            pmbus_slave_instance->p_user_data;
            group_command_table[inc].pmbus_slave_instance.total_supported_pages =
                                            pmbus_slave_instance->total_supported_pages;

            group_command_table[inc].write_buffer = write_buffer ;

            group_command_table[inc].cmd_info.command_code =
                                            cmd_info->command_code ;
            group_command_table[inc].cmd_info.is_extended_command =
                                            cmd_info->is_extended_command;
            /*
            * Specification does not say, whether it supports any
            *  type of command
            */
            group_command_table[inc].cmd_info.cmd_type = cmd_info->cmd_type ;
            group_command_table[inc].cmd_info.command_size = cmd_info->command_size ;

            ++ number_of_slave_in_grp_cmd ;
            error_code = PMBUS_SUCCESS ;
        }
    }
    return( error_code);
}

/*------------------------------------------------------------------------------
* PMBUS_send_group_command()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_error_code_t
PMBUS_send_group_command
(
    const pmbus_group_cmd_info_t *group_command_table
)
{
    pmbus_error_code_t grp_cmd_error = PMBUS_WRITE_SUCCESS ;

    ui2c_status_t i2c_dev_status = UI2C_FAILED ;
    uint8_t *data_buffer;
    uint8_t inc;
    uint8_t slave_number ;
    uint8_t command_size ;
    uint8_t bus_status ;

    pmbus_slave_info_t pmbus_slave_instance;

    for(slave_number =0u; slave_number< number_of_slave_in_grp_cmd; slave_number++)
    {
        pmbus_slave_instance.slave_address =
            group_command_table[slave_number].pmbus_slave_instance.slave_address;
        pmbus_slave_instance.is_pec_supported =
            group_command_table[slave_number].pmbus_slave_instance.is_pec_supported;
        pmbus_slave_instance.i2c_channel =
                    group_command_table[slave_number].pmbus_slave_instance.i2c_channel;
        /* Updating User data is not mandatory. However still doing it */
        pmbus_slave_instance.p_user_data =
                    group_command_table[slave_number].pmbus_slave_instance.p_user_data;

        command_size = group_command_table[slave_number].cmd_info.command_size;

        if((group_command_table[slave_number].cmd_info.cmd_type)!= PMBUS_SEND_TYPE)
        {
            uint8_t data_offset = 1u;
            uint8_t buffer_size;

            buffer_size = command_size+
            (( group_command_table[slave_number].cmd_info.is_extended_command)?1u:0u);

            buffer_size += ((pmbus_slave_instance.is_pec_supported)? 1u : 0u);

            buffer_size +=
            ((group_command_table[slave_number].cmd_info.cmd_type == PMBUS_BLOCK_TYPE) ? 1u :0u);
            buffer_size += WORD_SIZE ; /* Storing slave address and Command code */

            data_buffer = (uint8_t *)malloc(buffer_size*sizeof(uint8_t));

            if( data_buffer != NULL)
            {
                data_buffer[SLAVE_ADDR_OFFSET] = pmbus_slave_instance.slave_address;

                if( group_command_table[slave_number].cmd_info.is_extended_command)
                {
                    data_buffer[1] = PMBUS_COMMAND_EXTENSION_CODE;
                    data_buffer[2] =
                            group_command_table[slave_number].cmd_info.command_code;
                    data_offset = 3u;
                }
                else
                {
                    data_buffer[1] =
                            group_command_table[slave_number].cmd_info.command_code;
                    data_offset = 2u;
                }

                /*
                * This if statement need to commented if block command is not supported
                * by group command protocol
                */
                if(group_command_table[slave_number].cmd_info.cmd_type
                                                            == PMBUS_BLOCK_TYPE)
                {
                    data_buffer[data_offset] = command_size;
                    ++data_offset;
                }

                for( inc = 0u ; inc < command_size ; inc++ )
                {
                    data_buffer[inc+data_offset] = group_command_table[slave_number].write_buffer[inc];
                }

                if( pmbus_slave_instance.is_pec_supported)
                {
                    data_buffer[inc+data_offset] = pmbus_get_crc8
                                                (
                                                    data_buffer,
                                                    ((command_size)+ data_offset)
                                                );
                }

                bus_status = ( (slave_number == (number_of_slave_in_grp_cmd-1))
                                            ? UI2C_RELEASE_BUS : UI2C_HOLD_BUS);
                UI2C_write(
                        pmbus_slave_instance.i2c_channel,
                        pmbus_slave_instance.slave_address,
                        (data_buffer+1),
                        (command_size + (data_offset -1u)),
                        bus_status
                        );

                i2c_dev_status = UI2C_wait_complete(pmbus_slave_instance.i2c_channel);
                if (( i2c_dev_status !=UI2C_SUCCESS)
                                    &&
                    (grp_cmd_error == PMBUS_WRITE_SUCCESS)
                )
                {
                    grp_cmd_error = PMBUS_WRITE_FAILURE ;
                }
                free(data_buffer);
            }
        }
    }/* End of for loop */

    /* Clean up.Should table be cleared ? */
    /* Make sure to reset number of slave in group command */
    number_of_slave_in_grp_cmd= 0u ;

    return(grp_cmd_error);
}

/*------------------------------------------------------------------------------
* PMBUS_get_smbalert_source()
* See "pmbus.h" for details of how to use this function.
*/
pmbus_slave_info_t *
PMBUS_get_smbalert_source
(
    ui2c_channel_t * i2c_channel
)
{
    uint8_t read_buffer[WORD_SIZE];
    ui2c_status_t i2c_dev_status = UI2C_FAILED ;
    uint8_t slave_address = PMBUS_ARA_ERROR;

    UI2C_read
    (
        i2c_channel,
        PMBUS_ALERT_RESPONSE_ADDR,
        read_buffer,
        BYTE_SIZE,
        UI2C_RELEASE_BUS
    );

    i2c_dev_status = UI2C_wait_complete(i2c_channel);
    if ( i2c_dev_status ==UI2C_SUCCESS)
    {
        /* Check PEC error [TODO]*/
        slave_address = (read_buffer[0] & 0x7Fu);
    }
    return( pmbus_list_get_slave_info(slave_address,i2c_channel));
}

/*------------------------------------------------------------------------------
* PMBUS_get_linear_format_data()
* See "pmbus.h" for details of how to use this function.
*/
int32_t
PMBUS_get_linear_format_data
(
    uint8_t *data,
    uint8_t command_format,
    int16_t exponent_coeff
)
{
    int16_t exponent;
    int32_t mantissa, value ;
    uint16_t temp_data1, temp_data2;

    temp_data1 = data[1];
    temp_data1 = (uint16_t)(temp_data1<<BITS_SHIFT)  ;
    temp_data1 = temp_data1 | data[0];

    if ( command_format == PMBUS_VOUT_LINEAR_FORMAT)
    {
        exponent = exponent_coeff ;
        mantissa = (int32_t)temp_data1;
    }
    else
    {
        temp_data2 = temp_data1 >> 11u;
        temp_data2 = (uint16_t)temp_data2 & 0x001Fu ;
        exponent =  (int16_t)temp_data2;

        temp_data2 = temp_data1 & 0x07FFu;
        mantissa =  (int32_t)temp_data2;

        if (exponent > 0x0F)
        {
            /* sign extend exponent */
            temp_data2 = (uint16_t)exponent | 0xFFE0u;
            exponent = (int16_t)temp_data2;
        }
        if (mantissa > 0x03FF)
        {
            /* sign extend mantissa */
            mantissa |= 0xFFFFF800;
        }
    }

    value = mantissa;

    if (exponent < 0)
    {
    	value <<= -exponent;
    }
    else
    {
    	value >>= exponent;
    }

    /* Convert from mV to V */
    value /= 1000;

#if 0	// Removed by TM 20111128
    value = mantissa;
    value = value * 1000L;

    if (exponent >= 0)
    {
        value <<= exponent;
    }
    else
    {
        value >>= -exponent;
    }
#endif	// Removed by TM 20111128

    return(value) ;
}

/*------------------------------------------------------------------------------
* PMBUS_get_direct_format_data()
* See "pmbus.h" for details of how to use this function.
*/
int32_t
PMBUS_get_direct_format_data
(
    uint8_t *data,
    int16_t *direct_coeff
)
{
    int32_t value = 0;
    int16_t m, b, R ;
    uint16_t pmbus_temp_data ;

    if( (data != NULL) && (direct_coeff != NULL) )
    {
        m = direct_coeff[0];
        b = direct_coeff[1];
        R = direct_coeff[2];

        pmbus_temp_data = data[1];
        pmbus_temp_data = (uint16_t)(pmbus_temp_data<<BITS_SHIFT)  ;
        pmbus_temp_data = pmbus_temp_data | data[0];

        value = ( int32_t)pmbus_temp_data ;

        if (m == 0)
        {
            return 0;
        }

        /* X = 1/m * (Y * 10^-R - b) */
        R = -R;

        /* scale result to milli-units for everything but fans */
        R += 3;
        b *= 1000;

        while (R > 0)
        {
            value *= 10;
            R--;
        }
        while (R < 0)
        {
            value = DIV_ROUND_CLOSEST(value, 10);
            R++;
        }

        return (int32_t)((value - b) / m);
    }
    return (value);
}

/*------------------------------------------------------------------------------
* PMBUS_get_data_from_linear_frmt()
* See "pmbus.h" for details of how to use this function.
*/
void
PMBUS_get_data_from_linear_frmt
(
    uint8_t command_format,
    int16_t exponent_coeff,
    int32_t linear_data,
    uint8_t *converted_data
)
{
    int16_t exponent = 0, mantissa;
    uint8_t negative = 0u;

    /* simple case */
    if (linear_data == 0)
    {
        converted_data[0] = 0u;
        converted_data[1] = 0u;
    }
    if ( command_format == PMBUS_VOUT_LINEAR_FORMAT)
    {
    	/* PMBus specification v1.2 part II section 8.3.1
    	 * VOUT linear mode comprises
    	 * X = V * 2^N
    	 * X is voltage in volts
    	 * V is 16 bit unsigned integer mantissa
    	 * N is 5 bit two's complement signed integer
    	 *
    	 * linear_data contains int32_t representation of V
    	 * exponent_coeff contains int16_t representation of N
    	 *
    	 * We want real world voltage value in mV so:
    	 *
    	 * X = (linear_data * 1000) * (2^exponent_coeff)
    	 */

    	/* Ultimately we want voltage in mV not V */
    	linear_data *= 1000;

    	/* Multiply mantissa by 2^N */
    	if (exponent_coeff < 0)
    	{
    		linear_data >>= -exponent_coeff;
    	}
    	else
    	{
    		linear_data <<= exponent_coeff;
    	}

    	converted_data[0] = (uint8_t)linear_data;
    	converted_data[1] = (uint8_t)((uint32_t)linear_data >> BITS_SHIFT);

#if 0	// Removed by TM 20111128

        /* LINEAR16 does not support negative voltages */
        if (linear_data >= 0)
        {
            /*
            * For a static exponents, we don't have a choice
            * but to adjust the value to it.
            */
            if (exponent_coeff  < 0)
            {
                linear_data <<= -(exponent_coeff) ;
            }
            else
            {
                linear_data >>= exponent_coeff ;
            }
            linear_data = DIV_ROUND_CLOSEST(linear_data, 1000);
            converted_data[0] = (uint8_t)linear_data;
            converted_data[1] = (uint8_t)((uint32_t)linear_data >> BITS_SHIFT);
        }
#endif	// Removed by TM 20111128
    }
    else
    {
        if (linear_data < 0)
        {
            negative = 1u;
            linear_data = -linear_data;
        }

        /*
        * For simplicity, convert fan data to milli-units
        * before calculating the exponent.
        */
        linear_data = linear_data * 1000;

        /* Reduce large mantissa until it fits into 10 bit */
        while ((linear_data >= MAX_MANTISSA )&& (exponent < 15))
        {
            exponent++;
            linear_data >>= 1;
        }

        /* Increase small mantissa to improve precision */
        while ( (linear_data < MIN_MANTISSA) && (exponent > -15))
        {
            exponent--;
            linear_data <<= 1;
        }

        /* Convert mantissa from milli-units to units */
        mantissa = DIV_ROUND_CLOSEST(linear_data, 1000);

        /* Ensure that resulting number is within range */
        if (mantissa > 0x3ff)
        {
            mantissa = 0x3ff;
        }
        /* restore sign */
        if (negative )
        {
            mantissa = -mantissa;
        }

        /* Convert to 5 bit exponent, 11 bit mantissa */
        linear_data = ((mantissa & 0x7FF) | ((exponent << 11) & 0xf800))& 0xFFFF;
        converted_data[0] = (uint8_t)linear_data ;
        converted_data[1] =  (uint8_t)((uint32_t)linear_data >> BITS_SHIFT);
    }
}

/*------------------------------------------------------------------------------
* PMBUS_get_data_from_direct_frmt()
* See "pmbus.h" for details of how to use this function.
*/
void
PMBUS_get_data_from_direct_frmt
(
    int16_t *direct_coeff,
    int32_t direct_data,
    uint8_t *converted_data
)
{
    int16_t m, b, R ;

    if( direct_coeff != NULL )
    {
        m = direct_coeff[0];
        b = direct_coeff[1];
        R = direct_coeff[2];

        /* Calculate Y = (m * X + b) * 10^R */
        R -= 3;        /* Adjust R and b for data in milli-units */
        b *= 1000;

        /* This should be within range.*/
        direct_data = (direct_data * m) + b;

        while (R > 0)
        {
            direct_data *= 10;
            R--;
        }
        while (R < 0)
        {
            direct_data = DIV_ROUND_CLOSEST(direct_data, 10);
            R++;
        }
        converted_data[0] = (uint8_t)direct_data ;
        converted_data[1] = (uint8_t)((uint32_t)direct_data >> BITS_SHIFT);
    }
    else
    {
        converted_data[0] = 0u ;
        converted_data[1] = 0u ;
    }
}

/*------------------------------------------------------------------------------
* pmbus_list_addfront() - Linked list related static functions
* Function that adds a new node to the beginning of a linked list.
*/
static pmbus_error_code_t
pmbus_list_addfront
(
    pmbus_slave_info_t *pmbus_slave_instance
)
{
    pmbus_node_t *tempnode;
    pmbus_error_code_t error_code = PMBUS_FAILURE ;

    /*Create the new node and copy data.*/
    tempnode = (pmbus_node_t *)malloc(sizeof(pmbus_node_t));
    if( tempnode!= NULL )
    {
        tempnode->pmbus_slave_instance = pmbus_slave_instance ;

        if (pmbus_channel_list == NULL)
        {
            pmbus_channel_list = tempnode;
            pmbus_channel_list->next = NULL;
        }
        else
        {
            /*
            * Insert the new node before current pmbus_channel_list,
            * then make the new node the pmbus_channel_list.
            */
            tempnode->next = pmbus_channel_list;
            pmbus_channel_list = tempnode;
        }
        listlen++;
        error_code = PMBUS_SUCCESS ;
    }
    return(error_code);
}

/*------------------------------------------------------------------------------
* pmbus_list_addend() - Linked list related static functions
* Function that adds a new node to the end of a linked list.
*/
static pmbus_error_code_t
pmbus_list_addend
(
    pmbus_slave_info_t *pmbus_slave_instance
)
{
    pmbus_node_t *tempnode;
    pmbus_node_t *currentnode;
    pmbus_error_code_t error_code = PMBUS_FAILURE ;

    /*Create the new node and copy data.*/
    tempnode = (pmbus_node_t *)malloc(sizeof(pmbus_node_t));

    if(tempnode !=NULL )
    {
        tempnode->pmbus_slave_instance = pmbus_slave_instance ;

        /*Traverse the list to the last node.*/
        currentnode = pmbus_channel_list;
        while (currentnode->next != NULL)
        {
            currentnode = currentnode->next;
        }

        /*Set the new node's pointer to NULL.*/
        tempnode->next = NULL;

        /*Set the last node pointer's next to the new node.*/
        currentnode->next = tempnode;
        listlen++;
        error_code = PMBUS_SUCCESS ;
    }
    return(error_code);
}

/*------------------------------------------------------------------------------
* pmbus_list_find_position() - Linked list related static functions
* Determines which position in the list a node should be placed.
*/
static uint8_t
pmbus_list_find_position
(
    uint8_t slave_address
)
{
    pmbus_node_t *currentnode;
    uint8_t position = 1;

    /*If the list is not empty, traverse it.*/
    currentnode = pmbus_channel_list;

    if (pmbus_channel_list != NULL)
    {
        while (currentnode->next != NULL)
        {
            if (slave_address >= (currentnode->pmbus_slave_instance->slave_address))
                return position;
            currentnode = currentnode->next;
            position++;
        }
    }
    return position;
}

/*------------------------------------------------------------------------------
* pmbus_list_get_slave_info() - Linked list related static functions
* Determines pmbus infomation based on slave address and i2c channel.
*/
static pmbus_slave_info_t *
pmbus_list_get_slave_info
(
    uint8_t slave_address,
    ui2c_channel_t *i2c_channel
)
{
pmbus_node_t *currentnode;

/*If the list is not empty, traverse it.*/
currentnode = pmbus_channel_list;

if (pmbus_channel_list != NULL)
{

    while (currentnode->next != NULL)
    {
        if ( (slave_address == (currentnode->pmbus_slave_instance->slave_address))
                            &&
            (i2c_channel == (currentnode->pmbus_slave_instance->i2c_channel))
        )
        {
            return ( currentnode->pmbus_slave_instance );
        }

        currentnode = currentnode->next;
    }
}
return NULL;
}

/*------------------------------------------------------------------------------
* pmbus_list_addNode() - Linked list related static functions
* Adds a new node by determining where in the list it should go
*/
static pmbus_error_code_t
pmbus_list_addNode
(
    pmbus_slave_info_t *pmbus_slave_instance
)
{
    pmbus_node_t *tempnode;
    pmbus_node_t *previousnode;
    pmbus_node_t *currentnode;
    pmbus_error_code_t error_code = PMBUS_FAILURE ;

    uint8_t currentpos = 1u;
    uint8_t position = pmbus_list_find_position(pmbus_slave_instance->slave_address);

    currentnode = pmbus_channel_list;

    if (position == 1u)
    {
        /*Add to beginning of list. */
        error_code = pmbus_list_addfront(pmbus_slave_instance );
    }
    else if ( position >= listlen)
    {
        /* Add to end of list. */
        error_code = pmbus_list_addend ( pmbus_slave_instance );
    }
    else
    {
        /*Add somewhere in the middle of the list. */
        while (currentpos < position)
        {
            /*traverse the list until we find the location for the new node.*/
            currentpos++;
            previousnode = currentnode;
            currentnode = currentnode->next;
        }
        tempnode = (struct llnode *)malloc(sizeof(pmbus_node_t));

        if(tempnode != NULL)
        {
            tempnode->pmbus_slave_instance = pmbus_slave_instance ;
            previousnode->next = tempnode;
            tempnode->next = currentnode;
            listlen++;
            error_code = PMBUS_SUCCESS;
        }
    }
    return(error_code);
}

/*------------------------------------------------------------------------------
* pmbus_get_crc8()
* CRC-8 calculation for Packet error check ( PEC)
*/
static uint8_t
pmbus_get_crc8
(
    uint8_t *message,
    uint8_t nBytes
)
{
    uint8_t data;
    uint8_t remainder = 0u;
    uint8_t inc;

    /* Divide the message by the polynomial, a byte at a time. */
    for (inc = 0u; inc < nBytes; ++inc)
    {
        data = message[inc] ^ (remainder);
        remainder = crcTable[data] ^ (remainder << BITS_SHIFT);
    }

    /* The final remainder is the CRC. */
    return (remainder);
}

#ifdef __cplusplus
}
#endif
