/*******************************************************************************
 * (c) Copyright 2010 Actel Corporation.  All rights reserved.
 *
 *  This file contains the implementation of the functions used to dynamically
 *  control the linear transforms applied by the ACE post processing engine to
 *  the samples read from the SSE.
 *
 * SVN $Revision: 3954 $
 * SVN $Date: 2011-11-14 17:01:06 +0000 (Mon, 14 Nov 2011) $
 */

#include "mss_ace.h"
#include "mss_ace_configurator.h"
#include "mtd_data.h"
#include "envm_layout.h"
#include "../../CMSIS/a2fxxxm3.h"
#include "../../CMSIS/mss_assert.h"
#include "../../drivers_config/mss_ace/ace_config.h"

#ifdef __cplusplus
extern "C" {
#endif 

/*
 * The ACE_set_linear_transform() is only available when using ACE configuration
 * files generated by Libero 9.1 or later.
 */
#ifdef ACE_CFG_DATA_FORMAT_VERSION

/*------------------------------------------------------------------------------
 * Masks ans shift values used to derive the ABPS ranges from the analog block
 * configuration.
 */
#define ABPS1_CFG_BITS_MASK     (uint32_t)0x06
#define ABPS1_CFG_BITS_SHIFT    (uint32_t)1

#define ABPS2_CFG_BITS_MASK     (uint32_t)0x60
#define ABPS2_CFG_BITS_SHIFT    (uint32_t)5

/*------------------------------------------------------------------------------
 * One Bit DAC definitions.
 */
#define OBD_CURRENT     (uint32_t)1
#define OBD_VOLTAGE     (uint32_t)0

#define OBD_MODE_MASK    (uint32_t)0x01
#define OBD_CHOPPING_MASK    (uint32_t)0x02

/*-------------------------------------------------------------------------*//**
   Neutral factor and offset for m*x + c trnasform.
 */
#define NEUTRAL_M_FACTOR    0x4000
#define NEUTRAL_C_OFFSET    0x0000

/*-------------------------------------------------------------------------*//**
  Enumearation of the various input channel types. This is used to differentiate
  between channel types in order to extract the relevant factory calibration
  data(m1 and c1).
 */
typedef enum channel_type
{
    ABPS1_CHAN = 0,
    ABPS2_CHAN,
    CMB_CHAN,
    TMB_CHAN,
    DIRECT_ADC_INPUT_CHAN,
    OBDOUT_CHAN,
    FLOATING_CHAN
} cal_channel_type_t;

/*-------------------------------------------------------------------------*//**
  This data structure is used to store factory calibration data for a specific
  analog input.
 */
typedef struct __channel_calibration_t
{
    uint16_t mext;
    uint16_t m1;
    uint16_t c1;
} channel_calibration_t;

/*-------------------------------------------------------------------------*//**
  Local functions
 */
int32_t extend_sign
(
    uint16_t x
);

uint32_t adjust_to_24bit_ace_format
(
    int64_t signed48
);

uint32_t adjust_to_16bit_ace_format
(
    int64_t signed48
);

void get_calibration
(
    adc_channel_id_t channel_id,
    channel_calibration_t * p_calibration
);

void write_transform_coefficients
(
    ace_channel_handle_t channel_handle,
	uint32_t m,
	uint32_t c
);

/*-------------------------------------------------------------------------*//**
	  
 */
extern const uint8_t g_ace_external_varef_used[ACE_NB_OF_ADC];

extern ace_channel_desc_t g_ace_channel_desc_table[ACE_NB_OF_INPUT_CHANNELS];

extern const ppe_transforms_desc_t g_ace_ppe_transforms_desc_table[ACE_NB_OF_INPUT_CHANNELS];

/*------------------------------------------------------------------------------
 * Pointer to the manufacturing test data containing trimming information
 * generated during manufacturing.
 */
static const mtd_data_t * const p_mtd_data = (mtd_data_t *)MTD_ADDRESS;

/*-------------------------------------------------------------------------*//**
  See "mss_ace.h" for details of how to use this function.
 */
int16_t ACE_get_default_m_factor
(
    ace_channel_handle_t channel_handle
)
{
    ASSERT( channel_handle < NB_OF_ACE_CHANNEL_HANDLES );
    
    return g_ace_ppe_transforms_desc_table[channel_handle].m_ppe_offset;
}

/*-------------------------------------------------------------------------*//**
  See "mss_ace.h" for details of how to use this function.
 */
int16_t ACE_get_default_c_offset
(
    ace_channel_handle_t channel_handle
)
{
    ASSERT( channel_handle < NB_OF_ACE_CHANNEL_HANDLES );
    
    return g_ace_ppe_transforms_desc_table[channel_handle].c_ppe_offset;
}

/*-------------------------------------------------------------------------*//**
  See "mss_ace.h" for details of how to use this function.
  
  	m = m2 * m1 * mext
	c = (m2 * c1 * mext) + (c2 * mext)
 */
void ACE_set_linear_transform
(
    ace_channel_handle_t channel_handle,
	int16_t m2,
	int16_t c2
)
{
    adc_channel_id_t channel_id;
	uint32_t m;
	uint32_t c;
	int32_t m32;
	int64_t m64;
	int32_t c32;
	int64_t c64_1;
    int64_t c64_2;
    uint16_t m1;
    uint16_t c1;
    uint16_t mext;
    
    channel_calibration_t calibration = {0x4000u, 0x4000u, 0x0000u};
    
    ASSERT( channel_handle < NB_OF_ACE_CHANNEL_HANDLES );
    
    if(channel_handle < NB_OF_ACE_CHANNEL_HANDLES)
    {
        channel_id = g_ace_channel_desc_table[channel_handle].signal_id;
        
        get_calibration(channel_id, &calibration);
        
        m1 = calibration.m1;
        c1 = calibration.c1;
        
        mext = calibration.mext;

        /* 
         * m = m2 * m1 * mext
         */
        m32 = extend_sign(m2) * extend_sign(m1);
        m64 = (int64_t)m32 * extend_sign(mext);
        
        /* Convert 48-bit result to 32-bit ACE format result. */
        m = adjust_to_16bit_ace_format(m64);

        /*
         * c = (m2 * c1 * mext) + (c2 * mext)
         */
        c32 = extend_sign(m2) * extend_sign(c1);
        c64_1 = (int64_t)c32 * extend_sign(mext);

        c64_2 = ((int64_t)(extend_sign(c2) * extend_sign(mext))) << 14;
        
        c = adjust_to_24bit_ace_format(c64_1 + c64_2);
        
        write_transform_coefficients(channel_handle, m, c);
    }
}

/*-------------------------------------------------------------------------*//**
    Extend 16-bit signed number to 32-bit signed number.
 */
int32_t extend_sign
(
    uint16_t x
)
{
    int32_t y;
    const uint32_t sign_bit_mask = 0x00008000u;
    
    y = (x ^ sign_bit_mask) - sign_bit_mask;
    
    return y;
}

/*-------------------------------------------------------------------------*//**
  Take a 48-bit signed number, adjust it for saturation in the range -8 to
  +7.999, translate into 24-bit ACE format.
 */
uint32_t adjust_to_24bit_ace_format
(
    int64_t signed48
)
{
    int32_t ace24_format;
    const int64_t MAX_POSITIVE = 0x00001FFFFFFFFFFFuLL; /* +7.9999 */
    const int64_t MIN_NEGATIVE = 0xFFFF200000000000uLL; /* -8 */
    
    /* Check saturation. */
    if(signed48 > MAX_POSITIVE)
    {
        signed48 = MAX_POSITIVE;
    }
    else if(signed48 < MIN_NEGATIVE)
    {
        signed48 = MIN_NEGATIVE;
    }
    
    /* Adjust to 24-bit ACE format. */
    ace24_format = (uint32_t)(signed48 >> 14);
    
    return ace24_format;
}

/*-------------------------------------------------------------------------*//**
  Take a 48-bit signed number, adjust it for saturation in the range -8 to
  +7.999, translate into 16-bit ACE format.
 */
uint32_t adjust_to_16bit_ace_format
(
    int64_t signed48
)
{
    int32_t ace24_format;
    const int64_t MAX_POSITIVE = 0x00001FFFFFFFFFFFuLL; /* +7.9999 */
    const int64_t MIN_NEGATIVE = 0xFFFF200000000000uLL; /* -8 */
    
    /* Check saturation. */
    if(signed48 > MAX_POSITIVE)
    {
        signed48 = MAX_POSITIVE;
    }
    else if(signed48 < MIN_NEGATIVE)
    {
        signed48 = MIN_NEGATIVE;
    }
    
    /* Adjust to 24-bit ACE format. */
    ace24_format = (uint32_t)(signed48 >> 20);
    
    return ace24_format;
}

/*-------------------------------------------------------------------------*//**
	  
 */
void get_calibration
(
    adc_channel_id_t channel_id,
    channel_calibration_t * p_calibration
)
{
    const uint32_t channel_mask = 0x0000000F;
    const uint32_t CMB_MUX_SEL_MASK = 0x01;
    const uint32_t TMB_MUX_SEL_MASK = 0x01;

#ifdef SMARTFUSION_060_DEVICE
    const cal_channel_type_t channel_type_lut[16] =
    {
        FLOATING_CHAN,
        ABPS1_CHAN,
        ABPS2_CHAN,
        CMB_CHAN,
        TMB_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN
    };
#else
    const cal_channel_type_t channel_type_lut[16] =
    {
        FLOATING_CHAN,
        ABPS1_CHAN,
        ABPS2_CHAN,
        CMB_CHAN,
        TMB_CHAN,
        ABPS1_CHAN,
        ABPS2_CHAN,
        CMB_CHAN,
        TMB_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        DIRECT_ADC_INPUT_CHAN,
        FLOATING_CHAN,
        FLOATING_CHAN,
        OBDOUT_CHAN
    };
#endif

    cal_channel_type_t channel_type;
    uint32_t channel_nb;
    uint32_t adc_nb;
    uint32_t range;
    uint32_t quad_id;
    mtd_calibration_mc_t const * p_mc_coeff = 0;
    
    channel_nb = channel_id & channel_mask;
    channel_type = channel_type_lut[channel_nb];
    adc_nb = ((uint32_t)channel_id & 0x30u) >> 4u;
    
    quad_id = adc_nb * 2;
    
    if ( (channel_nb > 4) && (channel_nb < 9) ) { ++quad_id; }
    
    switch ( channel_type )
    {
    case ABPS1_CHAN:
        range = (ACE->ACB_DATA[quad_id].b8 & ABPS1_CFG_BITS_MASK) >> ABPS1_CFG_BITS_SHIFT;
        p_mc_coeff = &p_mtd_data->abps_calibration[quad_id][0][range];
        break;
        
    case ABPS2_CHAN:
        range = (ACE->ACB_DATA[quad_id].b8 & ABPS2_CFG_BITS_MASK) >> ABPS2_CFG_BITS_SHIFT;
        p_mc_coeff = &p_mtd_data->abps_calibration[quad_id][1][range];
        break;
        
    case CMB_CHAN:
        {
            uint32_t cmb_mux_sel = (uint32_t)ACE->ACB_DATA[quad_id].b9 & CMB_MUX_SEL_MASK;
            if ( cmb_mux_sel == 0 )
            {   /* current monitor */
                p_mc_coeff = &p_mtd_data->cm_calibration[quad_id];
            }
            else
            {   /* direct input */
                p_mc_coeff = &p_mtd_data->quads_direct_input_cal[quad_id][0];
            }
        }
        break;
        
    case TMB_CHAN:
        {
            uint32_t tmb_mux_sel = (uint32_t)ACE->ACB_DATA[quad_id].b10 & TMB_MUX_SEL_MASK;
            if ( tmb_mux_sel == 0 )
            {   /* temperature monitor */
                p_mc_coeff = &p_mtd_data->tm_calibration[quad_id];
            }
            else
            {   /* direct input */
                p_mc_coeff = &p_mtd_data->quads_direct_input_cal[quad_id][1];
            }
        }
        break;
        
    case DIRECT_ADC_INPUT_CHAN:
        {
#ifdef SMARTFUSION_060_DEVICE
            const uint32_t channel_to_direct_in_lut[16]
                = { 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
#else
            const uint32_t channel_to_direct_in_lut[16]
                = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0 };
#endif
            uint32_t direct_in_id;
            
            direct_in_id = channel_to_direct_in_lut[channel_id & channel_mask];
            p_mc_coeff = &p_mtd_data->adc_direct_input_cal[adc_nb][direct_in_id];
        }
        break;
        
    case OBDOUT_CHAN:
        {
            uint32_t obd_mode = (uint32_t)ACE->ACB_DATA[quad_id].b6 & OBD_MODE_MASK;
            uint32_t chopping_option = (uint32_t)ACE->ACB_DATA[quad_id].b6 & OBD_CHOPPING_MASK;
            if (obd_mode > 0)
            {
                obd_mode = 1;
            }
            if (chopping_option > 0)
            {
                chopping_option = 1;
            }
            p_mc_coeff = &p_mtd_data->obd_calibration[adc_nb][obd_mode][chopping_option];
        }
        break;
       
    case FLOATING_CHAN:
    default:
        /* Give neutral values is invalid channel. */
        p_calibration->m1 = NEUTRAL_M_FACTOR;
        p_calibration->c1 = NEUTRAL_C_OFFSET;
        break;
    }
    
    if (p_mc_coeff != 0)
    {
        p_calibration->m1 = p_mc_coeff->m;
        p_calibration->c1 = p_mc_coeff->c;
        
    }
    
    /*--------------------------------------------------------------------------
      Retrieve the value of the mext factor. This depends if external VAREF is
      used by the ADC sampling the analog input channel.
     */
    if (g_ace_external_varef_used[adc_nb])
    {
        p_calibration->mext = p_mtd_data->global_settings.varef_m;
    }
    else
    {
        p_calibration->mext = NEUTRAL_M_FACTOR;
    }
}

/*-------------------------------------------------------------------------*//**
  Write new m and c transform factors into the PPE RAM. The m and c factors
  should be in 32-bit ACE number format. The factors will be merged with
  relevant PE opcode into PPE RAM. The 32-bit factors are shifted right by one
  byte giving a 24-bit ACE number which is then merged with an 8-bit PPE opcode
  located in the most significant byte of the PPE RAM location.
 */
void write_transform_coefficients
(
    ace_channel_handle_t channel_handle,
	uint32_t m,
	uint32_t c
)
{
    uint16_t m_ppe_offset;
    uint16_t c_ppe_offset;
    const uint32_t PPE_OPCODE_MASK = 0xFF000000u;
    
    m_ppe_offset = g_ace_ppe_transforms_desc_table[channel_handle].m_ppe_offset;
    c_ppe_offset = g_ace_ppe_transforms_desc_table[channel_handle].c_ppe_offset;
    
    ACE->PPE_RAM_DATA[m_ppe_offset]
        = (ACE->PPE_RAM_DATA[m_ppe_offset] & PPE_OPCODE_MASK) | (m >> 8u);
        
    ACE->PPE_RAM_DATA[c_ppe_offset]
        = (ACE->PPE_RAM_DATA[c_ppe_offset] & PPE_OPCODE_MASK) | (c >> 8u);
}

#endif  /* ACE_CFG_DATA_FORMAT_VERSION */

#ifdef __cplusplus
}
#endif
