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

#ifndef SRC_XILINX_HPP_
#define SRC_XILINX_HPP_

#include <map>
#include <memory>
#include <string>
#include <vector>

#include "bpiFlash.hpp"
#include "configBitstreamParser.hpp"
#include "device.hpp"
#include "jedParser.hpp"
#include "jtag.hpp"
#include "flashInterface.hpp"

class Xilinx: public Device, FlashInterface {
	public:
		Xilinx(Jtag *jtag, const std::string &filename,
				const std::string &secondary_filename,
				const std::string &file_type,
				Device::prog_type_t prg_type,
				const std::string &device_package,
				const bool spi_flash_type,
				const std::string &spiOverJtagPath,
				const std::string &target_flash,
				bool verify, int8_t verbose,
				bool skip_load_bridge, bool skip_reset,
				bool read_dna, bool read_xadc);
		~Xilinx();

		void program(unsigned int offset, bool unprotect_flash) override;
		void program_spi(ConfigBitstreamParser * bit, std::string extention,
			unsigned int offset, bool unprotect_flash);
		void program_bpi(ConfigBitstreamParser * bit, unsigned int offset);
		void program_mem(ConfigBitstreamParser *bitfile);
		bool dumpFlash(uint32_t base_addr, uint32_t len) override;

		bool read_register(const std::string reg_name) override {
			displayRegister(reg_name, dumpRegister(reg_name));
			return true;
		}
		/*!
		 * \brief display register content (bits/slices)
		 * \in reg_name: register name
		 * \in reg_val: register value
		 */
		void displayRegister(const std::string reg_name, const uint32_t reg_val);
		/*!
		 * \brief read register value from the devices
		 * \in reg_name: register name
		 * \return register value
		 */
		uint32_t dumpRegister(const std::string reg_name);

		/*!
		 * \brief display SPI flash ID and status register
		 */
		bool detect_flash() override;
		/*!
		 * \brief protect SPI flash blocks
		 */
		bool protect_flash(uint32_t len) override;
		/*!
		 * \brief unprotect SPI flash blocks
		 */
		bool unprotect_flash() override;
		/*!
		 * \brief configure Quad mode for SPI Flash
		 */
		bool set_quad_bit(bool set_quad) override;
		/*!
		 * \brief erase SPI flash blocks
		 */
		bool bulk_erase_flash() override;

		uint32_t idCode() override;
		void reset() override;

		/* -------------- */
		/* xc3s management */
		/* -------------- */

		/*!
		 * \brief load SRAM (enable ISC, load
		 *        and disable ISC
		 * \return false if something wrong
		 */
		bool xc3s_flow_program(ConfigBitstreamParser *bit);

		/* ------------------- */
		/* xc95/xc3s managment */
		/* ------------------- */

		/*!
		 * \brief enable ISC mode
		 */
		void flow_enable();
		/*!
		 * \brief disable ISC mode
		 */
		void flow_disable();

		/* -------------- */
		/* xc95 managment */
		/* -------------- */

		/*!
		 * \brief erase internal flash
		 * \return false if something wrong
		 */
		bool flow_erase();
		/*!
		 * \brief program internal flash (enable ISC, erase flash,
		 *        program and disable ISC
		 * \return false if something wrong
		 */
		bool flow_program(JedParser *jed);

		/*!
		 * \brief fill a buffer with internal flash content
		 * \return buffer filled
		 */
		std::string flow_read();

		/* ------------------- */
		/* XCF JTAG Flash PROM */
		/* ------------------- */
		void xcf_flow_enable(uint8_t mode = 0x37);
		void xcf_flow_disable();
		bool xcf_flow_erase();
		bool xcf_program(ConfigBitstreamParser *bitfile);
		std::string xcf_read();

		/* -------------------- */
		/* XC2C (CoolRunner II) */
		/* -------------------- */
		/*!
		 * \brief configure instance using model name and idcode
		 * \param[in] idcode: targeted device idcode
		 */
		void xc2c_init(uint32_t idcode);
		/*!
		 * \brief reset device, force read configuration
		 */
		void xc2c_flow_reinit();
		/*!
		 * \brief erase full internal flash (optionally verify)
		 * \return false if erase fails, true otherwise
		 */
		bool xc2c_flow_erase();
		/*!
		 * \brief read full internal flash
		 * \return flash configuration data
		 */
		std::string xc2c_flow_read();
		/*!
		 * \brief write program to the flash (erase before, optional read after)
		 * \param[in] jed: bitstream instance
		 * \return false when erase or verify fails
		 */
		bool xc2c_flow_program(JedParser *jed);

		/* spi interface */
		int spi_put(uint8_t cmd, const uint8_t *tx, uint8_t *rx,
				uint32_t len) override;
		int spi_put(const uint8_t *tx, uint8_t *rx, uint32_t len) override;
		int spi_wait(uint8_t cmd, uint8_t mask, uint8_t cond,
				uint32_t timeout, bool verbose = false) override;

		/* SpiOverJtag v2 specifics methods */
		int spi_put_v2(uint8_t cmd, const uint8_t *tx, uint8_t *rx,
				uint32_t len);

	protected:
		/*!
		 * \brief prepare SPI flash access (need to have bridge in RAM)
		 */
		bool prepare_flash_access() override;
		/*!
		 * \brief end of SPI flash access
		 */
		bool post_flash_access() override;

	private:
		/* list of xilinx family devices */
		enum xilinx_family_t {
			XC95_FAMILY     = 0,
			XC2C_FAMILY,
			SPARTAN3_FAMILY,
			SPARTAN6_FAMILY,
			SPARTAN7_FAMILY,
			ARTIX_FAMILY,
			KINTEX_FAMILY,
			KINTEXUS_FAMILY,
			KINTEXUSP_FAMILY,
			ZYNQ_FAMILY,
			ZYNQMP_FAMILY,
			XCF_FAMILY,
			ARTIXUSP_FAMILY,
			SPARTANUSP_FAMILY,
			VIRTEXUS_FAMILY,
			VIRTEXUSP_FAMILY,
			UNKNOWN_FAMILY  = 999
		};

		xilinx_family_t _fpga_family; /**< used to store current family */

		/*!
		 * \brief xilinx ZynqMP Ultrascale+ specific initialization
		 * \param[in] family name
		 * \return true if device has been correctly initialized
		 */
		bool zynqmp_init(const std::string &family);

		/*!
		 * \brief with xilinx devices SPI flash direct access is not possible
		 * 		so a bridge must be loaded in RAM to access flash
		 * 	\return false if missing device mode, true otherwise
		 */
		bool load_bridge();

		/*!
		 * \brief load BPI flash bridge for parallel NOR flash access
		 * \return false if failed, true otherwise
		 */
		bool load_bpi_bridge();

		/*!
		 * \brief check if board uses BPI flash
		 */
		bool is_bpi_board() const { return _is_bpi_board; }

		/*!
		 * \brief read SpiOverJtag version to select between v1 and v2
		 * \return 2.0 for v2 or 1.0 for v1
		 */
		float get_spiOverJtag_version();

		enum xilinx_flash_chip_t {
			PRIMARY_FLASH = 0x1,
			SECONDARY_FLASH = 0x2
		};

		/* XADC */
		unsigned int xadc_read(uint16_t addr);
		void xadc_write(uint16_t addr, uint16_t data);
		unsigned int xadc_single(uint16_t ch);

		/* DNA */
		uint64_t fuse_dna_read(void);

		/*!
		 * \brief Starting from UltraScale, Xilinx devices can support dual
		 *        QSPI flash configuration, with two different flash chips
		 *        on the board. Target the selected one via the bridge by
		 *        chaging the USER instruction to use.
		 */
		void select_flash_chip(xilinx_flash_chip_t flash_chip);

		std::string _device_package;
		std::string _spiOverJtagPath; /**< spiOverJtag explicit path */
		int _xc95_line_len; /**< xc95 only: number of col by flash line */
		uint16_t _cpld_nb_row; /**< number of flash rows */
		uint16_t _cpld_nb_col; /**< number of cols in a row */
		uint16_t _cpld_addr_size; /**< number of addr bits */
		char _cpld_base_name[8]; /**< cpld name (without package size) */
		int _irlen; /**< IR bit length */
		std::map<std::string, std::vector<uint8_t>> _ircode_map; /**< bscan instructions based on model */
		std::string _secondary_filename; /* path to the secondary flash file (SPIx8) */
		std::string _secondary_file_extension; /* file type for the secondary flash file */
		int _flash_chips; /* bitfield to select the target in boards with two flash chips */
		std::string _user_instruction; /* which USER bscan instruction to interface with SPI */
		bool _soj_is_v2; /* SpiOverJtag version (1.0 or 2.0) */
		uint32_t _jtag_chain_len; /* Jtag Chain Length */
		bool _is_bpi_board; /* true if board uses BPI parallel flash */
		std::unique_ptr<BPIFlash> _bpi_flash; /* BPI flash instance */
};

#endif  // SRC_XILINX_HPP_
