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

#include "fsl_lpcmp.h"

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

#if defined(LPCMP_RSTS)
#define LPCMP_RESETS_ARRAY LPCMP_RSTS
#endif

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
#if defined(LPCMP_CLOCKS)
/*!
 * @brief Get instance number for LPCMP module.
 *
 * @param base LPCMP peripheral base address
 */
static uint32_t LPCMP_GetInstance(LPCMP_Type *base);
#endif /* LPCMP_CLOCKS */

/*******************************************************************************
 * Variables
 ******************************************************************************/
#if defined(LPCMP_CLOCKS)
/*! @brief Pointers to LPCMP bases for each instance. */
static LPCMP_Type *const s_lpcmpBases[] = LPCMP_BASE_PTRS;

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

#if defined(LPCMP_RESETS_ARRAY)
/* Reset array */
static const reset_ip_name_t s_lpcmpResets[] = LPCMP_RESETS_ARRAY;
#endif

/*******************************************************************************
 * Codes
 ******************************************************************************/
#if defined(LPCMP_CLOCKS)
static uint32_t LPCMP_GetInstance(LPCMP_Type *base)
{
    uint32_t instance;

    /* Find the instance index from base address mappings. */
    /*
     * $Branch Coverage Justification$
     * (instance >= ARRAY_SIZE(s_lpcmpBases)) not covered. The peripheral base
     * address is always valid and checked by assert.
     */
    for (instance = 0; instance < ARRAY_SIZE(s_lpcmpBases); instance++)
    {
        /*
         * $Branch Coverage Justification$
         * (s_lpcmpBases[instance] != base) not covered. The peripheral base
         * address is always valid and checked by assert.
         */
        if (MSDK_REG_SECURE_ADDR(s_lpcmpBases[instance]) == MSDK_REG_SECURE_ADDR(base))
        {
            break;
        }
    }

    assert(instance < ARRAY_SIZE(s_lpcmpBases));

    return instance;
}
#endif /* LPCMP_CLOCKS */

/*!
 * brief Initialize the LPCMP
 *
 * This function initializes the LPCMP module. The operations included are:
 * - Enabling the clock for LPCMP module.
 * - Configuring the comparator.
 * - Enabling the LPCMP module.
 * Note: For some devices, multiple LPCMP instance share the same clock gate. In this case, to enable the clock for
 * any instance enables all the LPCMPs. Check the chip reference manual for the clock assignment of the LPCMP.
 *
 * param base LPCMP peripheral base address.
 * param config Pointer to "lpcmp_config_t" structure.
 */
void LPCMP_Init(LPCMP_Type *base, const lpcmp_config_t *config)
{
    assert(config != NULL);

    uint32_t tmp32;

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

#if defined(LPCMP_RESETS_ARRAY)
    RESET_ReleasePeripheralReset(s_lpcmpResets[LPCMP_GetInstance(base)]);
#endif

    /* Configure. */
    LPCMP_Enable(base, false);

#if !(defined(FSL_FEATURE_LPCMP_HAS_NO_CCR0_CMP_STOP_EN) && FSL_FEATURE_LPCMP_HAS_NO_CCR0_CMP_STOP_EN)
    /* CCR0 register. */
#if defined(FSL_FEATURE_LPCMP_INSTANCE_SUPPORT_CCR0_CMP_STOP_ENn)
    if (1U == FSL_FEATURE_LPCMP_INSTANCE_SUPPORT_CCR0_CMP_STOP_ENn(base))
#endif /* FSL_FEATURE_LPCMP_INSTANCE_SUPPORT_CCR0_CMP_STOP_ENn */
    {
        if (config->enableStopMode)
        {
            base->CCR0 |= LPCMP_CCR0_CMP_STOP_EN_MASK;
        }
        else
        {
            base->CCR0 &= ~LPCMP_CCR0_CMP_STOP_EN_MASK;
        }
    }
#endif /* !(defined(FSL_FEATURE_LPCMP_HAS_NO_CCR0_CMP_STOP_EN) && FSL_FEATURE_LPCMP_HAS_NO_CCR0_CMP_STOP_EN) */

    /* CCR1 register. */
    tmp32 = (base->CCR1 & (~(LPCMP_CCR1_COUT_PEN_MASK | LPCMP_CCR1_COUT_SEL_MASK | LPCMP_CCR1_COUT_INV_MASK
#if defined(FSL_FEATURE_LPCMP_HAS_CCR1_FUNC_CLK_SEL) && FSL_FEATURE_LPCMP_HAS_CCR1_FUNC_CLK_SEL
                             | LPCMP_CCR1_FUNC_CLK_SEL_MASK
#endif /* FSL_FEATURE_LPCMP_HAS_CCR1_FUNC_CLK_SEL */
                             )));

    if (config->enableOutputPin)
    {
        tmp32 |= LPCMP_CCR1_COUT_PEN_MASK;
    }
    if (config->useUnfilteredOutput)
    {
        tmp32 |= LPCMP_CCR1_COUT_SEL_MASK;
    }
    if (config->enableInvertOutput)
    {
        tmp32 |= LPCMP_CCR1_COUT_INV_MASK;
    }
#if defined(FSL_FEATURE_LPCMP_HAS_CCR1_FUNC_CLK_SEL) && FSL_FEATURE_LPCMP_HAS_CCR1_FUNC_CLK_SEL
    tmp32 |= LPCMP_CCR1_FUNC_CLK_SEL(config->functionalSourceClock);
#endif /* FSL_FEATURE_LPCMP_HAS_CCR1_FUNC_CLK_SEL */
    base->CCR1 = tmp32;
    /* CCR2 register. */
    tmp32 = base->CCR2 & ~(LPCMP_CCR2_HYSTCTR_MASK | LPCMP_CCR2_CMP_NPMD_MASK | LPCMP_CCR2_CMP_HPMD_MASK);
    tmp32 |= LPCMP_CCR2_HYSTCTR(config->hysteresisMode);
    tmp32 |= ((uint32_t)(config->powerMode) << LPCMP_CCR2_CMP_HPMD_SHIFT);
    base->CCR2 = tmp32;

    LPCMP_Enable(base, true); /* Enable the LPCMP module. */
}

/*!
 * brief De-initializes the LPCMP module.
 *
 * This function de-initializes the LPCMP module. The operations included are:
 * - Disabling the LPCMP module.
 * - Disabling the clock for LPCMP module.
 *
 * This function disables the clock for the LPCMP.
 * Note: For some devices, multiple LPCMP instance shares the same clock gate. In this case, before disabling the
 * clock for the LPCMP, ensure that all the LPCMP instances are not used.
 *
 * param base LPCMP peripheral base address.
 */
void LPCMP_Deinit(LPCMP_Type *base)
{
    /* Disable the LPCMP module. */
    LPCMP_Enable(base, false);
#if defined(LPCMP_CLOCKS)
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Disable the clock. */
    CLOCK_DisableClock(s_lpcmpClocks[LPCMP_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
#endif /* LPCMP_CLOCKS */
}

/*!
 * brief Gets an available pre-defined settings for the comparator's configuration.
 *
 * This function initializes the comparator configuration structure to these default values:
 * code
 *   config->enableStopMode      = false;
 *   config->enableOutputPin     = false;
 *   config->useUnfilteredOutput = false;
 *   config->enableInvertOutput  = false;
 *   config->hysteresisMode      = kLPCMP_HysteresisLevel0;
 *   config->powerMode           = kLPCMP_LowSpeedPowerMode;
 *   config->functionalSourceClock = kLPCMP_FunctionalClockSource0;
 * endcode
 * param config Pointer to "lpcmp_config_t" structure.
 */
void LPCMP_GetDefaultConfig(lpcmp_config_t *config)
{
    /* Initializes the configure structure to zero. */
    (void)memset(config, 0, sizeof(*config));
#if !(defined(FSL_FEATURE_LPCMP_HAS_NO_CCR0_CMP_STOP_EN) && FSL_FEATURE_LPCMP_HAS_NO_CCR0_CMP_STOP_EN)
    config->enableStopMode = false;
#endif /* !(defined(FSL_FEATURE_LPCMP_HAS_NO_CCR0_CMP_STOP_EN) && FSL_FEATURE_LPCMP_HAS_NO_CCR0_CMP_STOP_EN) */
    config->enableOutputPin     = false;
    config->useUnfilteredOutput = false;
    config->enableInvertOutput  = false;
    config->hysteresisMode      = kLPCMP_HysteresisLevel0;
    config->powerMode           = kLPCMP_LowSpeedPowerMode;
#if defined(FSL_FEATURE_LPCMP_HAS_CCR1_FUNC_CLK_SEL) && FSL_FEATURE_LPCMP_HAS_CCR1_FUNC_CLK_SEL
    config->functionalSourceClock = kLPCMP_FunctionalClockSource0;
#endif /* FSL_FEATURE_LPCMP_HAS_CCR1_FUNC_CLK_SEL */
}

/*!
 * brief Select the input channels for LPCMP. This function determines which input
 *        is selected for the negative and positive mux.
 *
 * param base LPCMP peripheral base address.
 * param positiveChannel Positive side input channel number.
 * param negativeChannel Negative side input channel number.
 */
void LPCMP_SetInputChannels(LPCMP_Type *base, uint32_t positiveChannel, uint32_t negativeChannel)
{
    uint32_t tmp32;

    tmp32 = base->CCR2 & ~(LPCMP_CCR2_PSEL_MASK | LPCMP_CCR2_MSEL_MASK);
    tmp32 |= LPCMP_CCR2_PSEL(positiveChannel) | LPCMP_CCR2_MSEL(negativeChannel);
    base->CCR2 = tmp32;
}

/*!
 * brief Configures the filter.
 *
 * param base LPCMP peripheral base address.
 * param config Pointer to "lpcmp_filter_config_t" structure.
 */
void LPCMP_SetFilterConfig(LPCMP_Type *base, const lpcmp_filter_config_t *config)
{
    assert(config != NULL);

    uint32_t tmp32;

    tmp32 = base->CCR1 & ~(LPCMP_CCR1_FILT_PER_MASK | LPCMP_CCR1_FILT_CNT_MASK | LPCMP_CCR1_SAMPLE_EN_MASK);
    if (config->enableSample)
    {
        tmp32 |= LPCMP_CCR1_SAMPLE_EN_MASK;
    }
    tmp32 |= LPCMP_CCR1_FILT_PER(config->filterSamplePeriod) | LPCMP_CCR1_FILT_CNT(config->filterSampleCount);
    base->CCR1 = tmp32;
}

/*!
 * brief Configure the internal DAC module.
 *
 * param base LPCMP peripheral base address.
 * param config Pointer to "lpcmp_dac_config_t" structure. If config is "NULL", disable internal DAC.
 */
void LPCMP_SetDACConfig(LPCMP_Type *base, const lpcmp_dac_config_t *config)
{
    uint32_t tmp32;
    if (config == NULL)
    {
        tmp32 = 0U; /* Disable internal DAC. */
    }
    else
    {
        tmp32 = LPCMP_DCR_VRSEL(config->referenceVoltageSource) | LPCMP_DCR_DAC_DATA(config->DACValue);
        if (config->enableLowPowerMode)
        {
            tmp32 |= LPCMP_DCR_DAC_HPMD_MASK;
        }
        tmp32 |= LPCMP_DCR_DAC_EN_MASK;
    }
    base->DCR = tmp32;
}

#if defined(FSL_FEATURE_LPCMP_HAS_WINDOW_CONTROL) && FSL_FEATURE_LPCMP_HAS_WINDOW_CONTROL
/*!
 * @brief Configure the window control, users can use this API to implement operations on the window,
 * such as inverting the window signal, setting the window closing event(only valid in windowing mode),
 * and setting the COUTA signal after the window is closed(only valid in windowing mode).
 *
 * @param base LPCMP peripheral base address.
 * @param config Pointer "lpcmp_window_control_config_t" structure.
 */
void LPCMP_SetWindowControl(LPCMP_Type *base, const lpcmp_window_control_config_t *config)
{
    assert(config != NULL);

    uint32_t tmp32 = 0UL;

    tmp32 = (base->CCR1 & (~(LPCMP_CCR1_COUTA_CFG_MASK | LPCMP_CCR1_EVT_SEL_CFG_MASK | LPCMP_CCR1_WINDOW_INV_MASK)));

    if (config->enableInvertWindowSignal)
    {
        tmp32 |= LPCMP_CCR1_WINDOW_INV_MASK;
    }

    /* Set COUT event, which can close the active window in window mode. */
    tmp32 |= LPCMP_CCR1_EVT_SEL_CFG(config->closeWindowEvent);

    /* Set the COUTA signal value when the window is closed. */
    tmp32 |= LPCMP_CCR1_COUTA_CFG(config->COUTASignal);

    base->CCR1 = tmp32;
}
#endif /* FSL_FEATURE_LPCMP_HAS_WINDOW_CONTROL */

#if defined(FSL_FEATURE_LPCMP_HAS_ROUNDROBIN_MODE) && FSL_FEATURE_LPCMP_HAS_ROUNDROBIN_MODE
/*!
 * @brief Configure the roundrobin mode.
 *
 * @param base LPCMP peripheral base address.
 * @param config Pointer "lpcmp_roundrobin_config_t" structure.
 */
void LPCMP_SetRoundRobinConfig(LPCMP_Type *base, const lpcmp_roundrobin_config_t *config)
{
    assert(config != NULL);

    uint32_t tmp32 = 0UL;

    /* LPCMPx_RRCR0 register, Configuration options for the round-robin operation. */
    tmp32 = (base->RRCR0 &
             (~(LPCMP_RRCR0_RR_TRG_SEL_MASK | LPCMP_RRCR0_RR_NSAM_MASK | LPCMP_RRCR0_RR_CLK_SEL_MASK |
                LPCMP_RRCR0_RR_INITMOD_MASK | LPCMP_RRCR0_RR_SAMPLE_CNT_MASK | LPCMP_RRCR0_RR_SAMPLE_THRESHOLD_MASK)));

    tmp32 |=
        (LPCMP_RRCR0_RR_TRG_SEL(config->roundrobinTriggerSource) | LPCMP_RRCR0_RR_NSAM(config->sampleClockNumbers) |
         LPCMP_RRCR0_RR_CLK_SEL(config->roundrobinClockSource) | LPCMP_RRCR0_RR_INITMOD(config->initDelayModules) |
         LPCMP_RRCR0_RR_SAMPLE_CNT(config->channelSampleNumbers) |
         LPCMP_RRCR0_RR_SAMPLE_THRESHOLD(config->sampleTimeThreshhold));

    base->RRCR0 = tmp32;

    /* LPCMPx_RRCR1 register, Configure the fix port, fix channel and checker channel. */
    tmp32 =
        (base->RRCR1 & (~(LPCMP_RRCR1_FIXP_MASK | LPCMP_RRCR1_FIXCH_MASK | (0xFFUL << LPCMP_RRCR1_RR_CH0EN_SHIFT))));
    tmp32 |= (LPCMP_RRCR1_FIXP(config->fixedMuxPort) | LPCMP_RRCR1_FIXCH(config->fixedChannel) |
              ((uint32_t)(config->checkerChannelMask) << LPCMP_RRCR1_RR_CH0EN_SHIFT));

    base->RRCR1 = tmp32;
}

/*!
 * brief Configure the roundrobin internal timer reload value.
 *
 * param base LPCMP peripheral base address.
 * param value RoundRobin internal timer reload value, allowed range:0x0UL-0xFFFFFFFUL.
 */
void LPCMP_SetRoundRobinInternalTimer(LPCMP_Type *base, uint32_t value)
{
    uint32_t tmp32 = 0UL;

    tmp32 = (base->RRCR2 & (~LPCMP_RRCR2_RR_TIMER_RELOAD_MASK));
    tmp32 |= LPCMP_RRCR2_RR_TIMER_RELOAD(value);

    base->RRCR2 = tmp32;
}

#endif /* FSL_FEATURE_LPCMP_HAS_ROUNDROBIN_MODE */
