/* Copyright 2013-2014 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <skiboot.h>
#include <xscom.h>
#include <lock.h>
#include <timebase.h>
#include <chip.h>
#include <fsi-master.h>

/*
 * FSI Masters sit on OPB busses behind PIB2OPB bridges
 *
 * There are two cMFSI behind two different bridges at
 * different XSCOM addresses. For now we don't have them in
 * the device-tree so we hard code the address
 */
#define PIB2OPB_MFSI0_ADDR	0x20000
#define PIB2OPB_MFSI1_ADDR	0x30000

/*
 * Bridge registers on XSCOM that allow generatoin
 * of OPB cycles
 */
#define PIB2OPB_REG_CMD		0x0
#define   OPB_CMD_WRITE		0x80000000
#define   OPB_CMD_READ		0x00000000
#define   OPB_CMD_8BIT		0x00000000
#define   OPB_CMD_16BIT		0x20000000
#define   OPB_CMD_32BIT		0x60000000
#define PIB2OPB_REG_STAT	0x1
#define   OPB_STAT_ANY_ERR	0x80000000
#define   OPB_STAT_ERR_OPB      0x7FEC0000
#define   OPB_STAT_ERRACK       0x00100000
#define   OPB_STAT_BUSY		0x00010000
#define   OPB_STAT_READ_VALID   0x00020000
#define   OPB_STAT_ERR_CMFSI    0x0000FC00
#define   OPB_STAT_ERR_HMFSI    0x000000FC
#define   OPB_STAT_ERR_BASE	(OPB_STAT_ANY_ERR | \
				 OPB_STAT_ERR_OPB | \
				 OPB_STAT_ERRACK)
#define PIB2OPB_REG_LSTAT	0x2
#define PIB2OPB_REG_RESET	0x4
#define PIB2OPB_REG_cRSIC	0x5
#define PIB2OPB_REG_cRSIM       0x6
#define PIB2OPB_REG_cRSIS	0x7
#define PIB2OPB_REG_hRSIC	0x8
#define PIB2OPB_REG_hRSIM	0x9
#define PIB2OPB_REG_hRSIS	0xA

/* Low level errors from OPB contain the status in the bottom 32-bit
 * and one of these in the top 32-bit
 */
#define OPB_ERR_XSCOM_ERR	0x100000000ull
#define OPB_ERR_TIMEOUT_ERR	0x200000000ull
#define OPB_ERR_BAD_OPB_ADDR	0x400000000ull

/*
 * PIB2OPB 0 has 2 MFSIs, cMFSI and hMFSI, PIB2OPB 1 only
 * has cMFSI
 */
#define cMFSI_OPB_PORTS_BASE	0x40000
#define cMFSI_OPB_REG_BASE	0x03000
#define hMFSI_OPB_PORTS_BASE	0x80000
#define hMFSI_OPB_REG_BASE	0x03400
#define MFSI_OPB_PORT_STRIDE	0x08000

/* MFSI control registers */
#define MFSI_REG_MSTAP(__n)	(0x0D0 + (__n) * 4)
#define MFSI_REG_MATRB0		0x1D8
#define MFSI_REG_MDTRB0		0x1DC
#define MFSI_REG_MESRB0		0x1D0
#define MFSI_REG_MAESP0		0x050
#define MFSI_REG_MAEB		0x070
#define MFSI_REG_MSCSB0		0x1D4

/* FSI Slave registers */
#define FSI_SLAVE_REGS		0x000800	/**< FSI Slave Register */
#define FSI_SMODE		(FSI_SLAVE_REGS | 0x00)
#define FSI_SLBUS         	(FSI_SLAVE_REGS | 0x30)
#define FSI_SLRES		(FSI_SLAVE_REGS | 0x34)

#define FSI2PIB_ENGINE		0x001000	/**< FSI2PIB Engine (SCOM) */
#define FSI2PIB_RESET		(FSI2PIB_ENGINE | 0x18)
#define FSI2PIB_STATUS		(FSI2PIB_ENGINE | 0x1C)
#define FSI2PIB_COMPMASK	(FSI2PIB_ENGINE | 0x30)
#define FSI2PIB_TRUEMASK	(FSI2PIB_ENGINE | 0x34)

struct mfsi {
	uint32_t chip_id;
	uint32_t unit;
	uint32_t xscom_base;
	uint32_t ports_base;
	uint32_t reg_base;
	uint32_t err_bits;
};

#define mfsi_log(__lev, __m, __fmt, ...) \
	prlog(__lev, "MFSI %x:%x: " __fmt, __m->chip_id, __m->unit, ##__VA_ARGS__)
/*
 * Use a global FSI lock for now. Beware of re-entrancy
 * if we ever add support for normal chip XSCOM via FSI, in
 * which case we'll probably have to consider either per chip
 * lock (which can have AB->BA deadlock issues) or a re-entrant
 * global lock or something else. ...
 */
static struct lock fsi_lock = LOCK_UNLOCKED;

/*
 * OPB accessors
 */

/* We try up to 1.2ms for an OPB access */
#define MFSI_OPB_MAX_TRIES	1200

static uint64_t mfsi_opb_poll(struct mfsi *mfsi, uint32_t *read_data)
{
	unsigned long retries = MFSI_OPB_MAX_TRIES;
	uint64_t sval;
	uint32_t stat;
	int64_t rc;

	/* We try again every 10us for a bit more than 1ms */
	for (;;) {
		/* Read OPB status register */
		rc = xscom_read(mfsi->chip_id, mfsi->xscom_base + PIB2OPB_REG_STAT, &sval);
		if (rc) {
			/* Do something here ? */
			mfsi_log(PR_ERR, mfsi, "XSCOM error %lld read OPB STAT\n", rc);
			return OPB_ERR_XSCOM_ERR;
		}
		mfsi_log(PR_INSANE, mfsi, "  STAT=0x%16llx...\n", sval);

		stat = sval >> 32;

		/* Complete */
		if (!(stat & OPB_STAT_BUSY))
			break;
		if (retries-- == 0) {
			/* This isn't supposed to happen (HW timeout) */
			mfsi_log(PR_ERR, mfsi, "OPB POLL timeout !\n");
			return OPB_ERR_TIMEOUT_ERR | (stat & mfsi->err_bits);
		}
		time_wait_us(1);
	}

	/* Did we have an error ? */
	if (stat & mfsi->err_bits)
		return stat & mfsi->err_bits;

	if (read_data) {
		if (!(stat & OPB_STAT_READ_VALID)) {
			mfsi_log(PR_ERR, mfsi, "Read successful but no data !\n");

			/* What do do here ? can it actually happen ? */
			sval = 0xffffffff;
		}
		*read_data = sval & 0xffffffff;
	}

	return 0;
}

static uint64_t mfsi_opb_read(struct mfsi *mfsi, uint32_t opb_addr, uint32_t *data)
{
	uint64_t opb_cmd = OPB_CMD_READ | OPB_CMD_32BIT;
	int64_t rc;

	if (opb_addr > 0x00ffffff)
		return OPB_ERR_BAD_OPB_ADDR;

	opb_cmd |= opb_addr;
	opb_cmd <<= 32;

	mfsi_log(PR_INSANE, mfsi, "MFSI_OPB_READ: Writing 0x%16llx to XSCOM %x\n",
		 opb_cmd, mfsi->xscom_base);

	rc = xscom_write(mfsi->chip_id, mfsi->xscom_base + PIB2OPB_REG_CMD, opb_cmd);
	if (rc) {
		mfsi_log(PR_ERR, mfsi, "XSCOM error %lld writing OPB CMD\n", rc);
		return OPB_ERR_XSCOM_ERR;
	}
	return mfsi_opb_poll(mfsi, data);
}

static uint64_t mfsi_opb_write(struct mfsi *mfsi, uint32_t opb_addr, uint32_t data)
{
	uint64_t opb_cmd = OPB_CMD_WRITE | OPB_CMD_32BIT;
	int64_t rc;

	if (opb_addr > 0x00ffffff)
		return OPB_ERR_BAD_OPB_ADDR;

	opb_cmd |= opb_addr;
	opb_cmd <<= 32;
	opb_cmd |= data;

	mfsi_log(PR_INSANE, mfsi, "MFSI_OPB_WRITE: Writing 0x%16llx to XSCOM %x\n",
		 opb_cmd, mfsi->xscom_base);

	rc = xscom_write(mfsi->chip_id, mfsi->xscom_base + PIB2OPB_REG_CMD, opb_cmd);
	if (rc) {
		mfsi_log(PR_ERR, mfsi, "XSCOM error %lld writing OPB CMD\n", rc);
		return OPB_ERR_XSCOM_ERR;
	}
	return mfsi_opb_poll(mfsi, NULL);
}

static struct mfsi *mfsi_get(uint32_t chip_id, uint32_t unit)
{
	struct proc_chip *chip = get_chip(chip_id);
	struct mfsi *mfsi;

	if (!chip || unit > MFSI_hMFSI0)
		return NULL;
	mfsi = &chip->fsi_masters[unit];
	if (mfsi->xscom_base == 0)
		return NULL;
	return mfsi;
}

static int64_t mfsi_reset_pib2opb(struct mfsi *mfsi)
{
	uint64_t stat;
	int64_t rc;

	rc = xscom_write(mfsi->chip_id,
			 mfsi->xscom_base + PIB2OPB_REG_RESET, (1ul << 63));
	if (rc) {
		mfsi_log(PR_ERR, mfsi, "XSCOM error %lld resetting PIB2OPB\n", rc);
		return rc;
	}
	rc = xscom_write(mfsi->chip_id,
			 mfsi->xscom_base + PIB2OPB_REG_STAT, (1ul << 63));
	if (rc) {
		mfsi_log(PR_ERR, mfsi, "XSCOM error %lld resetting status\n", rc);
		return rc;
	}
	rc = xscom_read(mfsi->chip_id,
			mfsi->xscom_base + PIB2OPB_REG_STAT, &stat);
	if (rc) {
		mfsi_log(PR_ERR, mfsi, "XSCOM error %lld reading status\n", rc);
		return rc;
	}
	return 0;
}


static void mfsi_dump_pib2opb_state(struct mfsi *mfsi)
{
	uint64_t val;

	/* Dump a bunch of registers */
	if (xscom_read(mfsi->chip_id, mfsi->xscom_base + PIB2OPB_REG_CMD, &val))
		goto xscom_error;
	mfsi_log(PR_ERR, mfsi, " PIB2OPB CMD   = %016llx\n", val);
	if (xscom_read(mfsi->chip_id, mfsi->xscom_base + PIB2OPB_REG_STAT, &val))
		goto xscom_error;
	mfsi_log(PR_ERR, mfsi, " PIB2OPB STAT  = %016llx\n", val);
	if (xscom_read(mfsi->chip_id, mfsi->xscom_base + PIB2OPB_REG_LSTAT, &val))
		goto xscom_error;
	mfsi_log(PR_ERR, mfsi, " PIB2OPB LSTAT = %016llx\n", val);

	if (mfsi->unit == MFSI_cMFSI0 || mfsi->unit == MFSI_cMFSI1) {
		if (xscom_read(mfsi->chip_id, mfsi->xscom_base + PIB2OPB_REG_cRSIC, &val))
			goto xscom_error;
		mfsi_log(PR_ERR, mfsi, " PIB2OPB cRSIC = %016llx\n", val);
		if (xscom_read(mfsi->chip_id, mfsi->xscom_base + PIB2OPB_REG_cRSIM, &val))
			goto xscom_error;
		mfsi_log(PR_ERR, mfsi, " PIB2OPB cRSIM = %016llx\n", val);
		if (xscom_read(mfsi->chip_id, mfsi->xscom_base + PIB2OPB_REG_cRSIS, &val))
			goto xscom_error;
		mfsi_log(PR_ERR, mfsi, " PIB2OPB cRSIS = %016llx\n", val);
	} else if (mfsi->unit == MFSI_hMFSI0) {
		if (xscom_read(mfsi->chip_id, mfsi->xscom_base + PIB2OPB_REG_hRSIC, &val))
			goto xscom_error;
		mfsi_log(PR_ERR, mfsi, " PIB2OPB hRSIC = %016llx\n", val);
		if (xscom_read(mfsi->chip_id, mfsi->xscom_base + PIB2OPB_REG_hRSIM, &val))
			goto xscom_error;
		mfsi_log(PR_ERR, mfsi, " PIB2OPB hRSIM = %016llx\n", val);
		if (xscom_read(mfsi->chip_id, mfsi->xscom_base + PIB2OPB_REG_hRSIS, &val))
			goto xscom_error;
		mfsi_log(PR_ERR, mfsi, " PIB2OPB hRSIS = %016llx\n", val);
	}
	return;
 xscom_error:
	mfsi_log(PR_ERR, mfsi, "XSCOM error reading PIB2OPB registers\n");
}

static int64_t mfsi_dump_ctrl_regs(struct mfsi *mfsi)
{
	uint64_t opb_stat;
	uint32_t i;

	/* List of registers to dump (from HB) */
	static uint32_t dump_regs[] = {
		MFSI_REG_MATRB0,
		MFSI_REG_MDTRB0,
		MFSI_REG_MESRB0,
		MFSI_REG_MAESP0,
		MFSI_REG_MAEB,
		MFSI_REG_MSCSB0,
	};
	static const char * dump_regs_names[] = {
		"MFSI_REG_MATRB0",
		"MFSI_REG_MDTRB0",
		"MFSI_REG_MESRB0",
		"MFSI_REG_MAESP0",
		"MFSI_REG_MAEB  ",
		"MFSI_REG_MSCSB0",
        };
	for (i = 0; i < ARRAY_SIZE(dump_regs); i++) {
		uint32_t val;
	
		opb_stat = mfsi_opb_read(mfsi, mfsi->reg_base + dump_regs[i], &val);
		if (opb_stat) {
			/* Error on dump, give up */
			mfsi_log(PR_ERR, mfsi, " OPB stat 0x%016llx dumping reg %x\n",
				 opb_stat, dump_regs[i]);
			return OPAL_HARDWARE;
		}
		mfsi_log(PR_ERR, mfsi, " %s = %08x\n", dump_regs_names[i], val);
	}
	for (i = 0; i < 8; i++) {
		uint32_t val;
	
		opb_stat = mfsi_opb_read(mfsi, mfsi->reg_base + MFSI_REG_MSTAP(i), &val);
		if (opb_stat) {
			/* Error on dump, give up */
			mfsi_log(PR_ERR, mfsi, " OPB stat 0x%016llx dumping reg %x\n",
				 opb_stat, MFSI_REG_MSTAP(i));
			return OPAL_HARDWARE;
		}
		mfsi_log(PR_ERR, mfsi, " MFSI_REG_MSTAP%d = %08x\n", i, val);
	}
	return OPAL_SUCCESS;
}

static int64_t mfsi_master_cleanup(struct mfsi *mfsi, uint32_t port)
{
	uint64_t opb_stat;
	uint32_t port_base, compmask, truemask;

	/* Reset the bridge to clear up the residual errors */

	/* bit0 = Bridge: General reset */
	opb_stat = mfsi_opb_write(mfsi, mfsi->reg_base + MFSI_REG_MESRB0, 0x80000000u);
	if (opb_stat) {
		mfsi_log(PR_ERR, mfsi, " OPB stat 0x%016llx writing reset to MESRB0\n",
			 opb_stat);
		return OPAL_HARDWARE;
	}

	/* Calculate base address of port */
	port_base = mfsi->ports_base + port * MFSI_OPB_PORT_STRIDE;

	/* Perform error reset on Centaur fsi slave: */
	/*  write 0x4000000 to addr=834 */
  	opb_stat = mfsi_opb_write(mfsi, port_base + FSI_SLRES, 0x04000000);
	if (opb_stat) {
		mfsi_log(PR_ERR, mfsi,
			 " OPB stat 0x%016llx writing reset to FSI slave\n",
			 opb_stat);
		return OPAL_HARDWARE;
	}

	/* Further step is to issue a PIB reset to the FSI2PIB engine
	 * in busy state, i.e. write arbitrary data to 101c
         * (putcfam 1007) register of the previously failed FSI2PIB
         * engine on Centaur.
	 *
	 * XXX BenH: Should that be done by the upper FSI XSCOM layer ?
	 */
  	opb_stat = mfsi_opb_write(mfsi, port_base + FSI2PIB_STATUS, 0xFFFFFFFF);
	if (opb_stat) {
		mfsi_log(PR_ERR, mfsi,
			 " OPB stat 0x%016llx clearing FSI2PIB_STATUS\n",
			 opb_stat);
		return OPAL_HARDWARE;
	}

	/* Need to save/restore the true/comp masks or the FSP (PRD ?) will
	 * get annoyed
	 */
     	opb_stat = mfsi_opb_read(mfsi, port_base + FSI2PIB_COMPMASK, &compmask);
	if (opb_stat) {
		mfsi_log(PR_ERR, mfsi,
			 " OPB stat 0x%016llx reading FSI2PIB_COMPMASK\n",
			 opb_stat);
		return OPAL_HARDWARE;
	}
     	opb_stat = mfsi_opb_read(mfsi, port_base + FSI2PIB_TRUEMASK, &truemask);
	if (opb_stat) {
		mfsi_log(PR_ERR, mfsi,
			 " OPB stat 0x%016llx reading FSI2PIB_TRUEMASK\n",
			 opb_stat);
		return OPAL_HARDWARE;
	}

	/* Then, write arbitrary data to 1018  (putcfam 1006) to
         * reset any pending FSI2PIB errors.
	 */
  	opb_stat = mfsi_opb_write(mfsi, port_base + FSI2PIB_RESET, 0xFFFFFFFF);
	if (opb_stat) {
		mfsi_log(PR_ERR, mfsi,
			 " OPB stat 0x%016llx writing FSI2PIB_RESET\n",
			 opb_stat);
		return OPAL_HARDWARE;
	}

	/* Restore the true/comp masks */
  	opb_stat = mfsi_opb_write(mfsi, port_base + FSI2PIB_COMPMASK, compmask);
	if (opb_stat) {
		mfsi_log(PR_ERR, mfsi,
			 " OPB stat 0x%016llx writing FSI2PIB_COMPMASK\n",
			 opb_stat);
		return OPAL_HARDWARE;
	}
  	opb_stat = mfsi_opb_write(mfsi, port_base + FSI2PIB_TRUEMASK, truemask);
	if (opb_stat) {
		mfsi_log(PR_ERR, mfsi,
			 " OPB stat 0x%016llx writing FSI2PIB_TRUEMASK\n",
			 opb_stat);
		return OPAL_HARDWARE;
	}
	return OPAL_SUCCESS;
}

static int64_t mfsi_analyse_fsi_error(struct mfsi *mfsi)
{
	uint64_t opb_stat;
	uint32_t mesrb0;

	/* Most of the code below is adapted from HB. The main difference is
	 * that we don't gard
	 */

	/* Read MESRB0 */
	opb_stat = mfsi_opb_read(mfsi, mfsi->reg_base + MFSI_REG_MESRB0, &mesrb0);
	if (opb_stat) {
		mfsi_log(PR_ERR, mfsi, " OPB stat 0x%016llx reading MESRB0\n", opb_stat);
		return OPAL_HARDWARE;
	}
	mfsi_log(PR_ERR, mfsi, " MESRB0=%08x\n", mesrb0);

	/* bits 8:15 are internal parity errors in the master */
	if (mesrb0 & 0x00FF0000) {	
		mfsi_log(PR_ERR, mfsi, " Master parity error !\n");
	} else {
		/* bits 0:3 are a specific error code */
		switch ((mesrb0 & 0xF0000000) >> 28 ) {
		case 0x1: /* OPB error	*/
		case 0x2: /* Invalid state of OPB state machine */
			/* error is inside the OPB logic */
			mfsi_log(PR_ERR, mfsi, " OPB logic error !\n");
			break;
		case 0x3: /* Port access error */
			/* probably some kind of code collision */
			/* could also be something weird in the chip */
			mfsi_log(PR_ERR, mfsi, " Port access error !\n");
			break;
		case 0x4: /* ID mismatch */
			mfsi_log(PR_ERR, mfsi, " Port ID mismatch !\n");
			break;
		case 0x6: /* port timeout error */
			mfsi_log(PR_ERR, mfsi, " Port timeout !\n");
			break;
		case 0x7: /* master timeout error */
			mfsi_log(PR_ERR, mfsi, " Master timeout !\n");
			break;
		case 0x9: /* Any error response from Slave */
			mfsi_log(PR_ERR, mfsi, " Slave error response !\n");
			break;
		case 0xC: /* bridge parity error */
			mfsi_log(PR_ERR, mfsi, " Bridge parity error !\n");
			break;
		case 0xB: /* protocol error */
			mfsi_log(PR_ERR, mfsi, " Protocol error !\n");
			break;
		case 0x8: /* master CRC error */
			mfsi_log(PR_ERR, mfsi, " Master CRC error !\n");
			break;
		case 0xA: /* Slave CRC error */
			mfsi_log(PR_ERR, mfsi, " Slave CRC error !\n");
			break;
		default:
			mfsi_log(PR_ERR, mfsi, " Unknown error !\n");
			break;
		}
	}
	return OPAL_SUCCESS;
}

static int64_t mfsi_handle_error(struct mfsi *mfsi, uint32_t port,
				 uint64_t opb_stat, uint32_t fsi_addr)
{
	int rc;
	bool found_root_cause = false;

	mfsi_log(PR_ERR, mfsi, "Access error on port %d, stat=%012llx\n",
		 port, opb_stat);
	
	/* First handle stat codes we synthetized */
	if (opb_stat & OPB_ERR_XSCOM_ERR)
		return OPAL_HARDWARE;
	if (opb_stat & OPB_ERR_BAD_OPB_ADDR)
		return OPAL_PARAMETER;

	/* Dump a bunch of regisers from PIB2OPB and reset it */
	mfsi_dump_pib2opb_state(mfsi);

	/* Reset PIB2OPB */
	mfsi_reset_pib2opb(mfsi);

	/* This one is not supposed to happen but ... */
	if (opb_stat & OPB_ERR_TIMEOUT_ERR)
		return OPAL_HARDWARE;

	/* Dump some FSI control registers */
	rc = mfsi_dump_ctrl_regs(mfsi);

	/* If that failed, reset PIB2OPB again and return */
	if (rc) {
		mfsi_dump_pib2opb_state(mfsi);
		mfsi_reset_pib2opb(mfsi);
		return OPAL_HARDWARE;
	}

	/* Now check for known root causes (from HB) */

	/* First check if it's a ctrl register access error and we got an OPB NACK,
	 * which means an out of bounds control reg
	 */
	if ((opb_stat & OPB_STAT_ERRACK) &&
	    ((fsi_addr & ~0x2ffu) == mfsi->reg_base)) {		
		mfsi_log(PR_ERR, mfsi, " Error appears to be out of bounds reg %08x\n",
			 fsi_addr);
		found_root_cause = true;
	}
	/* Else check for other OPB errors */
	else if (opb_stat & OPB_STAT_ERR_OPB) {
		mfsi_log(PR_ERR, mfsi, " Error appears to be an OPB error\n");
		found_root_cause = true;
	}

	/* Root cause not found, dig into FSI logic */
	if (!found_root_cause) {
		rc = mfsi_analyse_fsi_error(mfsi);
		if (!rc) {
			/* If that failed too, reset the PIB2OPB again */
			mfsi_reset_pib2opb(mfsi);
		}
	}

	/* Cleanup MFSI master */
	mfsi_master_cleanup(mfsi, port);

	return OPAL_HARDWARE;
}

int64_t mfsi_read(uint32_t chip, uint32_t unit, uint32_t port,
		  uint32_t fsi_addr, uint32_t *data)
{
	struct mfsi *mfsi = mfsi_get(chip, unit);
	uint32_t port_addr;
	uint64_t opb_stat;
	int64_t rc = OPAL_SUCCESS;

	if (!mfsi || port > 7)
		return OPAL_PARAMETER;

	lock(&fsi_lock);

	/* Calculate port address */
	port_addr = mfsi->ports_base + port * MFSI_OPB_PORT_STRIDE;
	port_addr += fsi_addr;

	/* Perform OPB access */
	opb_stat = mfsi_opb_read(mfsi, port_addr, data);
	if (opb_stat)
		rc = mfsi_handle_error(mfsi, port, opb_stat, port_addr);

	unlock(&fsi_lock);

	return rc;
}

int64_t mfsi_write(uint32_t chip, uint32_t unit, uint32_t port,
		   uint32_t fsi_addr, uint32_t data)
{
	struct mfsi *mfsi = mfsi_get(chip, unit);
	uint32_t port_addr;
	uint64_t opb_stat;
	int64_t rc = OPAL_SUCCESS;

	if (!mfsi || port > 7)
		return OPAL_PARAMETER;

	lock(&fsi_lock);

	/* Calculate port address */
	port_addr = mfsi->ports_base + port * MFSI_OPB_PORT_STRIDE;
	port_addr += fsi_addr;

	/* Perform OPB access */
	opb_stat = mfsi_opb_write(mfsi, port_addr, data);
	if (opb_stat)
		rc = mfsi_handle_error(mfsi, port, opb_stat, port_addr);

	unlock(&fsi_lock);

	return rc;
}

static void mfsi_add(struct proc_chip *chip, struct mfsi *mfsi, uint32_t unit)
{
	mfsi->chip_id = chip->id;
	mfsi->unit = unit;

	/* We hard code everything for now */
	switch(unit) {
	case MFSI_cMFSI0:
		mfsi->xscom_base = PIB2OPB_MFSI0_ADDR;
		mfsi->ports_base = cMFSI_OPB_PORTS_BASE;
		mfsi->reg_base = cMFSI_OPB_REG_BASE;
		mfsi->err_bits = OPB_STAT_ERR_BASE | OPB_STAT_ERR_CMFSI;
		break;
	case MFSI_cMFSI1:
		mfsi->xscom_base = PIB2OPB_MFSI1_ADDR;
		mfsi->ports_base = cMFSI_OPB_PORTS_BASE;
		mfsi->reg_base = cMFSI_OPB_REG_BASE;
		mfsi->err_bits = OPB_STAT_ERR_BASE | OPB_STAT_ERR_CMFSI;
		break;
	case MFSI_hMFSI0:
		mfsi->xscom_base = PIB2OPB_MFSI0_ADDR;
		mfsi->ports_base = hMFSI_OPB_PORTS_BASE;
		mfsi->reg_base = hMFSI_OPB_REG_BASE;
		mfsi->err_bits = OPB_STAT_ERR_BASE | OPB_STAT_ERR_HMFSI;
		break;
	default:
		/* ??? */
		return;
	}

	/* Hardware Bug HW222712 on Murano DD1.0 causes the
	 * any_error bit to be un-clearable so we just
	 * have to ignore it. Additionally, HostBoot applies
	 * this to Venice too, though the comment there claims
	 * this is a Simics workaround.
	 *
	 * The doc says that bit can be safely ignored, so let's
	 * just not bother and always take it out.
	 */

	/* 16: cMFSI any-master-error */
	/* 24: hMFSI any-master-error */
	mfsi->err_bits &= 0xFFFF7F7F;

	mfsi_log(PR_INFO, mfsi, "Initialized\n");
}

void mfsi_init(void)
{
	struct proc_chip *chip;

	for_each_chip(chip) {
		chip->fsi_masters = zalloc(sizeof(struct mfsi) * 3);
		mfsi_add(chip, &chip->fsi_masters[MFSI_cMFSI0], MFSI_cMFSI0);
		mfsi_add(chip, &chip->fsi_masters[MFSI_hMFSI0], MFSI_hMFSI0);
		mfsi_add(chip, &chip->fsi_masters[MFSI_cMFSI1], MFSI_cMFSI1);

	}
}

