/*
 * Copyright (c) 2017, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 *  ======== ClockPTimer_nortos.c ========
 */

#include <stdlib.h>

#include <ti/devices/DeviceFamily.h>

#include <ti/drivers/dpl/ClockP.h>
#include <ti/drivers/dpl/HwiP.h>
#include <ti/drivers/dpl/SemaphoreP.h>
#include <ti/drivers/dpl/SwiP.h>

#include "QueueP.h"
#include "TimerP.h"

#define CPU_CLOCK_HZ            ((uint32_t)48000000)
#define SYSTICK_FREQ            1000

typedef struct _ClockP_Module_State {
    QueueP_Obj clockQ;
    volatile uint32_t ticks;
    SwiP_Struct swiStruct;
    SwiP_Handle swi;
    TimerP_Struct timerStruct;
    TimerP_Handle timer;
    volatile unsigned int numTickSkip;
    uint32_t nextScheduledTick;
    uint32_t maxSkippable;
    bool inWorkFunc;
    bool startDuringWorkFunc;
    bool ticking;
} ClockP_Module_State;

typedef struct _ClockP_Obj {
    QueueP_Elem elem;
    uint32_t timeout;
    uint32_t currTimeout;
    uint32_t period;
    volatile bool active;
    ClockP_Fxn fxn;
    uintptr_t arg;
} ClockP_Obj;

static ClockP_Module_State ClockP_module;
static bool ClockP_initialized = false;
static ClockP_Params ClockP_defaultParams = {
    .startFlag = false,
    .period = 0,
    .arg = 0,
};


/*
 *  Set the default clock period to 10 microseconds for CC13XX and
 *  CC26XX devices, 1 msec for all other devices.
 */
#if (DeviceFamily_PARENT == DeviceFamily_PARENT_CC13X0_CC26X0) || \
    (DeviceFamily_PARENT == DeviceFamily_PARENT_CC13X2_CC26X2)
uint32_t ClockP_tickPeriod = 10;
#else
uint32_t ClockP_tickPeriod = 1000;
#endif

void ClockP_workFuncDynamic(uintptr_t arg0, uintptr_t arg1);
void ClockP_doTick();

static void sleepTicks(uint32_t ticks);

static TimerP_FreqHz timerFreq;
static uint32_t usecsPerTimerTick;

/*
 *  ======== ClockP_Params_init ========
 */
void ClockP_Params_init(ClockP_Params *params)
{
    /* structure copy */
    *params = ClockP_defaultParams;
}

/*
 *  ======== ClockP_startup ========
 */
void ClockP_startup(void)
{
    SwiP_Params swiParams;
    TimerP_Params timerParams;

    if (!ClockP_initialized) {
        QueueP_init(&ClockP_module.clockQ);
        ClockP_module.ticks = 0;
        ClockP_module.numTickSkip = 1;
        ClockP_module.nextScheduledTick = 1;
        ClockP_module.maxSkippable = 0;
        ClockP_module.inWorkFunc = false;
        ClockP_module.startDuringWorkFunc = false;
        ClockP_module.ticking = false;

        SwiP_Params_init(&swiParams);
        swiParams.priority = ~0;  /* max priority */
        ClockP_module.swi = SwiP_construct(&ClockP_module.swiStruct,
                                           (SwiP_Fxn)ClockP_workFuncDynamic,
                                           &swiParams);

        TimerP_Params_init(&timerParams);
        timerParams.period = ClockP_tickPeriod;
        ClockP_module.timer = TimerP_construct(&ClockP_module.timerStruct,
                                               (TimerP_Fxn)ClockP_doTick,
                                               &timerParams);

        /* get the max ticks that can be skipped by the timer */
        ClockP_module.maxSkippable = TimerP_getMaxTicks(ClockP_module.timer);

        /* Used for ClockP_usleep() */
        TimerP_getFreq(ClockP_module.timer, &timerFreq);
        usecsPerTimerTick = 1000000 / timerFreq.lo;

        ClockP_initialized = true;
    }
}

/*
 *  ======== ClockP_getTicks  ========
 */
uint32_t ClockP_getTicks(void)
{
    uint32_t ticks;
    uintptr_t hwiKey;

    hwiKey = HwiP_disable();

    /* do not ask Timer to save NOW */
    ticks = TimerP_getCurrentTick(ClockP_module.timer, false);

    HwiP_restore(hwiKey);

    return (ticks);
}

/*
 *  ======== ClockP_getTicksUntilInterrupt  ========
 */
uint32_t ClockP_getTicksUntilInterrupt(void)
{
    uint32_t ticks;
    uint32_t current;
    uintptr_t key;

    key = HwiP_disable();

    /* do not ask Timer to save NOW */
    current = TimerP_getCurrentTick(ClockP_module.timer, false);

    ticks = ClockP_module.nextScheduledTick - current;

    /* clamp value to zero if nextScheduledTick is less than current */
    if (ticks > ClockP_module.maxSkippable) {
        ticks = 0;
    }

    HwiP_restore(key);

    return (ticks);
}

/*
 *  ======== ClockP_scheduleNextTick  ========
 *  Must be called with global interrupts disabled!
 */
void ClockP_scheduleNextTick(uint32_t deltaTicks, uint32_t absTick)
{
    /* now reprogram the timer for the new period and next interrupt */
    TimerP_setNextTick(ClockP_module.timer, deltaTicks);

    /* remember this */
    ClockP_module.numTickSkip = deltaTicks;
    ClockP_module.nextScheduledTick = absTick;
}

/*
 *  ======== ClockP_walkQueueDynamic ========
 *  Walk the Clock Queue for TickMode_DYNAMIC, optionally servicing a
 *  specific tick
 */
uint32_t ClockP_walkQueueDynamic(bool service, uint32_t thisTick)
{
    uint32_t distance = ~0;
    QueueP_Handle clockQ;
    QueueP_Elem *elem;
    ClockP_Obj *obj;
    uint32_t delta;

    /* Traverse clock queue */
    clockQ = &ClockP_module.clockQ;
    elem = (QueueP_Elem *)QueueP_head(clockQ);

    while (elem != (QueueP_Elem *)(clockQ)) {

        obj = (ClockP_Obj *)elem;
        elem = (QueueP_Elem *)QueueP_next(elem);

        /* if  the object is active ... */
        if (obj->active == true) {

            /* optionally service if tick matches timeout */
            if (service == true) {

                /* if this object is timing out update its state */
                if (obj->currTimeout == thisTick) {

                    if (obj->period == 0) { /* oneshot? */
                        /* mark object idle */
                        obj->active = false;
                    }
                    else {                      /* periodic */
                        /* refresh timeout */
                        obj->currTimeout += obj->period;
                    }

                    /* call handler */
                    obj->fxn(obj->arg);
                }
            }

            /* if object still active update distance to soonest tick */
            if (obj->active == true) {

                delta = obj->currTimeout - thisTick;

                /* if this is the soonest tick update distance to soonest */
                if (delta < distance) {
                    distance = delta;
                }

            }
        }
    }

    return (distance);
}

/*
 *  ======== ClockP_workFuncDynamic ========
 *  Service Clock Queue for TickMode_DYNAMIC
 */
void ClockP_workFuncDynamic(uintptr_t arg0, uintptr_t arg1)
{
    uint32_t distance;
    uint32_t serviceTick, serviceDelta;
    uint32_t ticksToService;
    unsigned int skippable;
    uint32_t nowTick, nowDelta, nextTick;
    uintptr_t hwiKey;

    hwiKey = HwiP_disable();

    /* get current tick count, signal Timer to save corresponding NOW info */
    nowTick = TimerP_getCurrentTick(ClockP_module.timer, true);

    /* set flags while actively servicing queue */
    ClockP_module.inWorkFunc = true;
    ClockP_module.startDuringWorkFunc = false;

    /* determine first tick expiration to service (the anticipated next tick) */
    serviceTick = ClockP_module.nextScheduledTick;
    ticksToService = nowTick - serviceTick;

    /*
     * if now hasn't caught up to nextScheduledTick,
     * a spurious interrupt has probably occurred.
     * ignore for now...
     */

    serviceDelta = serviceTick - ClockP_module.ticks;
    nowDelta = nowTick - ClockP_module.ticks;
    if (serviceDelta > nowDelta) {
        ClockP_module.inWorkFunc = false;
        HwiP_restore(hwiKey);
        return;
    }

    HwiP_restore(hwiKey);

    distance = 0;

    /* walk queue until catch up to current tick count */
    while (ticksToService >= distance) {
        serviceTick = serviceTick + distance;
        ticksToService -= distance;
        distance = ClockP_walkQueueDynamic(true, serviceTick);
    }

    /* now determine next needed tick and setup timer for that tick ... */
    hwiKey = HwiP_disable();

    /* if ClockP_start() during processing of Q, re-walk to update distance */
    if (ClockP_module.startDuringWorkFunc == true) {
        distance = ClockP_walkQueueDynamic(false, serviceTick);
    }

    /* if no active timeouts then skip the maximum supported by the timer */
    if (distance == ~0) {
        skippable = ClockP_module.maxSkippable;
        nextTick = serviceTick + skippable;
    }
    /* else, finalize how many ticks can skip */
    else {
        skippable = distance - ticksToService;
        if (skippable > ClockP_module.maxSkippable) {
            skippable = ClockP_module.maxSkippable;
        }
        nextTick = serviceTick + skippable;
    }

    /* reprogram timer for next expected tick */
    ClockP_scheduleNextTick(skippable, nextTick);

    ClockP_module.ticking = true;
    ClockP_module.inWorkFunc = false;
    ClockP_module.ticks = serviceTick;

    HwiP_restore(hwiKey);
}

/*
 *  ======== ClockP_doTick ========
 */
void ClockP_doTick()
{
    SwiP_post(ClockP_module.swi);
}

/*
 *  ======== ClockP_construct ========
 */
ClockP_Handle ClockP_construct(ClockP_Struct *handle, ClockP_Fxn fxn,
                               uint32_t timeout, ClockP_Params *params)
{
    ClockP_Obj *obj = (ClockP_Obj *)handle;

    if (handle == NULL) {
        return NULL;
    }

    ClockP_startup();

    if (params == NULL) {
        params = &ClockP_defaultParams;
    }

    obj->period = params->period;
    obj->timeout = timeout;
    obj->fxn = fxn;
    obj->arg = params->arg;
    obj->active = false;

    /*
     * Clock object is always placed on Clock work Q
     */
    QueueP_put(&ClockP_module.clockQ, &obj->elem);

    if (params->startFlag) {
        ClockP_start(obj);
    }

    return ((ClockP_Handle)handle);
}

/*
 *  ======== ClockP_add ========
 */
void ClockP_add(ClockP_Struct *handle, ClockP_Fxn fxn,
                uint32_t timeout, uintptr_t arg)
{
    ClockP_Obj *obj = (ClockP_Obj *)handle;

    obj->period = 0;
    obj->timeout = timeout;
    obj->fxn = fxn;
    obj->arg = arg;
    obj->active = false;

    /*
     * Clock object is always placed on Clock work Q
     */
    QueueP_put(&ClockP_module.clockQ, &obj->elem);
}

/*
 *  ======== ClockP_create ========
 */
ClockP_Handle ClockP_create(ClockP_Fxn clkFxn, uint32_t timeout,
                            ClockP_Params *params)
{
    ClockP_Handle handle;

    handle = (ClockP_Handle)malloc(sizeof(ClockP_Obj));

    /* ClockP_construct will check handle for NULL, no need here */
    handle = ClockP_construct((ClockP_Struct *)handle, clkFxn, timeout,
                              params);

    return (handle);
}

/*
 *  ======== ClockP_destruct ========
 */
void ClockP_destruct(ClockP_Struct *clk)
{
    ClockP_Obj *obj = (ClockP_Obj *)clk;
    uintptr_t key;

    key = HwiP_disable();
    QueueP_remove(&obj->elem);
    HwiP_restore(key);
}

/*
 *  ======== ClockP_delete ========
 */
void ClockP_delete(ClockP_Handle handle)
{
    ClockP_destruct((ClockP_Struct *)handle);

    free(handle);
}

/*
 *  ======== ClockP_start ========
 *  Set the Clock object's currTimeout value and set its active flag
 *  to true.
 */
void ClockP_start(ClockP_Handle handle)
{
    ClockP_Obj *obj = (ClockP_Obj *)handle;
    uintptr_t key = HwiP_disable();

    uint32_t nowTick, nowDelta;
    uint32_t scheduledTick, scheduledDelta;
    uint32_t remainingTicks;
    bool objectServiced = false;

    /* now see if need this new timeout before next scheduled tick ... */
    /* wait till after first tick */
    if ((ClockP_module.ticking == true) &&
        /* if Clock is NOT currently processing its Q */
        (ClockP_module.inWorkFunc == false)) {

        /*
         * get virtual current tick count,
         * signal Timer to save corresponding NOW info
         */
        nowTick = TimerP_getCurrentTick(ClockP_module.timer, true);

        nowDelta = nowTick - ClockP_module.ticks;
        scheduledDelta = ClockP_module.nextScheduledTick - ClockP_module.ticks;

        if (nowDelta <= scheduledDelta) {
            objectServiced = true;

            /* start new Clock object */
            obj->currTimeout = nowTick + obj->timeout;
            obj->active = true;

            /* get the next scheduled tick */
            scheduledTick = ClockP_module.nextScheduledTick;

            /* how many ticks until scheduled tick? */
            remainingTicks = scheduledTick - nowTick;

            if (obj->timeout < remainingTicks) {
                ClockP_scheduleNextTick(obj->timeout, obj->currTimeout);
            }
        }
    }

    if (objectServiced == false) {
        /*
         * get virtual current tick count,
         * DO NOT (!) signal Timer to save corresponding NOW info
         */
        nowTick = ClockP_getTicks();

        /* start new Clock object */
        obj->currTimeout = nowTick + obj->timeout;
        obj->active = true;

        if (ClockP_module.inWorkFunc == true) {
            ClockP_module.startDuringWorkFunc = true;
        }
    }

    HwiP_restore(key);
}

/*
 *  ======== ClockP_stop ========
 *  remove and clear Clock object's queue elem from clockQ
 */
void ClockP_stop(ClockP_Handle handle)
{
    ClockP_Obj *obj = (ClockP_Obj *)handle;

    obj->active = false;
}

/*
 *  ======== ClockP_setTimeout ========
 */
void ClockP_setTimeout(ClockP_Handle handle, uint32_t timeout)
{
    ClockP_Obj *obj = (ClockP_Obj *)handle;

    obj->timeout = timeout;
}

/*
 *  ======== ClockP_getTimeout ========
 */
uint32_t ClockP_getTimeout(ClockP_Handle handle)
{
    ClockP_Obj *obj = (ClockP_Obj *)handle;

    if (obj->active == true) {
        return (obj->currTimeout - ClockP_getTicks());
    }
    else {
        return (obj->timeout);
    }
}

/*
 *  ======== ClockP_isActive ========
 */
bool ClockP_isActive(ClockP_Handle handle)
{
    ClockP_Obj *obj = (ClockP_Obj *)handle;

    return (obj->active);
}

/*
 *  ======== ClockP_getCpuFreq ========
 */
void ClockP_getCpuFreq(ClockP_FreqHz *freq)
{
    freq->lo = (uint32_t)CPU_CLOCK_HZ;
    freq->hi = 0;
}

/*
 *  ======== ClockP_getSystemTickPeriod ========
 */
uint32_t ClockP_getSystemTickPeriod(void)
{
    return (ClockP_tickPeriod);
}

/*
 *  ======== ClockP_getSystemTicks ========
 */
uint32_t ClockP_getSystemTicks(void)
{
    uint32_t  ticks;
    uintptr_t key;

    ClockP_startup();

    key = HwiP_disable();

    /*
     *  Needs to be called with interrupts disabled, at least for
     *  MSP432, where ticks is computed based on the number of timer
     *  counter rollovers and the value of the timer counter register.
     */
    ticks = TimerP_getCurrentTick(ClockP_module.timer, false);

    HwiP_restore(key);

    return (ticks);
}

/*
 *  ======== ClockP_sleep ========
 */
void ClockP_sleep(uint32_t sec)
{
    uint64_t ticksToSleep;

    ClockP_startup();

    ticksToSleep = ((uint64_t)sec * (uint64_t)1000000) / (uint64_t)ClockP_tickPeriod;
    sleepTicks((uint32_t)ticksToSleep);
}

/*
 *  ======== ClockP_usleep ========
 */
void ClockP_usleep(uint32_t usec)
{
    uint64_t curTick, endTick;
    uint32_t ticksToSleep;

    ClockP_startup();

    curTick = TimerP_getCount64(ClockP_module.timer);

    /* Make sure we sleep at least one tick if usec > 0 */
    endTick = curTick + (usec + usecsPerTimerTick - 1) / usecsPerTimerTick;

    /*
     *  If usec > ClockP_tickPeriod, sleep for the appropriate number
     *  of clock ticks.
     */
    if (usec >= ClockP_tickPeriod) {
        ticksToSleep = usec / ClockP_tickPeriod;
        sleepTicks(ticksToSleep);
    }

    curTick = TimerP_getCount64(ClockP_module.timer);
    while (curTick < endTick) {
        curTick = TimerP_getCount64(ClockP_module.timer);;
    }
}

/*
 *  ======== ClockP_staticObjectSize ========
 *  Internal function for testing that ClockP_Struct is large enough
 *  to hold ClockP object.
 */
size_t ClockP_staticObjectSize(void)
{
    return (sizeof(ClockP_Obj));
}

/*
 *  ======== sleepTicks ========
 *  Sleep for a given number of ClockP ticks.
 */
static void sleepTicks(uint32_t ticks)
{
    SemaphoreP_Struct semStruct;
    SemaphoreP_Handle sem;

    if (ticks > 0) {
        /*
         *  Construct a semaphore with 0 count that will never be posted.
         *  We will timeout pending on this semaphore.
         */
        sem = SemaphoreP_construct(&semStruct, 0, NULL);
        SemaphoreP_pend(sem, ticks);
        SemaphoreP_destruct(&semStruct);
    }
}
