/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * MTK MSDC Host Controller interface specific code
 */
#include <assert.h>
#include <commonlib/bsd/helpers.h>
#include <commonlib/storage/sd_mmc.h>
#include <console/console.h>
#include <delay.h>
#include <device/mmio.h>
#include <lib.h>
#include <soc/msdc.h>
#include <string.h>
#include <timer.h>

static inline void msdc_set_field(void *reg, u32 field, u32 val)
{
	clrsetbits32(reg, field, val << __ffs(field));
}

/*
 * Periodically poll an address until a condition is met or a timeout occurs
 * @addr: Address to poll
 * @mask: mask condition
 * @timeout: Timeout in us, 0 means never timeout
 *
 * Returns 0 on success and -MSDC_NOT_READY upon a timeout.
 */
static int msdc_poll_timeout(void *addr, u32 mask)
{
	struct stopwatch timer;
	stopwatch_init_usecs_expire(&timer, MSDC_TIMEOUT_US);
	u32 reg;

	do {
		reg = read32(addr);
		if (stopwatch_expired(&timer))
			return -MSDC_NOT_READY;
		udelay(1);
	} while (reg & mask);

	return MSDC_SUCCESS;
}

/*
 * Wait for a bit mask in a given register. To avoid endless loops, a
 * time-out is implemented here.
 */
static int msdc_wait_done(void *addr, u32 mask, u32 *status)
{
	struct stopwatch timer;
	stopwatch_init_usecs_expire(&timer, CMD_TIMEOUT_MS);
	u32 reg;

	do {
		reg = read32(addr);
		if (stopwatch_expired(&timer)) {
			if (status)
				*status = reg;
			return -MSDC_NOT_READY;
		}
		udelay(1);
	} while (!(reg & mask));

	if (status)
		*status = reg;

	return MSDC_SUCCESS;
}

static void msdc_reset_hw(struct msdc_ctrlr *host)
{
	u32 val;

	setbits32(host->base + MSDC_CFG, MSDC_CFG_RST);
	if (msdc_poll_timeout(host->base + MSDC_CFG, MSDC_CFG_RST) != MSDC_SUCCESS)
		msdc_error("Softwave reset timeout!\n");

	setbits32(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR);
	if (msdc_poll_timeout(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR) != MSDC_SUCCESS)
		msdc_error("Clear FIFO timeout!\n");

	val = read32(host->base + MSDC_INT);
	write32(host->base + MSDC_INT, val);
}

static void msdc_init_hw(struct msdc_ctrlr *host)
{
	/* Configure to MMC/SD mode */
	setbits32(host->base + MSDC_CFG, MSDC_CFG_MODE);

	/* Reset */
	msdc_reset_hw(host);

	/* Set PIO mode */
	setbits32(host->base + MSDC_CFG, MSDC_CFG_PIO);

	write32(host->top_base + EMMC_TOP_CONTROL, 0);
	write32(host->top_base + EMMC_TOP_CMD, 0);

	write32(host->base + MSDC_IOCON, 0);
	clrbits32(host->base + MSDC_IOCON, MSDC_IOCON_DDLSEL);
	write32(host->base + MSDC_PATCH_BIT, 0x403c0046);
	msdc_set_field(host->base + MSDC_PATCH_BIT, MSDC_CKGEN_MSDC_DLY_SEL, 1);
	write32(host->base + MSDC_PATCH_BIT1, 0xffff4089);
	setbits32(host->base + EMMC50_CFG0, EMMC50_CFG_CFCSTS_SEL);

	msdc_set_field(host->base + MSDC_PATCH_BIT1,
		       MSDC_PATCH_BIT1_STOP_DLY, 3);
	clrbits32(host->base + SDC_FIFO_CFG, SDC_FIFO_CFG_WRVALIDSEL);
	clrbits32(host->base + SDC_FIFO_CFG, SDC_FIFO_CFG_RDVALIDSEL);

	clrbits32(host->base + MSDC_PATCH_BIT1, (1 << 7));

	msdc_set_field(host->base + MSDC_PATCH_BIT2, MSDC_PB2_RESPWAIT, 3);
	if (host->top_base)
		setbits32(host->top_base + EMMC_TOP_CONTROL, SDC_RX_ENH_EN);
	else
		setbits32(host->base + SDC_ADV_CFG0, SDC_RX_ENHANCE_EN);
	/* Use async fifo, then no need to tune internal delay */
	clrbits32(host->base + MSDC_PATCH_BIT2, MSDC_PATCH_BIT2_CFGRESP);
	setbits32(host->base + MSDC_PATCH_BIT2, MSDC_PATCH_BIT2_CFGCRCSTS);

	if (host->top_base) {
		setbits32(host->top_base + EMMC_TOP_CONTROL,
			  PAD_DAT_RD_RXDLY_SEL);
		clrbits32(host->top_base + EMMC_TOP_CONTROL, DATA_K_VALUE_SEL);
		setbits32(host->top_base + EMMC_TOP_CMD, PAD_CMD_RD_RXDLY_SEL);
	} else {
		setbits32(host->base + MSDC_PAD_TUNE,
			  MSDC_PAD_TUNE_RD_SEL | MSDC_PAD_TUNE_CMD_SEL);
	}

	/* Configure to enable SDIO mode. Otherwise, sdio cmd5 will fail. */
	setbits32(host->base + SDC_CFG, SDC_CFG_SDIO);

	/* Config SDIO device detect interrupt function */
	clrbits32(host->base + SDC_CFG, SDC_CFG_SDIOIDE);
	setbits32(host->base + SDC_ADV_CFG0, SDC_DAT1_IRQ_TRIGGER);

	/* Configure to default data timeout */
	msdc_set_field(host->base + SDC_CFG, SDC_CFG_DTOC, 3);

	msdc_debug("init hardware done!\n");
}

static void msdc_fifo_clr(struct msdc_ctrlr *host)
{
	setbits32(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR);

	if (msdc_poll_timeout(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR) != MSDC_SUCCESS)
		msdc_error("Clear FIFO timeout!\n");
}

static u32 msdc_cmd_find_resp(struct msdc_ctrlr *host, struct mmc_command *cmd)
{
	switch (cmd->resp_type) {
	case CARD_RSP_R1:
		return 0x1;
	case CARD_RSP_R1b:
		return 0x7;
	case CARD_RSP_R2:
		return 0x2;
	case CARD_RSP_R3:
		return 0x3;
	case CARD_RSP_NONE:
	default:
		return 0x0;
	}
}

static bool msdc_cmd_is_ready(struct msdc_ctrlr *host)
{
	int ret;

	ret = msdc_poll_timeout(host->base + SDC_STS, SDC_STS_CMDBUSY);
	if (ret != MSDC_SUCCESS) {
		msdc_error("CMD bus busy detected, SDC_STS: %#x\n",
			   read32(host->base + SDC_STS));
		msdc_reset_hw(host);
		return false;
	}

	ret = msdc_poll_timeout(host->base + SDC_STS, SDC_STS_SDCBUSY);
	if (ret != MSDC_SUCCESS) {
		msdc_error("SD controller busy detected, SDC_STS: %#x\n",
			   read32(host->base + SDC_STS));
		msdc_reset_hw(host);
		return false;
	}

	return true;
}

static u32 msdc_cmd_prepare_raw_cmd(struct msdc_ctrlr *host,
				    struct mmc_command *cmd,
				    struct mmc_data *data)
{
	u32 opcode = cmd->cmdidx;
	u32 resp_type = msdc_cmd_find_resp(host, cmd);
	u32 blocksize = 0;
	u32 dtype = 0;
	u32 rawcmd = 0;

	switch (opcode) {
	case MMC_CMD_WRITE_MULTIPLE_BLOCK:
	case MMC_CMD_READ_MULTIPLE_BLOCK:
		dtype = 2;
		break;
	case MMC_CMD_WRITE_SINGLE_BLOCK:
	case MMC_CMD_READ_SINGLE_BLOCK:
	case MMC_CMD_AUTO_TUNING_SEQUENCE:
		dtype = 1;
		break;
	case MMC_CMD_SEND_STATUS:
		if (data)
			dtype = 1;
	}

	if (data) {
		if (data->flags == DATA_FLAG_READ)
			rawcmd |= SDC_CMD_WR;

		if (data->blocks > 1)
			dtype = 2;

		blocksize = data->blocksize;
	}

	rawcmd |= (opcode << SDC_CMD_CMD_S) & SDC_CMD_CMD_M;
	rawcmd |= (resp_type << SDC_CMD_RSPTYP_S) & SDC_CMD_RSPTYP_M;
	rawcmd |= (blocksize << SDC_CMD_BLK_LEN_S) & SDC_CMD_BLK_LEN_M;
	rawcmd |= (dtype << SDC_CMD_DTYPE_S) & SDC_CMD_DTYPE_M;

	if (opcode == MMC_CMD_STOP_TRANSMISSION)
		rawcmd |= SDC_CMD_STOP;

	return rawcmd;
}

static int msdc_cmd_done(struct msdc_ctrlr *host, int events,
			 struct mmc_command *cmd)
{
	u32 *rsp = cmd->response;
	int ret = 0;

	if (cmd->resp_type & CARD_RSP_PRESENT) {
		if (cmd->resp_type & CARD_RSP_136) {
			rsp[0] = read32(host->base + SDC_RESP3);
			rsp[1] = read32(host->base + SDC_RESP2);
			rsp[2] = read32(host->base + SDC_RESP1);
			rsp[3] = read32(host->base + SDC_RESP0);
		} else {
			rsp[0] = read32(host->base + SDC_RESP0);
		}
	}

	if (!(events & MSDC_INT_CMDRDY)) {
		if (cmd->cmdidx != MMC_CMD_AUTO_TUNING_SEQUENCE) {
			/*
			 * should not clear fifo/interrupt as the tune data
			 * may have already come.
			 */
			msdc_reset_hw(host);
		}
		if (events & MSDC_INT_CMDTMO)
			ret = -ETIMEDOUT;
		else
			ret = -EIO;
	}

	return ret;
}

static int msdc_start_command(struct msdc_ctrlr *host, struct mmc_command *cmd,
			      struct mmc_data *data)
{
	u32 rawcmd, status;
	u32 blocks = 0;
	int ret;

	if (!msdc_cmd_is_ready(host))
		return -EIO;

	if (read32(host->base + MSDC_FIFOCS) &
	    (MSDC_FIFOCS_TXCNT | MSDC_FIFOCS_RXCNT)) {
		msdc_error("TX/RX FIFO non-empty before start of IO. Reset\n");
		msdc_reset_hw(host);
	}

	msdc_fifo_clr(host);

	rawcmd = msdc_cmd_prepare_raw_cmd(host, cmd, data);

	if (data)
		blocks = data->blocks;

	write32(host->base + MSDC_INT, CMD_INTS_MASK);
	write32(host->base + SDC_BLK_NUM, blocks);
	write32(host->base + SDC_ARG, cmd->cmdarg);
	write32(host->base + SDC_CMD, rawcmd);

	ret = msdc_wait_done(host->base + MSDC_INT, CMD_INTS_MASK, &status);
	if (ret != MSDC_SUCCESS)
		status = MSDC_INT_CMDTMO;

	return msdc_cmd_done(host, status, cmd);
}

static int msdc_send_command(struct sd_mmc_ctrlr *ctrlr,
	struct mmc_command *cmd, struct mmc_data *data)
{
	struct msdc_ctrlr *host;

	host = container_of(ctrlr, struct msdc_ctrlr, sd_mmc_ctrlr);

	return msdc_start_command(host, cmd, data);
}

static void msdc_set_clock(struct msdc_ctrlr *host, u32 clock)
{
	u32 mode, mode_shift;
	u32 div, div_mask;
	const u32 div_width = 12;
	u32 sclk;
	u32 hclk = host->src_hz;
	struct sd_mmc_ctrlr *ctrlr = &host->sd_mmc_ctrlr;

	if (clock >= hclk) {
		mode = 0x1;     /* no divisor */
		div = 0;
		sclk = hclk;
	} else {
		mode = 0x0;     /* use divisor */
		if (clock >= (hclk / 2)) {
			div = 0;        /* mean div = 1/2 */
			sclk = hclk / 2;        /* sclk = clk / 2 */
		} else {
			div = DIV_ROUND_UP(hclk, clock * 4);
			sclk = (hclk >> 2) / div;
		}
	}

	div_mask = (1 << div_width) - 1;
	mode_shift = 8 + div_width;
	assert(div <= div_mask);

	clrsetbits_le32(host->base + MSDC_CFG, (0x3 << mode_shift) | (div_mask << 8),
			(mode << mode_shift) | (div << 8));
	if (msdc_wait_done(host->base + MSDC_CFG, MSDC_CFG_CKSTB, NULL) != MSDC_SUCCESS)
		msdc_error("Failed while wait clock stable!\n");

	ctrlr->bus_hz = sclk;
	msdc_debug("sclk: %d\n", sclk);
}

static void msdc_set_buswidth(struct msdc_ctrlr *host, u32 width)
{
	u32 val = read32(host->base + SDC_CFG);

	val &= ~SDC_CFG_BUSWIDTH;

	switch (width) {
	default:
	case 1:
		val |= (MSDC_BUS_1BITS << 16);
		break;
	case 4:
		val |= (MSDC_BUS_4BITS << 16);
		break;
	case 8:
		val |= (MSDC_BUS_8BITS << 16);
		break;
	}

	write32(host->base + SDC_CFG, val);
	msdc_trace("Bus Width = %d\n", width);
}

static void msdc_set_ios(struct sd_mmc_ctrlr *ctrlr)
{
	struct msdc_ctrlr *host;

	host = container_of(ctrlr, struct msdc_ctrlr, sd_mmc_ctrlr);

	/* Set the clock frequency */
	if (ctrlr->bus_hz != ctrlr->request_hz)
		msdc_set_clock(host, ctrlr->request_hz);

	msdc_set_buswidth(host, ctrlr->bus_width);

}

static void msdc_update_pointers(struct msdc_ctrlr *host)
{
	struct sd_mmc_ctrlr *ctrlr = &host->sd_mmc_ctrlr;

	/* Update the routine pointers */
	ctrlr->send_cmd = &msdc_send_command;
	ctrlr->set_ios = &msdc_set_ios;

	ctrlr->f_min = 400 * 1000;
	ctrlr->f_max = 52 * 1000 * 1000;
	ctrlr->bus_width = 1;
	ctrlr->caps |= DRVR_CAP_HS | DRVR_CAP_HC;
	ctrlr->voltages = 0x40ff8080;
}

static void add_msdc(struct msdc_ctrlr *host)
{
	struct sd_mmc_ctrlr *ctrlr = &host->sd_mmc_ctrlr;

	msdc_update_pointers(host);

	/* Initialize the MTK MSDC controller */
	msdc_init_hw(host);

	/* Display the results */
	msdc_trace("%#010x: ctrlr->caps\n", ctrlr->caps);
	msdc_trace("%d.%03d MHz: ctrlr->f_max\n",
		    ctrlr->f_max / 1000000,
		    (ctrlr->f_max / 1000) % 1000);
	msdc_trace("%d.%03d MHz: ctrlr->f_min\n",
		    ctrlr->f_min / 1000000,
		    (ctrlr->f_min / 1000) % 1000);
	msdc_trace("%#010x: ctrlr->voltages\n", ctrlr->voltages);
}

static void msdc_controller_init(struct msdc_ctrlr *host, void *base, void *top_base)
{
	memset(host, 0, sizeof(*host));
	host->base = base;
	host->top_base = top_base;
	host->src_hz = 50 * 1000 * 1000;

	add_msdc(host);
}

int mtk_emmc_early_init(void *base, void *top_base)
{
	struct storage_media media = { 0 };
	struct msdc_ctrlr msdc_host;
	struct sd_mmc_ctrlr *mmc_ctrlr = &msdc_host.sd_mmc_ctrlr;

	/* Init mtk mmc ctrlr */
	msdc_controller_init(&msdc_host, base, top_base);

	media.ctrlr = mmc_ctrlr;
	SET_CLOCK(mmc_ctrlr, 400 * 1000);
	SET_BUS_WIDTH(mmc_ctrlr, 1);

	/* Send CMD1 */
	return  mmc_send_cmd1(&media);
}
