/**
 * \file    drv_timer.c
 * \brief   Software timer, extented by CPU core hardware timer
 *
 * \par     Copyright (c) 2018 UWE Technologies Corp., Ltd. All Rights Reserved.
 * \author  James
 * \date    2018-03-12
 * \version 
 * 
 * \history 
 *  Date        Note
 *  2018-03-12  Original Creation
 * 
 */

#include "libc.h"
#include "drivers.h"

#define DRV_HW_TIMER_CTRL_REG                               (0x40000014)
#define DRV_HW_TIMER_CTRL_REG_DRV_HW_TIMER3_BUSY_FLDSET     (0x00040000)
#define DRV_HW_TIMER3_REG                                   (0x40000020)

#pragma pack(1)
typedef struct 
{
    volatile drv_timer_t *timer;
    u32_t       duration_orig;
    u32_t       duration;
    void *p_cb;
} DRV_TIMER_INFO_t;
#pragma pack()

static u32_t drv_timer_bitmap = 0;
static DRV_TIMER_INFO_t drv_timers[DRV_TIMER_ID_MAX];
volatile u32_t drv_timer_tick = 0;


void drv_timer_init(void)
{
    // Enable software timer via hardware timer 3 
    DRV_REG_SET(DRV_HW_TIMER3_REG, 32000UL);
    // Set busy flag to enable
    *(volatile u32_t *)(DRV_HW_TIMER_CTRL_REG) |= DRV_HW_TIMER_CTRL_REG_DRV_HW_TIMER3_BUSY_FLDSET;
    drv_pd_intr_handler_set(DRV_PD_INTR_TYPE_HW_TIMER3, drv_timer_service);
}

bool_t drv_timer_start(u32_t timeout, volatile drv_timer_t *timer, void *cb)
{
    int i = DRV_TIMER_ID_MAX;

    if (!timer)
        return FALSE;

    u32_t intr_local = DRV_INTR_GLOBAL_GET();
    DRV_INTR_GLOBAL_DISABLE();

    if (timer->in_use)
    {
        if (SAL_IS_BIT_SET(drv_timer_bitmap, timer->id)) 
        {
            SAL_BIT_CLEAR(drv_timer_bitmap, timer->id);
        }
    }

    do 
    {
        i--;
        if (!SAL_IS_BIT_SET(drv_timer_bitmap, i)) 
        {
            timer->id = i;
            timer->in_use = TRUE;
            timer->timeout = FALSE;
            drv_timers[i].duration = timeout;
            drv_timers[i].duration_orig = drv_timers[i].duration;
            drv_timers[i].timer = timer;
            drv_timers[i].p_cb = cb;
            SAL_BIT_SET(drv_timer_bitmap, i);

            DRV_INTR_GLOBAL_SET(intr_local);
            return TRUE;
        }
    }
    while (i > 0);

    DRV_INTR_GLOBAL_SET(intr_local);

    return FALSE;
}

void drv_timer_stop(volatile drv_timer_t *timer)
{
    if (!timer)
        return;

    u32_t intr_local = DRV_INTR_GLOBAL_GET();
    DRV_INTR_GLOBAL_DISABLE();

    if (!(timer->in_use))
    {
        timer->timeout = FALSE;
        DRV_INTR_GLOBAL_SET(intr_local);
        return;
    }

    if (SAL_IS_BIT_SET(drv_timer_bitmap, timer->id)) 
    {
        SAL_BIT_CLEAR(drv_timer_bitmap, timer->id);
        drv_timers[timer->id].duration = 0;
        drv_timers[timer->id].duration_orig = 0;
        drv_timers[timer->id].timer = NULL;
        drv_timers[timer->id].p_cb = NULL;
        timer->in_use = FALSE;
        timer->timeout = FALSE;
    }
    DRV_INTR_GLOBAL_SET(intr_local);
}

#if DRV_TIEMR_READ_LOCK
bool_t drv_timer_started(volatile drv_timer_t *timer)
{
    bool_t ret = FALSE;
    if (!timer)
        return ret;

    u32_t intr_local = DRV_INTR_GLOBAL_GET();
    DRV_INTR_GLOBAL_DISABLE();

    ret = timer->in_use;
    DRV_INTR_GLOBAL_SET(intr_local);
    return ret;
}

bool_t drv_timer_stopped(volatile drv_timer_t *timer)
{
    bool_t ret = FALSE;
    if (!timer)
        return ret;

    u32_t intr_local = DRV_INTR_GLOBAL_GET();
    DRV_INTR_GLOBAL_DISABLE();

    ret = (timer->in_use == 0);
    DRV_INTR_GLOBAL_SET(intr_local);
    return ret;
}

bool_t drv_timer_expired(volatile drv_timer_t *timer)
{
    bool_t ret = FALSE;
    if (!timer)
        return ret;

    u32_t intr_local = DRV_INTR_GLOBAL_GET();
    DRV_INTR_GLOBAL_DISABLE();

    ret = timer->timeout;
    DRV_INTR_GLOBAL_SET(intr_local);
    return ret;
}
#endif /* DRV_TIEMR_READ_LOCK */

void drv_timer_free()
{
    int i = DRV_TIMER_ID_MAX;
    u32_t intr_local = DRV_INTR_GLOBAL_GET();
    DRV_INTR_GLOBAL_DISABLE();

    do 
    {
        i--;
        if (SAL_IS_BIT_SET(drv_timer_bitmap, i)) 
        {
            SAL_BIT_CLEAR(drv_timer_bitmap, i);
            drv_timers[i].timer->in_use = FALSE;
            drv_timers[i].timer->timeout = FALSE;
            drv_timers[i].duration = 0;
            drv_timers[i].duration_orig = 0;
            drv_timers[i].timer = NULL;
            drv_timers[i].p_cb = NULL;
        }
    }
    while (i>0);
    DRV_INTR_GLOBAL_SET(intr_local);
}

u32_t drv_timer_tick_get(void)
{
    u32_t ret;
    u32_t intr_local = DRV_INTR_GLOBAL_GET();
    DRV_INTR_GLOBAL_DISABLE();

    ret = drv_timer_tick;

    DRV_INTR_GLOBAL_SET(intr_local);

    return ret;
}

void drv_timer_service(u32_t arg)
{
    int i = DRV_TIMER_ID_MAX;
    ( void ) arg;

    drv_timer_tick++;

    do 
    {
        i--;
        if (SAL_IS_BIT_SET(drv_timer_bitmap, i)) 
        {
            if (!(--drv_timers[i].duration))
			{
                if (drv_timers[i].p_cb)
                {
                    if (drv_timers[i].timer->has_param)
                    {
                        ((v_func_ptr_t)(drv_timers[i].p_cb))(drv_timers[i].timer->param);
                    }
                    else {
                        ((v_func_v_ptr_t)(drv_timers[i].p_cb))();
                    }
                }
                if (drv_timers[i].timer->continuous)
                {
                    drv_timers[i].duration = drv_timers[i].duration_orig;
                }
                else {
                    SAL_BIT_CLEAR(drv_timer_bitmap, i);
                    drv_timers[i].timer->timeout = TRUE;
                    drv_timers[i].timer->in_use = FALSE;
                }
            }
        }
    }
    while (i>0);

    // Re-Enable software timer via PD hardware timer 3 
    DRV_REG_SET(DRV_HW_TIMER3_REG, 32000UL);
    *(volatile u32_t *)(DRV_HW_TIMER_CTRL_REG) |= DRV_HW_TIMER_CTRL_REG_DRV_HW_TIMER3_BUSY_FLDSET;
}

#if LIBC_PRINTF_ENABLED
void drv_timer_dump()
{
    const char * const timer_header = "  # EN  CONT     DURATION       REMAIN\n -- --- ---- ------------ ------------";
    int id;

    drv_uart_puts(timer_header);
    for (id = 0; id < DRV_TIMER_ID_MAX; id++)
    {
        if (SAL_IS_BIT_SET(drv_timer_bitmap, id))
        {
			printf(" %02d %-3s %-4s %12u %12u\n",
				id,
				"On",
				drv_timers[id].timer->continuous ? "Yes" : "No",
				drv_timers[id].duration_orig,
				drv_timers[id].duration
				);
        }
        else {
            printf(" %02d Off NA              0            0\n", id);
        }
    }
}
#endif /* LIBC_PRINTF_ENABLED */

