--Colin O'Flynn's wrapper for the PS/2 interface by  Daniel Quintero from opencores.org
--www.newae.com or coflynn@newae.com
--This module is designed to to interface to a PS/2 mouse and get information on the
--location of a virtual pointer and also the pressing of buttons
--Revision: 20031210

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use IEEE.NUMERIC_STD.ALL;

package PS2_mouse is 
component mouse is
   port 
      (
      clk_i       : in std_logic;
      rst_i       : in std_logic;
      nrst_i      : in std_logic;

      --hold the current X and Y location of the mouse pointer
      --set up as your normal cursor idea, stops at end of screen
      --0x000,0x000 = bottem left. 0x1FF,0x1FF = top right.
      x_loc_o     : out std_logic_vector (9 downto 0);
      y_loc_o     : out std_logic_vector (9 downto 0);

	 --mouse wheel
	 z_loc_o	   : out std_logic_vector (7 downto 0);

      --mouse buttons, active high
      rightbut_o  : out std_logic;
      leftbut_o   : out std_logic;

      --mouse OK signal, when 1 = mouse OK
      mouseok_o   : out std_logic;
      --slow clock signal, used for mouse timeout
      slow_clk    : in  std_logic;

      --connect to mouse, with pull-ups on lines as they are used
      --in open collector mode
      ps2_data_io : inout std_logic; 
      ps2_clk_io  : inout std_logic


      );

end component mouse;
end package PS2_mouse;

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity mouse is
   port 
      (
      clk_i       : in std_logic;
      rst_i       : in std_logic;
      nrst_i      : in    std_logic;

      --hold the current X and Y location of the mouse pointer
      --set up as your normal cursor idea
      --0x000,0x000 = top left. 0x1FF,0x1FF = bottom right.
      x_loc_o     : out std_logic_vector (9 downto 0);
      y_loc_o     : out std_logic_vector (9 downto 0);

	 --mouse wheel
	 z_loc_o	   : out std_logic_vector (7 downto 0);

      --mouse buttons, active high
      rightbut_o  : out std_logic;
      leftbut_o   : out std_logic;

      --mouse OK signal, when 1 = mouse OK
      mouseok_o   : out std_logic;
      --slow clock signal, used for mouse timeout
      slow_clk    : in  std_logic;

      --connect to mouse, with pull-ups on lines as they are used
      --in open collector mode
      ps2_data_io : inout std_logic; 
      ps2_clk_io  : inout std_logic

      );

end mouse;


architecture behaviour of mouse is 

--PS/2 UART for mouse
   component ps2
   port (
      clk_i        : in    std_logic;
      rst_i        : in    std_logic;
      data_o       : out   std_logic_vector(7 downto 0);
      data_i       : in    std_logic_vector(7 downto 0);
      ibf_clr_i    : in    std_logic;
      obf_set_i    : in    std_logic;
      ibf_o        : out   std_logic;
      obf_o        : out   std_logic;
      frame_err_o  : out   std_logic;
      parity_err_o : out   std_logic;
      err_clr_i    : in    std_logic;
      wdt_o        : out   std_logic;
      ps2_clk_io   : inout std_logic;
      ps2_data_io  : inout std_logic);
   end component;


   --possible responses
   constant BAT_OK      : std_logic_vector(7 downto 0) := x"AA";
      --a normal mouse
   constant MOUSE_ID    : std_logic_vector(7 downto 0) := x"00";
      --using microsoft Intelimouse w/ scroll wheel comptable mouse
   constant MOUSE_ID_INTELM  : std_logic_vector(7 downto 0) := x"03";
   constant ACK         : std_logic_vector(7 downto 0) := x"FA";
   
   --commands
   constant RESET_CMD   : std_logic_vector(7 downto 0) := x"FF";
   constant ENABLE_DATA : std_logic_vector(7 downto 0) := x"F4";
   constant SAMPLE_RATE : std_logic_vector(7 downto 0) := x"F3";
   constant DEVICE_TYPE : std_logic_vector(7 downto 0) := x"F2";

   --timeout counter, this many ticks of slow_clk should be ~500mS
   constant TIMEOUT_COUNTER : integer := 1562500;

   --used for the ps2 UART
   signal data_out         : std_logic_vector(7 downto 0);
   signal data_in          : std_logic_vector(7 downto 0);
   signal data_sel         : std_logic;
   signal ibf_clr, obf_set : std_logic;
   signal obf_set_final    : std_logic;
   signal ibf, obf         : std_logic;
   signal busy             : std_logic;
  -- signal parity_err       : std_logic;
  -- signal frame_err        : std_logic;
  -- signal err_clr          : std_logic;
   signal ps2_wdt          : std_logic;

   signal clr_ibf_signal   : std_logic;
   signal obf_set_sig      : std_logic;
   signal obf_set_sig1     : std_logic;
   
   signal obf_reset        : std_logic;
   signal obf_reset_reset  : std_logic;

   signal obf_delay1       : std_logic;
   signal obf_set_sig_delayed : std_logic;

   --for holding data in registers
   signal mouse_X          : std_logic_vector(9 downto 0);
   signal mouse_Y          : std_logic_vector(9 downto 0);
   signal mouse_Z		  : std_logic_vector(7 downto 0);

   signal mouse_Xu         : std_ulogic_vector(9 downto 0);
   signal mouse_Yu         : std_ulogic_vector(9 downto 0);
   signal mouse_Zu		  : std_ulogic_vector(7 downto 0);

   signal mouse_diff_X     : std_logic_vector(9 downto 0); --2's comp change in X
   signal mouse_diff_Y     : std_logic_vector(9 downto 0); --2's comp change in Y
   signal mouse_diff_Z     : std_logic_vector(7 downto 0); --2's comp change in Z
   signal mouse_but_left   : std_logic;
   signal mouse_but_right  : std_logic;

   --current state machine stuff

   type states_mouse is (ST_BAT, ST_ACK, ST_MOUSEID, ST_INTELM1b, ST_INTELM2a, ST_INTELM2b, ST_INTELM3a, ST_INTELM3b, ST_INTELM4, ST_INTELM5, ST_INTELM6, ST_BYTE1, ST_BYTE2, ST_BYTE3, ST_BYTE4);
		signal state : states_mouse;
      signal next_state : states_mouse;

   --stores the timeout counter value as used for pulling lines low
   signal line_delay_count : std_logic_vector(2 downto 0);

   --timeout counter
   signal timeout          : integer range 0 to TIMEOUT_COUNTER;

begin
   
   --PS2 port mapping
   ps2_uart: ps2
      port map (
         clk_i        => clk_i,
         rst_i        => nrst_i,
         data_o       => data_out,
         data_i       => data_in,
         ibf_clr_i    => ibf_clr,
         obf_set_i    => obf_set,
         ibf_o        => ibf,
         obf_o        => obf,
         --frame_err_o  => frame_err,
         --parity_err_o => parity_err,
         err_clr_i    => '0',
         wdt_o        => ps2_wdt,
         ps2_clk_io   => ps2_clk_io,
         ps2_data_io  => ps2_data_io); 
         

   statepr: process(state, rst_i, timeout, ibf)
   begin
   if (rst_i = '1' or timeout = TIMEOUT_COUNTER) then
      next_state <= ST_BAT;
      data_in <= RESET_CMD;
      obf_set_sig <= '1';
      mouseok_o <= '0';
      
	 --only reset this count on an actual reset
	 if rst_i = '1' then    
   	 	mouse_Z <= x"32";
	 end if;

   elsif ibf_clr = '1' then
       --This is the only time we need to write to the
       --mouse, so it is put here to eliminate some of
       --the logic (only two possibilties for data_in)
       obf_set_sig <= '0';

   elsif rising_edge(ibf) then

        case state is             
            when ST_BAT =>
               if data_out = BAT_OK then
                  next_state <= ST_MOUSEID;
               else
                  next_state <= ST_BAT;
               end if;

            when ST_MOUSEID =>

               if (data_out = MOUSE_ID) then
                  obf_set_sig <= '1';
                  data_in <= SAMPLE_RATE;
                  next_state <= ST_INTELM1b;

               else
                  next_state <= ST_BAT;

               end if;

   		 when ST_INTELM1b =>
		     if (data_out = ACK) then
				obf_set_sig <= '1';
				data_in <= x"C8";
				next_state <= ST_INTELM2a;
               else
			   next_state <= ST_BAT;
      
	          end if;

            when ST_INTELM2a =>
		     if (data_out = ACK) then
			     obf_set_sig <= '1';
				data_in <= SAMPLE_RATE;
				next_state <= ST_INTELM2b;
               else
			  next_state <= ST_BAT;
               end if;

            when ST_INTELM2b =>
		     if (data_out = ACK) then
				obf_set_sig <= '1';
				data_in <= x"64";
				next_state <= ST_INTELM3a;
               else
			  next_state <= ST_BAT;
               end if;

           when ST_INTELM3a =>
		     if (data_out = ACK) then
			     obf_set_sig <= '1';
				data_in <= SAMPLE_RATE;
				next_state <= ST_INTELM3b;
               else
			  next_state <= ST_BAT;
               end if;

            when ST_INTELM3b =>
		     if (data_out = ACK) then
				obf_set_sig <= '1';
				data_in <= x"50";
				next_state <= ST_INTELM4;
               else
			  next_state <= ST_BAT;
               end if;

            when ST_INTELM4 =>
		  	if (data_out = ACK) then
				obf_set_sig <= '1';
				data_in <= DEVICE_TYPE;
				next_state <= ST_INTELM5;
               else
			  next_state <= ST_BAT;
               end if;

            when ST_INTELM5 =>
		     if (data_out = ACK) then
			    next_state <= ST_INTELM6;
               else
			  next_state <= ST_BAT;
			end if;
					 
            when ST_INTELM6 =>

               if (data_out = MOUSE_ID_INTELM) then
                  obf_set_sig <= '1';
                  data_in <= ENABLE_DATA;
                  next_state <= ST_ACK;

               else
                  next_state <= ST_BAT;

               end if;


            when ST_ACK =>
               if (data_out = ACK) then
                  next_state <= ST_BYTE1;

                  mouseok_o <= '1'; --if mouse has got this far, mouse is OK
               else
                  next_state <= ST_BAT;
               end if;
                           
            when ST_BYTE1 =>
               
                  --bit 3 is always 1
               if (data_out(3) = '1') then

			   --note these signals are from the last time around, as they are
			   --all signals hence the mouse_diff_X changes that occur in this
			   --byte get updated next time....
                  mouse_X <= mouse_X + mouse_diff_X;
                  mouse_Y <= mouse_Y - mouse_diff_Y;
			   mouse_Z <= mouse_Z + mouse_diff_Z;

                  mouse_diff_Y(9) <= data_out(5);
                  mouse_diff_X(9) <= data_out(4);
			   mouse_diff_Y(8) <= data_out(5);
			   mouse_diff_X(8) <= data_out(4);
                  mouse_but_left <= data_out(0);
                  mouse_but_right <= data_out(1);
                  next_state <= ST_BYTE2;
               else --lost syncrinization with mouse
                  next_state <= ST_BYTE1;
               end if;

            when ST_BYTE2 =>
               mouse_diff_X(7 downto 0) <= data_out;
               next_state <= ST_BYTE3;

            when ST_BYTE3 =>
               mouse_diff_Y(7 downto 0) <= data_out;
               next_state <= ST_BYTE4;			                                                          

            when ST_BYTE4 =>
		     mouse_diff_Z <= data_out;
               next_state <= ST_BYTE1;

         end case;
   end if;
   end process;

   process(clk_i)
   begin
     if rising_edge(clk_i) then
         state <= next_state;
      end if;
   end process;

   --clears the IBF after it goes high, but with enough time for the
   --state machine to react on it
   process(clk_i, rst_i)
   begin
      if (rst_i = '1') then
         clr_ibf_signal <= '0';
      elsif falling_edge(clk_i) then
         clr_ibf_signal <= ibf;
      end if;
   end process;
   process(clk_i, rst_i)
   begin
      if (rst_i = '1') then
         ibf_clr <= '0';
      elsif rising_edge(clk_i) then
         ibf_clr <= clr_ibf_signal;
      end if;
   end process;

     
   --for the PS/2 modem part to work, the obf_set signal can only be held
   --high for less than one PS/2 clock cycle, in this case just one clock cycle
   --of the clk_i is used   
  process(obf_set_sig, obf_set, rst_i)
  begin
      if (rst_i = '1') then
         obf_set_sig1 <= obf_set_sig;
      elsif (obf_set = '1') then
         obf_set_sig1 <= '0';
      elsif rising_edge(obf_set_sig) then
         obf_set_sig1 <= '1';
      end if;
   end process;

   process(clk_i)
   begin
      if rising_edge(clk_i) then
         obf_set <= obf_set_sig1;
      end if;
   end process;


   process(slow_clk, ibf)
   begin
      if ibf = '1' then
         timeout <= 0;
      elsif rising_edge(slow_clk) then
         timeout <= timeout + 1;
      end if;
   end process;          

   x_loc_o <= mouse_X;
   y_loc_o <= Mouse_Y;
   z_loc_o <= Mouse_Z;

   leftbut_o <= mouse_but_left;
   rightbut_o <= mouse_but_right;
   

end behaviour;

