/*
 * Copyright 2022 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_common.h"
#include "fsl_lpflexcomm.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/

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

/*!
 * @brief Used for conversion between `void*` and `uint32_t`.
 */
typedef union pvoid_to_u32
{
    void *pvoid;
    uint32_t u32;
} pvoid_to_u32_t;

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
/*! @brief check whether lpflexcomm supports peripheral type */
static bool LP_FLEXCOMM_PeripheralIsPresent(LP_FLEXCOMM_Type *base, LP_FLEXCOMM_PERIPH_T periph);

/*! @brief Changes LP_FLEXCOMM mode. */
static status_t LP_FLEXCOMM_SetPeriph(uint32_t instance, LP_FLEXCOMM_PERIPH_T periph, int lock);

/*! @brief Common LPFLEXCOMM IRQhandle. */
static void LP_FLEXCOMM_CommonIRQHandler(uint32_t instance);
/*******************************************************************************
 * Variables
 ******************************************************************************/

/*! @brief Array to map LP_FLEXCOMM instance number to base address. */
static const uint32_t s_lpflexcommBaseAddrs[] = LP_FLEXCOMM_BASE_ADDRS;

/*! @brief Array to map LP_FLEXCOMM instance PTRS. */
static LP_FLEXCOMM_Type *const s_lpflexcommBase[] = LP_FLEXCOMM_BASE_PTRS;

/*! @brief Pointers to real IRQ handlers installed by drivers for each instance. */
static lpflexcomm_irq_handler_t s_lpflexcommIrqHandler[LP_FLEXCOMM_PERIPH_LPI2C + 1][ARRAY_SIZE(s_lpflexcommBaseAddrs)];

/*! @brief Pointers to handles for each instance to provide context to interrupt routines */
static void *s_lpflexcommHandle[LP_FLEXCOMM_PERIPH_LPI2C + 1][ARRAY_SIZE(s_lpflexcommBaseAddrs)];

/*! @brief Array to map LP_FLEXCOMM instance number to IRQ number. */
IRQn_Type const kFlexcommIrqs[] = LP_FLEXCOMM_IRQS;

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/*! @brief IDs of clock for each LP_FLEXCOMM module */
static const clock_ip_name_t s_lpflexcommClocks[] = LP_FLEXCOMM_CLOCKS;
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

#if !(defined(FSL_FEATURE_LP_FLEXCOMM_HAS_NO_RESET) && FSL_FEATURE_LP_FLEXCOMM_HAS_NO_RESET)
/*! @brief Pointers to LP_FLEXCOMM resets for each instance. */
static const reset_ip_name_t s_lpflexcommResets[] = LP_FLEXCOMM_RSTS;
#endif
/*******************************************************************************
 * Code
 ******************************************************************************/

/* check whether lpflexcomm supports peripheral type */
static bool LP_FLEXCOMM_PeripheralIsPresent(LP_FLEXCOMM_Type *base, LP_FLEXCOMM_PERIPH_T periph)
{
    if (periph == LP_FLEXCOMM_PERIPH_NONE)
    {
        return true;
    }
    else if (periph <= LP_FLEXCOMM_PERIPH_LPI2C)
    {
        return (base->PSELID & (1UL << ((uint32_t)periph + 3U))) > 0UL ? true : false;
    }
    else if (periph == LP_FLEXCOMM_PERIPH_LPI2CAndLPUART)
    {
        return true;
    }
    else
    {
        return false;
    }
}

/*! @brief Returns for LP_FLEXCOMM base address. */
uint32_t LP_FLEXCOMM_GetBaseAddress(uint32_t instance)
{
    if(instance < (uint32_t)ARRAY_SIZE(s_lpflexcommBaseAddrs))
    {
        return s_lpflexcommBaseAddrs[instance];
    }
    return 0U;
}

/*! brief Returns for LP_FLEXCOMM interrupt source,see #_lpflexcomm_interrupt_flag. */
uint32_t LP_FLEXCOMM_GetInterruptStatus(uint32_t instance)
{
    LP_FLEXCOMM_Type *base = s_lpflexcommBase[instance];
    return base->ISTAT;
}

/* Get the index corresponding to the LP_FLEXCOMM */
/*! brief Returns instance number for LP_FLEXCOMM module with given base address. */
uint32_t LP_FLEXCOMM_GetInstance(void *base)
{
    uint32_t i;
    pvoid_to_u32_t BaseAddr;
    BaseAddr.pvoid = base;

    for (i = 0U; i < (uint32_t)ARRAY_SIZE(s_lpflexcommBaseAddrs); i++)
    {
        if (BaseAddr.u32 == s_lpflexcommBaseAddrs[i])
        {
            break;
        }
    }

    assert(i < (uint32_t)ARRAY_SIZE(s_lpflexcommBaseAddrs));
    return i;
}

/* Changes LP_FLEXCOMM mode */
static status_t LP_FLEXCOMM_SetPeriph(uint32_t instance, LP_FLEXCOMM_PERIPH_T periph, int lock)
{
    assert(periph <= LP_FLEXCOMM_PERIPH_LPI2CAndLPUART);
    LP_FLEXCOMM_Type *base = s_lpflexcommBase[instance];

    /* Check whether peripheral type is present */
    if (!LP_FLEXCOMM_PeripheralIsPresent(base, periph))
    {
        return kStatus_OutOfRange;
    }

    /* Flexcomm is locked to different peripheral type than expected  */
    if (((base->PSELID & LP_FLEXCOMM_PSELID_LOCK_MASK) != 0U) &&
        ((base->PSELID & LP_FLEXCOMM_PSELID_PERSEL_MASK) != (uint32_t)periph))
    {
        return kStatus_Fail;
    }

    /* Check if we are asked to lock */
    if (lock != 0)
    {
        base->PSELID = (uint32_t)periph | LP_FLEXCOMM_PSELID_LOCK_MASK;
    }
    else
    {
        base->PSELID = (uint32_t)periph;
    }

    return kStatus_Success;
}

/*! brief Initializes LP_FLEXCOMM and selects peripheral mode according to the second parameter. */
status_t LP_FLEXCOMM_Init(uint32_t instance, LP_FLEXCOMM_PERIPH_T periph)
{
    assert(instance < (uint32_t)ARRAY_SIZE(s_lpflexcommBase));
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Enable the peripheral clock */
    CLOCK_EnableClock(s_lpflexcommClocks[instance]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

#if !(defined(FSL_FEATURE_LP_FLEXCOMM_HAS_NO_RESET) && FSL_FEATURE_LP_FLEXCOMM_HAS_NO_RESET)
    /* Reset the LP_FLEXCOMM module before configuring it.*/
    RESET_ClearPeripheralReset(s_lpflexcommResets[instance]);
#endif
    /* Set the LP_FLEXCOMM to given peripheral */
    return LP_FLEXCOMM_SetPeriph(instance, periph, 0);
}

/*! brief Deinitializes LP_FLEXCOMM. */
void LP_FLEXCOMM_Deinit(uint32_t instance)
{
    assert(instance < (uint32_t)ARRAY_SIZE(s_lpflexcommBase));
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Disable the peripheral clock */
    CLOCK_DisableClock(s_lpflexcommClocks[instance]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
    RESET_SetPeripheralReset(s_lpflexcommResets[instance]);
}

/*! brief Sets IRQ handler for given LP_FLEXCOMM module. It is used by drivers register IRQ handler according to
 * LP_FLEXCOMM mode */
void LP_FLEXCOMM_SetIRQHandler(uint32_t instance,
                               lpflexcomm_irq_handler_t handler,
                               void *lpflexcommHandle,
                               LP_FLEXCOMM_PERIPH_T periph)
{
    assert(instance < (uint32_t)ARRAY_SIZE(s_lpflexcommBase));
    /* Clear handler first to avoid execution of the handler with wrong handle */
    s_lpflexcommIrqHandler[periph][instance] = NULL;
    s_lpflexcommHandle[periph][instance]     = lpflexcommHandle;
    s_lpflexcommIrqHandler[periph][instance] = handler;
}

static void LP_FLEXCOMM_CommonIRQHandler(uint32_t instance)
{
    uint32_t interruptStat;

    interruptStat = LP_FLEXCOMM_GetInterruptStatus(instance);
    if ((interruptStat &
         ((uint32_t)kLPFLEXCOMM_I2cSlaveInterruptFlag | (uint32_t)kLPFLEXCOMM_I2cMasterInterruptFlag)) != 0U)
    {
        if (s_lpflexcommIrqHandler[LP_FLEXCOMM_PERIPH_LPI2C][instance] != NULL)
        {
            s_lpflexcommIrqHandler[LP_FLEXCOMM_PERIPH_LPI2C][instance](
                instance, s_lpflexcommHandle[LP_FLEXCOMM_PERIPH_LPI2C][instance]);
        }
    }
    if ((interruptStat & ((uint32_t)kLPFLEXCOMM_UartRxInterruptFlag | (uint32_t)kLPFLEXCOMM_UartTxInterruptFlag)) != 0U)
    {
        if (s_lpflexcommIrqHandler[LP_FLEXCOMM_PERIPH_LPUART][instance] != NULL)
        {
            s_lpflexcommIrqHandler[LP_FLEXCOMM_PERIPH_LPUART][instance](
                instance, s_lpflexcommHandle[LP_FLEXCOMM_PERIPH_LPUART][instance]);
        }
    }
    if (((interruptStat & (uint32_t)kLPFLEXCOMM_SpiInterruptFlag)) != 0U)
    {
        if (s_lpflexcommIrqHandler[LP_FLEXCOMM_PERIPH_LPSPI][instance] != NULL)
        {
            s_lpflexcommIrqHandler[LP_FLEXCOMM_PERIPH_LPSPI][instance](
                instance, s_lpflexcommHandle[LP_FLEXCOMM_PERIPH_LPSPI][instance]);
        }
    }
    SDK_ISR_EXIT_BARRIER;
}

/* IRQ handler functions overloading weak symbols in the startup */
#if defined(LP_FLEXCOMM0)
void LP_FLEXCOMM0_DriverIRQHandler(void);
void LP_FLEXCOMM0_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(0U);
}
#endif

#if defined(LP_FLEXCOMM1)
void LP_FLEXCOMM1_DriverIRQHandler(void);
void LP_FLEXCOMM1_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(1U);
}
#endif

#if defined(LP_FLEXCOMM2)
void LP_FLEXCOMM2_DriverIRQHandler(void);
void LP_FLEXCOMM2_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(2U);
}
#endif

#if defined(LP_FLEXCOMM3)
void LP_FLEXCOMM3_DriverIRQHandler(void);
void LP_FLEXCOMM3_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(3U);
}
#endif

#if defined(LP_FLEXCOMM4)
void LP_FLEXCOMM4_DriverIRQHandler(void);
void LP_FLEXCOMM4_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(4U);
}
#endif

#if defined(LP_FLEXCOMM5)
void LP_FLEXCOMM5_DriverIRQHandler(void);
void LP_FLEXCOMM5_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(5U);
}
#endif

#if defined(LP_FLEXCOMM6)
void LP_FLEXCOMM6_DriverIRQHandler(void);
void LP_FLEXCOMM6_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(6U);
}
#endif

#if defined(LP_FLEXCOMM7)
void LP_FLEXCOMM7_DriverIRQHandler(void);
void LP_FLEXCOMM7_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(7U);
}
#endif

#if defined(LP_FLEXCOMM8)
void LP_FLEXCOMM8_DriverIRQHandler(void);
void LP_FLEXCOMM8_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(8U);
}
#endif

#if defined(LP_FLEXCOMM9)
void LP_FLEXCOMM9_DriverIRQHandler(void);
void LP_FLEXCOMM9_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(9U);
}
#endif

#if defined(LP_FLEXCOMM10)
void LP_FLEXCOMM10_DriverIRQHandler(void);
void LP_FLEXCOMM10_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(10U);
}
#endif

#if defined(LP_FLEXCOMM11)
void LP_FLEXCOMM11_DriverIRQHandler(void);
void LP_FLEXCOMM11_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(11U);
}
#endif

#if defined(LP_FLEXCOMM12)
void LP_FLEXCOMM12_DriverIRQHandler(void);
void LP_FLEXCOMM12_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(12U);
}
#endif

#if defined(LP_FLEXCOMM13)
void LP_FLEXCOMM13_DriverIRQHandler(void);
void LP_FLEXCOMM13_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(13U);
}
#endif

#if defined(LP_FLEXCOMM17)
void LP_FLEXCOMM17_DriverIRQHandler(void);
void LP_FLEXCOMM17_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(17U);
}
#endif

#if defined(LP_FLEXCOMM18)
void LP_FLEXCOMM18_DriverIRQHandler(void);
void LP_FLEXCOMM18_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(18U);
}
#endif

#if defined(LP_FLEXCOMM19)
void LP_FLEXCOMM19_DriverIRQHandler(void);
void LP_FLEXCOMM19_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(19U);
}
#endif

#if defined(LP_FLEXCOMM20)
void LP_FLEXCOMM20_DriverIRQHandler(void);
void LP_FLEXCOMM20_DriverIRQHandler(void)
{
    LP_FLEXCOMM_CommonIRQHandler(20U);
}
#endif
