/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* This program is free software; you can redistribute it and/or modify      */
/* it under the terms of the GNU General Public License as published by      */
/* the Free Software Foundation, using version 2 of the License.             */
/*                                                                           */
/* This program is distributed in the hope that it will be useful,           */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/

#include "common.h"
#include <asm/div64.h>
#include <asm/time.h>        /* to_tm() */

/* Internal timer object representation */
struct osal_timer {
    int started;        /* non-zero if timer is started */
    u32 when_started;    /* jiffies when started */

    u32 first_timeout;    /* jiffies before first expire */
    u32 period;        /* period in jiffies */
    void (*handler)(void *);
    void *arg;

    spinlock_t lock;    /* guards against races between reading timer
                   value and changing in interrupt handler */

    struct timer_list timer;
};


static struct handle_table timer_handle_table;
#define allocate_osal_timer_handle(handle) \
    ALLOCATE_HANDLE_MACRO(&timer_handle_table, handle, TMOSAL_ERR_TIMER_FULL)
#define allocate_osal_timer(ot) \
    ALLOCATE_ZEROED_MACRO(ot)
#define bind_osal_timer_to_handle(handle, ot) \
    BIND_OBJECT_MACRO(&timer_handle_table, handle, ot)
#define cancel_osal_timer_handle(handle) \
    CANCEL_HANDLE_MACRO(&timer_handle_table, handle)
#define get_osal_timer(handle, ot) \
    GET_OBJECT_MACRO(&timer_handle_table, handle, ot, TMOSAL_ERR_HANDLE)
#define put_osal_timer(handle) \
    PUT_OBJECT_MACRO(&timer_handle_table, handle)
#define mark_dead_osal_timer(handle) \
    MARK_DEAD_OBJECT_MACRO(&timer_handle_table, handle)


/* Several math routines */

static inline u32 lo(u64 v)
{
    return v & 0xFFFFFFFF;
}

static inline u32 hi(u64 v)
{
    return v >> 32;
}

static inline u64 mku64(u32 hi, u32 lo)
{
    return ((u64)hi << 32) | lo;
}

static inline u64 u64m(u32 v1, u32 v2)
{
    return (u64)v1 * v2;
}

static inline u32 u32md(u32 n, u32 m, u32 d)
{
    u64 t;

    t = u64m(n, m);
    do_div(t, d);
    return t;
}

static inline u64 u64md(u64 n, u32 m, u32 d)
{
    u64 m1, m2, t;
    u32 r;

    m1 = u64m(lo(n), m);
    m2 = u64m(hi(n), m) + hi(m1);    /* m2 . lo(m1) is 96-bit multiplication value */
    r = do_div(m2, d);        /* divide high 64 bits; result goes to m2 */
    t = mku64(r, lo(m1));
    do_div(t, d);            /* divide the rest */
    return t + mku64(lo(m2), 0);    /* assume that result fits in 64 bits: just drop hi(m2) */
}


/* Convert between u32 jiffies and tmosalTime_t */

static inline void jiffies_to_osal_time(u32 jif, tmosalTime_t *t)
{
    u32 msecs;

    /* do this in 32 bits for now */
#if 1000 % HZ == 0
    msecs = jif * (1000 / HZ);
#else
    msecs = u32md(jif, 1000, HZ);
#endif

    t->seconds = msecs / 1000;
    t->milliseconds = msecs % 1000;
}

static inline u32 osal_time_to_jiffies(tmosalTime_t *t)
{
    return u32md(t->seconds * 1000 + t->milliseconds, HZ, 1000);
}


/* Timer function */

static void timer_function(unsigned long arg)
{
    struct osal_timer *ot = (struct osal_timer *)arg;
    unsigned long flags;
    void (*f)(void *);
    void *a;

    /* locking actually needed only on SMP here, but wont hurt on UP
     * since intr handler will never be called while lock is held */

    spin_lock_irqsave(&ot->lock, flags);
    f = ot->handler;
    a = ot->arg;
    if (ot->period) {
        ot->first_timeout = 0;    /* pSoS implementation does so */
        ot->when_started = jiffies;
        ot->timer.expires = ot->when_started + ot->period;
        add_timer(&ot->timer);
    } else
        ot->started = 0;
    spin_unlock_irqrestore(&ot->lock, flags);

    /* at last, call the handler! */
    if (f)
        f(a);
}


/* Time-related API */

tmErrorCode_t tmosalGetSysTime(ptmosalSysTime_t pioSysTime)
{
    u64 t;

    if (!pioSysTime)
        return TMOSAL_ERR_BAD_PARAM;

    t = get_jiffies_64() - INITIAL_JIFFIES;
#if 1000 % HZ == 0
    t = t * (1000 / HZ);
#else
    t = u64md(t, 1000, HZ);
#endif

    pioSysTime->msLo = lo(t);
    pioSysTime->msHi = hi(t);
    return TM_OK;
}
EXPORT_SYMBOL(tmosalGetSysTime);


tmErrorCode_t tmosalGetDateTime(ptmosalDateTime_t pioDateTime)
{
    struct timeval tv;
    struct rtc_time tm;

    if (!pioDateTime)
        return TMOSAL_ERR_BAD_PARAM;

    do_gettimeofday(&tv);
    rtc_time_to_tm(tv.tv_sec, &tm);

    pioDateTime->year = tm.tm_year;
    pioDateTime->month = tm.tm_mon + 1;
    pioDateTime->day = tm.tm_mday;
    pioDateTime->hour = tm.tm_hour;
    pioDateTime->minute = tm.tm_min;
    pioDateTime->second = tm.tm_sec;
    pioDateTime->millisecond = tv.tv_usec / 1000;
    return TM_OK;
}
EXPORT_SYMBOL(tmosalGetDateTime);


tmErrorCode_t tmosalSetDateTime(ptmosalDateTime_t pkDateTime)
{
    struct timespec ts;

    if (unlikely(!pkDateTime))
        return TMOSAL_ERR_BAD_PARAM;

#define CHECK(field, min, max) do {                    \
    if (!(pkDateTime->field >= min && pkDateTime->field <= max))    \
        return TMOSAL_ERR_BAD_PARAM;                \
} while (0)
    CHECK(year, 1980, 2099);
    CHECK(month, 1, 12);
    CHECK(day, 1, 31);
    CHECK(hour, 0, 23);
    CHECK(minute, 0, 59);
    CHECK(second, 0, 59);
    CHECK(millisecond, 0, 999);
#undef CHECK

    ts.tv_sec = mktime(pkDateTime->year, pkDateTime->month,
               pkDateTime->day, pkDateTime->hour,
               pkDateTime->minute, pkDateTime->second);
    ts.tv_nsec = pkDateTime->millisecond * 1000000 + 500000;
    do_settimeofday(&ts);
    return TM_OK;
}
EXPORT_SYMBOL(tmosalSetDateTime);


tmErrorCode_t tmosalTimerCreate(ptmosalTimerHandle_t pioHandle,
        const tmosalTimerCreateFlags_t kFlags)
{
    u32 handle;
    struct osal_timer *ot;

    BUG_IF_INTERRUPT();
    BUG_IF_NOT_PREEMPTIBLE();

    if (unlikely(!pioHandle))
        return TMOSAL_ERR_BAD_PARAM;

    allocate_osal_timer_handle(handle);
    allocate_osal_timer(ot);
    if (unlikely(!ot)) {
        cancel_osal_timer_handle(handle);
        return TMOSAL_ERR_OUT_OF_MEMORY;
    }

    ot->lock = SPIN_LOCK_UNLOCKED;
    init_timer(&ot->timer);
    ot->timer.function = timer_function;
    ot->timer.data = (unsigned long)ot;

    STAT_HANDLE_CREATE(curTimerNr, maxTimerNr);
    bind_osal_timer_to_handle(handle, ot);
    *pioHandle = handle;
    return TM_OK;
}
EXPORT_SYMBOL(tmosalTimerCreate);


tmErrorCode_t tmosalTimerDestroy(const tmosalTimerHandle_t kHandle)
{
    struct osal_timer *ot;

    BUG_IF_INTERRUPT();        /* del_timer_sync() unsafe in atomic context */
    BUG_IF_NOT_PREEMPTIBLE();

    get_osal_timer(kHandle, ot);

    del_timer_sync(&ot->timer);    /* valid both for active and inactive timers */

    mark_dead_osal_timer(kHandle);
    put_osal_timer(kHandle);
    STAT_HANDLE_DESTROY(curTimerNr);
    return TM_OK;
}
EXPORT_SYMBOL(tmosalTimerDestroy);


tmErrorCode_t tmosalTimerStart(const tmosalTimerHandle_t kHandle)
{
    struct osal_timer *ot;
    unsigned long flags;
    u32 d;

    get_osal_timer(kHandle, ot);
    spin_lock_irqsave(&ot->lock, flags);

    if (unlikely(ot->started)) {
        spin_unlock_irqrestore(&ot->lock, flags);
        put_osal_timer(kHandle);
        return TM_OK;
    }

    ot->started = 1;
    ot->when_started = jiffies;

    d = ot->first_timeout;
    if (!d)
        d = ot->period;
    if (d) {
        /* add_timer() will take care of possible jiffy increase
         * while this code is running */
        ot->timer.expires = ot->when_started + d;
        add_timer(&ot->timer);
    }

    spin_unlock_irqrestore(&ot->lock, flags);
    put_osal_timer(kHandle);
    return TM_OK;
}
EXPORT_SYMBOL(tmosalTimerStart);


tmErrorCode_t tmosalTimerStop(const tmosalTimerHandle_t kHandle)
{
    struct osal_timer *ot;
    unsigned long flags;

    get_osal_timer(kHandle, ot);
    spin_lock_irqsave(&ot->lock, flags);

    if (unlikely(!ot->started)) {
        spin_unlock_irqrestore(&ot->lock, flags);
        put_osal_timer(kHandle);
        return TMOSAL_ERR_TIMER_STOPPED;
    }

    del_timer_sync(&ot->timer);
    ot->started = 0;
    spin_unlock_irqrestore(&ot->lock, flags);
    put_osal_timer(kHandle);
    return TM_OK;
}
EXPORT_SYMBOL(tmosalTimerStop);


tmErrorCode_t tmosalTimerGetTime(const tmosalTimerHandle_t kHandle,
        ptmosalTime_t pioCurrentValue)
{
    struct osal_timer *ot;
    unsigned long flags;
    u32 jif;

    if (unlikely(!pioCurrentValue))
        return TMOSAL_ERR_BAD_PARAM;

    get_osal_timer(kHandle, ot);
    spin_lock_irqsave(&ot->lock, flags);

    if (unlikely(!ot->started)) {
        spin_unlock_irqrestore(&ot->lock, flags);
        put_osal_timer(kHandle);
        return TMOSAL_ERR_HANDLE;    /* pSoS version returns this */
    }

    jif = jiffies - ot->when_started;
    spin_unlock_irqrestore(&ot->lock, flags);
    put_osal_timer(kHandle);

    jiffies_to_osal_time(jif, pioCurrentValue);
    return TM_OK;
}
EXPORT_SYMBOL(tmosalTimerGetTime);


tmErrorCode_t tmosalTimerInstanceGetSetup(const tmosalTimerHandle_t kHandle,
        ptmosalTimerSetup_t pioSetup)
{
    struct osal_timer *ot;
    unsigned long flags;

    if (unlikely(!pioSetup))
        return TMOSAL_ERR_BAD_PARAM;

    get_osal_timer(kHandle, ot);
    spin_lock_irqsave(&ot->lock, flags);
    jiffies_to_osal_time(ot->first_timeout, &pioSetup->value);
    jiffies_to_osal_time(ot->period, &pioSetup->modulus);
    pioSetup->pTimerHandler = ot->handler;
    pioSetup->pContextArgument = ot->arg;
    spin_unlock_irqrestore(&ot->lock, flags);
    put_osal_timer(kHandle);
    return TM_OK;
}
EXPORT_SYMBOL(tmosalTimerInstanceGetSetup);

tmErrorCode_t tmosalTimerInstanceSetup(const tmosalTimerHandle_t kHandle,
        const ptmosalTimerSetup_t pkSetup)
{
    struct osal_timer *ot;
    unsigned long flags;

    if (unlikely(!pkSetup))
        return TMOSAL_ERR_BAD_PARAM;

    get_osal_timer(kHandle, ot);
    spin_lock_irqsave(&ot->lock, flags);

    if (unlikely(ot->started)) {
        spin_unlock_irqrestore(&ot->lock, flags);
        put_osal_timer(kHandle);
        return TMOSAL_ERR_HANDLE;
    }

    ot->first_timeout = osal_time_to_jiffies(&pkSetup->value);
    if(unlikely(ot->first_timeout == 0))
    {
        return TMOSAL_ERR_BAD_PARAM;
    }
    ot->period = osal_time_to_jiffies(&pkSetup->modulus);
    ot->handler = pkSetup->pTimerHandler;
    ot->arg = pkSetup->pContextArgument;

    spin_unlock_irqrestore(&ot->lock, flags);
    put_osal_timer(kHandle);
    return TM_OK;
}
EXPORT_SYMBOL(tmosalTimerInstanceSetup);


static inline tmosalTime_t normalize(const tmosalTime_t t)
{
    tmosalTime_t ret;

    ret.seconds = t.seconds + t.milliseconds / 1000;
    ret.milliseconds = t.milliseconds % 1000;

    return ret;
}

tmosalTime_t tmosalAddTime(const tmosalTime_t kTimeA, const tmosalTime_t kTimeB)
{
    tmosalTime_t tmp;

    tmp.seconds = kTimeA.seconds + kTimeB.seconds;
    tmp.milliseconds = kTimeA.milliseconds + kTimeB.milliseconds;
    return normalize(tmp);
}
EXPORT_SYMBOL(tmosalAddTime);


tmosalTime_t tmosalSubTime(const tmosalTime_t kTimeA, const tmosalTime_t kTimeB)
{
    tmosalTime_t tmpa, tmpb, ret;

    tmpa = normalize(kTimeA);
    tmpb = normalize(kTimeB);

    ret.seconds = tmpa.seconds - tmpb.seconds - 1;
    ret.milliseconds = tmpa.milliseconds - tmpb.milliseconds + 1000;
    ret = normalize(ret);    /* will work even for negative ret.seconds */

    if (ret.seconds < 0)
        ret.seconds = ret.milliseconds = 0;

    return ret;
}
EXPORT_SYMBOL(tmosalSubTime);


int osal_time_submodule_init(void)
{
    return init_handle_table(&timer_handle_table, TIMER_HANDLE_TABLE_INITIAL_SIZE, "timer");
}

void osal_time_submodule_uninit(void)
{
    uninit_handle_table(&timer_handle_table);
}
