/* 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 "chipset.h"
#include "console.h"
#include "endian.h"
#include "gpio.h"
#include "hooks.h"
#include "hwtimer.h"
#include "intc.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"

#include "bootblock_data.h"

#define CPRINTS(format, args...) cprints(CC_SPI, format, ## args)

enum emmc_cmd {
	EMMC_ERROR = -1,
	EMMC_IDLE = 0,
	EMMC_PRE_IDLE,
	EMMC_BOOT,
};

static void emmc_reset_spi_tx(void)
{
	/* Reset TX FIFO and count monitor */
	IT83XX_SPI_TXFCR = IT83XX_SPI_TXFR | IT83XX_SPI_TXFCMR;
	/* Send idle state (high/0xff) if master clocks in data. */
	IT83XX_SPI_FCR = 0;
}

static void emmc_reset_spi_rx(void)
{
	/* End Rx FIFO access */
	IT83XX_SPI_TXRXFAR = 0;
	/* Reset RX FIFO and count monitor */
	IT83XX_SPI_FCR = IT83XX_SPI_RXFR | IT83XX_SPI_RXFCMR;
}

/*
 * Set SPI module work as eMMC Alternative Boot Mode.
 * (CS# pin isn't required, and dropping data until CMD goes low)
 */
static void emmc_enable_spi(void)
{
	/* Set SPI pin mux to eMMC (GPM2:CLK, GPM3:CMD, GPM6:DATA0) */
	IT83XX_GCTRL_PIN_MUX0 |= BIT(7);
	/* Enable eMMC Alternative Boot Mode */
	IT83XX_SPI_EMMCBMR |= IT83XX_SPI_EMMCABM;
	/* Reset TX and RX FIFO */
	emmc_reset_spi_tx();
	emmc_reset_spi_rx();
	/* Response idle state (high) */
	IT83XX_SPI_SPISRDR = 0xff;
	/* FIFO will be overwritten once it's full */
	IT83XX_SPI_GCR2 = 0;
	/* Write to clear pending interrupt bits */
	IT83XX_SPI_ISR = 0xff;
	IT83XX_SPI_RX_VLISR = IT83XX_SPI_RVLI;
	/* Enable RX fifo full interrupt */
	IT83XX_SPI_IMR = 0xff;
	IT83XX_SPI_RX_VLISMR |= IT83XX_SPI_RVLIM;
	IT83XX_SPI_IMR &= ~IT83XX_SPI_RX_FIFO_FULL;
	/*
	 * Enable interrupt to detect AP's BOOTBLOCK_EN_L. So EC is able to
	 * switch SPI module back to communication mode once BOOTBLOCK_EN_L
	 * goes high (AP Jumped to bootloader).
	 */
	gpio_clear_pending_interrupt(GPIO_BOOTBLOCK_EN_L);
	gpio_enable_interrupt(GPIO_BOOTBLOCK_EN_L);

	disable_sleep(SLEEP_MASK_EMMC);
	CPRINTS("eMMC emulation enabled");
}
DECLARE_HOOK(HOOK_CHIPSET_STARTUP, emmc_enable_spi, HOOK_PRIO_FIRST);

static void emmc_init_spi(void)
{
	/* Enable alternate function */
	gpio_config_module(MODULE_SPI_FLASH, 1);
}
DECLARE_HOOK(HOOK_INIT, emmc_init_spi, HOOK_PRIO_INIT_SPI + 1);

static void emmc_send_data_over_spi(uint8_t *tx, int tx_size, int rst_tx)
{
	int i;

	/* Reset TX FIFO and count monitor */
	if (rst_tx)
		IT83XX_SPI_TXFCR = IT83XX_SPI_TXFR | IT83XX_SPI_TXFCMR;
	/* CPU access TX FIFO1 and FIFO2 */
	IT83XX_SPI_TXRXFAR = IT83XX_SPI_CPUTFA;

	/* Write response data to TX FIFO */
	for (i = 0; i < tx_size; i += 4)
		IT83XX_SPI_CPUWTFDB0 = *(uint32_t *)(tx + i);
	/*
	 * After writing data to TX FIFO is finished, this bit will
	 * be to indicate the SPI slave controller.
	 */
	IT83XX_SPI_TXFCR = IT83XX_SPI_TXFS;
	/* End CPU access TX FIFO */
	IT83XX_SPI_TXRXFAR = 0;
	/* SPI module access TX FIFO */
	IT83XX_SPI_FCR = IT83XX_SPI_SPISRTXF;
}

static void emmc_bootblock_transfer(void)
{
	int tx_size, sent = 0, remaining = sizeof(bootblock_raw_data);
	uint8_t *raw = (uint8_t *)bootblock_raw_data;
	const uint32_t timeout_us = 200;
	uint32_t start;

	/*
	 * HW will transmit the data of FIFO1 or FIFO2 in turn.
	 * So when a FIFO is empty, we need to fill the FIFO out
	 * immediately.
	 */
	emmc_send_data_over_spi(&raw[sent], 256, 1);
	sent += 256;

	while (sent < remaining) {
		/* Wait for FIFO1 or FIFO2 have been transmitted */
		start = __hw_clock_source_read();
		while (!(IT83XX_SPI_TXFSR & BIT(0)) &&
			(__hw_clock_source_read() - start < timeout_us))
			;
		/* Abort an ongoing transfer due to a command is received. */
		if (IT83XX_SPI_ISR & IT83XX_SPI_RX_FIFO_FULL)
			break;
		/* fill out next 128 bytes to FIFO1 or FIFO2 */
		tx_size = (remaining - sent) < 128 ? (remaining - sent) : 128;
		emmc_send_data_over_spi(&raw[sent], tx_size, 0);
		sent += tx_size;
	}
}

static enum emmc_cmd emmc_parse_command(int index, uint32_t *cmd0)
{
	int32_t shift0;
	uint32_t data[3];

	data[0] = htobe32(cmd0[index]);
	data[1] = htobe32(cmd0[index+1]);
	data[2] = htobe32(cmd0[index+2]);

	if ((data[0] & 0xff000000) != 0x40000000) {
		/* Figure out alignment (cmd starts with 01) */
		/* Number of leading ones. */
		shift0 = __builtin_clz(~data[0]);

		data[0] = (data[0] << shift0) | (data[1] >> (32-shift0));
		data[1] = (data[1] << shift0) | (data[2] >> (32-shift0));
	}

	if (data[0] == 0x40000000 && data[1] == 0x0095ffff) {
		/* 400000000095 GO_IDLE_STATE */
		CPRINTS("goIdle");
		return EMMC_IDLE;
	}

	if (data[0] == 0x40f0f0f0 && data[1] == 0xf0fdffff) {
		/* 40f0f0f0f0fd GO_PRE_IDLE_STATE */
		CPRINTS("goPreIdle");
		return EMMC_PRE_IDLE;
	}

	if (data[0] == 0x40ffffff && data[1] == 0xfae5ffff) {
		/* 40fffffffae5 BOOT_INITIATION */
		CPRINTS("bootInit");
		return EMMC_BOOT;
	}

	CPRINTS("eMMC error");
	return EMMC_ERROR;
}

void spi_emmc_cmd0_isr(uint32_t *cmd0_payload)
{
	enum emmc_cmd cmd;

	for (int i = 0; i < 8; i++) {
		if (cmd0_payload[i] == 0xffffffff)
			continue;

		cmd = emmc_parse_command(i, &cmd0_payload[i]);

		if (cmd == EMMC_IDLE || cmd == EMMC_PRE_IDLE) {
			/* Abort an ongoing transfer. */
			emmc_reset_spi_tx();
			break;
		}

		if (cmd == EMMC_BOOT) {
			emmc_bootblock_transfer();
			break;
		}
	}
}
