library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

use work.flashrom_pkg.all;

entity flashrom_controller is
	port(
		clk               : in  std_logic;
		rst               : in  std_logic;
		clr               : in  std_logic;

		-- Control IF
		ready             : out std_logic; -- The controller is ready to accept commands
		page              : in  std_logic_vector(FLASHROM_PAGE_ADDR_WIDTH - 1 downto 0);
		sync_stb          : in  std_logic; -- Synchronize current memory page with chip, only sampled when ready
		load_stb          : in  std_logic; -- Load page into local buffer, only sampled when ready
		status_update_stb : in  std_logic; -- Update status vector
		status            : out std_logic_vector(7 downto 0); -- value of the status register (update using status_update_stb) 
		info              : out std_logic_vector(31 downto 0); -- value of the information register (updated on reset)

		-- Data IF		
		data_in           : in  std_logic_vector(7 downto 0);
		data_in_next      : out std_logic;
		data_out          : out std_logic_vector(7 downto 0);
		data_out_valid    : out std_logic;

		-- SPI flash hardware signals
		spi_si            : out std_logic; -- SPI serial in
		spi_so            : in  std_logic; -- SPI serial out
		spi_sck           : out std_logic; -- SPI clock
		spi_reset_n       : out std_logic; -- SPI hard reset
		spi_cs_n          : out std_logic; -- SPI chip select
		spi_wp_n          : out std_logic -- SPI write protect
	);
end entity flashrom_controller;

architecture RTL of flashrom_controller is
	constant spif_max_word_length  : integer := 32;
	constant max_dummy_bits        : integer := 63;
	constant bootup_delay          : integer := 4000; -- TODO: These times need to be dependent on f_clk!
	constant status_reg_poll_delay : integer := 100000; -- TODO: These times need to be dependent on f_clk!

	type state_t is (INIT, GETINFO, GETSTATUS, LOADPAGE, WRITEPAGE, WAIT_BUSY, IDLE);
	signal state : state_t;

	signal spif_data_in_valid   : std_logic;
	signal spif_data_in         : std_logic_vector(spif_max_word_length - 1 downto 0);
	signal spif_data_in_length  : integer range 0 to spif_max_word_length;
	signal spif_data_next       : std_logic;
	signal spif_data_out        : std_logic_vector(spif_max_word_length - 1 downto 0);
	signal spif_data_out_valid  : std_logic;
	signal spif_data_out_length : integer range 0 to spif_max_word_length;

	signal words_sent               : integer range 0 to 511;
	signal spif_data_out_dummy_bits : integer range 0 to max_dummy_bits;
	signal spif_transmission_active : std_logic;
	signal command_is_latched       : boolean;
begin
	spi_wp_n <= '1';

	flashrom_spi_inst : entity work.flashrom_spi
		generic map(
			clk_divider     => 4,
			max_word_length => spif_max_word_length,
			max_dummy_bits  => max_dummy_bits)
		port map(
			clk                 => clk,
			rst                 => rst,
			clr                 => clr,
			spi_si              => spi_si,
			spi_so              => spi_so,
			spi_sck             => spi_sck,
			spi_cs_n            => spi_cs_n,
			data_in_valid       => spif_data_in_valid,
			data_in             => spif_data_in,
			data_in_length      => spif_data_in_length,
			data_next           => spif_data_next,
			data_out            => spif_data_out,
			data_out_valid      => spif_data_out_valid,
			data_out_length     => spif_data_out_length,
			data_out_dummy_bits => spif_data_out_dummy_bits,
			transmission_active => spif_transmission_active);

	flashrom_controller_p : process(clk, rst) is
		variable write_cnt : integer range 0 to bootup_delay;
		variable delay_cnt : integer range 0 to status_reg_poll_delay;

		variable done : boolean;

		procedure default_state is
		begin
			spi_reset_n        <= '1';
			spif_data_in_valid <= '0';
			done               := false;
			command_is_latched <= false;
		end procedure default_state;

		procedure reset_state is
		begin
			default_state;
			state       <= INIT;
			spi_reset_n <= '0';
			words_sent  <= 0;
			write_cnt   := 0;

			spif_data_in             <= (others => '0');
			spif_data_in_length      <= 0;
			spif_data_out_length     <= 0;
			spif_data_out_dummy_bits <= 0;

			write_cnt := 0;
			delay_cnt := 0;
		end procedure reset_state;

		procedure run_command_single(constant data_in_length : integer; constant data_out_length : integer; constant data_out_dummy_bits : integer; constant nTxWords : integer; constant data_in : std_logic_vector) is
		begin
			spif_data_in_length      <= data_in_length;
			spif_data_out_length     <= data_out_length;
			spif_data_out_dummy_bits <= data_out_dummy_bits;
			spif_data_in_valid       <= '1';
			spif_data_in             <= data_in & padBits(spif_data_in, data_in);

			if write_cnt = nTxWords then
				spif_data_in_valid <= '0';
			elsif spif_data_next = '1' then
				write_cnt := write_cnt + 1;
			end if;

			if spif_data_out_valid = '1' and (write_cnt = nTxWords) then
				spif_data_in_valid <= '0';
				done               := true;
				state              <= IDLE;
				write_cnt          := 0;
			end if;
		end procedure run_command_single;

	begin
		if rst = '1' then
			reset_state;
		elsif rising_edge(clk) then
			default_state;
			if clr = '1' then
				reset_state;
			else
				case state is
					when INIT =>
						words_sent <= 0;
						if delay_cnt = bootup_delay then
							delay_cnt := 0;
							state     <= GETINFO;
						else
							delay_cnt := delay_cnt + 1;
						end if;
					when GETINFO =>     -- TODO: In simulation I can only call this command once?!
						run_command_single(8, 32, 8, 5, FLASHROM_COMMAND_MANUFACTURER_ID);
						if done then
							info <= spif_data_out;
						end if;
					when IDLE =>
						write_cnt := 0;
						delay_cnt := 0;
						if status_update_stb = '1' then
							state <= GETSTATUS;
						end if;
						if load_stb = '1' then
							state <= LOADPAGE;
						end if;
						if sync_stb = '1' then
							state <= WRITEPAGE;
						end if;
					when GETSTATUS =>
						run_command_single(8, 8, 16, 3, FLASHROM_COMMAND_GET_STATUS);
						if done then
							status <= spif_data_out(7 downto 0);
						end if;
					when WAIT_BUSY =>
						if delay_cnt = status_reg_poll_delay then
							run_command_single(8, 8, 16, 3, FLASHROM_COMMAND_GET_STATUS);
							if done then
								status    <= spif_data_out(7 downto 0);
								delay_cnt := 0;
								if spif_data_out(7) = '1' then
									state <= IDLE;
								else
									state <= WAIT_BUSY;
								end if;
							end if;
						else
							delay_cnt := delay_cnt + 1;
						end if;
					when LOADPAGE =>
						run_command_single(32, 8, 34, 258, FLASHROM_COMMAND_CONT_ARRAY_READ & page);
						if spif_data_next = '1' or command_is_latched then
							command_is_latched  <= true;
							spif_data_in_length <= 8;
							spif_data_in        <= (others => '0');
						end if;
						if done then
							report "Load page done." severity note;
						end if;
					when WRITEPAGE =>
						run_command_single(32, 8, 34, 257, FLASHROM_COMMAND_WRITE_THROUGH_1 & page);
						if spif_data_next = '1' or command_is_latched then
							command_is_latched  <= true;
							spif_data_in_length <= 8;
							spif_data_in        <= data_in & padBits(spif_data_in, data_in);
						end if;
						if done then
							delay_cnt := 0;
							state     <= WAIT_BUSY;
						end if;
				end case;
			end if;
		end if;
	end process flashrom_controller_p;
	ready <= '1' when state = IDLE else '0';

	data_out       <= spif_data_out(7 downto 0);
	data_out_valid <= spif_data_out_valid when state = LOADPAGE else '0';
	data_in_next   <= spif_data_next when (state = WRITEPAGE) and command_is_latched else '0';
end architecture RTL;
