/* SPDX-License-Identifier: LGPL-2.1-only OR BSD-3-Clause */
/******************************************************************************
 *
 * This file is provided under a dual license.  When you use or
 * distribute this software, you may choose to be licensed under
 * version 2.1 of the GNU Lesser General Public License
 * ("LGPLv2.1 License") or BSD License.
 *
 * LGPLv2.1 License
 *
 * Copyright(C) 2019 MediaTek Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * 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 https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html for more
 * details.
 *
 * BSD LICENSE
 *
 * Copyright(C) 2019 MediaTek Inc.
 * 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 the copyright holder 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
 * HOLDER 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.
 *
 *****************************************************************************/



#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/param.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/sched.h>
#include "x_assert.h"
#include "x_os.h"
#include "x_ckgen.h"
#include "x_bim.h"
#include "x_timer.h"
#include "x_util.h"

#define TIMER_LIMIT             0xffffffff
#define TIMER_ID                1
#define TIMER_REG               (REG_RW_TIMER0_LOW + TIMER_ID * 8)

static VOID TimerHISR(unsigned long param);

static const UINT32 s_timer_res = 1000 / HZ;
static TIME_STAMP_T _arTimeStamp[MAX_SW_TIMESTAMP_SIZE];
static UINT32 _u4TimeStampSize = 0;
static struct timer_list os_tick_timer = TIMER_INITIALIZER(TimerHISR, 0, 0);

/* FIXME: ask why? MHF Linux - mcn03047 */ /* MHF Linux - jiang */
static wait_queue_head_t wt_tick;
static int tick_cond;

static int s_timer_inited=0;

static VOID TimerHISR(unsigned long param)
{
    UNUSED(param);
#if 1  /* MHF Linux - jiang */
    tick_cond++;
    wake_up_interruptible(&wt_tick);
#endif

    os_tick_timer.expires ++;
    add_timer(&os_tick_timer);
}

VOID x_polling_tick(void)
{
    wait_event_interruptible(wt_tick,(tick_cond > 0));
    if (tick_cond > 0)
       tick_cond--;
}

/* MHF Linux - jiang */

/*----------------------------------------------------------------------------
 * Function: x_os_drv_get_time()
 *
 * Description:
 *      to get system boot up time.
 *
 * Inputs: -
 *
 * Outputs: -
 *
 * Returns: -
 *---------------------------------------------------------------------------*/
VOID x_os_drv_get_time(UINT32 *pui4Seconds, UINT32 *pui4Micros)
{
    HAL_TIME_T rTime;
    HAL_GetTime(&rTime);
    if ((pui4Seconds == NULL) || (pui4Micros == NULL))
    {
        return;
    }
    *pui4Seconds = rTime.u4Seconds;
    *pui4Micros = rTime.u4Micros;
}

//FIXME: ask user to stop use it
/*----------------------------------------------------------------------------
 * Function: os_get_sys_tick()
 *
 * Description:
 *
 * Inputs: -
 *
 * Outputs: -
 *
 * Returns:
 *      Middleware tick count.
 *---------------------------------------------------------------------------*/
UINT32
os_get_sys_tick(void)
{
    return(jiffies);
}

/*----------------------------------------------------------------------------
 * Function: x_os_drv_get_fine_tick
 *
 * Description:
 *      This function returns the current tick value of hardware timer running
 *      at bus clock speed
 *
 * Inputs: -
 *
 * Outputs: -
 *
 * Returns:
 *      The current tick value of hardware timer
 *---------------------------------------------------------------------------*/
UINT32
x_os_drv_get_fine_tick(VOID)
{
    UINT32 u4Ticks;

    u4Ticks = BIM_READ32(TIMER_REG);
    return TIMER_LIMIT - u4Ticks;
}


/*----------------------------------------------------------------------------
 * Function: x_os_drv_set_timestamp()
 *
 * Description:
 *      to get system boot up time.
 *
 * Inputs: -
 *
 * Outputs: -
 *
 * Returns: -
 *---------------------------------------------------------------------------*/
VOID x_os_drv_set_timestamp(CHAR *szString)
{
    if (_u4TimeStampSize >= MAX_SW_TIMESTAMP_SIZE)
    {
        _u4TimeStampSize = MAX_SW_TIMESTAMP_SIZE - 1;
    }
    _arTimeStamp[_u4TimeStampSize].u4TimeStamp = BIM_READ32(REG_RW_TIMER2_LOW);
    _arTimeStamp[_u4TimeStampSize].szString = szString;
    _u4TimeStampSize++;
}

/*----------------------------------------------------------------------------
 * Function: x_os_drv_get_timestamp()
 *
 * Description:
 *      to get system boot up timestamp list.
 *
 * Inputs: -
 *
 * Outputs: -
 *
 * Returns: -
 *---------------------------------------------------------------------------*/
TIME_STAMP_T * x_os_drv_get_timestamp(UINT32 *pu4Size)
{
    *pu4Size = _u4TimeStampSize;
    return _arTimeStamp;
}
/*----------------------------------------------------------------------------
 * Function: x_os_drv_get_raw_timestamp()
 *
 * Description:
 *      return us time form cold boot
 *
 * Inputs: -
 *
 * Outputs: -
 *
 * Returns: -
 *---------------------------------------------------------------------------*/
UINT32 x_os_drv_get_raw_timestamp(VOID)
{
	UINT32 u4_time_lo1, u4_time_hi1;
	UINT32 u4Val ;
	UINT64 u8Time;

    u4_time_lo1 = BIM_READ32(REG_RW_TIMER2_LOW);
    u4_time_hi1 = BIM_READ32(REG_RW_TIMER2_HIGH);
	u8Time =  ~(((UINT64)u4_time_hi1<< 32) + (UINT64)u4_time_lo1);
	u4Val = GET_XTAL_CLK()/1000000;
	u4Val = (UINT32) u8Div6432(u8Time, (UINT64)u4Val ,NULL);
	 // Printf("%6d.%03d ms \n", (int)(u4Val / 1000), (int)(u4Val % 1000));
	 return u4Val ;
}

VOID x_os_drv_reset_timestamp(VOID)
{
    _u4TimeStampSize = 0;
}

typedef struct os_timer_light
{
    struct timer_list timer;
    x_os_timer_cb_fct pf_callback;
    VOID *pv_tag;
    BOOL fg_active;
    unsigned long interval;
} OS_TIMER_LIGHT_T;



/*----------------------------------------------------------------------------
 * Function: x_os_drv_get_tick_period()
 *
 * Description:
 *      this API returns tick's resolution is millisecond (ms). it performs
 *      1. get underlying OS HZ definition.
 *      2. round up resolution.
 *
 * Inputs: -
 *
 * Outputs: -
 *
 * Returns:
 *      tick's resolution is millisecond (ms).
 *---------------------------------------------------------------------------*/
UINT32
x_os_drv_get_tick_period()
{
    return s_timer_res;
}

static void TimerProc(unsigned long id)
{
    OS_TIMER_LIGHT_T *pt_timer;

    pt_timer = (OS_TIMER_LIGHT_T *)(id);
    pt_timer->pf_callback((HANDLE_T)(pt_timer), pt_timer->pv_tag);

    if (pt_timer->interval != 0)
    {
        pt_timer->timer.expires += pt_timer->interval;
        add_timer(&pt_timer->timer);
    }
    else
    {
        pt_timer->fg_active = FALSE;
    }
}


INT32 x_timer_create (HANDLE_T *ph_timer)
{
    OS_TIMER_LIGHT_T *pt_timer;

    if (ph_timer == NULL)
    {
        return OSR_INV_ARG;
    }

    pt_timer = kcalloc(1, sizeof(OS_TIMER_LIGHT_T), GFP_KERNEL);
    if (pt_timer == NULL)
    {
        return OSR_NO_RESOURCE;
    }
    //FILL_CALLER(pt_timer);

    init_timer(&pt_timer->timer);

    *ph_timer = (HANDLE_T)(pt_timer);
    return OSR_OK;
}


INT32 x_timer_start (HANDLE_T           h_timer,
                     UINT32             ui4_delay,
                     TIMER_FLAG_T       e_flags,
                     x_os_timer_cb_fct  pf_callback,
                     VOID*              pv_tag)
{
    OS_TIMER_LIGHT_T *pt_timer;
    unsigned long j;

    if ((pf_callback == NULL) || (ui4_delay == 0) ||
        ((e_flags != X_TIMER_FLAG_ONCE) && (e_flags != X_TIMER_FLAG_REPEAT)))
    {
        return OSR_INV_ARG;
    }

    j = jiffies;
    pt_timer = (OS_TIMER_LIGHT_T *)(h_timer);
    pt_timer->timer.expires = j + ui4_delay / s_timer_res;
    pt_timer->timer.function = &TimerProc;
    pt_timer->timer.data = (unsigned long)pt_timer;
    if (e_flags == X_TIMER_FLAG_ONCE)
    {
        pt_timer->interval = 0;
    }
    else
    {
        pt_timer->interval = ui4_delay / s_timer_res;
    }

    pt_timer->pf_callback = pf_callback;
    pt_timer->pv_tag = pv_tag;
    pt_timer->fg_active = TRUE;
    add_timer(&pt_timer->timer);

    return OSR_OK;
}


INT32 x_timer_stop (HANDLE_T  h_timer)
{
    OS_TIMER_LIGHT_T *pt_timer;

    pt_timer = (OS_TIMER_LIGHT_T *)(h_timer);
    if (pt_timer->fg_active)
    {
        del_timer_sync(&pt_timer->timer);
        pt_timer->fg_active = FALSE;
        pt_timer->timer.expires -= jiffies;
    }
    return OSR_OK;
}


INT32 x_timer_delete (HANDLE_T  h_timer)
{
    OS_TIMER_LIGHT_T *pt_timer;

    pt_timer = (OS_TIMER_LIGHT_T *)(h_timer);
    if (pt_timer->fg_active)
    {
        del_timer_sync(&pt_timer->timer);
    }
    kfree(pt_timer);
    return OSR_OK;
}


INT32 x_timer_resume (HANDLE_T  h_timer)
{
    OS_TIMER_LIGHT_T *pt_timer;

    pt_timer = (OS_TIMER_LIGHT_T *)(h_timer);
    if (!pt_timer->fg_active)
    {
        pt_timer->timer.expires += jiffies;
        pt_timer->fg_active = TRUE;
        add_timer(&pt_timer->timer);
    }

    return OSR_OK;
}


UINT32 x_os_get_sys_tick (VOID)
{
    return jiffies;
}


UINT32 x_os_get_sys_tick_period (VOID)
{
    return s_timer_res;
}


INT32 os_timer_init (VOID)
{
    if (s_timer_inited == 1)
    {
        return OSR_OK;
    }

    s_timer_inited = 1;

#if 1       /* MHF Linux - jiang */
    init_waitqueue_head(&wt_tick);
    tick_cond = 0;
#endif

    os_tick_timer.expires = jiffies + 1;
    add_timer(&os_tick_timer);

    return OSR_OK;
}

INT32
_timer_init(void)
{
    return os_timer_init();
}

