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

/* General Purpose SPI master module for MCHP MEC */

#include "common.h"
#include "console.h"
#include "dma.h"
#include "gpio.h"
#include "registers.h"
#include "spi.h"
#include "timer.h"
#include "util.h"
#include "hooks.h"
#include "task.h"
#include "spi_chip.h"
#include "gpspi_chip.h"
#include "tfdp_chip.h"

#define CPUTS(outstr) cputs(CC_SPI, outstr)
#define CPRINTS(format, args...) cprints(CC_SPI, format, ## args)

#define SPI_BYTE_TRANSFER_TIMEOUT_US (3 * MSEC)
/* One byte at 12 MHz full duplex = 0.67 us */
#define SPI_BYTE_TRANSFER_POLL_INTERVAL_US 20

/*
 * GP-SPI
 */

/**
 * Return zero based GPSPI controller index given hardware port.
 * @param hw_port b[7:4]==1 (GPSPI), b[3:0]=0(GPSPI0), 1(GPSPI1)
 * @return 0(GPSPI0) or 1(GPSPI1)
 */
static uint8_t gpspi_port_to_ctrl_id(uint8_t hw_port)
{
	return (hw_port & 0x01);
}

static int gpspi_wait_byte(const int ctrl)
{
	timestamp_t deadline;

	deadline.val = get_time().val + SPI_BYTE_TRANSFER_TIMEOUT_US;
	while ((MCHP_SPI_SR(ctrl) & 0x3) != 0x3) {
		if (timestamp_expired(deadline, NULL))
			return EC_ERROR_TIMEOUT;
		usleep(SPI_BYTE_TRANSFER_POLL_INTERVAL_US);
	}
	return EC_SUCCESS;
}

/* NOTE: auto-read must be disabled before calling this routine! */
static void gpspi_rx_fifo_clean(const int ctrl)
{
	uint8_t dummy = 0;

	/* If ACTIVE and/or RXFF then clean it */
	if ((MCHP_SPI_SR(ctrl) & 0x4) == 0x4)
		dummy += MCHP_SPI_RD(ctrl);

	if ((MCHP_SPI_SR(ctrl) & 0x2) == 0x2)
		dummy += MCHP_SPI_RD(ctrl);
}
/*
 * NOTE: auto-read must be disabled before calling this routine!
 */
#ifndef CONFIG_MCHP_GPSPI_TX_DMA
static int gpspi_tx(const int ctrl, const uint8_t *txdata, int txlen)
{
	int i;
	int ret;
	uint8_t dummy = 0;

	gpspi_rx_fifo_clean(ctrl);

	ret = EC_SUCCESS;
	for (i = 0; i < txlen; ++i) {
		MCHP_SPI_TD(ctrl) = txdata[i];
		ret = gpspi_wait_byte(ctrl);
		if (ret != EC_SUCCESS)
			break;
		dummy += MCHP_SPI_RD(ctrl);
	}

	return ret;
}
#endif

int gpspi_transaction_async(const struct spi_device_t *spi_device,
				const uint8_t *txdata, int txlen,
				uint8_t *rxdata, int rxlen)
{
	int hw_port, ctrl;
	int ret = EC_SUCCESS;
	int cs_asserted = 0;
	const struct dma_option *opdma;
#ifdef CONFIG_MCHP_GPSPI_TX_DMA
	dma_chan_t *chan;
#endif
	if (spi_device == NULL)
		return EC_ERROR_PARAM1;

	hw_port = spi_device->port;

	ctrl = gpspi_port_to_ctrl_id(hw_port);

	/* Disable auto read */
	MCHP_SPI_CR(ctrl) &= ~BIT(5);

	if ((txdata != NULL) && (txdata != 0)) {
#ifdef CONFIG_MCHP_GPSPI_TX_DMA
		opdma = spi_dma_option(spi_device, SPI_DMA_OPTION_WR);
		if (opdma == NULL)
			return EC_ERROR_INVAL;

		gpspi_rx_fifo_clean(ctrl);

		dma_prepare_tx(opdma, txlen, txdata);

		chan = dma_get_channel(opdma->channel);

		gpio_set_level(spi_device->gpio_cs, 0);
		cs_asserted = 1;

		dma_go(chan);
		ret = dma_wait(opdma->channel);
		if (ret == EC_SUCCESS)
			ret = gpspi_wait_byte(ctrl);

		dma_disable(opdma->channel);
		dma_clear_isr(opdma->channel);

		gpspi_rx_fifo_clean(ctrl);
#else
		gpio_set_level(spi_device->gpio_cs, 0);
		cs_asserted = 1;

		ret = gpspi_tx(ctrl, txdata, txlen);
#endif
	}

	if (ret == EC_SUCCESS)
		if ((rxlen != 0) && (rxdata != NULL)) {
			ret = EC_ERROR_INVAL;
			opdma = spi_dma_option(spi_device, SPI_DMA_OPTION_RD);
			if (opdma != NULL) {
				if (!cs_asserted)
					gpio_set_level(spi_device->gpio_cs, 0);
				/* Enable auto read */
				MCHP_SPI_CR(ctrl) |= BIT(5);
				dma_start_rx(opdma, rxlen, rxdata);
				MCHP_SPI_TD(ctrl) = 0;
				ret = EC_SUCCESS;
			}
		}

	return ret;
}

int gpspi_transaction_flush(const struct spi_device_t *spi_device)
{
	int ctrl, hw_port;
	int ret;
	enum dma_channel chan;
	const struct dma_option *opdma;
	timestamp_t deadline;

	if (spi_device == NULL)
		return EC_ERROR_PARAM1;

	hw_port = spi_device->port;
	ctrl = gpspi_port_to_ctrl_id(hw_port);
	opdma = spi_dma_option(spi_device, SPI_DMA_OPTION_RD);
	chan = opdma->channel;

	ret = dma_wait(chan);

	/* Disable auto read */
	MCHP_SPI_CR(ctrl) &= ~BIT(5);

	deadline.val = get_time().val + SPI_BYTE_TRANSFER_TIMEOUT_US;
	/* Wait for FIFO empty SPISR_TXBE */
	while ((MCHP_SPI_SR(ctrl) & 0x01) != 0x1) {
		if (timestamp_expired(deadline, NULL)) {
			ret = EC_ERROR_TIMEOUT;
			break;
		}
		usleep(SPI_BYTE_TRANSFER_POLL_INTERVAL_US);
	}

	dma_disable(chan);
	dma_clear_isr(chan);
	if (MCHP_SPI_SR(ctrl) & 0x2)
		hw_port = MCHP_SPI_RD(ctrl);

	gpio_set_level(spi_device->gpio_cs, 1);

	return ret;
}

int gpspi_transaction_wait(const struct spi_device_t *spi_device)
{
	const struct dma_option *opdma;

	opdma = spi_dma_option(spi_device, SPI_DMA_OPTION_RD);

	return dma_wait(opdma->channel);
}

/**
 * Enable GPSPI controller and MODULE_SPI_MASTER pins
 *
 * @param hw_port b[7:4]=1 b[3:0]=0(GPSPI0), 1(GPSPI1)
 * @param enable
 * @return EC_SUCCESS or EC_ERROR_INVAL if port is unrecognized
 * @note called from mec1701/spi.c
 *
 */
int gpspi_enable(int hw_port, int enable)
{
	uint32_t ctrl;

	if ((hw_port != GPSPI0_PORT) && (hw_port != GPSPI1_PORT))
		return EC_ERROR_INVAL;

	gpio_config_module(MODULE_SPI_MASTER, (enable > 0));

	ctrl = (uint32_t)hw_port & 0x0f;

	if (enable) {

		if (ctrl)
			MCHP_PCR_SLP_DIS_DEV(MCHP_PCR_GPSPI1);
		else
			MCHP_PCR_SLP_DIS_DEV(MCHP_PCR_GPSPI0);

		/* Set enable bit in SPI_AR */
		MCHP_SPI_AR(ctrl) |= 0x1;

		/* Set SPDIN to 0 -> Full duplex */
		MCHP_SPI_CR(ctrl) &= ~(0x3 << 2);

		/* Set CLKPOL, TCLKPH, RCLKPH to 0 */
		MCHP_SPI_CC(ctrl) &= ~0x7;

		/* Set LSBF to 0 -> MSB first */
		MCHP_SPI_CR(ctrl) &= ~0x1;
	} else {
		/* soft reset */
		MCHP_SPI_CR(ctrl) |= (1u << 4);

		/* Clear enable bit in SPI_AR */
		MCHP_SPI_AR(ctrl) &= ~0x1;

		if (ctrl)
			MCHP_PCR_SLP_EN_DEV(MCHP_PCR_GPSPI1);
		else
			MCHP_PCR_SLP_EN_DEV(MCHP_PCR_GPSPI0);
	}

	return EC_SUCCESS;
}

