/***************************************************************************//**
 * @file    drv_acmp.c
 * @brief   ACMP (analog comparator) driver of RT-Thread RTOS for EFM32
 *  COPYRIGHT (C) 2012, RT-Thread Development Team
 * @author  onelife
 * @version 1.0
 *******************************************************************************
 * @section License
 * The license and distribution terms for this file may be found in the file
 * LICENSE in this distribution or at http://www.rt-thread.org/license/LICENSE
 *******************************************************************************
 * @section Change Logs
 * Date         Author      Notes
 * 2011-02-21   onelife     Initial creation for EFM32
 * 2011-06-17   onelife     Modify init and control function for efm32lib v2
 *  upgrading
 ******************************************************************************/

/***************************************************************************//**
 * @addtogroup efm32
 * @{
 ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "board.h"
#include "drv_acmp.h"

#if (defined(RT_USING_ACMP0) || defined(RT_USING_ACMP1))
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
#ifdef RT_ACMP_DEBUG
#define acmp_debug(format,args...)          rt_kprintf(format, ##args)
#else
#define acmp_debug(format,args...)
#endif

/* Private variables ---------------------------------------------------------*/
#ifdef RT_USING_ACMP0
    static struct rt_device acmp0_device;
#endif

#ifdef RT_USING_ACMP1
    static struct rt_device acmp1_device;
#endif

/* Private function prototypes -----------------------------------------------*/
ACMP_WarmTime_TypeDef efm32_acmp_WarmTimeCalc(rt_uint32_t hfperFreq);

/* Private functions ---------------------------------------------------------*/
/***************************************************************************//**
 * @brief
 *   Initialize ACMP device
 *
 * @details
 *
 * @note
 *
 * @param[in] dev
 *   Pointer to device descriptor
 *
 * @return
 *   Error code
 ******************************************************************************/
 static rt_err_t rt_acmp_init(rt_device_t dev)
{
    RT_ASSERT(dev != RT_NULL);

    struct efm32_acmp_device_t *acmp;

    acmp = (struct efm32_acmp_device_t *)(dev->user_data);

    acmp->hook.cbFunc   = RT_NULL;
    acmp->hook.userPtr  = RT_NULL;

    return RT_EOK;
}

/***************************************************************************//**
 * @brief
 *  Configure ACMP device
 *
 * @details
 *
 * @note
 *
 * @param[in] dev
 *  Pointer to device descriptor
 *
 * @param[in] cmd
 *  ACMP control command
 *
 * @param[in] args
 *  Arguments
 *
 * @return
 *  Error code
 ******************************************************************************/
static rt_err_t rt_acmp_control(
    rt_device_t     dev,
    rt_uint8_t      cmd,
    void            *args)
{
    RT_ASSERT(dev != RT_NULL);

    struct efm32_acmp_device_t *acmp;

    acmp = (struct efm32_acmp_device_t *)(dev->user_data);

    switch (cmd)
    {
    case RT_DEVICE_CTRL_SUSPEND:
        /* Suspend device */
        dev->flag |= RT_DEVICE_FLAG_SUSPENDED;
        ACMP_Disable(acmp->acmp_device);
        break;

    case RT_DEVICE_CTRL_RESUME:
        /* Resume device */
        dev->flag &= ~RT_DEVICE_FLAG_SUSPENDED;
        ACMP_Enable(acmp->acmp_device);
        break;

    case RT_DEVICE_CTRL_ACMP_INIT:
        {
            rt_bool_t int_en = false;
            struct efm32_acmp_control_t *control;

            control = (struct efm32_acmp_control_t *)args;
            acmp_debug("ACMP: control -> init start\n");

            /* Configure ACMPn */
            if (control->init == RT_NULL)
            {
                return -RT_ERROR;
            }
            ACMP_Init(acmp->acmp_device, control->init);
            ACMP_ChannelSet(acmp->acmp_device, control->negInput, control->posInput);
            if (control->output != RT_NULL)
            {
                ACMP_GPIOSetup(
                    acmp->acmp_device,
                    control->output->location,
                    control->output->enable,
                    control->output->invert);
                int_en = true;
            }
            if (control->hook.cbFunc != RT_NULL)
            {
                acmp->hook.cbFunc = control->hook.cbFunc;
                acmp->hook.userPtr = control->hook.userPtr;
                int_en = true;
            }

            if (int_en)
            {
                /* Enable edge interrupt */
                ACMP_IntEnable(acmp->acmp_device, ACMP_IEN_EDGE);
                ACMP_IntClear(acmp->acmp_device, ACMP_IFC_EDGE);

                /* Enable ACMP0/1 interrupt vector in NVIC */
                NVIC_ClearPendingIRQ(ACMP0_IRQn);
                NVIC_SetPriority(ACMP0_IRQn, EFM32_IRQ_PRI_DEFAULT);
                NVIC_EnableIRQ(ACMP0_IRQn);
            }
        }
        break;

    case RT_DEVICE_CTRL_ACMP_OUTPUT:
        *((rt_bool_t *)args) = \
            (acmp->acmp_device->STATUS & ACMP_STATUS_ACMPOUT) ? true : false;
        break;

        default:
            return -RT_ERROR;
    }

    return RT_EOK;
}

/***************************************************************************//**
 * @brief
 *  Register ACMP device
 *
 * @details
 *
 * @note
 *
 * @param[in] device
 *  Pointer to device descriptor
 *
 * @param[in] name
 *  Device name
 *
 * @param[in] flag
 *  Configuration flags
 *
 * @param[in] acmp
 *  Pointer to ACMP device descriptor
 *
 * @return
 *  Error code
 ******************************************************************************/
rt_err_t rt_hw_acmp_register(
    rt_device_t     device,
    const char      *name,
    rt_uint32_t     flag,
    struct efm32_acmp_device_t *acmp)
{
    RT_ASSERT(device != RT_NULL);

    device->type        = RT_Device_Class_Char; /* fixme: should be acmp type */
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;
    device->init        = rt_acmp_init;
    device->open        = RT_NULL;
    device->close       = RT_NULL;
    device->read        = RT_NULL;
    device->write       = RT_NULL;
    device->control     = rt_acmp_control;
    device->user_data   = acmp;

    /* register a character device */
    return rt_device_register(device, name, flag);
}

/***************************************************************************//**
 * @brief
 *  ACMP edge trigger interrupt handler
 *
 * @details
 *
 * @note
 ******************************************************************************/
void rt_hw_acmp_isr(rt_device_t dev)
{
    RT_ASSERT(dev != RT_NULL);

    struct efm32_acmp_device_t *acmp;

    acmp = (struct efm32_acmp_device_t *)(dev->user_data);

    if (acmp->hook.cbFunc != RT_NULL)
    {
        (acmp->hook.cbFunc)(acmp->hook.userPtr);
    }
}

/***************************************************************************//**
 * @brief
 *  Initialize the specified ACMP unit
 *
 * @details
 *
 * @note
 *
 * @param[in] device
 *  Pointer to device descriptor
 *
 * @param[in] unitNumber
 *  Unit number
 *
 * @return
 *  Pointer to ACMP device
 ******************************************************************************/
static struct efm32_acmp_device_t *rt_hw_acmp_unit_init(
    rt_device_t device,
    rt_uint8_t  unitNumber)
{
    struct efm32_acmp_device_t      *acmp;
    efm32_irq_hook_init_t           hook;
    CMU_Clock_TypeDef               acmpClock;

    do
    {
        /* Allocate device */
        acmp = rt_malloc(sizeof(struct efm32_acmp_device_t));
        if (acmp == RT_NULL)
        {
            acmp_debug("ACMP err: no mem for ACMP%d\n", unitNumber);
            break;
        }

        /* Initialization */
        if (unitNumber >= ACMP_COUNT)
        {
            break;
        }
        switch (unitNumber)
        {
        case 0:
            acmp->acmp_device   = ACMP0;
            acmpClock           = (CMU_Clock_TypeDef)cmuClock_ACMP0;
            break;

        case 1:
            acmp->acmp_device   = ACMP1;
            acmpClock           = (CMU_Clock_TypeDef)cmuClock_ACMP1;
            break;

        default:
            break;
        }

        /* Enable ACMP clock */
        CMU_ClockEnable(acmpClock, true);

        /* Reset */
        ACMP_Reset(acmp->acmp_device);

        /* Config interrupt and NVIC */
        hook.type           = efm32_irq_type_acmp;
        hook.unit           = unitNumber;
        hook.cbFunc         = rt_hw_acmp_isr;
        hook.userPtr        = device;
        efm32_irq_hook_register(&hook);

        return acmp;
    } while(0);

    if (acmp)
    {
        rt_free(acmp);
    }
    rt_kprintf("ACMP: Init failed!\n");
    return RT_NULL;
}

/***************************************************************************//**
 * @brief
 *  Initialize all ACMP module related hardware and register ACMP device to
 *  kernel
 *
 * @details
 *
 * @note
 *
 ******************************************************************************/
void rt_hw_acmp_init(void)
{
    struct efm32_acmp_device_t  *acmp;

#ifdef RT_USING_ACMP0
    if ((acmp = rt_hw_acmp_unit_init(&acmp0_device, 0)) != RT_NULL)
    {
        rt_hw_acmp_register(&acmp0_device, RT_ACMP0_NAME, EFM32_NO_DATA, acmp);
    }
#endif

#ifdef RT_USING_ACMP1
    if ((acmp = rt_hw_acmp_unit_init(&acmp1_device, 1)) != RT_NULL)
    {
        rt_hw_acmp_register(&acmp1_device, RT_ACMP1_NAME, EFM32_NO_DATA, acmp);
    }
#endif

}

/***************************************************************************//**
 * @brief
 *    Calculate the warm-up time value providing at least 10us
 *
 * @param[in] hfperFreq
 *  Frequency in Hz of reference HFPER clock. Set to 0 to use currently defined
 *  HFPER clock setting
 *
 * @return
 *  Warm-up time value to use for ACMP in order to achieve at least 10us
 ******************************************************************************/
ACMP_WarmTime_TypeDef efm32_acmp_WarmTimeCalc(rt_uint32_t hfperFreq)
{
    if (!hfperFreq)
    {
        hfperFreq = CMU_ClockFreqGet(cmuClock_HFPER);

        /* Just in case, make sure we get non-zero freq for below calculation */
        if (!hfperFreq)
        {
          hfperFreq = 1;
        }
    }

    /* Determine number of HFPERCLK cycle >= 10us */
    if (4 * 1000000 / hfperFreq > 10)
    {
        return acmpWarmTime4;
    }
    else if (8 * 1000000 / hfperFreq > 10)
    {
        return acmpWarmTime8;
    }
    else if (16 * 1000000 / hfperFreq > 10)
    {
        return acmpWarmTime16;
    }
    else if (32 * 1000000 / hfperFreq > 10)
    {
        return acmpWarmTime32;
    }
    else if (64 * 1000000 / hfperFreq > 10)
    {
        return acmpWarmTime64;
    }
    else if (128 * 1000000 / hfperFreq > 10)
    {
        return acmpWarmTime128;
    }
    else if (256 * 1000000 / hfperFreq > 10)
    {
        return acmpWarmTime256;
    }
    else if (512 * 1000000 / hfperFreq > 10)
    {
        return acmpWarmTime512;
    }
}

#endif
/***************************************************************************//**
 * @}
 ******************************************************************************/