-----------------------------------------------------------------------------
-- Title      : M25Pxxx Flash Controller
-- Project    : TTL to blocking/RS-485 converter
-------------------------------------------------------------------------------
-- File       : m25p_flash.vhd
-- Authors    : Tomasz Wlostowski
--              Theodor-Adrian Stana
-- Company    : CERN
-- Created    : 2013-01-24
-- Last update: 2013-08-21
-- Platform   : FPGA-generic
-- Standard   : VHDL'93
-------------------------------------------------------------------------------
-- Description: Controller for M25Pxxx series of SPI flash memories. Provides
-- read and write access to a Flash memory by means of external command bits.
-------------------------------------------------------------------------------
--
-- Copyright (c) 2013 CERN / BE-CO-HT
--
-- This source file is free software; you can redistribute it   
-- and/or modify it under the terms of the GNU Lesser General   
-- Public License as published by the Free Software Foundation; 
-- either version 2.1 of the License, or (at your option) any   
-- later version.                                               
--
-- This source is distributed in the hope that it will be       
-- useful, but WITHOUT ANY WARRANTY; without even the implied   
-- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      
-- PURPOSE.  See the GNU Lesser General Public License for more 
-- details.                                                     
--
-- You should have received a copy of the GNU Lesser General    
-- Public License along with this source; if not, download it   
-- from http://www.gnu.org/licenses/lgpl-2.1.html
--
-------------------------------------------------------------------------------

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

--use work.sxldr_wbgen2_pkg.all;

entity m25p_flash is
  
  port (
    clk_sys_i : in std_logic;
    rst_n_i   : in std_logic;

--    -- Wishbone registers (FAR register access)
--    regs_i : in  t_sxldr_out_registers;
--    regs_o : out t_sxldr_in_registers;

    -- Data readout interface. 

    -- start address for read operations
    addr_i : in std_logic_vector(23 downto 0);

    -- data request: when 1, the controller reads subsequent bytes from
    -- the flash, starting from addr_i address.
    rstat_i  : in std_logic;
    serase_i : in std_logic;
    write_i  : in std_logic;
    read_i   : in std_logic;
    endcmd_i : in std_logic;

    -- read and write data I/O
    data_i : in  std_logic_vector(7 downto 0);
    data_o : out std_logic_vector(7 downto 0);

    -- when 1, data_o contains a valid byte and the controller is ready to accept
    -- another command
    ready_o : out std_logic;

    -- SPI bus, connect to the flash memory.
    spi_cs_n_o : out std_logic;
    spi_sclk_o : out std_logic;
    spi_mosi_o : out std_logic;
    spi_miso_i : in  std_logic
    );

end m25p_flash;

architecture behavioral of m25p_flash is

  --============================================================================
  -- Type declarations
  --============================================================================
  type t_state is 
    (
      -- idle state
      IDLE,
      -- sector erase cmd address states
      SEWECMD,
      SEWECSEL,
      SECMD,
      SECSEL,
      SEADDR0,
      SEADDR1,
      SEADDR2,
      -- write cmd states
      WWECMD,
      WWECSEL,
      WCSEL,
      WCMD,
      WADDR0,
      WADDR1,
      WADDR2,
      WDATA,
      -- read cmd states
      RCSEL,
      RCMD,
      RADDR0,
      RADDR1,
      RADDR2,
      DUMMY_XFER,
      RDATA,
      -- read status register cmd states
      RSTATCSEL,
      RSTATCMD
    );

  --============================================================================
  -- Component declarations
  --============================================================================
  component spi_master
    generic (
      g_div_ratio_log2 : integer;
      g_num_data_bits  : integer);
    port (
      clk_sys_i  : in  std_logic;
      rst_n_i    : in  std_logic;
      cs_i       : in  std_logic;
      start_i    : in  std_logic;
      cpol_i     : in  std_logic;
      data_i     : in  std_logic_vector(g_num_data_bits - 1 downto 0);
      ready_o    : out std_logic;
      data_o     : out std_logic_vector(g_num_data_bits - 1 downto 0);
      spi_cs_n_o : out std_logic;
      spi_sclk_o : out std_logic;
      spi_mosi_o : out std_logic;
      spi_miso_i : in  std_logic);
  end component;

  --============================================================================
  -- Signal declarations
  --============================================================================
  signal spi_cs, spi_cs_muxed                       : std_logic;
  signal spi_start, spi_start_host, spi_start_muxed : std_logic;
  signal spi_wdata, spi_wdata_host, spi_wdata_muxed : std_logic_vector(7 downto 0);
  signal spi_rdata                                  : std_logic_vector(7 downto 0);
  signal spi_ready                                  : std_logic;

  signal fsm_cmd     : std_logic_vector(2 downto 0);
  signal fsm_cmd_reg : std_logic_vector(2 downto 0);
  signal state       : t_state;
  signal ready_int   : std_logic;
  
  signal send_cmd    : std_logic;

begin  -- rtl

--  -- Host flash data register (bidirectional), updated by writing to FAR.RDATA
--  p_host_spi_registers : process(clk_sys_i)
--  begin
--    if rising_edge(clk_sys_i) then
--      if rst_n_i = '0' then
--        spi_start_host <= '0';
--        spi_wdata_host <= (others => '0');
--      elsif regs_i.far_data_load_o = '1' then
--        spi_wdata_host <= regs_i.far_data_o;
--        spi_start_host <= regs_i.far_xfer_o;
--      else
--        spi_start_host <= '0';
--      end if;
--    end if;
--  end process;


--  -- Multplexes the access between to the flash SPI controller between
--  -- the bootloader host (through FAR register) and the flash readout
--  -- FSM. 
--  p_mux_spi_access : process(spi_cs, spi_start, spi_wdata, spi_start_host, spi_wdata, spi_ready, regs_i, state)
--  begin
--    spi_cs_muxed       <= regs_i.far_cs_o or spi_cs;
--    spi_wdata_muxed    <= spi_wdata_host or spi_wdata;
--    spi_start_muxed    <= spi_start_host or spi_start;
--  end process;

--  regs_o.far_ready_i <= spi_ready;
--  regs_o.far_data_i <= spi_rdata;
  
  -- SPI Master: executes SPI read/write transactions.
  U_SPI_Master : spi_master
    generic map (
      g_div_ratio_log2 => 0,
      g_num_data_bits  => 8)
    port map (
      clk_sys_i  => clk_sys_i,
      rst_n_i    => rst_n_i,
      cs_i       => spi_cs, -- spi_cs_muxed,
      start_i    => spi_start, -- spi_start_muxed,
      cpol_i     => '0',
      data_i     => spi_wdata, -- spi_wdata_muxed,
      ready_o    => spi_ready,
      data_o     => spi_rdata,
      spi_cs_n_o => spi_cs_n_o,
      spi_sclk_o => spi_sclk_o,
      spi_mosi_o => spi_mosi_o,
      spi_miso_i => spi_miso_i);


  -- Main State machine
  p_main_fsm : process(clk_sys_i)
  begin
    if rising_edge(clk_sys_i) then
      if rst_n_i = '0' then
        state     <= IDLE;
        spi_start <= '0';
        spi_cs    <= '0';
        spi_wdata <= (others => '0');
        ready_int <= '1';
        data_o    <= (others => '0');
        send_cmd  <= '1';
--        -- any access to FAR register stops internal bus request
--      elsif(regs_i.far_data_load_o = '1') then
--        spi_start <= '0';
--        spi_cs    <= '0';
--        spi_wdata <= (others => '0');
--        state <= IDLE;
      else
        case state is
          -- Idle: wait for ERASE, WRITE, READ or END commands (in this order)
          when IDLE =>
            if (serase_i = '1') then
              spi_cs    <= '0';
              spi_start <= '1';
              ready_int <= '0';
              state     <= SEWECSEL;
            elsif (write_i = '1') then
              if (send_cmd = '1') then
                send_cmd  <= '0';
                spi_cs    <= '0';
                spi_start <= '1';
                ready_int <= '0';
                state     <= WWECSEL;
              else
                spi_wdata <= data_i;
                spi_start <= '1';
                ready_int <= '0';
                state     <= WDATA;
              end if;
            elsif (read_i = '1') then
              if (send_cmd = '1') then
                send_cmd  <= '0';
                spi_cs    <= '0';
                spi_start <= '1';
                ready_int <= '0';
                state     <= RCSEL;
              else
                spi_start <= '1';
                ready_int <= '0';
                state     <= RDATA;
              end if;
            elsif (rstat_i = '1') then
              spi_cs    <= '0';
              spi_start <= '1';
              ready_int <= '0';
              state     <= RSTATCSEL;
            elsif (endcmd_i = '1') then
              spi_cs   <= '0';
              send_cmd <= '1';
              state    <= IDLE;
            end if;

            --if set_addr_i = '1' then
            --  spi_cs    <= '0';
            --  spi_start <= '1';
            --  ready_int <= '0';
            --  state     <= RCSEL;
            --elsif read_i = '1' then
            --  spi_start <= '1';
            --  ready_int <= '0';
            --  state     <= RDATA;
            --else
            --  spi_start <= '0';
            --  ready_int <= '1';
            --end if;

          --===================================================================
          -- Erase sequence
          --===================================================================
          -- executes a dummy SPI cycle with the SPI chip disabled (CS = 0), to
          -- make sure it will correctly interpret the next transfer as a
          -- WRITE ENABLE command
          when SEWECSEL =>
            if(spi_ready = '1') then
              spi_cs    <= '1';
              spi_start <= '1';
              spi_wdata <= x"06";
              state     <= SEWECMD;
            else
              spi_start <= '0';
            end if;

          -- the write enable command is sent during this cycle
          when SEWECMD =>
            spi_start <= '0';
            if (spi_ready = '1') then
              spi_cs    <= '0';
              spi_start <= '1';
              state     <= SECSEL;
            end if;

          -- executes a dummy SPI cycle with the SPI chip disabled (CS = 0), to
          -- make sure it will correctly interpret the previous WRITE ENABLE
          -- transfer
          when SECSEL =>
            if(spi_ready = '1') then
              spi_cs    <= '1';
              spi_start <= '1';
              spi_wdata <= x"D8";
              state     <= SECMD;
            else
              spi_start <= '0';
            end if;

          -- Send command 0xD8 (sector erase)
          when SECMD =>
            if(spi_ready = '1') then
              state     <= SEADDR0;
              spi_wdata <= addr_i(23 downto 16);
              spi_start <= '1';
            else
              spi_start <= '0';
            end if;

          -- Send 1st byte of erase address
          when SEADDR0 =>
            if(spi_ready = '1') then
              state     <= SEADDR1;
              spi_wdata <= addr_i(15 downto 8);
              spi_start <= '1';
            else
              spi_start <= '0';
            end if;

          -- Send 2nd byte of erase address
          when SEADDR1 =>
            if(spi_ready = '1') then
              state     <= SEADDR2;
              spi_wdata <= addr_i(7 downto 0);
              spi_start <= '1';
            else
              spi_start <= '0';
            end if;

          -- Send 3rd byte of erase address
          when SEADDR2 =>
            if(spi_ready = '1') then
              ready_int <= '1';
              state     <= IDLE;
            else
              spi_start <= '0';
              ready_int <= '0';
            end if;

          --===================================================================
          -- Write sequence
          --===================================================================
          -- executes a dummy SPI cycle with the SPI chip disabled (CS = 0), to
          -- make sure it will correctly interpret the next transfer as a
          -- WRITE ENABLE command
          when WWECSEL =>
            if(spi_ready = '1') then
              spi_cs    <= '1';
              spi_start <= '1';
              spi_wdata <= x"06";
              state     <= WWECMD;
            else
              spi_start <= '0';
            end if;

          -- the write enable command is sent during this cycle
          when WWECMD =>
            spi_start <= '0';
            if (spi_ready = '1') then
              spi_cs    <= '0';
              spi_start <= '1';
              state     <= WCSEL;
            end if;

          -- executes a dummy SPI cycle with the SPI chip disabled (CS = 0), to
          -- make sure it will correctly interpret the previous WRITE ENABLE
          -- transfer
          when WCSEL =>
            if(spi_ready = '1') then
              spi_cs    <= '1';
              spi_start <= '1';
              spi_wdata <= x"02";
              state     <= WCMD;
            else
              spi_start <= '0';
            end if;

          -- Send command 0x02 (page program)
          when WCMD =>
            if(spi_ready = '1') then
              state     <= WADDR0;
              spi_wdata <= addr_i(23 downto 16);
              spi_start <= '1';
            else
              spi_start <= '0';
            end if;

          -- Send 1st byte of write address
          when WADDR0 =>
            if(spi_ready = '1') then
              state     <= WADDR1;
              spi_wdata <= addr_i(15 downto 8);
              spi_start <= '1';
            else
              spi_start <= '0';
            end if;

          -- Send 2nd byte of write address
          when WADDR1 =>
            if(spi_ready = '1') then
              state     <= WADDR2;
              spi_wdata <= addr_i(7 downto 0);
              spi_start <= '1';
            else
              spi_start <= '0';
            end if;

          -- Send 3rd byte of read address
          when WADDR2 =>
            if(spi_ready = '1') then
              spi_wdata <= data_i;
              spi_start <= '1';
              state     <= WDATA;
            else
              spi_start <= '0';
            end if;

          -- wait for completion of write cycle
          when WDATA =>
            spi_start <= '0';
            if(spi_ready = '1')then
              ready_int <= '1';
              state     <= IDLE;
            else
              ready_int <= '0';
            end if;

          --===================================================================
          -- Read sequence
          --===================================================================
          -- executes a dummy SPI cycle with the SPI chip disabled (CS = 0), to
          -- make sure it will correctly interpret the next transfer as a READ
          -- command
          when RCSEL =>
            if(spi_ready = '1') then
              spi_cs    <= '1';
              spi_start <= '1';
              spi_wdata <= x"0B";
              state     <= RCMD;
            else
              spi_start <= '0';
            end if;

          -- Send command 0x0B (page program)
          when RCMD =>
            if(spi_ready = '1') then
              state     <= RADDR0;
              spi_wdata <= addr_i(23 downto 16);
              spi_start <= '1';
            else
              spi_start <= '0';
            end if;

          -- Send 1st byte of write address
          when RADDR0 =>
            if(spi_ready = '1') then
              state     <= RADDR1;
              spi_wdata <= addr_i(15 downto 8);
              spi_start <= '1';
            else
              spi_start <= '0';
            end if;

          -- Send 2nd byte of write address
          when RADDR1 =>
            if(spi_ready = '1') then
              state     <= RADDR2;
              spi_wdata <= addr_i(7 downto 0);
              spi_start <= '1';
            else
              spi_start <= '0';
            end if;

          -- Send 3rd byte of read address
          when RADDR2 =>
            if(spi_ready = '1') then
              spi_wdata <= "XXXXXXXX";
              spi_start <= '1';
              state     <= DUMMY_XFER;
            else
              spi_start <= '0';
            end if;

          -- dummy transfer (necessary for fast read mode)
          when DUMMY_XFER =>
            spi_start <= '0';
            if(spi_ready = '1') then
              state     <= RDATA;
              spi_start <= '1';
            end if;

            -- Data readout: waits for completion of read transaction initiated
            -- upon assertion of read_i and returns the byte read data_o.
          when RDATA =>
            spi_start <= '0';
            if(spi_ready = '1')then
              data_o    <= spi_rdata;
              ready_int <= '1';
              state     <= IDLE;
            else
              ready_int <= '0';
            end if;

          --===================================================================
          -- Read status register sequence
          --===================================================================
          -- executes a dummy SPI cycle with the SPI chip disabled (CS = 0), to
          -- make sure it will correctly interpret the next transfer as a
          -- READ STATUS REGISTER command
          when RSTATCSEL =>
            if(spi_ready = '1') then
              spi_cs    <= '1';
              spi_start <= '1';
              spi_wdata <= x"05";
              state     <= RSTATCMD;
            else
              spi_start <= '0';
            end if;

          -- Send command 0x05 (page program)
          when RSTATCMD =>
            if (spi_ready = '1') then
              ready_int <= '1';
              state     <= IDLE;
            else
              spi_start <= '0';
              ready_int <= '0';
            end if;

          --===================================================================
          -- Unknown state
          --===================================================================
          when others =>
            state <= IDLE;

        end case;
      end if;
    end if;
  end process;

  -- De-assert ready flag early
  ready_o <= ready_int and not (serase_i or write_i or read_i or rstat_i or endcmd_i);
  
end behavioral;

