/*
 * Copyright (c) 2016 Google, Inc
 *
 * Modified from coreboot
 *
 * SPDX-License-Identifier:	GPL-2.0
 */

#include <common.h>
#include <errno.h>
#include <asm/intel_regs.h>
#include <asm/io.h>
#include <asm/arch/pch.h>

#define IOBP_RETRY 1000

/* IO Buffer Programming */
#define IOBPIRI		0x2330
#define IOBPD		0x2334
#define IOBPS		0x2338
#define  IOBPS_READY	0x0001
#define  IOBPS_TX_MASK	0x0006
#define  IOBPS_MASK     0xff00
#define  IOBPS_READ     0x0600
#define  IOBPS_WRITE	0x0700
#define IOBPU		0x233a
#define  IOBPU_MAGIC	0xf000
#define  IOBP_PCICFG_READ	0x0400
#define  IOBP_PCICFG_WRITE	0x0500

static inline int iobp_poll(void)
{
	unsigned try;

	for (try = IOBP_RETRY; try > 0; try--) {
		u16 status = readw(RCB_REG(IOBPS));
		if ((status & IOBPS_READY) == 0)
			return 1;
		udelay(10);
	}

	printf("IOBP: timeout waiting for transaction to complete\n");
	return 0;
}

int pch_iobp_trans_start(u32 address, int op)
{
	if (!iobp_poll())
		return 0;

	/* Set the address */
	writel(address, RCB_REG(IOBPIRI));

	/* READ OPCODE */
	clrsetbits_le16(RCB_REG(IOBPS), IOBPS_MASK, op);

	return 1;
}

int pch_iobp_trans_finish(void)
{
	u16 status;

	/* Undocumented magic */
	writew(IOBPU_MAGIC, RCB_REG(IOBPU));

	/* Set ready bit */
	setbits_le16(RCB_REG(IOBPS), IOBPS_READY);

	if (!iobp_poll())
		return 1;

	/* Check for successful transaction */
	status = readw(RCB_REG(IOBPS));
	if (status & IOBPS_TX_MASK)
		return 1;

	return 0;
}

u32 pch_iobp_read(u32 address)
{
	if (!pch_iobp_trans_start(address, IOBPS_READ))
		return 0;
	if (pch_iobp_trans_finish()) {
		printf("IOBP: read 0x%08x failed\n", address);
		return 0;
	}

	/* Read IOBP data */
	return readl(RCB_REG(IOBPD));
}

int pch_iobp_write(u32 address, u32 data)
{
	if (!pch_iobp_trans_start(address, IOBPS_WRITE))
		return -EIO;

	writel(data, RCB_REG(IOBPD));

	if (pch_iobp_trans_finish()) {
		printf("IOBP: write 0x%08x failed\n", address);
		return -EIO;
	}

	return 0;
}

int pch_iobp_update(u32 address, u32 andvalue, u32 orvalue)
{
	u32 data = pch_iobp_read(address);

	/* Update the data */
	data &= andvalue;
	data |= orvalue;

	return pch_iobp_write(address, data);
}

int pch_iobp_exec(u32 addr, u16 op_code, u8 route_id, u32 *data, u8 *resp)
{
	if (!data || !resp)
		return 0;

	*resp = -1;
	if (!iobp_poll())
		return -EIO;

	writel(addr, RCB_REG(IOBPIRI));
	clrsetbits_le16(RCB_REG(IOBPS), 0xff00, op_code);
	writew(IOBPU_MAGIC | route_id, RCB_REG(IOBPU));

	writel(*data, RCB_REG(IOBPD));
	/* Set IOBPS[0] to trigger IOBP transaction*/
	setbits_le16(RCB_REG(IOBPS), 1);

	if (!iobp_poll())
		return -EIO;

	*resp = (readw(RCB_REG(IOBPS)) & IOBPS_TX_MASK) >> 1;
	*data = readl(RCB_REG(IOBPD));

	return 0;
}