gpio: Add interrupts and trigger registers
authorMatt Johnston <matt@codeconstruct.com.au>
Thu, 8 Sep 2022 05:26:00 +0000 (13:26 +0800)
committerMatt Johnston <matt@codeconstruct.com.au>
Mon, 17 Oct 2022 05:21:02 +0000 (13:21 +0800)
Allows to trigger on rising/falling/both edge, as well
as high/low level.

Registers are compatible with Linux ftgpio010 driver.

Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
gpio.vhdl
include/microwatt_soc.h

index d5454a214f7bf9aa4977861213136f1992c4e9af..3df610fad857d0d9262e2f873e76e78eae3722d5 100644 (file)
--- a/gpio.vhdl
+++ b/gpio.vhdl
@@ -39,20 +39,37 @@ architecture behaviour of gpio is
     constant GPIO_REG_DATA_SET : std_ulogic_vector(GPIO_REG_BITS-1 downto 0) := "00100";
     constant GPIO_REG_DATA_CLR : std_ulogic_vector(GPIO_REG_BITS-1 downto 0) := "00101";
 
+    constant GPIO_REG_INT_EN   : std_ulogic_vector(GPIO_REG_BITS-1 downto 0) := "01000";
+    constant GPIO_REG_INT_STAT : std_ulogic_vector(GPIO_REG_BITS-1 downto 0) := "01001";
+    -- write 1 to clear
+    constant GPIO_REG_INT_CLR : std_ulogic_vector(GPIO_REG_BITS-1 downto 0) := "01100";
+    -- edge 0, level 1
+    constant GPIO_REG_INT_TYPE : std_ulogic_vector(GPIO_REG_BITS-1 downto 0) := "01101";
+    -- for edge: trigger on either edge = 1
+    constant GPIO_REG_INT_BOTH_EDGE : std_ulogic_vector(GPIO_REG_BITS-1 downto 0) := "01110";
+    -- for edge: rising 0, falling 1
+    -- for level: high 0, low 1
+    constant GPIO_REG_INT_LEVEL : std_ulogic_vector(GPIO_REG_BITS-1 downto 0) := "01111";
+
     -- Current output value and direction
     signal reg_data : std_ulogic_vector(NGPIO - 1 downto 0);
     signal reg_dirn : std_ulogic_vector(NGPIO - 1 downto 0);
+    signal reg_in0  : std_ulogic_vector(NGPIO - 1 downto 0);
     signal reg_in1  : std_ulogic_vector(NGPIO - 1 downto 0);
     signal reg_in2  : std_ulogic_vector(NGPIO - 1 downto 0);
 
+    signal reg_intr_en    : std_ulogic_vector(NGPIO - 1 downto 0);
+    signal reg_intr_hit   : std_ulogic_vector(NGPIO - 1 downto 0);
+    signal reg_intr_type : std_ulogic_vector(NGPIO - 1 downto 0);
+    signal reg_intr_level : std_ulogic_vector(NGPIO - 1 downto 0);
+    signal reg_intr_both : std_ulogic_vector(NGPIO - 1 downto 0);
+
     signal wb_rsp   : wb_io_slave_out;
     signal reg_out  : std_ulogic_vector(NGPIO - 1 downto 0);
 
+    constant ZEROS : std_ulogic_vector(NGPIO-1 downto 0) := (others => '0');
 begin
-
-    -- No interrupt facility for now
-    intr <= '0';
-
+    intr <= '0' when reg_intr_hit = ZEROS else '1';
     gpio_out <= reg_data;
     gpio_dir <= reg_dirn;
 
@@ -60,22 +77,62 @@ begin
     wb_rsp.ack <= wb_in.cyc and wb_in.stb;
     with wb_in.adr(GPIO_REG_BITS - 1 downto 0) select reg_out <=
         reg_data when GPIO_REG_DATA_OUT,
-        reg_in2  when GPIO_REG_DATA_IN,
+        reg_in1  when GPIO_REG_DATA_IN,
         reg_dirn when GPIO_REG_DIR,
+        reg_intr_en when GPIO_REG_INT_EN,
+        reg_intr_hit when GPIO_REG_INT_STAT,
+        reg_intr_type when GPIO_REG_INT_TYPE,
+        reg_intr_both when GPIO_REG_INT_BOTH_EDGE,
+        reg_intr_level when GPIO_REG_INT_LEVEL,
         (others => '0') when others;
     wb_rsp.dat(wb_rsp.dat'left downto NGPIO) <= (others => '0');
     wb_rsp.dat(NGPIO - 1 downto 0) <= reg_out;
     wb_rsp.stall <= '0';
 
     regs_rw: process(clk)
+       variable trig : std_logic_vector(0 to 2);
+       variable change : std_logic;
+       variable intr_hit : boolean;
     begin
         if rising_edge(clk) then
             wb_out <= wb_rsp;
+            for i in NGPIO - 1 downto 0 loop
+                -- interrupt triggers. reg_in1 is current value
+                if reg_intr_type(i) = '0' then
+                    -- edge
+                    change := '0' when (reg_in1(i) = reg_in2(i)) else '1';
+                    trig := change & reg_intr_both(i) & reg_intr_level(i);
+                    case trig is
+                        -- both
+                        when "110" | "111" => intr_hit := true;
+                        -- rising
+                        when "100" => intr_hit := reg_in1(i) = '1';
+                        -- falling
+                        when "101" => intr_hit := reg_in1(i) = '0';
+                        when others => intr_hit := false;
+                    end case;
+                else
+                    -- level
+                    intr_hit := reg_in1(i) = not reg_intr_level(i);
+                end if;
+                reg_intr_hit(i) <= '1' when intr_hit and reg_intr_en(i) = '1';
+
+            end loop;
+
+            -- previous value for interrupt edge detection
             reg_in2 <= reg_in1;
-            reg_in1 <= gpio_in;
+            -- 2 flip flops to cross from async input to sys clock domain
+            reg_in1 <= reg_in0;
+            reg_in0 <= gpio_in;
+
             if rst = '1' then
                 reg_data <= (others => '0');
                 reg_dirn <= (others => '0');
+                reg_intr_en <= (others => '0');
+                reg_intr_hit <= (others => '0');
+                reg_intr_type <= (others => '0');
+                reg_intr_both <= (others => '0');
+                reg_intr_level <= (others => '0');
                 wb_out.ack <= '0';
             else
                 if wb_in.cyc = '1' and wb_in.stb = '1' and wb_in.we = '1' then
@@ -88,6 +145,16 @@ begin
                             reg_data <= reg_data or wb_in.dat(NGPIO - 1 downto 0);
                         when GPIO_REG_DATA_CLR =>
                             reg_data <= reg_data and not wb_in.dat(NGPIO - 1 downto 0);
+                        when GPIO_REG_INT_EN =>
+                            reg_intr_en <= wb_in.dat(NGPIO - 1 downto 0);
+                        when GPIO_REG_INT_CLR =>
+                            reg_intr_hit <= reg_intr_hit and not wb_in.dat(NGPIO - 1 downto 0);
+                        when GPIO_REG_INT_TYPE =>
+                            reg_intr_type <= wb_in.dat(NGPIO - 1 downto 0);
+                        when GPIO_REG_INT_BOTH_EDGE =>
+                            reg_intr_both <= wb_in.dat(NGPIO - 1 downto 0);
+                        when GPIO_REG_INT_LEVEL =>
+                            reg_intr_level <= wb_in.dat(NGPIO - 1 downto 0);
                         when others =>
                     end case;
                 end if;
@@ -96,4 +163,4 @@ begin
     end process;
 
 end architecture behaviour;
-        
+
index 7d68025361e93829641eb8eb10ff62fea499c4db..6a28b7dbd7bbbc67ce3629e07618a82f3365c6f1 100644 (file)
@@ -14,6 +14,7 @@
 #define XICS_ICP_BASE   0xc0004000  /* Interrupt controller */
 #define XICS_ICS_BASE   0xc0005000  /* Interrupt controller */
 #define SPI_FCTRL_BASE  0xc0006000  /* SPI flash controller registers */
+#define GPIO_BASE       0xc0007000  /* GPIO registers */
 #define DRAM_CTRL_BASE 0xc8000000  /* LiteDRAM control registers */
 #define LETH_CSR_BASE  0xc8020000  /* LiteEth CSR registers */
 #define LETH_SRAM_BASE 0xc8030000  /* LiteEth MMIO space */
 #define   SPI_REG_AUTO_CFG_CSTOUT_SHIFT         24    /* CS timeout */
 #define   SPI_REG_AUTO_CFG_CSTOUT_MASK          (0x3f << SPI_REG_AUTO_CFG_CSTOUT_SHIFT)
 
+/*
+ * Register definitions for GPIO
+ */
+#define GPIO_REG_DATA_OUT  0x00
+#define GPIO_REG_DATA_IN   0x04
+#define GPIO_REG_DIR       0x08
+#define GPIO_REG_DATA_SET  0x10
+#define GPIO_REG_DATA_CLR  0x14
+
+#define GPIO_REG_INT_EN    0x20
+#define GPIO_REG_INT_STAT  0x24
+#define GPIO_REG_INT_CLR   0x30
+#define GPIO_REG_INT_TYPE  0x34
+#define GPIO_REG_INT_BOTH_EDGE 0x38
+#define GPIO_REG_INT_LEVEL 0x3C
 
 #endif /* __MICROWATT_SOC_H */