/* 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.
 *
 * AMD FP6 USB/DP Mux.
 */

#include "amd_fp6.h"
#include "chipset.h"
#include "common.h"
#include "hooks.h"
#include "i2c.h"
#include "queue.h"
#include "timer.h"
#include "usb_mux.h"

#define CPRINTSUSB(format, args...) cprints(CC_USBCHARGE, format, ## args)
#define CPRINTFUSB(format, args...) cprintf(CC_USBCHARGE, format, ## args)

/*
 * The recommendation from "3.3.2 Command Timeout" is 250ms,
 * however empirical testing found that a 100ms timeout is sufficient.
 */
#define WRITE_CMD_TIMEOUT_MS 100

/* Command retry interval */
#define CMD_RETRY_INTERVAL_MS 1000

/*
 * Local data structure for saving mux state so it can be restored after
 * an AP reset.
 */
static struct {
	const struct usb_mux *mux;
	uint8_t val;
	bool write_pending;
} saved_mux_state[CONFIG_USB_PD_PORT_MAX_COUNT];

static int amd_fp6_mux_port0_read(const struct usb_mux *me, uint8_t *val)
{
	uint8_t payload[3] = { 0 };
	bool mux_ready;

	RETURN_ERROR(i2c_xfer(me->i2c_port, me->i2c_addr_flags, NULL, 0,
			      payload, 3));

	/*
	 * payload[0]: Status/ID
	 * payload[1]: Port 0 Control/Status
	 * payload[2]: Port 1 Control/Status (unused on FP6)
	 */
	mux_ready = !!((payload[0] >> AMD_FP6_MUX_PD_STATUS_OFFSET)
						& AMD_FP6_MUX_PD_STATUS_READY);

	if (!mux_ready)
		return EC_ERROR_BUSY;
	*val = payload[1];

	return EC_SUCCESS;
}

static int amd_fp6_mux_port0_write(const struct usb_mux *me, uint8_t write_val)
{
	uint8_t read_val;
	uint8_t port_status;
	timestamp_t start;

	/* Check if mux is ready */
	RETURN_ERROR(amd_fp6_mux_port0_read(me, &read_val));

	/* Write control register */
	RETURN_ERROR(
		i2c_write8(me->i2c_port, me->i2c_addr_flags, 0, write_val));

	/*
	 * Read status until write command finishes or times out.
	 * The mux has an internal opaque timeout, which we wrap with our own
	 * timeout to be safe.
	 */
	start = get_time();
	while (time_since32(start) < WRITE_CMD_TIMEOUT_MS * MSEC) {

		RETURN_ERROR(amd_fp6_mux_port0_read(me, &read_val));
		port_status = read_val >> AMD_FP6_MUX_PORT_STATUS_OFFSET;

		if (port_status == AMD_FP6_MUX_PORT_CMD_COMPLETE)
			return EC_SUCCESS;
		else if (port_status == AMD_FP6_MUX_PORT_CMD_TIMEOUT)
			return EC_ERROR_TIMEOUT;
		else if (port_status == AMD_FP6_MUX_PORT_CMD_BUSY)
			msleep(5);
		else
			return EC_ERROR_UNKNOWN;
	}

	return EC_ERROR_TIMEOUT;
}

/*
 * Keep trying to write the saved mux state until successful or SOC leaves
 * S0 power state.
 */
static void amd_fp6_set_mux_retry(void);
DECLARE_DEFERRED(amd_fp6_set_mux_retry);
static void amd_fp6_set_mux_retry(void)
{
	int rv;
	bool try_again = false;

	/*
	 * Mux can only be written in S0, stop trying.
	 * Will try again on chipset_resume.
	 */
	if (!chipset_in_state(CHIPSET_STATE_ON))
		return;

	for (int i = 0; i < ARRAY_SIZE(saved_mux_state); i++) {
		/* Make sure mux_state is initialized */
		if (saved_mux_state[i].mux == NULL ||
		    !saved_mux_state[i].write_pending)
			continue;

		rv = amd_fp6_mux_port0_write(saved_mux_state[i].mux,
					     saved_mux_state[i].val);

		if (rv)
			try_again = true;
		else
			saved_mux_state[i].write_pending = false;
	}
	if (try_again)
		hook_call_deferred(&amd_fp6_set_mux_retry_data,
				   CMD_RETRY_INTERVAL_MS * MSEC);
}


static int amd_fp6_set_mux(const struct usb_mux *me, mux_state_t mux_state,
			   bool *ack_required)
{
	uint8_t val;

	/* This driver does not use host command ACKs */
	*ack_required = false;

	if (mux_state == USB_PD_MUX_NONE)
		/*
		 * LOW_POWER must be set when connection mode is
		 * set to 00b (safe state)
		 */
		val = AMD_FP6_MUX_MODE_SAFE | AMD_FP6_MUX_LOW_POWER;
	else if ((mux_state & USB_PD_MUX_USB_ENABLED) &&
		 (mux_state & USB_PD_MUX_DP_ENABLED))
		val = AMD_FP6_MUX_MODE_DOCK;
	else if (mux_state & USB_PD_MUX_USB_ENABLED)
		val = AMD_FP6_MUX_MODE_USB;
	else if (mux_state & USB_PD_MUX_DP_ENABLED)
		val = AMD_FP6_MUX_MODE_DP;
	else {
		CPRINTSUSB("C%d: unhandled mux_state %x\n", me->usb_port,
			   mux_state);
		return EC_ERROR_INVAL;
	}

	if (mux_state & USB_PD_MUX_POLARITY_INVERTED)
		val |= AMD_FP6_MUX_ORIENTATION;

	saved_mux_state[me->usb_port].mux = me;
	saved_mux_state[me->usb_port].val = val;

	/* Mux is not powered in Z1 */
	if (chipset_in_state(CHIPSET_STATE_HARD_OFF))
		return (mux_state == USB_PD_MUX_NONE) ? EC_SUCCESS
						      : EC_ERROR_NOT_POWERED;

	saved_mux_state[me->usb_port].write_pending = true;
	amd_fp6_set_mux_retry();

	return EC_SUCCESS;
}

static int amd_fp6_get_mux(const struct usb_mux *me, mux_state_t *mux_state)
{
	uint8_t val;
	bool inverted;
	uint8_t mode;

	/* Mux is not powered in Z1 */
	if (chipset_in_state(CHIPSET_STATE_HARD_OFF))
		return USB_PD_MUX_NONE;

	RETURN_ERROR(amd_fp6_mux_port0_read(me, &val));

	mode = (val & AMD_FP6_MUX_MODE_MASK);
	inverted = !!(val & AMD_FP6_MUX_ORIENTATION);

	if (mode == AMD_FP6_MUX_MODE_USB)
		*mux_state = USB_PD_MUX_USB_ENABLED;
	else if (mode == AMD_FP6_MUX_MODE_DP)
		*mux_state = USB_PD_MUX_DP_ENABLED;
	else if (mode == AMD_FP6_MUX_MODE_DOCK)
		*mux_state = USB_PD_MUX_USB_ENABLED | USB_PD_MUX_DP_ENABLED;
	else /* AMD_FP6_MUX_MODE_SAFE */
		*mux_state = USB_PD_MUX_NONE;

	if (inverted)
		*mux_state |= USB_PD_MUX_POLARITY_INVERTED;

	return EC_SUCCESS;
}

/*
 * The FP6 USB Mux will not be ready for writing until *sometime* after S0.
 */
static void amd_fp6_chipset_resume(void)
{
	for (int i = 0; i < ARRAY_SIZE(saved_mux_state); i++)
		saved_mux_state[i].write_pending = true;
	hook_call_deferred(&amd_fp6_set_mux_retry_data,
			   CMD_RETRY_INTERVAL_MS * MSEC);
}
DECLARE_HOOK(HOOK_CHIPSET_RESUME, amd_fp6_chipset_resume, HOOK_PRIO_DEFAULT);

static int amd_fp6_chipset_reset(const struct usb_mux *me)
{
	amd_fp6_chipset_resume();
	return EC_SUCCESS;
}

const struct usb_mux_driver amd_fp6_usb_mux_driver = {
	.set = &amd_fp6_set_mux,
	.get = &amd_fp6_get_mux,
	.chipset_reset = &amd_fp6_chipset_reset
};
