// SPDX-License-Identifier: Apache-2.0
/*
 * Copyright (C) 2019 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
 */

#include "altera.hpp"

#include <string.h>

#include <string>

#include "common.hpp"
#include "jtag.hpp"
#include "device.hpp"
#include "epcq.hpp"
#include "progressBar.hpp"
#include "rawParser.hpp"
#if defined (_WIN64) || defined (_WIN32)
#include "pathHelper.hpp"
#endif

#define IDCODE 6
#define USER0  0x0C
#define USER1  0x0E
#define BYPASS 0x3FF
#define IRLENGTH 10

Altera::Altera(Jtag *jtag, const std::string &filename,
	const std::string &file_type, Device::prog_type_t prg_type,
	const std::string &device_package,
	const std::string &spiOverJtagPath, bool verify, int8_t verbose,
	bool skip_load_bridge, bool skip_reset):
	Device(jtag, filename, file_type, verify, verbose),
	SPIInterface(filename, verbose, 256, verify, skip_load_bridge,
				 skip_reset),
	_device_package(device_package), _spiOverJtagPath(spiOverJtagPath),
	_vir_addr(0x1000), _vir_length(14)
{
	if (prg_type == Device::RD_FLASH) {
		_mode = Device::READ_MODE;
	} else {
		if (!_file_extension.empty()) {
			if (_file_extension == "svf") {
				_mode = Device::MEM_MODE;
			} else if (_file_extension == "rpd" ||
					_file_extension == "rbf") {
				if (prg_type == Device::WR_SRAM)
					_mode = Device::MEM_MODE;
				else
					_mode = Device::SPI_MODE;
			} else { // unknown type -> sanity check
				if (prg_type == Device::WR_SRAM) {
					printError("file has an unknown type:");
					printError("\tplease use rbf or svf file");
					printError("\tor use --write-flash with: ", false);
					printError("-b board_name or --fpga_part xxxx");
					throw std::runtime_error("Error: wrong file");
				} else {
					_mode = Device::SPI_MODE;
				}
			}
		}
	}
}

Altera::~Altera()
{}
void Altera::reset()
{
	/* PULSE_NCONFIG */
	unsigned char tx_buff[2] = {0x01, 0x00};
	_jtag->set_state(Jtag::TEST_LOGIC_RESET);
	_jtag->shiftIR(tx_buff, NULL, IRLENGTH);
	_jtag->toggleClk(1);
	_jtag->set_state(Jtag::TEST_LOGIC_RESET);
}

void Altera::programMem(RawParser &_bit)
{
	int byte_length = _bit.getLength()/8;
	const uint8_t *data = _bit.getData();

	uint32_t clk_period = 1e9/static_cast<float>(_jtag->getClkFreq());

	unsigned char cmd[2];
	unsigned char tx[864/8], rx[864/8];

	memset(tx, 0, 864/8);
	/* enddr idle
	 * endir irpause
	 * state idle
	 */
	/* ir 0x02 IRLENGTH */
	*reinterpret_cast<uint16_t *>(cmd) = 0x02;
	_jtag->shiftIR(cmd, NULL, IRLENGTH, Jtag::PAUSE_IR);
	/* RUNTEST IDLE 12000 TCK ENDSTATE IDLE; */
	_jtag->set_state(Jtag::RUN_TEST_IDLE);
	_jtag->toggleClk(1000000/clk_period);
	/* write */
	ProgressBar progress("Load SRAM", byte_length, 50, _quiet);

	int xfer_len = 512;
	int tx_len;
	Jtag::tapState_t tx_end;

	for (int i=0; i < byte_length; i+=xfer_len) {
		if (i + xfer_len > byte_length) {  // last packet with some size
			tx_len = (byte_length - i) * 8;
			tx_end = Jtag::EXIT1_DR;
		} else {
			tx_len = xfer_len * 8;
			tx_end = Jtag::SHIFT_DR;
		}
		_jtag->shiftDR(data+i, NULL, tx_len, tx_end);
		progress.display(i);
	}
	progress.done();

	/* reboot */
	/* SIR 10 TDI (004); */
	*reinterpret_cast<uint16_t *>(cmd) = 0x04;
	_jtag->shiftIR(cmd, NULL, IRLENGTH, Jtag::PAUSE_IR);
	/* RUNTEST 60 TCK; */
	_jtag->set_state(Jtag::RUN_TEST_IDLE);
	_jtag->toggleClk(5000/clk_period);
	/*
	 * SDR 864 TDI
	 * (000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000)
	 * TDO (00000000000000000000
	 *     0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000)
	 *     MASK (00000000000000000000000000000000000000000000000000
	 *         0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000);
	 */
	_jtag->shiftDR(tx, rx, 864, Jtag::RUN_TEST_IDLE);
	/* TBD -> something to check */
	/* SIR 10 TDI (003); */
	*reinterpret_cast<uint16_t *>(cmd) = 0x003;
	_jtag->shiftIR(cmd, NULL, IRLENGTH, Jtag::PAUSE_IR);
	/* RUNTEST 49152 TCK; */
	_jtag->set_state(Jtag::RUN_TEST_IDLE);
	_jtag->toggleClk(4099645/clk_period);
	/* RUNTEST 512 TCK; */
	_jtag->set_state(Jtag::RUN_TEST_IDLE);
	_jtag->toggleClk(512);
	 /* SIR 10 TDI (3FF); */
	*reinterpret_cast<uint16_t *>(cmd) = BYPASS;
	_jtag->shiftIR(cmd, NULL, IRLENGTH, Jtag::PAUSE_IR);

	/* RUNTEST 12000 TCK; */
	_jtag->set_state(Jtag::RUN_TEST_IDLE);
	_jtag->toggleClk(1000000/clk_period);
	/* -> idle */
	_jtag->set_state(Jtag::RUN_TEST_IDLE);
}

bool Altera::post_flash_access()
{
	if (_skip_reset)
		printInfo("Skip resetting device");
	else
		reset();
	return true;
}

bool Altera::prepare_flash_access()
{
	if (_skip_load_bridge) {
		printInfo("Skip loading bridge for spiOverjtag");
		return true;
	}
	return load_bridge();
}

bool Altera::load_bridge()
{
	std::string bitname;
	if (!_spiOverJtagPath.empty()) {
		bitname = _spiOverJtagPath;
	} else {
		if (_device_package.empty()) {
			printError("Can't program SPI flash: missing device-package information");
			return false;
		}

		bitname = get_shell_env_var("OPENFPGALOADER_SOJ_DIR", DATA_DIR "/openFPGALoader");
#ifdef HAS_ZLIB
		bitname += "/spiOverJtag_" + _device_package + ".rbf.gz";
#else
		bitname += "/spiOverJtag_" + _device_package + ".rbf";
#endif
	}

#if defined (_WIN64) || defined (_WIN32)
	/* Convert relative path embedded at compile time to an absolute path */
	bitname = PathHelper::absolutePath(bitname);
#endif

	std::cout << "use: " << bitname << std::endl;

	/* first: load spi over jtag */
	try {
		RawParser bridge(bitname, false);
		bridge.parse();
		programMem(bridge);
	} catch (std::exception &e) {
		printError(e.what());
		throw std::runtime_error(e.what());
	}

	return true;
}

void Altera::program(unsigned int offset, bool unprotect_flash)
{
	if (_mode == Device::NONE_MODE)
		return;
	/* in all case we consider svf is mandatory
	 * MEM_MODE : svf file provided for constructor
	 *            is the bitstream to use
	 * SPI_MODE : svf file provided is bridge to have
	 *            access to the SPI flash
	 */
	/* mem mode -> svf */
	if (_mode == Device::MEM_MODE) {
		RawParser _bit(_filename, false);
		_bit.parse();
		programMem(_bit);
	} else if (_mode == Device::SPI_MODE) {
		// reverse only bitstream raw binaries data no
		bool reverseOrder = false;
		if (_file_extension == "rbf" || _file_extension == "rpd")
			reverseOrder = true;

		/* prepare data to write */
		const uint8_t *data = NULL;
		int length = 0;

		RawParser bit(_filename, reverseOrder);
		try {
			bit.parse();
			data = bit.getData();
			length = bit.getLength() / 8;
		} catch (std::exception &e) {
			printError(e.what());
			throw std::runtime_error(e.what());
		}

		if (!SPIInterface::write(offset, data, length, unprotect_flash))
			throw std::runtime_error("Fail to write data");
	}
}

uint32_t Altera::idCode()
{
	unsigned char tx_data[4] = {IDCODE};
	unsigned char rx_data[4];
	_jtag->go_test_logic_reset();
	_jtag->shiftIR(tx_data, NULL, IRLENGTH);
	memset(tx_data, 0, 4);
	_jtag->shiftDR(tx_data, rx_data, 32);
	return ((rx_data[0] & 0x000000ff) |
		((rx_data[1] << 8) & 0x0000ff00) |
		((rx_data[2] << 16) & 0x00ff0000) |
		((rx_data[3] << 24) & 0xff000000));
}

/* SPI interface */

int Altera::spi_put(uint8_t cmd, const uint8_t *tx, uint8_t *rx, uint32_t len)
{
	/* +1 because send first cmd + len byte + 1 for rx due to a delay of
	 * one bit
	 */
	int xfer_len = len + 1 + ((rx == NULL) ? 0 : 1);
	uint8_t jtx[xfer_len];
	uint8_t jrx[xfer_len];

	if (tx != NULL) {
		for (uint32_t i = 0; i < len; i++)
			jtx[i] = RawParser::reverseByte(tx[i]);
	}

	shiftVIR(RawParser::reverseByte(cmd));
	shiftVDR(jtx, (rx) ? jrx : NULL, 8 * xfer_len);

	if (rx) {
		for (uint32_t i = 0; i < len; i++) {
			rx[i] = RawParser::reverseByte(jrx[i+1] >> 1) | (jrx[i+2] & 0x01);
		}
	}

	return 0;
}
int Altera::spi_put(const uint8_t *tx, uint8_t *rx, uint32_t len)
{
	return spi_put(tx[0], &tx[1], rx, len-1);
}

int Altera::spi_wait(uint8_t cmd, uint8_t mask, uint8_t cond,
		uint32_t timeout, bool verbose)
{
	uint8_t rx[3];
	uint8_t tmp;
	uint32_t count = 0;
	bool first = true;

	shiftVIR(RawParser::reverseByte(cmd));
	do {
		if (first) {
			first = false;
			shiftVDR(NULL, rx, 24, Jtag::SHIFT_DR);
			tmp = RawParser::reverseByte(rx[1] >> 1) | (rx[2] & 0x01);
		} else {
			_jtag->shiftDR(NULL, rx, 16, Jtag::SHIFT_DR);
			tmp = RawParser::reverseByte(rx[0] >> 1) | (rx[1] & 0x01);
		}

		count++;
		if (count == timeout){
			printf("timeout: %x %x %x\n", tmp, rx[0], rx[1]);
			break;
		}

		if (verbose) {
			printf("%x %x %x %u\n", tmp, mask, cond, count);
		}
	} while ((tmp & mask) != cond);
	_jtag->set_state(Jtag::UPDATE_DR);

	if (count == timeout) {
		printf("%x\n", tmp);
		std::cout << "wait: Error" << std::endl;
		return -1;
	}
	return 0;
}

/* VIrtual Jtag Access */
void Altera::shiftVIR(uint32_t reg)
{
	uint32_t len = _vir_length;
	uint32_t mask = (1 << len) - 1;
	uint32_t tmp = (reg & mask) | _vir_addr;
	uint8_t *tx = (uint8_t *) & tmp;
	uint8_t tx_ir[2] = {USER1, 0};

	_jtag->set_state(Jtag::RUN_TEST_IDLE);

	_jtag->shiftIR(tx_ir, NULL, IRLENGTH, Jtag::UPDATE_IR);
	/* len + 1 + 1 => IRLENGTH + Slave ID + 1 (ASMI/SFL) */
	_jtag->shiftDR(tx, NULL, len/* + 2*/, Jtag::UPDATE_DR);
}

void Altera::shiftVDR(uint8_t * tx, uint8_t * rx, uint32_t len,
		Jtag::tapState_t end_state, bool debug)
{
	(void) debug;
	uint8_t tx_ir[2] = {USER0, 0};
	_jtag->shiftIR(tx_ir, NULL, IRLENGTH, Jtag::UPDATE_IR);
	_jtag->shiftDR(tx, rx, len, end_state);
}
