/* 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.
 */

/*
 * Functions required for UFP_D operation
 */

#include "console.h"
#include "gpio.h"
#include "hooks.h"
#include "system.h"
#include "task.h"
#include "usb_pd.h"
#include "usb_pd_dp_ufp.h"


#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)

enum hpd_state {
	LOW_WAIT,
	HIGH_CHECK,
	HIGH_WAIT,
	LOW_CHECK,
	IRQ_CHECK,
};

#define EDGE_QUEUE_DEPTH BIT(3)
#define EDGE_QUEUE_MASK (EDGE_QUEUE_DEPTH - 1)
#define HPD_QUEUE_DEPTH BIT(2)
#define HPD_QUEUE_MASK (HPD_QUEUE_DEPTH - 1)
#define HPD_T_IRQ_MIN_PULSE 250
#define HPD_T_IRQ_MAX_PULSE (2 * MSEC)
#define HPD_T_MIN_DP_ATTEN (10 * MSEC)

struct hpd_mark {
	int level;
	uint64_t ts;
};

struct hpd_edge {
	int overflow;
	uint32_t head;
	uint32_t tail;
	struct hpd_mark buffer[EDGE_QUEUE_DEPTH];
};

struct hpd_info {
	enum hpd_state state;
	int count;
	int send_enable;
	uint64_t timer;
	uint64_t last_send_ts;
	enum hpd_event queue[HPD_QUEUE_DEPTH];
	struct hpd_edge edges;
};

static struct hpd_info hpd;
static struct mutex hpd_mutex;

static int alt_dp_mode_opos[CONFIG_USB_PD_PORT_MAX_COUNT];

void pd_ufp_set_dp_opos(int port, int opos)
{
	alt_dp_mode_opos[port] = opos;
}

int pd_ufp_get_dp_opos(int port)
{
	return alt_dp_mode_opos[port];
}

void pd_ufp_enable_hpd_send(int port)
{
	/*
	 * This control is used ensure that a DP_ATTENTION message is not sent
	 * to the DFP-D before a DP_CONFIG messaage has been received. This
	 * control is not strictly required by the spec, but some port partners
	 * will get confused if DP_ATTENTION is sent prior to DP_CONFIG.
	 */
	hpd.send_enable = 1;
}

static void hpd_to_dp_attention(void)
{
	int port = hpd_config.port;
	int evt_index = hpd.count - 1;
	uint32_t vdm[2];
	uint32_t svdm_header;
	enum hpd_event evt;
	int opos = pd_ufp_get_dp_opos(port);

	if (!opos)
		return;

	/* Get the next hpd event from the queue */
	evt = hpd.queue[evt_index];
	/* Save timestamp of when most recent DP attention message was sent */
	hpd.last_send_ts = get_time().val;

	/*
	 * Construct DP Attention message. This consists of the VDM header and
	 * the DP_STATUS VDO.
	 */
	svdm_header = VDO_SVDM_VERS(pd_get_vdo_ver(port, TCPC_TX_SOP)) |
			       VDO_OPOS(opos) | CMD_ATTENTION;
	vdm[0] = VDO(USB_SID_DISPLAYPORT, 1, svdm_header);

	vdm[1] = VDO_DP_STATUS((evt == hpd_irq), /* IRQ_HPD */
				(evt != hpd_low), /* HPD_HI|LOW */
				0, /* request exit DP */
				0, /* request exit USB */
				dock_get_mf_preference(), /* MF pref */
				1, /* enabled */
				0, /* power low */
				0x2);

	/* Send request to DPM to send an attention VDM */
	pd_request_vdm_attention(port, vdm, ARRAY_SIZE(vdm));

	/* If there are still events, need to shift the buffer */
	if (--hpd.count) {
		int i;

		for (i = 0; i < hpd.count; i++)
			hpd.queue[i] = hpd.queue[i + 1];
	}
}

static void hpd_queue_event(enum hpd_event evt)
{
	/*
	 * HPD events are put into a queue. However, this queue is not a typical
	 * FIFO queue. Instead there are special rules based on which type of
	 * event is being added.
	 *     HPD_LOW -> always resets the queue and must be in slot 0
	 *     HPD_HIGH -> must follow a HPD_LOW, so can only be in slot 0 or
	 *                 slot 1.
	 *     HPD_IRQ  -> There shall never be more than 2 HPD_IRQ events
	 *                 stored in the queue and HPD_IRQ must follow HPD_HIGH
	 *
	 * Worst case for queueing HPD events is 4 events in the queue:
	 *    0 - HPD_LOW
	 *    1 - HPD_HIGH
	 *    2 - HPD_IRQ
	 *    3 - HPD_IRQ
	 *
	 * The above rules mean that HPD_LOW and HPD_HIGH events can always be
	 * added to the queue since high must follow low and a low event resets
	 * the queue. HPD_IRQ events are checked to make sure that they don't
	 * overflow the queue and to ensure that no more than 2 hpd_irq events
	 * are kept in the queue.
	 */
	if (evt == hpd_irq) {
		if ((hpd.count >= HPD_QUEUE_DEPTH) || ((hpd.count  >= 2) &&
			(hpd.queue[hpd.count - 2] == hpd_irq))) {
			CPRINTS("hpd: discard hpd: count - %d",
				hpd.count);
			return;
		}
	}

	if (evt == hpd_low) {
		hpd.count = 0;
	}

	/* Add event to the queue */
	hpd.queue[hpd.count++] = evt;
}

static void hpd_to_pd_converter(int level, uint64_t ts)
{
	/*
	 * HPD edges are marked in the irq routine. The converter state machine
	 * runs in the hooks task and so there will be some delay between when
	 * the edge was captured and when that edge is processed here in the
	 * state machine. This means that the delitch timer (250 uSec) may have
	 * already expired or is about to expire.
	 *
	 * If transitioning to timing dependent state, need to ensure the state
	 * machine is executed again. All timers are relative to the ts value
	 * passed into this routine. The timestamps passed into this routine
	 * are either the values latched in the irq routine, or the current
	 * time latched by the calling function. From the perspective of the
	 * state machine, ts represents the current time.
	 *
	 * Note that all hpd queue events are contingent on detecting edges
	 * on the incoming hpd gpio signal. The hpd->dp attention converter is
	 * enabled/disabled as part of the svdm dp enter/exit response handler
	 * functions. When the converter is disabled, gpio interrupts for the
	 * hpd gpio signal are disabled so it will never execute, unless the
	 * converter is enabled, and the converter is only enabled when the
	 * UFP-D is actively in ALT-DP mode.
	 */
	switch (hpd.state) {
	case LOW_WAIT:
		/*
		 * In this state only expected event is a level change from low
		 * to high.
		 */
		if (level) {
			hpd.state = HIGH_CHECK;
			hpd.timer = ts + HPD_T_IRQ_MIN_PULSE;
		}
		break;
	case HIGH_CHECK:
		/*
		 * In this state if level is high and deglitch timer is
		 * exceeded, then state advances to HIGH_WAIT, otherwise return
		 * to LOW_WAIT state.
		 */
		if (!level || (ts <= hpd.timer)) {
			hpd.state = LOW_WAIT;
		} else {
			hpd.state = HIGH_WAIT;
			hpd_queue_event(hpd_high);
		}
		break;
	case HIGH_WAIT:
		/*
		 * In this state, only expected event is a level change from
		 * high to low. If current level is low, then advance to
		 * LOW_CHECK for deglitch checking.
		 */
		if (!level) {
			hpd.state = LOW_CHECK;
			hpd.timer = ts + HPD_T_IRQ_MIN_PULSE;
		}
		break;
	case LOW_CHECK:
		/*
		 * This state is used to deglitch high->low level
		 * change. However, due to processing latency, it's possible to
		 * detect hpd_irq event if level is high and low pulse width was
		 * valid.
		 */
		if (!level) {
			/* Still low, now wait for IRQ or LOW determination */
			hpd.timer = ts + (HPD_T_IRQ_MAX_PULSE -
					  HPD_T_IRQ_MIN_PULSE);
			hpd.state = IRQ_CHECK;

		} else {
			uint64_t irq_ts = hpd.timer + HPD_T_IRQ_MAX_PULSE -
				HPD_T_IRQ_MIN_PULSE;
			/*
			 * If hpd is high now, this must have been an edge
			 * event, but still need to determine if the pulse width
			 * is longer than hpd_irq min pulse width. State will
			 * advance to HIGH_WAIT, but if pulse width is < 2 msec,
			 * must send hpd_irq event.
			 */
			if ((ts >= hpd.timer) && (ts <= irq_ts)) {
				/* hpd irq detected */
				hpd_queue_event(hpd_irq);
			}
			hpd.state = HIGH_WAIT;
		}
		break;
	case IRQ_CHECK:
		/*
		 * In this state deglitch time has already passed. If current
		 * level is low and hpd_irq timer has expired, then go to
		 * LOW_WAIT as hpd_low event has been detected. If level is high
		 * and low pulse is < hpd_irq, hpd_irq event has been detected.
		 */
		if (level) {
			hpd.state = HIGH_WAIT;
			if (ts <= hpd.timer) {
				hpd_queue_event(hpd_irq);
			}
		} else if (ts >  hpd.timer) {
			hpd.state = LOW_WAIT;
			hpd_queue_event(hpd_low);
		}
		break;
	}
}

static void manage_hpd(void);
DECLARE_DEFERRED(manage_hpd);

static void manage_hpd(void)
{
	int level;
	uint64_t ts = get_time().val;
	uint32_t num_hpd_events = (hpd.edges.head - hpd.edges.tail) &
		EDGE_QUEUE_MASK;

	/*
	 * HPD edges are detected via GPIO interrupts. The ISR routine adds edge
	 * info to a queue and scheudles this routine. If this routine is called
	 * without a new edge detected, then it is being called due to a timer
	 * event.
	 */

	/* First check to see overflow condition has occurred */
	if (hpd.edges.overflow) {
		/* Disable hpd interrupts */
		usb_pd_hpd_converter_enable(0);
		/* Re-enable hpd converter */
		usb_pd_hpd_converter_enable(1);
	}

	if (num_hpd_events) {
		while(num_hpd_events-- > 0) {
			int idx = hpd.edges.tail;

			level = hpd.edges.buffer[idx].level;
			ts = hpd.edges.buffer[idx].ts;

			hpd_to_pd_converter(level, ts);
			hpd.edges.tail = (hpd.edges.tail + 1) & EDGE_QUEUE_MASK;
		}
	} else {
		/* no new edge event, so get current time and level */
		level = gpio_get_level(hpd_config.signal);
		ts = get_time().val;
		hpd_to_pd_converter(level, ts);
	}

	/*
	 * If min time spacing requirement is exceeded and a hpd_event is
	 * queued, then send DP_ATTENTION message.
	 */
	if (hpd.count > 0) {
		/*
		 * If at least one hpd event is pending in the queue, send
		 * a DP_ATTENTION message if a DP_CONFIG message has been
		 * received and have passed the minimum spacing interval.
		 */
		if (hpd.send_enable &&
		    ((get_time().val - hpd.last_send_ts) >
		     HPD_T_MIN_DP_ATTEN)) {
			/* Generate DP_ATTENTION event pending in queue */
			hpd_to_dp_attention();
		} else {
			uint32_t callback_us;

			/*
			 * Need to wait until until min spacing requirement of
			 * DP attention messages. Set callback time to the min
			 * value required. This callback time could be changed
			 * based on hpd interrupts.
			 *
			 * This wait is also used to prevent a DP_ATTENTION
			 * message from being sent before at least one DP_CONFIG
			 * message has been received. If DP_ATTENTION messages
			 * need to be delayed for this reason, then just wait
			 * the minimum time spacing.
			 */
			callback_us = HPD_T_MIN_DP_ATTEN -
				(get_time().val - hpd.last_send_ts);
			if (callback_us <= 0 ||
			    callback_us > HPD_T_MIN_DP_ATTEN)
				callback_us = HPD_T_MIN_DP_ATTEN;
			hook_call_deferred(&manage_hpd_data, callback_us);
		}
	}

	/*
	 * Because of the delay between gpio edge irq, and when those edge
	 * events are processed here, all timers must be done relative to the
	 * timing marker stored in the hpd edge queue. If the state machine
	 * required a new timer, then hpd.timer will be advanced relative to the
	 * ts that was passed into the state machine.
	 *
	 * If the deglitch timer is active, then it can likely already have been
	 * expired when the edge gets processed. So if the timer is active the
	 * deferred callback must be requested.
	 *.
	 */
	if (hpd.timer > ts) {
		uint64_t callback_us = 0;
		uint64_t now = get_time().val;

		/* If timer is in the future, adjust the callback timer */
		if (now < hpd.timer)
			callback_us = (hpd.timer - now) & 0xffffffff;

		hook_call_deferred(&manage_hpd_data, callback_us);
	}
}

void usb_pd_hpd_converter_enable(int enable)
{
	/*
	 * The hpd converter should be enabled as part of the UFP-D enter mode
	 * response function. Likewise, the converter should be disabled by the
	 * exit mode function. In addition, the coverter may get disabled so
	 * that it can be reset in the case that the input gpio edges queue
	 * overflows. A muxtex must be used here since this function may be
	 * called from the PD task (enter/exit response mode functions) or from
	 * the hpd event handler state machine (hook task).
	 */
	mutex_lock(&hpd_mutex);

	if (enable) {
		gpio_disable_interrupt(hpd_config.signal);
		/* Reset HPD event queue */
		hpd.state = LOW_WAIT;
		hpd.count = 0;
		hpd.timer = 0;
	        hpd.last_send_ts = 0;
		hpd.send_enable = 0;

		/* Reset hpd signal edges queue */
		hpd.edges.head = 0;
		hpd.edges.tail = 0;
		hpd.edges.overflow = 0;

		/* If signal is high, need to ensure state machine executes */
		if (gpio_get_level(hpd_config.signal))
			hook_call_deferred(&manage_hpd_data, 0);

		/* Enable hpd edge detection */
		gpio_enable_interrupt(hpd_config.signal);
	} else {
		gpio_disable_interrupt(hpd_config.signal);
		hook_call_deferred(&manage_hpd_data, -1);
	}

	mutex_unlock(&hpd_mutex);
}

void usb_pd_hpd_edge_event(int signal)
{
	int  next_head = (hpd.edges.head + 1) & EDGE_QUEUE_MASK;
	struct hpd_mark mark;

	/* Get current timestamp and level */
	mark.ts = get_time().val;
	mark.level = gpio_get_level(hpd_config.signal);

	/* Add this edge to the buffer if there is space */
	if (next_head != hpd.edges.tail) {
		hpd.edges.buffer[hpd.edges.head].ts = mark.ts;
		hpd.edges.buffer[hpd.edges.head].level = mark.level;
		hpd.edges.head = next_head;
	} else {
		/* Edge queue is overflowing, need to reset the converter */
		hpd.edges.overflow = 1;
	}
	/* Schedule HPD state machine to run ASAP */
	hook_call_deferred(&manage_hpd_data, 0);
}
