/********************************************************************************
 *                            USDK(ZS283A)
 *                            Module: SYSTEM
 *                 Copyright(c) 2003-2017 Actions Semiconductor,
 *                            All Rights Reserved.
 *
 * History:
 *      <author>      <time>                      <version >          <desc>
 *      wuyufan   2019-3-29-1:27:29             1.0             build this file
 ********************************************************************************/
/*!
 * \file     hrtimer.c
 * \brief
 * \author
 * \version  1.0
 * \date  2019-3-29-1:27:29
 *******************************************************************************/
#include <device.h>
#include <init.h>
#include <soc.h>
#include <drivers/hrtimer.h>
#include <string.h>

#define MAX_TIMER_COUNT TIMER_MAX_CYCLES_VALUE

struct hrtimer_ctx
{
    timer_register_t *timer_register;
    sys_dlist_t hrtimer_q;
    uint32_t timer_load_val;
    uint64_t current_jiffies;
    uint64_t adjust_jiffies;
};

static struct hrtimer_ctx g_hrtimer_ctx;

//#define HRTIMER_DEBUG

#ifdef HRTIMER_DEBUG
#define TT_DEBUG(fmt, ...) printk("[%d]" fmt, k_uptime_get_32(), ##__VA_ARGS__)
#else
#define TT_DEBUG(fmt, ...)
#endif

static inline struct hrtimer_ctx *_get_hrtimer_ctx(void)
{
    return &g_hrtimer_ctx;
}

static inline struct hrtimer *_find_first_hrtimer_to_unpend(void)
{
    struct hrtimer_ctx *ctx = _get_hrtimer_ctx();

	return (struct hrtimer *)sys_dlist_peek_head(&ctx->hrtimer_q);
}

static inline struct hrtimer *_find_next_hrtimer(struct hrtimer *ttimer)
{
    struct hrtimer_ctx *ctx = _get_hrtimer_ctx();

    return (struct hrtimer *)sys_dlist_peek_next(&ctx->hrtimer_q, (void *)ttimer);
}

static inline uint64_t _hrtimer_us_to_tick(uint32_t m)
{
    return ((uint64_t)m * (uint64_t)CONFIG_HOSC_CLK_MHZ);
}

static void _hrtimer_reload(timer_register_t *timer_register, uint32_t value)
{
    if (value > MAX_TIMER_COUNT) {
        value = MAX_TIMER_COUNT;
    }

    //clear pending stop timer
    timer_register->ctl  = (1 << T0_CTL_ZIPD);
	
	timer_reg_wait();

    timer_register->val = value;

    // start timer, not reload
    timer_register->ctl = 0x22;
}

static uint64_t _get_current_jiffies(void)
{
    uint32_t val;
    uint32_t flags;

    struct hrtimer_ctx *ctx = _get_hrtimer_ctx();

    flags = irq_lock();

    /*hw timer bug, we should check*/
    if (ctx->timer_load_val > ctx->timer_register->cnt) {
        val = ctx->timer_load_val - ctx->timer_register->cnt;
    } else {
        val = 0;
    }

    if (ctx->adjust_jiffies < ctx->current_jiffies + (uint64_t)val) {
        ctx->adjust_jiffies = ctx->current_jiffies + (uint64_t)val;
    }

    irq_unlock(flags);

    return ctx->adjust_jiffies;
}

static void _update_expires_to_timer(uint64_t expires)
{
    uint32_t val;

    struct hrtimer_ctx *ctx = _get_hrtimer_ctx();

    if (expires <= ctx->current_jiffies || expires > (ctx->current_jiffies + MAX_TIMER_COUNT)) return;

    /*hw timer bug, we should check*/
    if (ctx->timer_load_val > ctx->timer_register->cnt) {
        val = ctx->timer_load_val - ctx->timer_register->cnt;
    } else {
        val = 0;
    }

    if (expires > ctx->current_jiffies + val) {
        val = (uint32_t) (expires - ctx->current_jiffies - val);
    } else {
        val = 1;
    }

    ctx->timer_load_val = expires - ctx->current_jiffies;

    _hrtimer_reload(ctx->timer_register, val);
}

static void _hrtimer_remove(struct hrtimer *timer)
{
    u32_t irq_flags;

	struct hrtimer *in_q, *next;

    struct hrtimer_ctx *ctx = _get_hrtimer_ctx();

    irq_flags = irq_lock();

	SYS_DLIST_FOR_EACH_CONTAINER_SAFE(&ctx->hrtimer_q, in_q, next, node) {
		if (timer == in_q) {
			sys_dlist_remove(&in_q->node);
			irq_unlock(irq_flags);
			return;
		}
	}

	irq_unlock(irq_flags);
}

static void _hrtimer_insert(struct hrtimer *ttimer,
				 u32_t expiry_time)
{
    u32_t irq_flags;

    struct hrtimer_ctx *ctx = _get_hrtimer_ctx();

	sys_dlist_t *thread_timer_q = &ctx->hrtimer_q;
	struct hrtimer *in_q;

    irq_flags = irq_lock();

	SYS_DLIST_FOR_EACH_CONTAINER(thread_timer_q, in_q, node) {
		if (ttimer->expiry_time < in_q->expiry_time) {
			sys_dlist_insert(&in_q->node,
						&ttimer->node);
			goto check_queue;
		}
	}	
	sys_dlist_append(thread_timer_q, &ttimer->node);

check_queue:
    if(ttimer == _find_first_hrtimer_to_unpend()){
        _update_expires_to_timer(ttimer->expiry_time);
    }

	irq_unlock(irq_flags);
}

void hrtimer_init(struct hrtimer *ttimer, hrtimer_expiry_t expiry_fn,
		       void *expiry_fn_arg)
{
	__ASSERT(ttimer != NULL, "");

	TT_DEBUG("timer %p: init func %p arg 0x%p\n", ttimer,
		ttimer->expiry_fn, ttimer->expiry_fn_arg);

	/* remove hrtimer if already submited */
	_hrtimer_remove(ttimer);

	memset(ttimer, 0, sizeof(struct hrtimer));
	ttimer->expiry_time = ULLONG_MAX;
	ttimer->expiry_fn = expiry_fn;
	ttimer->expiry_fn_arg = expiry_fn_arg;
	sys_dlist_init(&ttimer->node);
}


void hrtimer_start(struct hrtimer *ttimer, s32_t duration, s32_t period)
{
	uint64_t current_jiffies, ticks;

	__ASSERT((ttimer != NULL) && (ttimer->expiry_fn != NULL), "");

	_hrtimer_remove(ttimer);

	current_jiffies = _get_current_jiffies();

	ticks = _hrtimer_us_to_tick(duration);

	ttimer->expiry_time = current_jiffies + ticks;

	ttimer->period = period;
	ttimer->duration = duration;

	TT_DEBUG("timer %p: start duration %d period %d\n",
		ttimer, duration, period);

	_hrtimer_insert(ttimer, ttimer->expiry_time);
}

void hrtimer_stop(struct hrtimer *ttimer)
{
    u32_t flags;

    struct hrtimer *next;

	__ASSERT(ttimer != NULL, "");

	TT_DEBUG("timer %p: stop\n", ttimer);

	flags = irq_lock();

	/* check if the current timer is the mininum one and then update the current timer  */
	if(ttimer == _find_first_hrtimer_to_unpend()){
		next = _find_next_hrtimer(ttimer);
		if(next){
            _update_expires_to_timer(next->expiry_time);
		}
	}

	_hrtimer_remove(ttimer);

	irq_unlock(flags);
}

bool hrtimer_is_running(struct hrtimer *ttimer)
{
	struct hrtimer *in_q;
	bool result = false;
	u32_t flags;

    struct hrtimer_ctx *ctx = _get_hrtimer_ctx();

	__ASSERT(ttimer != NULL, "");

	flags = irq_lock();

	SYS_DLIST_FOR_EACH_CONTAINER(&ctx->hrtimer_q, in_q, node) {
		if (ttimer == in_q) {
			result = true;
			break;
		}
	}
	irq_unlock(flags);
	return result;
}

void hrtimer_handle_expired(void)
{
    struct hrtimer_ctx *ctx;
    struct hrtimer *tmpNode;
	sys_dlist_t call_and_del;
	struct hrtimer *cur;
    u32_t key ;
    ctx = _get_hrtimer_ctx();

	key = irq_lock();
    sys_dlist_init(&call_and_del);

    ctx->current_jiffies += ctx->timer_load_val;

    SYS_DLIST_FOR_EACH_CONTAINER_SAFE(&ctx->hrtimer_q, cur, tmpNode, node){
        if (cur->expiry_time > ctx->current_jiffies) {
            break;
        }

        sys_dlist_remove(&cur->node);
        sys_dlist_append(&call_and_del, &cur->node);
    }

	// amend timer load value to be the latest timer node value
    if (sys_dlist_is_empty(&(ctx->hrtimer_q))\
        || (cur && cur->expiry_time > (ctx->current_jiffies + MAX_TIMER_COUNT))) {
        ctx->timer_load_val = MAX_TIMER_COUNT;
    } else {
		if(cur)
			ctx->timer_load_val = cur->expiry_time - ctx->current_jiffies;
		else
			ctx->timer_load_val = MAX_TIMER_COUNT;
    }

    _hrtimer_reload(ctx->timer_register, ctx->timer_load_val);

    SYS_DLIST_FOR_EACH_CONTAINER_SAFE(&call_and_del, cur, tmpNode, node){
		// timer is expired and start to excute timer function
        sys_dlist_remove(&cur->node);

		// check if the timer is period or not
        if (cur->period != 0) {
			// if is period timer, add the timer into the queue again
			hrtimer_start(cur, cur->period, cur->period);
        }

        TT_DEBUG("timer %p: call %p\n", cur, cur->expiry_fn);
        irq_unlock(key);
        //sys_irq_unlock(&flags);
        /* invoke thread timer expiry function */
        cur->expiry_fn(cur, cur->expiry_fn_arg);
        key = irq_lock();
        //sys_irq_lock(&flags);
    }
    irq_unlock(key);
   // sys_irq_unlock(&flags);
}

static int hrtimer_runtime_init(const struct device *arg)
{
    struct hrtimer_ctx *ctx = _get_hrtimer_ctx();
	ARG_UNUSED(arg);
#ifdef CONFIG_SOC_SERIES_LEOPARD
	acts_clock_peripheral_enable(CLOCK_ID_TIMER1);
	acts_reset_peripheral(CLOCK_ID_TIMER1);
	timer_reg_wait();
#endif
	sys_write32(0x0, CMU_TIMER1CLK); //select hosc

    ctx->timer_register = (timer_register_t *)T1_CTL;
	ctx->timer_load_val = MAX_TIMER_COUNT;

	sys_dlist_init(&ctx->hrtimer_q);

	_hrtimer_reload(ctx->timer_register, ctx->timer_load_val);

    IRQ_CONNECT(IRQ_ID_TIMER1, 0, hrtimer_handle_expired, 0, 0);

    irq_enable(IRQ_ID_TIMER1);
	return 0;
}

#if defined(CONFIG_PM_DEVICE)

#define MUTIPLE soc_rc32K_mutiple_hosc()
#define S3_SOURCE (0x4)

static int hrtimer_runtime_resume(void)
{
    struct hrtimer_ctx *ctx = _get_hrtimer_ctx();
    struct hrtimer *tmpNode;
    struct hrtimer *cur;
    uint32_t val;

    if (sys_dlist_is_empty(&(ctx->hrtimer_q))) {
        ctx->timer_register->ctl = 0x22;
        return 0;
    }

    if (ctx->timer_register->ctl & 0x1) { //irq pending
        sys_write32(0x0, CMU_TIMER1CLK); //select hosc
        return 0;
    }


    val = ctx->timer_register->val - ctx->timer_register->cnt;
    val = val*MUTIPLE;

    if (ctx->adjust_jiffies < ctx->current_jiffies + (uint64_t)val) {
        ctx->adjust_jiffies = ctx->current_jiffies + (uint64_t)val;
    }

    ctx->current_jiffies = ctx->adjust_jiffies;

    SYS_DLIST_FOR_EACH_CONTAINER_SAFE(&ctx->hrtimer_q, cur, tmpNode, node) {
        if (cur->expiry_time > ctx->current_jiffies) {
            ctx->timer_load_val = cur->expiry_time - ctx->current_jiffies;
            break;
        }
    }

	sys_write32(0x0, CMU_TIMER1CLK); //select hosc

    _hrtimer_reload(ctx->timer_register, ctx->timer_load_val);
	return 0;
}

static void hrtimer_runtime_suspend(void)
{
    struct hrtimer_ctx *ctx = _get_hrtimer_ctx();
    struct hrtimer *tmpNode;
    struct hrtimer *cur;
    uint32_t load_val;

    ctx->current_jiffies = _get_current_jiffies();

    if (sys_dlist_is_empty(&(ctx->hrtimer_q))){
        ctx->timer_register->ctl = 0;
        return;
    }

    SYS_DLIST_FOR_EACH_CONTAINER_SAFE(&ctx->hrtimer_q, cur, tmpNode, node){
        if (cur->expiry_time > ctx->current_jiffies) {
            ctx->timer_load_val = cur->expiry_time - ctx->current_jiffies;
            break;
        }
    }

    load_val = ctx->timer_load_val / MUTIPLE;

    sys_write32(S3_SOURCE, CMU_TIMER1CLK); //select RC4M

    _hrtimer_reload(ctx->timer_register, load_val);

    return;

}


int hrtimer_pm_ctrl(const struct device *device, enum pm_device_action action)
{
    if (action == PM_DEVICE_ACTION_RESUME){
        hrtimer_runtime_resume();
    } else if (action == PM_DEVICE_ACTION_SUSPEND){
        hrtimer_runtime_suspend();
    }
    return 0;
}

SYS_DEVICE_DEFINE("hrtimer", hrtimer_runtime_init, hrtimer_pm_ctrl,
		PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
#else
SYS_INIT(hrtimer_runtime_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
#endif