/*
 * Copyright (c) 2016-2020, NVIDIA CORPORATION.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
 */

#include <linux/ktime.h>
#include <linux/delay.h>

#include <nvgpu/timers.h>
#include <nvgpu/soc.h>
#include <nvgpu/gk20a.h>

#include "platform_gk20a.h"

/*
 * Returns 1 if the platform is pre-Si and should ignore the timeout checking.
 * Setting %NVGPU_TIMER_NO_PRE_SI will make this always return 0 (i.e do the
 * timeout check regardless of platform).
 */
static int nvgpu_timeout_is_pre_silicon(struct nvgpu_timeout *timeout)
{
	if (timeout->flags & NVGPU_TIMER_NO_PRE_SI)
		return 0;

	return !nvgpu_platform_is_silicon(timeout->g);
}

/**
 * nvgpu_timeout_init - Init timer.
 *
 * @g        - nvgpu device.
 * @timeout  - The timer.
 * @duration - Timeout in milliseconds or number of retries.
 * @flags    - Flags for timer.
 *
 * This configures the timeout to start the timeout duration now, i.e: when this
 * function is called. Available flags to pass to @flags:
 *
 *   %NVGPU_TIMER_CPU_TIMER
 *   %NVGPU_TIMER_RETRY_TIMER
 *   %NVGPU_TIMER_NO_PRE_SI
 *   %NVGPU_TIMER_SILENT_TIMEOUT
 *
 * If neither %NVGPU_TIMER_CPU_TIMER or %NVGPU_TIMER_RETRY_TIMER is passed then
 * a CPU timer is used by default.
 */
int nvgpu_timeout_init(struct gk20a *g, struct nvgpu_timeout *timeout,
		       u32 duration, unsigned long flags)
{
	if (flags & ~NVGPU_TIMER_FLAG_MASK)
		return -EINVAL;

	(void) memset(timeout, 0, sizeof(*timeout));

	timeout->g = g;
	timeout->flags = flags;

	if (flags & NVGPU_TIMER_RETRY_TIMER)
		timeout->retries.max_attempts = duration;
	else
		timeout->time = ktime_to_ns(ktime_add_ns(ktime_get(),
					(s64)NSEC_PER_MSEC * duration));

	return 0;
}

static int nvgpu_timeout_expired_msg_cpu(struct nvgpu_timeout *timeout,
					 void *caller,
					 const char *fmt, va_list args)
{
	struct gk20a *g = timeout->g;
	ktime_t now = ktime_get();

	if (nvgpu_timeout_is_pre_silicon(timeout))
		return 0;

	if (ktime_after(now, ns_to_ktime(timeout->time))) {
		if (!(timeout->flags & NVGPU_TIMER_SILENT_TIMEOUT)) {
			char buf[128];

			(void) vsnprintf(buf, sizeof(buf), fmt, args);

			nvgpu_err(g, "Timeout detected @ %pF %s", caller, buf);
		}

		return -ETIMEDOUT;
	}

	return 0;
}

static int nvgpu_timeout_expired_msg_retry(struct nvgpu_timeout *timeout,
					   void *caller,
					   const char *fmt, va_list args)
{
	struct gk20a *g = timeout->g;

	if (nvgpu_timeout_is_pre_silicon(timeout))
		return 0;

	if (timeout->retries.attempted >= timeout->retries.max_attempts) {
		if (!(timeout->flags & NVGPU_TIMER_SILENT_TIMEOUT)) {
			char buf[128];

			(void) vsnprintf(buf, sizeof(buf), fmt, args);

			nvgpu_err(g, "No more retries @ %pF %s", caller, buf);
		}

		return -ETIMEDOUT;
	}

	timeout->retries.attempted++;

	return 0;
}

/**
 * nvgpu_timeout_expired_msg_impl - Check if a timeout has expired.
 *
 * @timeout - The timeout to check.
 * @caller  - Address of the caller of this function.
 * @fmt     - The fmt string.
 *
 * Returns -ETIMEDOUT if the timeout has expired, 0 otherwise.
 *
 * If a timeout occurs and %NVGPU_TIMER_SILENT_TIMEOUT is not set in the timeout
 * then a message is printed based on %fmt.
 */
int nvgpu_timeout_expired_msg_impl(struct nvgpu_timeout *timeout,
			      void *caller, const char *fmt, ...)
{
	int ret;
	va_list args;

	va_start(args, fmt);
	if (timeout->flags & NVGPU_TIMER_RETRY_TIMER)
		ret = nvgpu_timeout_expired_msg_retry(timeout, caller, fmt,
						      args);
	else
		ret = nvgpu_timeout_expired_msg_cpu(timeout, caller, fmt,
						    args);
	va_end(args);

	return ret;
}

/**
 * nvgpu_timeout_peek_expired - Check the status of a timeout.
 *
 * @timeout - The timeout to check.
 *
 * Returns true if the timeout is expired, false otherwise. In the case of
 * retry timers this will not increment the underlying retry count. Also if the
 * timer has expired no messages will be printed.
 *
 * This function honors the pre-Si check as well.
 */
bool nvgpu_timeout_peek_expired(struct nvgpu_timeout *timeout)
{
	if (nvgpu_timeout_is_pre_silicon(timeout))
		return false;

	if (timeout->flags & NVGPU_TIMER_RETRY_TIMER)
		return timeout->retries.attempted >=
					timeout->retries.max_attempts;
	else
		return ktime_after(ktime_get(), ns_to_ktime(timeout->time));
}

/**
 * nvgpu_udelay - Delay for some number of microseconds.
 *
 * @usecs - Microseconds to wait for.
 *
 * Wait for at least @usecs microseconds. This is not guaranteed to be perfectly
 * accurate. This is normally backed by a busy-loop so this means waits should
 * be kept short, below 100us. If longer delays are necessary then
 * nvgpu_msleep() should be preferred.
 *
 * Alternatively, on some platforms, nvgpu_usleep_range() is usable. This
 * function will attempt to not use a busy-loop.
 */
void nvgpu_udelay(unsigned int usecs)
{
	udelay(usecs);
}

/**
 * nvgpu_usleep_range - Sleep for a range of microseconds.
 *
 * @min_us - Minimum wait time.
 * @max_us - Maximum wait time.
 *
 * Wait for some number of microseconds between @min_us and @max_us. This,
 * unlike nvgpu_udelay(), will attempt to sleep for the passed number of
 * microseconds instead of busy looping. Not all platforms support this,
 * and in that case this reduces to nvgpu_udelay(min_us).
 *
 * Linux note: this is not safe to use in atomic context. If you are in
 * atomic context you must use nvgpu_udelay().
 */
void nvgpu_usleep_range(unsigned int min_us, unsigned int max_us)
{
	usleep_range(min_us, max_us);
}

/**
 * nvgpu_msleep - Sleep for some milliseconds.
 *
 * @msecs - Sleep for at least this many milliseconds.
 *
 * Sleep for at least @msecs of milliseconds. For small @msecs (less than 20 ms
 * or so) the sleep will be significantly longer due to scheduling overhead and
 * mechanics.
 */
void nvgpu_msleep(unsigned int msecs)
{
	msleep(msecs);
}

/**
 * nvgpu_current_time_ms - Time in milliseconds from a monotonic clock.
 *
 * Return a clock in millisecond units. The start time of the clock is
 * unspecified; the time returned can be compared with older ones to measure
 * durations. The source clock does not jump when the system clock is adjusted.
 */
s64 nvgpu_current_time_ms(void)
{
	return ktime_to_ms(ktime_get());
}

/**
 * nvgpu_current_time_us - Time in microseconds from a monotonic clock.
 *
 * Return a clock in microsecond units. The start time of the clock is
 * unspecified; the time returned can be compared with older ones to measure
 * durations. The source clock does not jump when the system clock is adjusted.
 */
s64 nvgpu_current_time_us(void)
{
	return ktime_to_us(ktime_get());
}

u64 nvgpu_us_counter(void)
{
	return (u64)nvgpu_current_time_us();
}

/**
 * nvgpu_current_time_ns - Time in nanoseconds from a monotonic clock.
 *
 * Return a clock in nanosecond units. The start time of the clock is
 * unspecified; the time returned can be compared with older ones to measure
 * durations. The source clock does not jump when the system clock is adjusted.
 */
s64 nvgpu_current_time_ns(void)
{
	return ktime_to_ns(ktime_get());
}

/**
 * nvgpu_hr_timestamp_us - Time in microseconds from a monotonic clock.
 *
 * Return a clock in microsecond units. The start time of the clock is
 * unspecified; the time returned can be compared with older ones to measure
 * durations. The source clock does not jump when the system clock is adjusted.
 */
u64 nvgpu_hr_timestamp_us(void)
{
	return nvgpu_us_counter();
}

/**
 * nvgpu_hr_timestamp - Opaque 'high resolution' time stamp.
 *
 * Return a "high resolution" time stamp. It does not really matter exactly what
 * it is, so long as it generally returns unique values and monotonically
 * increases - wrap around _is_ possible though in a system running for long
 * enough.
 *
 * Note: what high resolution means is system dependent.
 */
u64 nvgpu_hr_timestamp(void)
{
	return get_cycles();
}
