// © 2020 Raptor Engineering, LLC
//
// Released under the terms of the AGPL v3
// See the LICENSE file for full details

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#include <generated/csr.h>

#include "fsi.h"

#define DEBUG

// General FSI register definitions for IBM CFAM slaves
#define IBM_CFAM_FSI_SMODE		0x0800
#define IBM_CFAM_FSI_SISC		0x0802
#define IBM_CFAM_FSI_SSTAT		0x0805

// Boot-related CFAM register definitions for IBM POWER9 processors
#define IBM_POWER9_FSI_A_SI1S		0x081c
#define IBM_POWER9_LL_MODE_REG		0x0840
#define IBM_POWER9_FSI2PIB_CHIPID	0x100a
#define IBM_POWER9_FSI2PIB_INTERRUPT	0x100b
#define IBM_POWER9_FSI2PIB_TRUE_MASK	0x100d
#define IBM_POWER9_CBS_CS		0x2801
#define IBM_POWER9_SBE_CTRL_STATUS	0x2808
#define IBM_POWER9_SBE_MSG_REGISTER	0x2809
#define IBM_POWER9_ROOT_CTRL0		0x2810
#define IBM_POWER9_PERV_CTRL0		0x281a
#define IBM_POWER9_HB_MBX5_REG		0x283c
#define IBM_POWER9_SCRATCH_REGISTER_8	0x283f
#define IBM_POWER9_ROOT_CTRL8		0x2918
#define IBM_POWER9_ROOT_CTRL1_CLEAR	0x2931

// Platform data
#define IBM_POWER9_SLAVE_ID		0

static int check_device_id(void) {
	uint32_t devid_high = openfsi_master_interface_device_id_high_read();
	uint32_t devid_low = openfsi_master_interface_device_id_low_read();

	if ((devid_high == 0x7c525054) && (devid_low == 0x4653494d)) {
		return 0;
	}

	printf("Raptor OpenFSI master not found!\n");
	return -1;
}

static int access_fsi_mem(uint8_t slave_id, uint32_t address, fsi_data_length_t data_length, uint8_t write, uint32_t* data) {
	int ret = 0;
	uint32_t word;
	uint8_t status_code;

	if (!data)
		return -1;

	// Set up request
	word = openfsi_master_interface_sid_adr_read();
	word &= ~(FSI_MASTER_SID_SLAVE_ID_MASK << FSI_MASTER_SID_SLAVE_ID_SHIFT);
	word |= ((slave_id & FSI_MASTER_SID_SLAVE_ID_MASK) << FSI_MASTER_SID_SLAVE_ID_SHIFT);
	word &= ~(FSI_MASTER_SID_ADDRESS_MASK << FSI_MASTER_SID_ADDRESS_SHIFT);
	word |= ((address & FSI_MASTER_SID_ADDRESS_MASK) << FSI_MASTER_SID_ADDRESS_SHIFT);
	word &= ~(FSI_MASTER_SID_DATA_LENGTH_MASK << FSI_MASTER_SID_DATA_LENGTH_SHIFT);
	word |= ((data_length & FSI_MASTER_SID_DATA_LENGTH_MASK) << FSI_MASTER_SID_DATA_LENGTH_SHIFT);
	openfsi_master_interface_sid_adr_write(word);
	if (write) {
		openfsi_master_interface_tx_data_write(*data);
	}

	// Set direction and start operation
	word = openfsi_master_interface_control_reg_read();
	word &= ~(FSI_MASTER_CTL_DATA_DIRECTION_MASK << FSI_MASTER_CTL_DATA_DIRECTION_SHIFT);
	word |= ((write & FSI_MASTER_CTL_DATA_DIRECTION_MASK) << FSI_MASTER_CTL_DATA_DIRECTION_SHIFT);
	word &= ~(FSI_MASTER_CTL_CYCLE_START_MASK << FSI_MASTER_CTL_CYCLE_START_SHIFT);
	word |= ((1 & FSI_MASTER_CTL_CYCLE_START_MASK) << FSI_MASTER_CTL_CYCLE_START_SHIFT);
	openfsi_master_interface_control_reg_write(word);

	// Wait for operation to complete
	while (!(openfsi_master_interface_status_reg_read() & (FSI_MASTER_CTL_CYCLE_START_MASK << FSI_MASTER_CTL_CYCLE_START_SHIFT)));

	// Read status register
	word = openfsi_master_interface_status_reg_read();
	status_code = (word >> FSI_MASTER_STAT_CYCLE_ERROR_SHIFT) & FSI_MASTER_STAT_CYCLE_ERROR_MASK;

	// Read data
	if (!write)
		*data = openfsi_master_interface_rx_data_read();

#ifdef DEBUG
	printf("%s(): address 0x%06x, data: 0x%08x sta: 0x%08x\n", __FUNCTION__, address, *data, word);
#endif

	if (status_code) {
#ifdef DEBUG
		printf("[WARNING] FSI master returned error code %d on access to FSI address 0x%06x\n", status_code, address);
#endif
		if (!ret)
			ret = -1;
	}

	// Clear any operation request flag
	word = openfsi_master_interface_control_reg_read();
	word &= ~(FSI_MASTER_CTL_CYCLE_START_MASK << FSI_MASTER_CTL_CYCLE_START_SHIFT);
	word |= ((0 & FSI_MASTER_CTL_CYCLE_START_MASK) << FSI_MASTER_CTL_CYCLE_START_SHIFT);
	openfsi_master_interface_control_reg_write(word);

	return ret;
}

static int access_cfam(uint8_t slave_id, uint32_t cfam_address, fsi_data_length_t data_length, uint8_t write, uint32_t* data) {
	// CFAM to FSI address mangling
	uint32_t fsi_address = (cfam_address & 0xfc00) | ((cfam_address & 0x3ff) << 2);

	return access_fsi_mem(slave_id, fsi_address, data_length, write, data);
}

static int initialize_fsi_master(void) {
	uint32_t word;

	if (check_device_id())
		return -1;

	// Clear any pending operation requests
	word = openfsi_master_interface_control_reg_read();
	word &= ~(FSI_MASTER_CTL_CYCLE_START_MASK << FSI_MASTER_CTL_CYCLE_START_SHIFT);
	word |= ((0 & FSI_MASTER_CTL_CYCLE_START_MASK) << FSI_MASTER_CTL_CYCLE_START_SHIFT);
	openfsi_master_interface_control_reg_write(word);

	// Wait for any running operation(s) to complete
	while (openfsi_master_interface_status_reg_read() & (FSI_MASTER_CTL_CYCLE_START_MASK << FSI_MASTER_CTL_CYCLE_START_SHIFT));

	// Set up ACK to CMD turnaround delay and enable CRC protection / enhanced error recovery
	word = openfsi_master_interface_control_reg_read();
	word &= ~(FSI_MASTER_CTL_CMD_ISSUE_DELAY_MASK << FSI_MASTER_CTL_CMD_ISSUE_DELAY_SHIFT);
	word |= ((20 & FSI_MASTER_CTL_CMD_ISSUE_DELAY_MASK) << FSI_MASTER_CTL_CMD_ISSUE_DELAY_SHIFT);
	word |= 1 << FSI_MASTER_CTL_ENABLE_CRC_SHIFT;
	word |= 1 << FSI_MASTER_CTL_ENABLE_EER_SHIFT;
	openfsi_master_interface_control_reg_write(word);

#ifdef DEBUG
	printf("%s(): after setup: ctl 0x%08x, sta 0x%08x\n", __FUNCTION__, openfsi_master_interface_control_reg_read(), openfsi_master_interface_status_reg_read());
#endif

	return 0;
}

int run_pre_ipl_fixups(void) {
	uint32_t word;
	uint32_t fsi_cfam_error_register;

	// Set up FSI slave...
	// [31]=1:	Warm start done
	// [29]=1:	HW CRC check enabled
	// [26:24]:	FSI slave ID (0)
	// [23:20]:	Echo delay (7)
	// [19:16]:	Send delay (7)
	// [11:8]:	Clock ratio (8)
	word = 0xa0ff0800;
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_CFAM_FSI_SMODE, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &word))
		goto fail;

	// Ensure asynchronous clock mode is set
	word = 0x0000001;
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_LL_MODE_REG, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &word))
		goto fail;

	// Configure SBE to pre-IPL state
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_LL_MODE_REG, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_READ, &word))
		goto fail;
	word &= ~(0x1 << 31);	// Clear SBE IPL start flag
	word &= ~(0x1 << 29);	// Run SCAN0 and CLOCKSTART during IPL
	word &= ~(0x1 << 28);	// Clear SBE start prevention flag
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_LL_MODE_REG, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &word))
		goto fail;

	// Clear SBE status mailbox
	word = 0x00000000;
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_SBE_MSG_REGISTER, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &word))
		goto fail;

	// Read CFAM error IRQ register
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_CFAM_FSI_SISC, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_READ, &word))
		goto fail;
	fsi_cfam_error_register = word;

	// Read CFAM status register
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_CFAM_FSI_SSTAT, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_READ, &word))
		goto fail;

	// Clear CFAM error IRQ register
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_CFAM_FSI_SISC, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &fsi_cfam_error_register))
		goto fail;

	return 0;

fail:
	return -1;
}

int start_ipl(int side) {
	uint32_t word;

	if (initialize_fsi_master())
		goto fail;

	if ((side < 0) || (side > 1)) {
		printf("Invalid side %d specified\n", side);
		return -2;
	}

	printf("Starting IPL on side %d\n", side);

	if (run_pre_ipl_fixups())
		goto fail;

	// Ensure asynchronous clock mode is set
	word = 0x0000001;
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_LL_MODE_REG, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &word))
		goto fail;

	// Clock mux select override
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_ROOT_CTRL8, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_READ, &word))
		goto fail;
	word |= 0xc;
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_ROOT_CTRL8, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &word))
		goto fail;

	// Setup FSI2PIB to report checkstop
	word = 0x20000000;
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_FSI_A_SI1S, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &word))
		goto fail;

	// Enable XSTOP/ATTN interrupt
	word = 0x60000000;
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_FSI2PIB_TRUE_MASK, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &word))
		goto fail;

	// Arm XSTOP/ATTN interrupt
	word = 0xffffffff;
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_FSI2PIB_INTERRUPT, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &word))
		goto fail;

	// SBE boot side configuration
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_SBE_CTRL_STATUS, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_READ, &word))
		goto fail;
	if (side == 0)
		word &= ~(0x1 << 14);
	else
		word |= 0x1 << 14;
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_SBE_CTRL_STATUS, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &word))
		goto fail;

	// Ensure edge-triggered SBE start bit is deasserted prior to ignition...
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_CBS_CS, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_READ, &word))
		goto fail;
	word &= ~(0x1 << 31);
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_CBS_CS, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &word))
		goto fail;

	// ...and light the fuse!
	word |= 0x1 << 31;
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_CBS_CS, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_WRITE, &word))
		goto fail;

	return 0;

fail:
	printf("IPL failed!\n");
	return -1;
}

int get_sbe_status(void) {
	uint32_t word;

	// SBE
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_SBE_MSG_REGISTER, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_READ, &word))
		goto fail;
	printf("CFAM 0x%06x: 0x%08x\n", IBM_POWER9_SBE_MSG_REGISTER, word);

	// Hostboot
	if (access_cfam(IBM_POWER9_SLAVE_ID, IBM_POWER9_HB_MBX5_REG, FSI_DATA_LENGTH_WORD, FSI_DIRECTION_READ, &word))
		goto fail;
	printf("CFAM 0x%06x: 0x%08x\n", IBM_POWER9_HB_MBX5_REG, word);

	return 0;

fail:
	printf("Processor not responding!\n");
	return -1;
}