/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2021 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_adc16.h"

/* Component ID definition, used by tools. */
#ifndef FSL_COMPONENT_ID
#define FSL_COMPONENT_ID "platform.drivers.adc16"
#endif

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
/*!
 * @brief Get instance number for ADC16 module.
 *
 * @param base ADC16 peripheral base address
 */
static uint32_t ADC16_GetInstance(ADC_Type *base);

/*******************************************************************************
 * Variables
 ******************************************************************************/
/*! @brief Pointers to ADC16 bases for each instance. */
static ADC_Type *const s_adc16Bases[] = ADC_BASE_PTRS;

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/*! @brief Pointers to ADC16 clocks for each instance. */
static const clock_ip_name_t s_adc16Clocks[] = ADC16_CLOCKS;
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

/*******************************************************************************
 * Code
 ******************************************************************************/
static uint32_t ADC16_GetInstance(ADC_Type *base)
{
    uint32_t instance;

    /* Find the instance index from base address mappings. */
    for (instance = 0; instance < ARRAY_SIZE(s_adc16Bases); instance++)
    {
        if (s_adc16Bases[instance] == base)
        {
            break;
        }
    }

    assert(instance < ARRAY_SIZE(s_adc16Bases));

    return instance;
}

/*!
 * brief Initializes the ADC16 module.
 *
 * param base   ADC16 peripheral base address.
 * param config Pointer to configuration structure. See "adc16_config_t".
 */
void ADC16_Init(ADC_Type *base, const adc16_config_t *config)
{
    assert(NULL != config);

    uint32_t tmp32;

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Enable the clock. */
    CLOCK_EnableClock(s_adc16Clocks[ADC16_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

    /* ADCx_CFG1. */
    tmp32 = ADC_CFG1_ADICLK(config->clockSource) | ADC_CFG1_MODE(config->resolution);
    if (kADC16_LongSampleDisabled != config->longSampleMode)
    {
        tmp32 |= ADC_CFG1_ADLSMP_MASK;
    }
    tmp32 |= ADC_CFG1_ADIV(config->clockDivider);
    if (true == config->enableLowPower)
    {
        tmp32 |= ADC_CFG1_ADLPC_MASK;
    }
    base->CFG1 = tmp32;

    /* ADCx_CFG2. */
    tmp32 = base->CFG2 & ~(ADC_CFG2_ADACKEN_MASK | ADC_CFG2_ADHSC_MASK | ADC_CFG2_ADLSTS_MASK);
    if (kADC16_LongSampleDisabled != config->longSampleMode)
    {
        tmp32 |= ADC_CFG2_ADLSTS(config->longSampleMode);
    }
    if (true == config->enableHighSpeed)
    {
        tmp32 |= ADC_CFG2_ADHSC_MASK;
    }
    if (true == config->enableAsynchronousClock)
    {
        tmp32 |= ADC_CFG2_ADACKEN_MASK;
    }
    base->CFG2 = tmp32;

    /* ADCx_SC2. */
    tmp32 = base->SC2 & ~(ADC_SC2_REFSEL_MASK);
    tmp32 |= ADC_SC2_REFSEL(config->referenceVoltageSource);
    base->SC2 = tmp32;

    /* ADCx_SC3. */
    if (true == config->enableContinuousConversion)
    {
        base->SC3 |= ADC_SC3_ADCO_MASK;
    }
    else
    {
        base->SC3 &= ~ADC_SC3_ADCO_MASK;
    }

#if defined(FSL_FEATURE_ADC16_HAS_HW_AVERAGE) && FSL_FEATURE_ADC16_HAS_HW_AVERAGE
    ADC16_SetHardwareAverage(base, config->hardwareAverageMode);
#endif /* FSL_FEATURE_ADC16_HAS_HW_AVERAGE */
}

/*!
 * brief De-initializes the ADC16 module.
 *
 * param base ADC16 peripheral base address.
 */
void ADC16_Deinit(ADC_Type *base)
{
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Disable the clock. */
    CLOCK_DisableClock(s_adc16Clocks[ADC16_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
}

/*!
 * brief Gets an available pre-defined settings for the converter's configuration.
 *
 * This function initializes the converter configuration structure with available settings. The default values are as
 * follows.
 * code
 *   config->referenceVoltageSource     = kADC16_ReferenceVoltageSourceVref;
 *   config->clockSource                = kADC16_ClockSourceAsynchronousClock;
 *   config->enableAsynchronousClock    = false;
 *   config->clockDivider               = kADC16_ClockDivider8;
 *   config->resolution                 = kADC16_ResolutionSE12Bit;
 *   config->longSampleMode             = kADC16_LongSampleDisabled;
 *   config->enableHighSpeed            = false;
 *   config->enableLowPower             = false;
 *   config->enableContinuousConversion = false;
 * endcode
 * param config Pointer to the configuration structure.
 */
void ADC16_GetDefaultConfig(adc16_config_t *config)
{
    assert(NULL != config);

    /* Initializes the configure structure to zero. */
    (void)memset(config, 0, sizeof(*config));

    config->referenceVoltageSource     = kADC16_ReferenceVoltageSourceVref;
    config->clockSource                = kADC16_ClockSourceAsynchronousClock;
    config->enableAsynchronousClock    = false;
    config->clockDivider               = kADC16_ClockDivider8;
    config->resolution                 = kADC16_ResolutionSE12Bit;
    config->longSampleMode             = kADC16_LongSampleDisabled;
    config->enableHighSpeed            = false;
    config->enableLowPower             = false;
    config->enableContinuousConversion = false;
#if defined(FSL_FEATURE_ADC16_HAS_HW_AVERAGE) && FSL_FEATURE_ADC16_HAS_HW_AVERAGE
    config->hardwareAverageMode = kADC16_HardwareAverageDisabled;
#endif /* FSL_FEATURE_ADC16_HAS_HW_AVERAGE */
}

#if defined(FSL_FEATURE_ADC16_HAS_CALIBRATION) && FSL_FEATURE_ADC16_HAS_CALIBRATION
/*!
 * brief  Automates the hardware calibration.
 *
 * This auto calibration helps to adjust the plus/minus side gain automatically.
 * Execute the calibration before using the converter. Note that the hardware trigger should be used
 * during the calibration.
 *
 * param  base ADC16 peripheral base address.
 *
 * return                 Execution status.
 * retval kStatus_Success Calibration is done successfully.
 * retval kStatus_Fail    Calibration has failed.
 */
status_t ADC16_DoAutoCalibration(ADC_Type *base)
{
    bool bHWTrigger = false;
    uint32_t tmp32;
    status_t status = kStatus_Success;

    /* The calibration would be failed when in hardwar mode.
     * Remember the hardware trigger state here and restore it later if the hardware trigger is enabled.*/
    if (0U != (ADC_SC2_ADTRG_MASK & base->SC2))
    {
        bHWTrigger = true;
        base->SC2 &= ~ADC_SC2_ADTRG_MASK;
    }

    /* Clear the CALF and launch the calibration. */
    base->SC3 |= ADC_SC3_CAL_MASK | ADC_SC3_CALF_MASK;
    while (0U == ((uint32_t)kADC16_ChannelConversionDoneFlag & ADC16_GetChannelStatusFlags(base, 0U)))
    {
        /* Check the CALF when the calibration is active. */
        if (0U != ((uint32_t)kADC16_CalibrationFailedFlag & ADC16_GetStatusFlags(base)))
        {
            status = kStatus_Fail;
            break;
        }
    }
    (void)base->R[0]; /* Dummy read to clear COCO caused by calibration. */

    /* Restore the hardware trigger setting if it was enabled before. */
    if (bHWTrigger)
    {
        base->SC2 |= ADC_SC2_ADTRG_MASK;
    }
    /* Check the CALF at the end of calibration. */
    if (0U != ((uint32_t)kADC16_CalibrationFailedFlag & ADC16_GetStatusFlags(base)))
    {
        status = kStatus_Fail;
    }
    if (kStatus_Success != status) /* Check if the calibration process is succeed. */
    {
        return status;
    }

    /* Calculate the calibration values. */
    tmp32 = base->CLP0;
    tmp32 += base->CLP1;
    tmp32 += base->CLP2;
    tmp32 += base->CLP3;
    tmp32 += base->CLP4;
    tmp32 += base->CLPS;
    tmp32    = 0x8000U | (tmp32 >> 1U);
    base->PG = tmp32;

#if defined(FSL_FEATURE_ADC16_HAS_DIFF_MODE) && FSL_FEATURE_ADC16_HAS_DIFF_MODE
    tmp32 = base->CLM0;
    tmp32 += base->CLM1;
    tmp32 += base->CLM2;
    tmp32 += base->CLM3;
    tmp32 += base->CLM4;
    tmp32 += base->CLMS;
    tmp32    = 0x8000U | (tmp32 >> 1U);
    base->MG = tmp32;
#endif /* FSL_FEATURE_ADC16_HAS_DIFF_MODE */

    return kStatus_Success;
}
#endif /* FSL_FEATURE_ADC16_HAS_CALIBRATION */

#if defined(FSL_FEATURE_ADC16_HAS_MUX_SELECT) && FSL_FEATURE_ADC16_HAS_MUX_SELECT
/*!
 * brief Sets the channel mux mode.
 *
 * Some sample pins share the same channel index. The channel mux mode decides which pin is used for an
 * indicated channel.
 *
 * param base ADC16 peripheral base address.
 * param mode Setting channel mux mode. See "adc16_channel_mux_mode_t".
 */
void ADC16_SetChannelMuxMode(ADC_Type *base, adc16_channel_mux_mode_t mode)
{
    if (kADC16_ChannelMuxA == mode)
    {
        base->CFG2 &= ~ADC_CFG2_MUXSEL_MASK;
    }
    else /* kADC16_ChannelMuxB. */
    {
        base->CFG2 |= ADC_CFG2_MUXSEL_MASK;
    }
}
#endif /* FSL_FEATURE_ADC16_HAS_MUX_SELECT */

/*!
 * brief Configures the hardware compare mode.
 *
 * The hardware compare mode provides a way to process the conversion result automatically by using hardware. Only the
 * result
 * in the compare range is available. To compare the range, see "adc16_hardware_compare_mode_t" or the appopriate
 * reference
 * manual for more information.
 *
 * param base     ADC16 peripheral base address.
 * param config   Pointer to the "adc16_hardware_compare_config_t" structure. Passing "NULL" disables the feature.
 */
void ADC16_SetHardwareCompareConfig(ADC_Type *base, const adc16_hardware_compare_config_t *config)
{
    uint32_t tmp32 = base->SC2 & ~(ADC_SC2_ACFE_MASK | ADC_SC2_ACFGT_MASK | ADC_SC2_ACREN_MASK);

    if (NULL == config) /* Pass "NULL" to disable the feature. */
    {
        base->SC2 = tmp32;
        return;
    }
    /* Enable the feature. */
    tmp32 |= ADC_SC2_ACFE_MASK;

    /* Select the hardware compare working mode. */
    switch (config->hardwareCompareMode)
    {
        case kADC16_HardwareCompareMode0:
            break;
        case kADC16_HardwareCompareMode1:
            tmp32 |= ADC_SC2_ACFGT_MASK;
            break;
        case kADC16_HardwareCompareMode2:
            tmp32 |= ADC_SC2_ACREN_MASK;
            break;
        case kADC16_HardwareCompareMode3:
            tmp32 |= ADC_SC2_ACFGT_MASK | ADC_SC2_ACREN_MASK;
            break;
        default:
            assert(false);
            break;
    }
    base->SC2 = tmp32;

    /* Load the compare values. */
    base->CV1 = ADC_CV1_CV(config->value1);
    base->CV2 = ADC_CV2_CV(config->value2);
}

#if defined(FSL_FEATURE_ADC16_HAS_HW_AVERAGE) && FSL_FEATURE_ADC16_HAS_HW_AVERAGE
/*!
 * brief Sets the hardware average mode.
 *
 * The hardware average mode provides a way to process the conversion result automatically by using hardware. The
 * multiple
 * conversion results are accumulated and averaged internally making them easier to read.
 *
 * param base  ADC16 peripheral base address.
 * param mode  Setting the hardware average mode. See "adc16_hardware_average_mode_t".
 */
void ADC16_SetHardwareAverage(ADC_Type *base, adc16_hardware_average_mode_t mode)
{
    uint32_t tmp32 = base->SC3 & ~(ADC_SC3_AVGE_MASK | ADC_SC3_AVGS_MASK);

    if (kADC16_HardwareAverageDisabled != mode)
    {
        tmp32 |= ADC_SC3_AVGE_MASK | ADC_SC3_AVGS(mode);
    }
    base->SC3 = tmp32;
}
#endif /* FSL_FEATURE_ADC16_HAS_HW_AVERAGE */

#if defined(FSL_FEATURE_ADC16_HAS_PGA) && FSL_FEATURE_ADC16_HAS_PGA
/*!
 * brief Configures the PGA for the converter's front end.
 *
 * param base    ADC16 peripheral base address.
 * param config  Pointer to the "adc16_pga_config_t" structure. Passing "NULL" disables the feature.
 */
void ADC16_SetPGAConfig(ADC_Type *base, const adc16_pga_config_t *config)
{
    uint32_t tmp32;

    if (!config) /* Passing "NULL" is to disable the feature. */
    {
        base->PGA = 0U;
        return;
    }

    /* Enable the PGA and set the gain value. */
    tmp32 = ADC_PGA_PGAEN_MASK | ADC_PGA_PGAG(config->pgaGain);

    /* Configure the misc features for PGA. */
    if (config->enableRunInNormalMode)
    {
        tmp32 |= ADC_PGA_PGALPb_MASK;
    }
#if defined(FSL_FEATURE_ADC16_HAS_PGA_CHOPPING) && FSL_FEATURE_ADC16_HAS_PGA_CHOPPING
    if (config->disablePgaChopping)
    {
        tmp32 |= ADC_PGA_PGACHPb_MASK;
    }
#endif /* FSL_FEATURE_ADC16_HAS_PGA_CHOPPING */
#if defined(FSL_FEATURE_ADC16_HAS_PGA_OFFSET_MEASUREMENT) && FSL_FEATURE_ADC16_HAS_PGA_OFFSET_MEASUREMENT
    if (config->enableRunInOffsetMeasurement)
    {
        tmp32 |= ADC_PGA_PGAOFSM_MASK;
    }
#endif /* FSL_FEATURE_ADC16_HAS_PGA_OFFSET_MEASUREMENT */
    base->PGA = tmp32;
}
#endif /* FSL_FEATURE_ADC16_HAS_PGA */

/*!
 * brief  Gets the status flags of the converter.
 *
 * param  base ADC16 peripheral base address.
 *
 * return      Flags' mask if indicated flags are asserted. See "_adc16_status_flags".
 */
uint32_t ADC16_GetStatusFlags(ADC_Type *base)
{
    uint32_t ret = 0;

    if (0U != (base->SC2 & ADC_SC2_ADACT_MASK))
    {
        ret |= (uint32_t)kADC16_ActiveFlag;
    }
#if defined(FSL_FEATURE_ADC16_HAS_CALIBRATION) && FSL_FEATURE_ADC16_HAS_CALIBRATION
    if (0U != (base->SC3 & ADC_SC3_CALF_MASK))
    {
        ret |= (uint32_t)kADC16_CalibrationFailedFlag;
    }
#endif /* FSL_FEATURE_ADC16_HAS_CALIBRATION */
    return ret;
}

/*!
 * brief  Clears the status flags of the converter.
 *
 * param  base ADC16 peripheral base address.
 * param  mask Mask value for the cleared flags. See "_adc16_status_flags".
 */
void ADC16_ClearStatusFlags(ADC_Type *base, uint32_t mask)
{
#if defined(FSL_FEATURE_ADC16_HAS_CALIBRATION) && FSL_FEATURE_ADC16_HAS_CALIBRATION
    if (0U != (mask & (uint32_t)kADC16_CalibrationFailedFlag))
    {
        base->SC3 |= ADC_SC3_CALF_MASK;
    }
#endif /* FSL_FEATURE_ADC16_HAS_CALIBRATION */
}

/*!
 * brief Configures the conversion channel.
 *
 * This operation triggers the conversion when in software trigger mode. When in hardware trigger mode, this API
 * configures the channel while the external trigger source helps to trigger the conversion.
 *
 * Note that the "Channel Group" has a detailed description.
 * To allow sequential conversions of the ADC to be triggered by internal peripherals, the ADC has more than one
 * group of status and control registers, one for each conversion. The channel group parameter indicates which group of
 * registers are used, for example, channel group 0 is for Group A registers and channel group 1 is for Group B
 * registers. The
 * channel groups are used in a "ping-pong" approach to control the ADC operation.  At any point, only one of
 * the channel groups is actively controlling ADC conversions. The channel group 0 is used for both software and
 * hardware
 * trigger modes. Channel group 1 and greater indicates multiple channel group registers for
 * use only in hardware trigger mode. See the chip configuration information in the appropriate MCU reference manual for
 * the
 * number of SC1n registers (channel groups) specific to this device.  Channel group 1 or greater are not used
 * for software trigger operation. Therefore, writing to these channel groups does not initiate a new conversion.
 * Updating the channel group 0 while a different channel group is actively controlling a conversion is allowed and
 * vice versa. Writing any of the channel group registers while that specific channel group is actively controlling a
 * conversion aborts the current conversion.
 *
 * param base          ADC16 peripheral base address.
 * param channelGroup  Channel group index.
 * param config        Pointer to the "adc16_channel_config_t" structure for the conversion channel.
 */
void ADC16_SetChannelConfig(ADC_Type *base, uint32_t channelGroup, const adc16_channel_config_t *config)
{
    assert(channelGroup < ADC_SC1_COUNT);
    assert(NULL != config);

    uint32_t sc1 = ADC_SC1_ADCH(config->channelNumber); /* Set the channel number. */

#if defined(FSL_FEATURE_ADC16_HAS_DIFF_MODE) && FSL_FEATURE_ADC16_HAS_DIFF_MODE
    /* Enable the differential conversion. */
    if (true == config->enableDifferentialConversion)
    {
        sc1 |= ADC_SC1_DIFF_MASK;
    }
#endif /* FSL_FEATURE_ADC16_HAS_DIFF_MODE */
    /* Enable the interrupt when the conversion is done. */
    if (true == config->enableInterruptOnConversionCompleted)
    {
        sc1 |= ADC_SC1_AIEN_MASK;
    }
    base->SC1[channelGroup] = sc1;
}

/*!
 * brief  Gets the status flags of channel.
 *
 * param  base         ADC16 peripheral base address.
 * param  channelGroup Channel group index.
 *
 * return              Flags' mask if indicated flags are asserted. See "_adc16_channel_status_flags".
 */
uint32_t ADC16_GetChannelStatusFlags(ADC_Type *base, uint32_t channelGroup)
{
    assert(channelGroup < ADC_SC1_COUNT);

    uint32_t ret = 0U;

    if (0U != (base->SC1[channelGroup] & ADC_SC1_COCO_MASK))
    {
        ret |= (uint32_t)kADC16_ChannelConversionDoneFlag;
    }
    return ret;
}
