/* Copyright 2021 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "assert.h"
#include "common.h"
#include "console.h"
#include "limits.h"
#include "system.h"
#include "usb_pd_timer.h"
#include "usb_tc_sm.h"

#define MAX_PD_PORTS	CONFIG_USB_PD_PORT_MAX_COUNT
#define MAX_PD_TIMERS	PD_TIMER_COUNT
#define PD_TIMERS_ALL_MASK ((uint32_t)(((uint64_t)1 << PD_TIMER_COUNT) - 1))

#define MAX_EXPIRE	(0x7FFFFFFF)
#define NO_TIMEOUT	(-1)
#define EXPIRE_NOW	(0)

#define PD_SET_ACTIVE(p, m)	atomic_or(&timer_active[p], (m))
#define PD_CLR_ACTIVE(p, m)	atomic_clear_bits(&timer_active[p], (m))
#define PD_CHK_ACTIVE(p, m)	(timer_active[p] & (m))

#define PD_SET_DISABLED(p, m)	atomic_or(&timer_disabled[p], (m))
#define PD_CLR_DISABLED(p, m)	atomic_clear_bits(&timer_disabled[p], (m))
#define PD_CHK_DISABLED(p, m)	(timer_disabled[p] & (m))

static uint32_t timer_active[MAX_PD_PORTS];
static uint32_t timer_disabled[MAX_PD_PORTS];
static uint64_t timer_expires[MAX_PD_PORTS][MAX_PD_TIMERS];

/*
 * CONFIG_CMD_PD_TIMER debug variables
 */
static int count[MAX_PD_PORTS];
static int max_count[MAX_PD_PORTS];

__maybe_unused static __const_data const char * const pd_timer_names[] = {
	[PE_TIMER_BIST_CONT_MODE]	= "PE-BIST_CONT_MODE",
	[PE_TIMER_CHUNKING_NOT_SUPPORTED] = "PE-CHUNKING_NOT_SUPPORTED",
	[PE_TIMER_DISCOVER_IDENTITY]	= "PE-DISCOVER_IDENTITY",
	[PE_TIMER_NO_RESPONSE]		= "PE-NO_RESPONSE",
	[PE_TIMER_PR_SWAP_WAIT]		= "PE-PR_SWAP_WAIT",
	[PE_TIMER_PS_HARD_RESET]	= "PE-PS_HARD_RESET",
	[PE_TIMER_PS_SOURCE]		= "PE-PS_SOURCE",
	[PE_TIMER_PS_TRANSITION]	= "PE-PS_TRANSITION",
	[PE_TIMER_SENDER_RESPONSE]	= "PE-SENDER_RESPONSE",
	[PE_TIMER_SINK_REQUEST]		= "PE-SINK_REQUEST",
	[PE_TIMER_SOURCE_CAP]		= "PE-SOURCE_CAP",
	[PE_TIMER_SRC_TRANSITION]	= "PE-SRC_TRANSITION",
	[PE_TIMER_SWAP_SOURCE_START]	= "PE-SWAP_SOURCE_START",
	[PE_TIMER_TIMEOUT]		= "PE-TIMEOUT",
	[PE_TIMER_VCONN_ON]		= "PE-VCONN_ON",
	[PE_TIMER_VDM_RESPONSE]		= "PE-VDM_RESPONSE",
	[PE_TIMER_WAIT_AND_ADD_JITTER]	= "PE-WAIT_AND_ADD_JITTER",

	[PR_TIMER_CHUNK_SENDER_REQUEST]	= "PR-CHUNK_SENDER_REQUEST",
	[PR_TIMER_CHUNK_SENDER_RESPONSE] = "PR-CHUNK_SENDER_RESPONSE",
	[PR_TIMER_HARD_RESET_COMPLETE]	= "PR-HARD_RESET_COMPLETE",
	[PR_TIMER_SINK_TX]		= "PR-SINK_TX",
	[PR_TIMER_TCPC_TX_TIMEOUT]	= "PR-TCPC_TX_TIMEOUT",

	[TC_TIMER_CC_DEBOUNCE]		= "TC-CC_DEBOUNCE",
	[TC_TIMER_LOW_POWER_EXIT_TIME]	= "TC-LOW_POWER_EXIT_TIME",
	[TC_TIMER_LOW_POWER_TIME]	= "TC-LOW_POWER_TIME",
	[TC_TIMER_NEXT_ROLE_SWAP]	= "TC-NEXT_ROLE_SWAP",
	[TC_TIMER_PD_DEBOUNCE]		= "TC-PD_DEBOUNCE",
	[TC_TIMER_TIMEOUT]		= "TC-TIMEOUT",
	[TC_TIMER_TRY_WAIT_DEBOUNCE]	= "TC-TRY_WAIT_DEBOUNCE",
	[TC_TIMER_VBUS_DEBOUNCE]	= "TC-VBUS_DEBOUNCE",
};

/*****************************************************************************
 * PD_TIMER private functions
 *
 * The view of timers to the outside world is enabled and disabled. Internally
 * timers that are enabled are in the active and inactive states. An active
 * timer has a valid timeout value that gets checked for expiration and can
 * adjust the task wakeup time.  An inactive timer is assumed to have expired
 * already and will always return that it is still expired.  This timer state
 * will not adjust the task scheduling timeout value.
 */
static void pd_timer_inactive(int port, enum pd_task_timer timer)
{
	uint32_t mask = 1 << timer;

	if (PD_CHK_ACTIVE(port, mask)) {
		PD_CLR_ACTIVE(port, mask);

		if (IS_ENABLED(CONFIG_CMD_PD_TIMER))
			count[port]--;
	}
	PD_CLR_DISABLED(port, mask);
}

static bool pd_timer_is_active(int port, enum pd_task_timer timer)
{
	uint32_t mask = 1 << timer;

	return PD_CHK_ACTIVE(port, mask);
}

static bool pd_timer_is_inactive(int port, enum pd_task_timer timer)
{
	uint32_t mask = 1 << timer;

	return !PD_CHK_ACTIVE(port, mask) && !PD_CHK_DISABLED(port, mask);
}

/*****************************************************************************
 * PD_TIMER public functions
 */
void pd_timer_init(int port)
{
	if (IS_ENABLED(CONFIG_CMD_PD_TIMER))
		count[port] = 0;

	PD_CLR_ACTIVE(port, PD_TIMERS_ALL_MASK);
	PD_SET_DISABLED(port, PD_TIMERS_ALL_MASK);
}

void pd_timer_enable(int port, enum pd_task_timer timer, uint32_t expires_us)
{
	uint32_t mask = 1 << timer;

	if (!PD_CHK_ACTIVE(port, mask)) {
		PD_SET_ACTIVE(port, mask);

		if (IS_ENABLED(CONFIG_CMD_PD_TIMER)) {
			count[port]++;
			if (count[port] > max_count[port])
				max_count[port] = count[port];
		}
	}
	PD_CLR_DISABLED(port, mask);
	timer_expires[port][timer] = get_time().val + expires_us;
}

void pd_timer_disable(int port, enum pd_task_timer timer)
{
	uint32_t mask = 1 << timer;

	if (PD_CHK_ACTIVE(port, mask)) {
		PD_CLR_ACTIVE(port, mask);

		if (IS_ENABLED(CONFIG_CMD_PD_TIMER))
			count[port]--;
	}
	PD_SET_DISABLED(port, mask);
}

void pd_timer_disable_range(int port, enum pd_timer_range range)
{
	int start, end;
	enum pd_task_timer timer;

	switch (range) {
	case PE_TIMER_RANGE:
		start	= PE_TIMER_START;
		end	= PE_TIMER_END;
		break;
	case PR_TIMER_RANGE:
		start	= PR_TIMER_START;
		end	= PR_TIMER_END;
		break;
	case TC_TIMER_RANGE:
		start	= TC_TIMER_START;
		end	= TC_TIMER_END;
		break;
	default:
		return;
	}

	for (timer = start; timer <= end; ++timer)
		pd_timer_disable(port, timer);
}

bool pd_timer_is_disabled(int port, enum pd_task_timer timer)
{
	uint32_t mask = 1 << timer;

	return PD_CHK_DISABLED(port, mask);
}

bool pd_timer_is_expired(int port, enum pd_task_timer timer)
{
	if (pd_timer_is_active(port, timer)) {
		if (get_time().val >= timer_expires[port][timer]) {
			pd_timer_inactive(port, timer);
			return true;
		}
		return false;
	}
	return pd_timer_is_inactive(port, timer);
}

void pd_timer_manage_expired(int port)
{
	int timer;

	if (timer_active[port])
		for (timer = 0; timer < MAX_PD_TIMERS; ++timer)
			if (pd_timer_is_active(port, timer) &&
			    pd_timer_is_expired(port, timer))
				pd_timer_inactive(port, timer);
}

int pd_timer_next_expiration(int port)
{
	int timer;
	int ret_value = MAX_EXPIRE;
	uint64_t now = get_time().val;

	for (timer = 0; timer < MAX_PD_TIMERS; ++timer) {
		/* Only use active timers for the next expired value */
		if (pd_timer_is_active(port, timer)) {
			int delta;
			uint64_t t_value = timer_expires[port][timer];

			if (t_value <= now) {
				ret_value = EXPIRE_NOW;
				break;
			}

			delta = t_value - now;
			if (ret_value > delta)
				ret_value = delta;
		}
	}

	if (ret_value == MAX_EXPIRE)
		ret_value = NO_TIMEOUT;

	return ret_value;
}

#ifdef CONFIG_CMD_PD_TIMER
void pd_timer_dump(int port)
{
	int timer;
	uint64_t now = get_time().val;

	ccprints("Timers(%d): cur=%d max=%d",
		port, count[port], max_count[port]);

	for (timer = 0; timer < MAX_PD_TIMERS; ++timer) {
		if (pd_timer_is_disabled(port, timer)) {
			continue;
		} else if (pd_timer_is_active(port, timer)) {
			uint32_t delta = 0;

			if (now < timer_expires[port][timer])
				delta = timer_expires[port][timer] - now;

			ccprints("[%2d] Active:   %s (%d%s)",
				timer, pd_timer_names[timer], (uint32_t)delta,
				tc_event_loop_is_paused(port)
					? "-PAUSED"
					: "");
		} else {
			ccprints("[%2d] Inactive: %s",
				timer, pd_timer_names[timer]);
		}
	}
}
#endif /* CONFIG_CMD_PD_TIMER */
