//*****************************************************************************
//
//  am_hal_adc.c
//! @file
//!
//! @brief Functions for interfacing with the Analog to Digital Converter.
//!
//! @addtogroup adc2 Analog-to-Digital Converter (ADC)
//! @ingroup apollo2hal
//! @{
//
//*****************************************************************************

//*****************************************************************************
//
// Copyright (c) 2017, Ambiq Micro
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// This is part of revision v1.2.10-2-gea660ad-hotfix2 of the AmbiqSuite Development Package.
//
//*****************************************************************************

#include "am_mcu_apollo.h"

//*****************************************************************************
//
//! @brief Private SRAM view of temperature trims.
//!
//! This static SRAM union is private to the ADC HAL functions.
//
//*****************************************************************************
static union
{
    //! These trim values are loaded as uint32_t values.
    struct
    {
              //! Temperature of the package test head (in degrees Kelvin)
              uint32_t ui32CalibrationTemperature;

              //! Voltage corresponding to temperature measured on test head.
              uint32_t ui32CalibrationVoltage;

              //! ADC offset voltage measured on the package test head.
              uint32_t ui32CalibrationOffset;

              //! Flag if default (guess) or measured.
              bool bMeasured;
    } ui32;
    //! These trim values are accessed as floats when used in temp calculations.
    struct
    {
              //! Temperature of the package test head in degrees Kelvin
              float    fCalibrationTemperature;

              //! Voltage corresponding to temperature measured on test head.
              float    fCalibrationVoltage;

              //! ADC offset voltage measured on the package test head.
              float    fCalibrationOffset;

              //! Flag if default (guess) or measured.
              float fMeasuredFlag;
    } flt;
} priv_temp_trims;

//*****************************************************************************
//
//! @brief Configure the ADC.
//!
//! @param psConfig - pointer to the configuration structure for the ADC.
//!
//! This function may be used to perform the initial setup of the ADC based on
//! setting found in a configuration structure.
//!
//! @return None.
//
//*****************************************************************************
void
am_hal_adc_config(am_hal_adc_config_t *psConfig)
{
    //
    // Set general ADC configuration parameters.
    //
    AM_REG(ADC, CFG) = (psConfig->ui32Clock |
                        psConfig->ui32TriggerConfig |
                        psConfig->ui32Reference |
                        psConfig->ui32ClockMode |
                        psConfig->ui32PowerMode |
                        psConfig->ui32Repeat |
                        AM_REG_ADC_CFG_ADCEN(1));

    //
    // Grab the temperature trims.
    //
    priv_temp_trims.ui32.ui32CalibrationTemperature =
                  am_hal_flash_load_ui32(AM_HAL_ADC_CALIB_TEMP_ADDR);
    priv_temp_trims.ui32.ui32CalibrationVoltage     =
                  am_hal_flash_load_ui32(AM_HAL_ADC_CALIB_AMBIENT_ADDR);
    priv_temp_trims.ui32.ui32CalibrationOffset      =
                  am_hal_flash_load_ui32(AM_HAL_ADC_CALIB_ADC_OFFSET_ADDR);

    if ( (priv_temp_trims.ui32.ui32CalibrationTemperature == 0xffffffff)    ||
         (priv_temp_trims.ui32.ui32CalibrationVoltage     == 0xffffffff)    ||
         (priv_temp_trims.ui32.ui32CalibrationOffset      == 0xffffffff) )
    {
        //
        // Since the device has not been calibrated on the tester, we'll load
        // default calibration values.  These default values should result
        // in worst-case temperature measurements of +-6 degress C.
        //
        priv_temp_trims.flt.fCalibrationTemperature = AM_HAL_ADC_CALIB_TEMP_DEFAULT;
        priv_temp_trims.flt.fCalibrationVoltage     = AM_HAL_ADC_CALIB_AMBIENT_DEFAULT;
        priv_temp_trims.flt.fCalibrationOffset      = AM_HAL_ADC_CALIB_ADC_OFFSET_DEFAULT;
        priv_temp_trims.ui32.bMeasured = false;
    }
    else
    {
        priv_temp_trims.ui32.bMeasured = true;
    }
}

//*****************************************************************************
//
//! @brief Get the temperature trim parameters after configuring the ADC.
//!
//! @param pfTemp - pointer to a location to store the calibration temperature.
//! @param pfVoltage - pointer to a location to store the calibration voltage.
//! @param pfOffsetV - pointer to a location to store the calibration offset.
//!
//! This function may be used to access the actual temperature sensor trim
//! values from the private structure.
//!
//! WARNING: only call this after the ADC has been configured with
//!          am_hal_adc_config.
//!
//! @return None.
//
//*****************************************************************************
void
am_hal_adc_temp_trims_get(float * pfTemp, float * pfVoltage, float * pfOffsetV)
{
    //
    // Return trim temperature as a float, if you can.
    //
    if ( pfTemp != NULL )
    {
        *pfTemp = priv_temp_trims.flt.fCalibrationTemperature;
    }

    //
    // Return trim voltage as a float, if you can.
    //
    if ( pfVoltage != NULL )
    {
        *pfVoltage = priv_temp_trims.flt.fCalibrationVoltage;
    }

    //
    // Return trim ADC offset voltage as a float, if you can.
    //
    if ( pfOffsetV != NULL )
    {
        *pfOffsetV = priv_temp_trims.flt.fCalibrationOffset;
    }
}

//*****************************************************************************
//
//! @brief Set the ADC window parameters.
//!
//! @param ui32Upper - the upper limit for the ADC window.
//! @param ui32Upper - the lower limit for the ADC window.
//!
//! This function may be used to change the ADC window parameters. Please note
//! that the upper and lower limits are only 16-bits wide in the ADC hardware.
//! This function will ignore the upper 16 bits of these arguments.
//!
//! @return None.
//
//*****************************************************************************
void
am_hal_adc_window_set(uint32_t ui32Upper, uint32_t ui32Lower)
{
    //
    // Set the window limits for the ADC.
    //
     AM_BFW(ADC, WULIM, ULIM, ui32Upper);
     AM_BFW(ADC, WLLIM, LLIM, ui32Lower);
}

//*****************************************************************************
//
//! @brief Configure a single ADC slot.
//!
//! @param ui32SlotNumber - the number of the ADC slot to be configured.
//! @param ui32SlotConfig - contains slot-specific options.
//!
//! This function may be used to configure the settings for an individual ADC
//! slot. The parameter \b ui32SlotConfig should be the logical 'OR' of a slot
//! average macro, a slot hold-time macro, a slot channel macro, and
//! optionally, the slot window enable macro.
//!
//! @return None.
//
//*****************************************************************************
void
am_hal_adc_slot_config(uint32_t ui32SlotNumber, uint32_t ui32SlotConfig)
{
    uint32_t ui32RegOffset;

    //
    // Make sure we're accessing a real slot.
    //
    am_hal_debug_assert_msg((ui32SlotNumber & 0xFFFFFFFF0) == 0,
                            "Trying to configure an ADC slot that doesn't exist.");

    //
    // Locate the correct register for this ADC slot.
    //
    ui32RegOffset = (AM_REG_ADCn(0) + AM_REG_ADC_SL0CFG_O + (4 * ui32SlotNumber));

    //
    // Write the register with the caller's configuration value.
    //
    AM_REGVAL(ui32RegOffset) = ui32SlotConfig;
}

//*****************************************************************************
//
//! @brief Peek at the next fifo entry.
//!
//! This function reads the oldest value in the ADC sample fifo but doesn't
//! actually advance the fifo to the next entry. This function is useful when
//! you need information from the fifo but you don't want to also empty the
//! fifo. This could be helpful if you want to check the FIFO depth without
//! pulling any data out.
//!
//! The value returned by this function is the raw 32-bit value provided by the
//! ADC hardware. In order to interpret this value, you will need to use one of
//! the following macros.
//!
//! @return 32-bit FIFO entry.
//!
//
//*****************************************************************************
uint32_t
am_hal_adc_fifo_peek(void)
{
    uint32_t ui32FIFOValue;

    //
    // Grab a value from the ADC FIFO.
    //
    ui32FIFOValue = AM_REG(ADC, FIFO);

    //
    // Return FIFO entry.
    //
    return ui32FIFOValue;
}

//*****************************************************************************
//
//! @brief
//!
//! This function reads the oldest value in the ADC fifo and then pops the
//! fifo. Use this function when you actually want to pull data out of the
//! fifo.
//!
//! @return 32-bit FIFO entry.
//!
//
//*****************************************************************************
uint32_t
am_hal_adc_fifo_pop(void)
{
    uint32_t ui32FIFOValue;

    //
    // Grab a value from the ADC FIFO.
    //
    ui32FIFOValue = AM_REG(ADC, FIFO);

    //
    // Pop the FIFO.
    //
    AM_REG(ADC, FIFO) = 0;

    //
    // Return FIFO valid bits.
    //
    return ui32FIFOValue;
}

//*****************************************************************************
//
//! @brief Issue Software Trigger to the ADC.
//!
//! This function issues the software trigger to the ADC.
//!
//! @return None.
//
//*****************************************************************************
void
am_hal_adc_trigger(void)
{
    //
    // Write to the Software trigger register in the ADC.
    //
    AM_REG(ADC, SWT) = 0x37;
}

//*****************************************************************************
//
//! @brief Enable the ADC.
//!
//! Use this function to enable the ADC.
//!
//! @return None.
//
//*****************************************************************************
void
am_hal_adc_enable(void)
{
    //
    // Enable the ADC.
    //
    AM_BFW(ADC, CFG, ADCEN, 0x1);
}

//*****************************************************************************
//
//! @brief Disable the ADC.
//!
//! Use this function to disable the ADC.
//!
//! @return None.
//
//*****************************************************************************
void
am_hal_adc_disable(void)
{
    //
    // Disable the ADC.
    //
    AM_BFW(ADC, CFG, ADCEN, 0x0);
}

//*****************************************************************************
//
//! @brief Enable selected ADC Interrupts.
//!
//! @param ui32Interrupt - Use the macro bit fields provided in am_hal_adc.h.
//!
//! Use this function to enable the ADC interrupts.
//!
//! @return None.
//
//*****************************************************************************
void
am_hal_adc_int_enable(uint32_t ui32Interrupt)
{
    //
    // Enable the interrupts.
    //
    AM_REG(ADC, INTEN) |= ui32Interrupt;
}

//*****************************************************************************
//
//! @brief Return enabled ADC Interrupts.
//!
//! Use this function to get all enabled ADC interrupts.
//!
//! @return enabled ADC Interrupts.
//
//*****************************************************************************
uint32_t
am_hal_adc_int_enable_get(void)
{
    //
    // Return enabled interrupts.
    //
    return AM_REG(ADC, INTEN);
}

//*****************************************************************************
//
//! @brief Disable selected ADC Interrupts.
//!
//! @param ui32Interrupt - Use the macro bit fields provided in am_hal_adc.h.
//!
//! Use this function to disable the ADC interrupts.
//!
//! @return None.
//
//*****************************************************************************
void
am_hal_adc_int_disable(uint32_t ui32Interrupt)
{
    //
    // Disable the interrupts.
    //
    AM_REG(ADC, INTEN) &= ~ui32Interrupt;
}

//*****************************************************************************
//
//! @brief Clear selected ADC Interrupts.
//!
//! @param ui32Interrupt - Use the macro bit fields provided in am_hal_adc.h.
//!
//! Use this function to clear the ADC interrupts.
//!
//! @return None.
//
//*****************************************************************************
void
am_hal_adc_int_clear(uint32_t ui32Interrupt)
{
    //
    // Clear the interrupts.
    //
    AM_REG(ADC, INTCLR) = ui32Interrupt;
}

//*****************************************************************************
//
//! @brief Set selected ADC Interrupts.
//!
//! @param ui32Interrupt - Use the macro bit fields provided in am_hal_adc.h.
//!
//! Use this function to set the ADC interrupts.
//!
//! @return None.
//
//*****************************************************************************
void
am_hal_adc_int_set(uint32_t ui32Interrupt)
{
    //
    // Set the interrupts.
    //
    AM_REG(ADC, INTSET) = ui32Interrupt;
}

//*****************************************************************************
//
//! @brief Return either enabled or raw selected ADC interrupt status.
//!
//! @param bEnabledOnly - return the status of only the enabled interrupts.
//!
//! Use this function to get the ADC interrupt status.
//!
//! @return enabled or raw ADC interrupt status.
//
//*****************************************************************************
uint32_t
am_hal_adc_int_status_get(bool bEnabledOnly)
{
    //
    // Return the status.
    //
    if (bEnabledOnly)
    {
        uint32_t u32RetVal = AM_REG(ADC, INTEN);
        u32RetVal &= AM_REG(ADC, INTSTAT);
        return u32RetVal;
    }
    else
    {
        return AM_REG(ADC, INTSTAT);
    }
}

//*****************************************************************************
//
//! @brief Return temperature in degrees C of supplied voltage.
//!
//! @param fVoltage - return the temperature corresponding to this voltage.
//!
//! Use this function to convert volts from the temperature sensor into degrees
//! C.  Caller converts ADC binary code to volts based on reference used.
//! This routine looks up the trim parameters and returns corrected temperature.
//!
//! The computation is based on a line running through 0 degrees K.
//! We find the slope from the trimmed temperature calibration point.
//!
//!
//! @return the temperature in degrees C.
//
//*****************************************************************************
float
am_hal_adc_volts_to_celsius(float fVoltage)
{
    float fTemp;

    //
    // Get calibration temperature from trimmed values & convert to degrees K.
    //
    float fCalibration_temp = priv_temp_trims.flt.fCalibrationTemperature;

    //
    // Get remaining trimmed values.
    //
    float fCalibration_voltage = priv_temp_trims.flt.fCalibrationVoltage;
    float fCalibration_offset  = priv_temp_trims.flt.fCalibrationOffset;

    //
    // Compute the temperature.
    //
    fTemp  = fCalibration_temp;
    fTemp /= (fCalibration_voltage - fCalibration_offset);
    fTemp *= (fVoltage - fCalibration_offset);

    //
    // Give it back to the caller in Celsius.
    //
    return fTemp - 273.15f;
}

//*****************************************************************************
//
// End Doxygen group.
//! @}
//
//*****************************************************************************
