/* Copyright 2020 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 <atomic.h>
#include <device.h>
#include <drivers/espi.h>
#include <logging/log.h>
#include <kernel.h>
#include <stdint.h>
#include <zephyr.h>

#include "acpi.h"
#include "chipset.h"
#include "common.h"
#include "espi.h"
#include "gpio.h"
#include "hooks.h"
#include "i8042_protocol.h"
#include "keyboard_protocol.h"
#include "lpc.h"
#include "port80.h"
#include "power.h"
#include "soc_espi.h"
#include "task.h"
#include "timer.h"
#include "zephyr_espi_shim.h"

#define VWIRE_PULSE_TRIGGER_TIME 65

LOG_MODULE_REGISTER(espi_shim, CONFIG_ESPI_LOG_LEVEL);

/* host command packet handler structure */
static struct host_packet lpc_packet;
/*
 * For the eSPI host command, request & response use the same share memory.
 * This is for input request temp buffer.
 */
static uint8_t params_copy[EC_LPC_HOST_PACKET_SIZE] __aligned(4);
static bool init_done;

/*
 * A mapping of platform/ec signals to Zephyr virtual wires.
 *
 * This should be a macro which takes a parameter M, and does a
 * functional application of M to 2-tuples of (platform/ec signal,
 * zephyr vwire).
 */
#define VW_SIGNAL_TRANSLATION_LIST(M)                                      \
	M(VW_SLP_S3_L, ESPI_VWIRE_SIGNAL_SLP_S3)                           \
	M(VW_SLP_S4_L, ESPI_VWIRE_SIGNAL_SLP_S4)                           \
	M(VW_SLP_S5_L, ESPI_VWIRE_SIGNAL_SLP_S5)                           \
	M(VW_SUS_STAT_L, ESPI_VWIRE_SIGNAL_SUS_STAT)                       \
	M(VW_PLTRST_L, ESPI_VWIRE_SIGNAL_PLTRST)                           \
	M(VW_OOB_RST_WARN, ESPI_VWIRE_SIGNAL_OOB_RST_WARN)                 \
	M(VW_OOB_RST_ACK, ESPI_VWIRE_SIGNAL_OOB_RST_ACK)                   \
	M(VW_WAKE_L, ESPI_VWIRE_SIGNAL_WAKE)                               \
	M(VW_PME_L, ESPI_VWIRE_SIGNAL_PME)                                 \
	M(VW_ERROR_FATAL, ESPI_VWIRE_SIGNAL_ERR_FATAL)                     \
	M(VW_ERROR_NON_FATAL, ESPI_VWIRE_SIGNAL_ERR_NON_FATAL)             \
	M(VW_PERIPHERAL_BTLD_STATUS_DONE, ESPI_VWIRE_SIGNAL_SLV_BOOT_DONE) \
	M(VW_SCI_L, ESPI_VWIRE_SIGNAL_SCI)                                 \
	M(VW_SMI_L, ESPI_VWIRE_SIGNAL_SMI)                                 \
	M(VW_HOST_RST_ACK, ESPI_VWIRE_SIGNAL_HOST_RST_ACK)                 \
	M(VW_HOST_RST_WARN, ESPI_VWIRE_SIGNAL_HOST_RST_WARN)               \
	M(VW_SUS_ACK, ESPI_VWIRE_SIGNAL_SUS_ACK)                           \
	M(VW_SUS_WARN_L, ESPI_VWIRE_SIGNAL_SUS_WARN)                       \
	M(VW_SUS_PWRDN_ACK_L, ESPI_VWIRE_SIGNAL_SUS_PWRDN_ACK)             \
	M(VW_SLP_A_L, ESPI_VWIRE_SIGNAL_SLP_A)                             \
	M(VW_SLP_LAN, ESPI_VWIRE_SIGNAL_SLP_LAN)                           \
	M(VW_SLP_WLAN, ESPI_VWIRE_SIGNAL_SLP_WLAN)

/*
 * These two macros are intended to be used as as the M parameter to
 * the list above, generating case statements returning the
 * translation for the first parameter to the second, and the second
 * to the first, respectively.
 */
#define CASE_CROS_TO_ZEPHYR(A, B) \
	case A:                   \
		return B;
#define CASE_ZEPHYR_TO_CROS(A, B) CASE_CROS_TO_ZEPHYR(B, A)

/* Translate a platform/ec signal to a Zephyr signal */
static enum espi_vwire_signal signal_to_zephyr_vwire(enum espi_vw_signal signal)
{
	switch (signal) {
		VW_SIGNAL_TRANSLATION_LIST(CASE_CROS_TO_ZEPHYR);
	default:
		LOG_ERR("Invalid virtual wire signal (%d)", signal);
		return -1;
	}
}

/* Translate a Zephyr vwire to a platform/ec signal */
static enum espi_vw_signal zephyr_vwire_to_signal(enum espi_vwire_signal vwire)
{
	switch (vwire) {
		VW_SIGNAL_TRANSLATION_LIST(CASE_ZEPHYR_TO_CROS);
	default:
		LOG_ERR("Invalid zephyr vwire (%d)", vwire);
		return -1;
	}
}

/*
 * Bit field for each signal which can have an interrupt enabled.
 * Note the interrupt is always enabled, it just depends whether we
 * route it to the power_signal_interrupt handler or not.
 */
static atomic_t signal_interrupt_enabled;

/* To be used with VW_SIGNAL_TRASLATION_LIST */
#define CASE_CROS_TO_BIT(A, _) CASE_CROS_TO_ZEPHYR(A, BIT(A - VW_SIGNAL_START))

/* Convert from an EC signal to the corresponding interrupt enabled bit. */
static uint32_t signal_to_interrupt_bit(enum espi_vw_signal signal)
{
	switch (signal) {
		VW_SIGNAL_TRANSLATION_LIST(CASE_CROS_TO_BIT);
	default:
		return 0;
	}
}

/* Callback for vwire received */
static void espi_vwire_handler(const struct device *dev,
			       struct espi_callback *cb,
			       struct espi_event event)
{
	int ec_signal = zephyr_vwire_to_signal(event.evt_details);

	if (IS_ENABLED(CONFIG_PLATFORM_EC_POWERSEQ) &&
	    (signal_interrupt_enabled & signal_to_interrupt_bit(ec_signal))) {
		power_signal_interrupt(ec_signal);
	}
}

#ifdef CONFIG_PLATFORM_EC_CHIPSET_RESET_HOOK
static void espi_chipset_reset(void)
{
	hook_notify(HOOK_CHIPSET_RESET);
}
DECLARE_DEFERRED(espi_chipset_reset);

/* Callback for reset */
static void espi_reset_handler(const struct device *dev,
			       struct espi_callback *cb,
			       struct espi_event event)
{
	hook_call_deferred(&espi_chipset_reset_data, MSEC);

}
#endif /* CONFIG_PLATFORM_EC_CHIPSET_RESET_HOOK */

#define ESPI_NODE DT_NODELABEL(espi0)
static const struct device *espi_dev;


int espi_vw_set_wire(enum espi_vw_signal signal, uint8_t level)
{
	int ret = espi_send_vwire(espi_dev, signal_to_zephyr_vwire(signal),
				  level);

	if (ret != 0)
		LOG_ERR("Encountered error sending virtual wire signal");

	return ret;
}

int espi_vw_get_wire(enum espi_vw_signal signal)
{
	uint8_t level;

	if (espi_receive_vwire(espi_dev, signal_to_zephyr_vwire(signal),
			       &level) < 0) {
		LOG_ERR("Encountered error receiving virtual wire signal");
		return 0;
	}

	return level;
}

int espi_vw_enable_wire_int(enum espi_vw_signal signal)
{
	atomic_or(&signal_interrupt_enabled, signal_to_interrupt_bit(signal));
	return 0;
}

int espi_vw_disable_wire_int(enum espi_vw_signal signal)
{
	atomic_and(&signal_interrupt_enabled, ~signal_to_interrupt_bit(signal));
	return 0;
}

uint8_t *lpc_get_memmap_range(void)
{
	uint32_t lpc_memmap = 0;

	if (espi_read_lpc_request(espi_dev, EACPI_GET_SHARED_MEMORY,
				  &lpc_memmap) != 0) {
		LOG_ERR("Get lpc_memmap failed!\n");
	}

	return (uint8_t *)lpc_memmap;
}

/**
 * Update the level-sensitive wake signal to the AP.
 *
 * @param wake_events	Currently asserted wake events
 */
static void lpc_update_wake(host_event_t wake_events)
{
	/*
	 * Mask off power button event, since the AP gets that through a
	 * separate dedicated GPIO.
	 */
	wake_events &= ~EC_HOST_EVENT_MASK(EC_HOST_EVENT_POWER_BUTTON);

	/* Signal is asserted low when wake events is non-zero */
	gpio_set_level(GPIO_EC_PCH_WAKE_ODL, !wake_events);
}

static void lpc_generate_smi(void)
{
	/* Enforce signal-high for long enough to debounce high */
	espi_vw_set_wire(VW_SMI_L, 1);
	udelay(VWIRE_PULSE_TRIGGER_TIME);
	espi_vw_set_wire(VW_SMI_L, 0);
	udelay(VWIRE_PULSE_TRIGGER_TIME);
	espi_vw_set_wire(VW_SMI_L, 1);
}

static void lpc_generate_sci(void)
{
	/* Enforce signal-high for long enough to debounce high */
	espi_vw_set_wire(VW_SCI_L, 1);
	udelay(VWIRE_PULSE_TRIGGER_TIME);
	espi_vw_set_wire(VW_SCI_L, 0);
	udelay(VWIRE_PULSE_TRIGGER_TIME);
	espi_vw_set_wire(VW_SCI_L, 1);
}

void lpc_update_host_event_status(void)
{
	uint32_t enable;
	uint32_t status;
	int need_sci = 0;
	int need_smi = 0;

	if (!init_done)
		return;

	/* Disable PMC1 interrupt while updating status register */
	enable = 0;
	espi_write_lpc_request(espi_dev, ECUSTOM_HOST_SUBS_INTERRUPT_EN,
			       &enable);

	espi_read_lpc_request(espi_dev, EACPI_READ_STS, &status);
	if (lpc_get_host_events_by_type(LPC_HOST_EVENT_SMI)) {
		/* Only generate SMI for first event */
		if (!(status & EC_LPC_STATUS_SMI_PENDING))
			need_smi = 1;

		status |= EC_LPC_STATUS_SMI_PENDING;
		espi_write_lpc_request(espi_dev, EACPI_WRITE_STS, &status);
	} else {
		status &= ~EC_LPC_STATUS_SMI_PENDING;
		espi_write_lpc_request(espi_dev, EACPI_WRITE_STS, &status);
	}

	espi_read_lpc_request(espi_dev, EACPI_READ_STS, &status);
	if (lpc_get_host_events_by_type(LPC_HOST_EVENT_SCI)) {
		/* Generate SCI for every event */
		need_sci = 1;

		status |= EC_LPC_STATUS_SCI_PENDING;
		espi_write_lpc_request(espi_dev, EACPI_WRITE_STS, &status);
	} else {
		status &= ~EC_LPC_STATUS_SCI_PENDING;
		espi_write_lpc_request(espi_dev, EACPI_WRITE_STS, &status);
	}

	*(host_event_t *)host_get_memmap(EC_MEMMAP_HOST_EVENTS) =
		lpc_get_host_events();

	enable = 1;
	espi_write_lpc_request(espi_dev, ECUSTOM_HOST_SUBS_INTERRUPT_EN,
			       &enable);

	/* Process the wake events. */
	lpc_update_wake(lpc_get_host_events_by_type(LPC_HOST_EVENT_WAKE));

	/* Send pulse on SMI signal if needed */
	if (need_smi)
		lpc_generate_smi();

	/* ACPI 5.0-12.6.1: Generate SCI for SCI_EVT=1. */
	if (need_sci)
		lpc_generate_sci();
}

static void host_command_init(void)
{
	/* We support LPC args and version 3 protocol */
	*(lpc_get_memmap_range() + EC_MEMMAP_HOST_CMD_FLAGS) =
		EC_HOST_CMD_FLAG_LPC_ARGS_SUPPORTED |
		EC_HOST_CMD_FLAG_VERSION_3;

	/* Sufficiently initialized */
	init_done = 1;

	lpc_update_host_event_status();
}

DECLARE_HOOK(HOOK_INIT, host_command_init, HOOK_PRIO_INIT_LPC);

static void handle_acpi_write(uint32_t data)
{
	uint8_t value, result;
	uint8_t is_cmd = (data >> NPCX_ACPI_TYPE_POS) & 0x01;
	uint32_t status;

	value = (data >> NPCX_ACPI_DATA_POS) & 0xff;

	/* Handle whatever this was. */
	if (acpi_ap_to_ec(is_cmd, value, &result)) {
		data = result;
		espi_write_lpc_request(espi_dev, EACPI_WRITE_CHAR, &data);
	}

	/* Clear processing flag */
	espi_read_lpc_request(espi_dev, EACPI_READ_STS, &status);
	status &= ~EC_LPC_STATUS_PROCESSING;
	espi_write_lpc_request(espi_dev, EACPI_WRITE_STS, &status);

	/*
	 * ACPI 5.0-12.6.1: Generate SCI for Input Buffer Empty / Output Buffer
	 * Full condition on the kernel channel.
	 */
	lpc_generate_sci();
}

static void lpc_send_response_packet(struct host_packet *pkt)
{
	uint32_t data;

	/* TODO(b/176523211): check whether add EC_RES_IN_PROGRESS handle */

	/* Write result to the data byte.  This sets the TOH status bit. */
	data = pkt->driver_result;
	espi_write_lpc_request(espi_dev, ECUSTOM_HOST_CMD_SEND_RESULT, &data);
}

static void handle_host_write(uint32_t data)
{
	uint32_t shm_mem_host_cmd;

	if (EC_COMMAND_PROTOCOL_3 != (data & 0xff)) {
		LOG_ERR("Don't support this version of the host command");
		/* TODO:(b/175217186): error response for other versions */
		return;
	}

	espi_read_lpc_request(espi_dev, ECUSTOM_HOST_CMD_GET_PARAM_MEMORY,
			      &shm_mem_host_cmd);

	lpc_packet.send_response = lpc_send_response_packet;

	lpc_packet.request = (const void *)shm_mem_host_cmd;
	lpc_packet.request_temp = params_copy;
	lpc_packet.request_max = sizeof(params_copy);
	/* Don't know the request size so pass in the entire buffer */
	lpc_packet.request_size = EC_LPC_HOST_PACKET_SIZE;

	lpc_packet.response = (void *)shm_mem_host_cmd;
	lpc_packet.response_max = EC_LPC_HOST_PACKET_SIZE;
	lpc_packet.response_size = 0;

	lpc_packet.driver_result = EC_RES_SUCCESS;

	host_packet_receive(&lpc_packet);
	return;
}

void lpc_set_acpi_status_mask(uint8_t mask)
{
	uint32_t status;
	espi_read_lpc_request(espi_dev, EACPI_READ_STS, &status);
	status |= mask;
	espi_write_lpc_request(espi_dev, EACPI_WRITE_STS, &status);
}

void lpc_clear_acpi_status_mask(uint8_t mask)
{
	uint32_t status;
	espi_read_lpc_request(espi_dev, EACPI_READ_STS, &status);
	status &= ~mask;
	espi_write_lpc_request(espi_dev, EACPI_WRITE_STS, &status);
}

/* Get protocol information */
static enum ec_status lpc_get_protocol_info(struct host_cmd_handler_args *args)
{
	struct ec_response_get_protocol_info *r = args->response;

	memset(r, 0, sizeof(*r));
	r->protocol_versions = BIT(3);
	r->max_request_packet_size = EC_LPC_HOST_PACKET_SIZE;
	r->max_response_packet_size = EC_LPC_HOST_PACKET_SIZE;
	r->flags = 0;

	args->response_size = sizeof(*r);

	return EC_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_GET_PROTOCOL_INFO, lpc_get_protocol_info,
		     EC_VER_MASK(0));

/*
 * This function is needed only for the obsolete platform which uses the GPIO
 * for KBC's IRQ.
 */
void lpc_keyboard_resume_irq(void) {}

void lpc_keyboard_clear_buffer(void)
{
	/* Clear OBF flag in host STATUS and HIKMST regs */
	espi_write_lpc_request(espi_dev, E8042_CLEAR_OBF, 0);
}
int lpc_keyboard_has_char(void)
{
	uint32_t status;

	/* if OBF bit is '1', that mean still have a data in DBBOUT */
	espi_read_lpc_request(espi_dev, E8042_OBF_HAS_CHAR, &status);
	return status;
}

void lpc_keyboard_put_char(uint8_t chr, int send_irq)
{
	uint32_t kb_char = chr;

	espi_write_lpc_request(espi_dev, E8042_WRITE_KB_CHAR, &kb_char);
	LOG_INF("KB put %02x", kb_char);
}

/* Put an aux char to host buffer by HIMDO and assert status bit 5. */
void lpc_aux_put_char(uint8_t chr, int send_irq)
{
	uint32_t kb_char = chr;
	uint32_t status = I8042_AUX_DATA;

	espi_write_lpc_request(espi_dev, E8042_SET_FLAG, &status);
	espi_write_lpc_request(espi_dev, E8042_WRITE_KB_CHAR, &kb_char);
	LOG_INF("AUX put %02x", kb_char);
}

static void kbc_ibf_obe_handler(uint32_t data)
{
#ifdef HAS_TASK_KEYPROTO
	uint8_t is_ibf = (data >> NPCX_8042_EVT_POS) & NPCX_8042_EVT_IBF;
	uint32_t status = I8042_AUX_DATA;

	if (is_ibf) {
		keyboard_host_write((data >> NPCX_8042_DATA_POS) & 0xFF,
				    (data >> NPCX_8042_TYPE_POS) & 0xFF);
	} else if (IS_ENABLED(CONFIG_8042_AUX)) {
		espi_write_lpc_request(espi_dev, E8042_CLEAR_FLAG, &status);
	}
	task_wake(TASK_ID_KEYPROTO);
#endif
}

int lpc_keyboard_input_pending(void)
{
	uint32_t status;

	/* if IBF bit is '1', that mean still have a data in DBBIN */
	espi_read_lpc_request(espi_dev, E8042_IBF_HAS_CHAR, &status);
	return status;
}

static void espi_peripheral_handler(const struct device *dev,
				    struct espi_callback *cb,
				    struct espi_event event)
{
	uint16_t event_type = event.evt_details;

	if (IS_ENABLED(CONFIG_PLATFORM_EC_PORT80) &&
	    event_type == ESPI_PERIPHERAL_DEBUG_PORT80) {
		port_80_write(event.evt_data);
	}

	if (IS_ENABLED(CONFIG_PLATFORM_EC_ACPI) &&
	    event_type == ESPI_PERIPHERAL_HOST_IO) {
		handle_acpi_write(event.evt_data);
	}

	if (IS_ENABLED(CONFIG_PLATFORM_EC_HOSTCMD) &&
	    event_type == ESPI_PERIPHERAL_EC_HOST_CMD) {
		handle_host_write(event.evt_data);
	}

	if (IS_ENABLED(CONFIG_ESPI_PERIPHERAL_8042_KBC) &&
	    IS_ENABLED(HAS_TASK_KEYPROTO) &&
	    event_type == ESPI_PERIPHERAL_8042_KBC) {
		kbc_ibf_obe_handler(event.evt_data);
	}
}

int zephyr_shim_setup_espi(void)
{
	static struct {
		struct espi_callback cb;
		espi_callback_handler_t handler;
		enum espi_bus_event event_type;
	} callbacks[] = {
		{
			.handler = espi_vwire_handler,
			.event_type = ESPI_BUS_EVENT_VWIRE_RECEIVED,
		},
		{
			.handler = espi_peripheral_handler,
			.event_type = ESPI_BUS_PERIPHERAL_NOTIFICATION,
		},
#ifdef CONFIG_PLATFORM_EC_CHIPSET_RESET_HOOK
		{
			.handler = espi_reset_handler,
			.event_type = ESPI_BUS_RESET,
		},
#endif
	};

	struct espi_cfg cfg = {
		.io_caps = ESPI_IO_MODE_SINGLE_LINE,
		.channel_caps = ESPI_CHANNEL_VWIRE | ESPI_CHANNEL_PERIPHERAL |
				ESPI_CHANNEL_OOB,
		.max_freq = 20,
	};

	espi_dev = DEVICE_DT_GET(ESPI_NODE);
	if (!device_is_ready(espi_dev)) {
		LOG_ERR("Error: device %s is not ready", espi_dev->name);
		return -1;
	}

	/* Configure eSPI */
	if (espi_config(espi_dev, &cfg)) {
		LOG_ERR("Failed to configure eSPI device");
		return -1;
	}

	/* Setup callbacks */
	for (size_t i = 0; i < ARRAY_SIZE(callbacks); i++) {
		espi_init_callback(&callbacks[i].cb, callbacks[i].handler,
				   callbacks[i].event_type);
		espi_add_callback(espi_dev, &callbacks[i].cb);
	}

	return 0;
}
