/*
 * osal_timer.c
 *
 * osal driver
 *
 * Copyright (c) 2020-2021 Huawei Device Co., Ltd.
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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.
 *
 */

#include "osal_timer.h"
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/signal.h>
#include <linux/timer.h>
#include "hdf_log.h"
#include "osal_mem.h"
#include "osal_mutex.h"

#define HDF_LOG_TAG osal_timer

typedef enum {
	OSAL_TIMER_ONCE,
	OSAL_TIMER_LOOP,
} OsalTimerMode;

struct osal_ktimer {
	uintptr_t arg;
	struct timer_list timer;
	OsalTimerFunc func;
	uint32_t msec;
	struct OsalMutex mutex;
	OsalTimerMode mode;
	bool stop_flag;
};

static void osal_timer_callback(struct timer_list *arg)
{
	struct osal_ktimer *ktimer = NULL;
	uint32_t msec;
	OsalTimerMode mode;
	bool stop_flag = false;

	if (arg == NULL) {
		HDF_LOGI("%s timer is stopped", __func__);
		return;
	}

	ktimer = from_timer(ktimer, arg, timer);

	OsalMutexTimedLock(&ktimer->mutex, HDF_WAIT_FOREVER);
	mode = ktimer->mode;
	stop_flag = ktimer->stop_flag;
	OsalMutexUnlock(&ktimer->mutex);

	if (!stop_flag) {
		ktimer->func(ktimer->arg);
		OsalMutexTimedLock(&ktimer->mutex, HDF_WAIT_FOREVER);
		msec = ktimer->msec;
		OsalMutexUnlock(&ktimer->mutex);
		if (mode == OSAL_TIMER_LOOP) {
			ktimer->timer.expires = jiffies + msecs_to_jiffies(msec);
			mod_timer(&ktimer->timer, ktimer->timer.expires);
		}
	} else {
		del_timer(&ktimer->timer);
		OsalMutexDestroy(&ktimer->mutex);
		OsalMemFree(ktimer);
		HDF_LOGI("%s timer is stop", __func__);
	}
}

int32_t OsalTimerCreate(OsalTimer *timer, uint32_t interval, OsalTimerFunc func, uintptr_t arg)
{
	struct osal_ktimer *ktimer = NULL;

	if (func == NULL || timer == NULL || interval == 0) {
		HDF_LOGE("%s invalid para", __func__);
		return HDF_ERR_INVALID_PARAM;
	}

	ktimer = (struct osal_ktimer *)OsalMemCalloc(sizeof(*ktimer));
	if (ktimer == NULL) {
		HDF_LOGE("%s malloc fail", __func__);
		timer->realTimer = NULL;
		return HDF_ERR_MALLOC_FAIL;
	}

	ktimer->arg = arg;
	ktimer->func = func;
	ktimer->msec = interval;
	ktimer->stop_flag = false;
	OsalMutexInit(&ktimer->mutex);
	timer->realTimer = (void *)ktimer;

	return HDF_SUCCESS;
}
EXPORT_SYMBOL(OsalTimerCreate);

static int32_t OsalTimerStart(OsalTimer *timer, OsalTimerMode mode)
{
	struct osal_ktimer *ktimer = NULL;
	struct timer_list *timer_id = NULL;

	if (timer == NULL || timer->realTimer == NULL) {
		HDF_LOGE("%s invalid para", __func__);
		return HDF_ERR_INVALID_PARAM;
    }

	ktimer = (struct osal_ktimer *)timer->realTimer;
	timer_id = &ktimer->timer;
	timer_setup(timer_id, osal_timer_callback, 0);
	ktimer->mode = mode;
	timer_id->expires = jiffies + msecs_to_jiffies(ktimer->msec);
	add_timer(timer_id);

	return HDF_SUCCESS;
}

int32_t OsalTimerStartOnce(OsalTimer *timer)
{
	return OsalTimerStart(timer, OSAL_TIMER_ONCE);
}
EXPORT_SYMBOL(OsalTimerStartOnce);

int32_t OsalTimerStartLoop(OsalTimer *timer)
{
	return OsalTimerStart(timer, OSAL_TIMER_LOOP);
}

EXPORT_SYMBOL(OsalTimerStartLoop);

int32_t OsalTimerSetTimeout(OsalTimer *timer, uint32_t interval)
{
	struct osal_ktimer *ktimer = NULL;

	if (timer == NULL || timer->realTimer == NULL || interval == 0) {
		HDF_LOGE("%s invalid para", __func__);
		return HDF_ERR_INVALID_PARAM;
	}

	ktimer = (struct osal_ktimer *)timer->realTimer;
	if (ktimer->msec == interval)
		return HDF_SUCCESS;

	OsalMutexTimedLock(&ktimer->mutex, HDF_WAIT_FOREVER);
	ktimer->msec = interval;
	OsalMutexUnlock(&ktimer->mutex);

	return HDF_SUCCESS;
}
EXPORT_SYMBOL(OsalTimerSetTimeout);

int32_t OsalTimerDelete(OsalTimer *timer)
{
	struct osal_ktimer *ktimer = NULL;

	if (timer == NULL || timer->realTimer == NULL) {
		HDF_LOGE("%s invalid para", __func__);
		return HDF_ERR_INVALID_PARAM;
	}

	ktimer = (struct osal_ktimer *)timer->realTimer;
	OsalMutexTimedLock(&ktimer->mutex, HDF_WAIT_FOREVER);
	ktimer->stop_flag = true;
	OsalMutexUnlock(&ktimer->mutex);

	if (ktimer->mode == OSAL_TIMER_ONCE)
		mod_timer(&ktimer->timer, ktimer->timer.expires);

	timer->realTimer = NULL;

	return HDF_SUCCESS;
}
EXPORT_SYMBOL(OsalTimerDelete);