From 27f35141de490dc5dfb525e4b50cd47a453dddbd Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sun, 12 Mar 2023 21:19:43 +0100 Subject: [PATCH] gateware/firmware: Add better support for buttons The press events are detected in the gateware and latched and the firmware can just read them as "events". They also trigger sys clock domain wake up if it was sleeping. This is better since the 'sys' clock domain can be shutdown for some non-negligible amount of time and it could lead to missed presses or latency. Signed-off-by: Sylvain Munaut --- firmware/main/pmu.c | 49 ++++++-- firmware/main/pmu.h | 5 + gateware/rtl/pmu.v | 290 +++++++++++++++++++++++++++++++------------- 3 files changed, 252 insertions(+), 92 deletions(-) diff --git a/firmware/main/pmu.c b/firmware/main/pmu.c index 2139803..95e5d01 100644 --- a/firmware/main/pmu.c +++ b/firmware/main/pmu.c @@ -14,17 +14,22 @@ struct wb_pmu { uint32_t stat; } __attribute__((packed,aligned(4))); -#define PMU_CTRL_SYS_SHUTDOWN (1 << 7) -#define PMU_CTRL_SYS_SUSPEND (1 << 6) -#define PMU_CTRL_USB_OFF (1 << 5) -#define PMU_CTRL_USB_ON (1 << 4) -#define PMU_CTRL_BOOT_EXEC (1 << 2) -#define PMU_CTRL_BOOT_SEL(n) (((n) & 3) << 0) +#define PMU_CTRL_BOOT_EXEC (1 << 18) +#define PMU_CTRL_BOOT_SEL(n) (((n) & 3) << 16) +#define PMU_CTRL_SYS_SHUTDOWN (1 << 11) +#define PMU_CTRL_SYS_SUSPEND (1 << 10) +#define PMU_CTRL_USB_OFF (1 << 9) +#define PMU_CTRL_USB_ON (1 << 8) +#define PMU_CTRL_BTN2_ACK (1 << 3) +#define PMU_CTRL_BTN1_ACK (1 << 1) -#define PMU_STAT_USB (1 << 4) -#define PMU_STAT_CHARGING (1 << 3) -#define PMU_STAT_VBUS (1 << 2) -#define PMU_STAT_BTN_MSK (3 << 0) +#define PMU_STAT_USB (1 << 8) +#define PMU_STAT_VBUS (1 << 5) +#define PMU_STAT_CHARGING (1 << 4) +#define PMU_STAT_BTN2_EVT (1 << 3) +#define PMU_STAT_BTN2_LONG (1 << 2) +#define PMU_STAT_BTN1_EVT (1 << 1) +#define PMU_STAT_BTN1_LONG (1 << 0) static volatile struct wb_pmu * const pmu_regs= (void*)(PMU_BASE); @@ -90,5 +95,27 @@ pmu_is_vbus_present(void) uint8_t pmu_get_buttons(void) { - return pmu_regs->stat & PMU_STAT_BTN_MSK; + uint32_t btn; + uint32_t ack = 0; + uint8_t rv = 0; + + /* Read status */ + btn = pmu_regs->stat; + + /* Translate to events */ + if (btn & PMU_STAT_BTN1_EVT) { + rv |= (btn & PMU_STAT_BTN1_LONG) ? BTN_1_LONG : BTN_1_SHORT; + ack |= PMU_CTRL_BTN1_ACK; + } + + if (btn & PMU_STAT_BTN2_EVT) { + rv |= (btn & PMU_STAT_BTN2_LONG) ? BTN_2_LONG : BTN_2_SHORT; + ack |= PMU_CTRL_BTN2_ACK; + } + + /* Ack */ + if (ack) + pmu_regs->ctrl = ack; + + return rv; } diff --git a/firmware/main/pmu.h b/firmware/main/pmu.h index 9ec799c..bf8782d 100644 --- a/firmware/main/pmu.h +++ b/firmware/main/pmu.h @@ -22,4 +22,9 @@ bool pmu_usb_state(void); bool pmu_is_charging(void); bool pmu_is_vbus_present(void); +#define BTN_2_LONG (1 << 3) +#define BTN_2_SHORT (1 << 2) +#define BTN_1_LONG (1 << 1) +#define BTN_1_SHORT (1 << 0) + uint8_t pmu_get_buttons(void); diff --git a/gateware/rtl/pmu.v b/gateware/rtl/pmu.v index 8b7fd01..81530f4 100644 --- a/gateware/rtl/pmu.v +++ b/gateware/rtl/pmu.v @@ -41,33 +41,28 @@ module pmu ( input wire rst ); - integer i; - // Signals // ------- // Bus interface - wire bus_clr_rd; - wire bus_clr_wr; - wire bus_is_suspend; - reg [3:0] bus_ack_dly; - reg bus_we; + wire bus_clr_rd; + wire bus_clr_wr; + wire bus_is_suspend; + reg [3:0] bus_ack_dly; + reg bus_we; // Control signals - reg ctrl_sys_shutdown; - reg ctrl_sys_suspend; - reg ctrl_usb_off; - reg ctrl_usb_on; + reg ctrl_sys_shutdown; + reg ctrl_sys_suspend; + reg ctrl_usb_off; + reg ctrl_usb_on; + reg [1:0] ctrl_btn_ack; + wire [1:0] stat_btn [0:1]; // Buttons wire btn_clk; - wire [1:0] btn_iob; - reg [2:0] btn_sync [0:1]; - reg [4:0] btn_cnt [0:1]; - reg [1:0] btn_val; - reg [1:0] btn_val_wb; - reg [16:0] btn_long_cnt [0:1]; - reg [1:0] btn_long; + wire [1:0] btn_trigged; + wire [1:0] btn_verylong; // Reboot reg boot_wb_now; @@ -90,14 +85,14 @@ module pmu ( // ------------- // Clears - assign bus_clr_rd = wb_ack | ~wb_cyc; - assign bus_clr_wr = wb_ack | ~wb_cyc | ~wb_we; + assign bus_clr_rd = wb_ack | ~wb_cyc; + assign bus_clr_wr = wb_ack | ~wb_cyc | ~wb_we; // Ack always @(posedge clk) wb_ack <= wb_cyc & (~bus_is_suspend | bus_ack_dly[3]) & ~wb_ack; - assign bus_is_suspend = wb_we & wb_wdata[6]; + assign bus_is_suspend = wb_we & wb_wdata[10]; always @(posedge clk) if (bus_clr_wr) @@ -113,10 +108,11 @@ module pmu ( wb_rdata <= { 27'd0, usb_ena, + 2'b00, stat_pwr_usb, stat_pwr_chg, - btn_val_wb[1], - btn_val_wb[0] + stat_btn[1], + stat_btn[0] }; // Writes @@ -127,12 +123,16 @@ module pmu ( ctrl_sys_suspend <= 1'b0; ctrl_usb_off <= 1'b0; ctrl_usb_on <= 1'b0; + ctrl_btn_ack[1] <= 1'b0; + ctrl_btn_ack[0] <= 1'b0; end else begin bus_we <= 1'b1; - ctrl_sys_shutdown <= wb_wdata[7]; - ctrl_sys_suspend <= wb_wdata[6]; - ctrl_usb_off <= wb_wdata[5]; - ctrl_usb_on <= wb_wdata[4]; + ctrl_sys_shutdown <= wb_wdata[11]; + ctrl_sys_suspend <= wb_wdata[10]; + ctrl_usb_off <= wb_wdata[ 9]; + ctrl_usb_on <= wb_wdata[ 8]; + ctrl_btn_ack[1] <= wb_wdata[ 3]; + ctrl_btn_ack[0] <= wb_wdata[ 1]; end always @(posedge clk or posedge rst) @@ -140,8 +140,8 @@ module pmu ( boot_wb_now <= 1'b0; boot_wb_sel <= 2'b00; end else if (bus_we) begin - boot_wb_now <= wb_wdata[2]; - boot_wb_sel <= wb_wdata[1:0]; + boot_wb_now <= wb_wdata[18]; + boot_wb_sel <= wb_wdata[17:16]; end @@ -156,57 +156,18 @@ module pmu ( .CLKLF (btn_clk) ); - // IOBs - SB_IO #( - .PIN_TYPE(6'b000001), // PIN_OUTPUT_NONE, PIN_INPUT - .PULLUP(1'b1), - .IO_STANDARD("SB_LVCMOS") - ) btn_iob_I[1:0] ( - .PACKAGE_PIN (btn), - .D_IN_0 (btn_iob) + // Submodule + pmu_button btn_I[1:0] ( + .si_stat ({stat_btn[1], stat_btn[0]}), + .si_ack (ctrl_btn_ack), + .si_clk (clk), + .si_rst (rst), + .btn_pad (btn), + .btn_trigged (btn_trigged), + .btn_verylong (btn_verylong), + .btn_clk (btn_clk) ); - // Synchronizer - initial - for (i=0; i<2; i=i+1) - btn_sync[i] = 1; - - always @(posedge btn_clk) - for (i=0; i<2; i=i+1) - btn_sync[i] <= { btn_sync[i][1:0], btn_iob[i] }; - - // Small counter for debounce - initial - for (i=0; i<2; i=i+1) - btn_cnt[i] = 0; - - always @(posedge btn_clk) - for (i=0; i<2; i=i+1) - btn_cnt[i] <= btn_sync[i][2] ? (btn_cnt[i] + {4'h0, btn_cnt[i][4]}) : 5'h10; - - always @(*) - for (i=0; i<2; i=i+1) - btn_val[i] = btn_cnt[i][4]; - - // Synchronize value in sys clock domain - always @(posedge clk) - for (i=0; i<2; i=i+1) - btn_val_wb[i] <= btn_val[i]; - - // Long counter to detect very long presses - // ( ~6.5 sec ) - initial - for (i=0; i<2; i=i+1) - btn_long_cnt[i] = 0; - - always @(posedge btn_clk) - for (i=0; i<2; i=i+1) - btn_long_cnt[i] <= (btn_long_cnt[i] + btn_val[i]) & {17{btn_val[i]}}; - - always @(*) - for (i=0; i<2; i=i+1) - btn_long[i] = btn_long_cnt[i][16]; - // Reboot // ------ @@ -214,7 +175,7 @@ module pmu ( // Command from wb or button force always @(*) begin - if (btn_long[0]) begin + if (btn_verylong[0]) begin boot_now = 1'b1; boot_sel = 2'b01; end else begin @@ -252,13 +213,13 @@ module pmu ( end // Enable system clock on wakeup - assign sys_start = wakeup; + assign sys_start = wakeup | |btn_trigged; // Disable system clock on requests assign sys_stop = ctrl_sys_suspend; // Power-off on long press and on requests - assign pwr_off_trig = btn_long[1] | ctrl_sys_shutdown; + assign pwr_off_trig = btn_verylong[1] | ctrl_sys_shutdown; always @(posedge pwr_off_trig or posedge rst) if (rst) @@ -276,3 +237,170 @@ module pmu ( usb_ena <= (usb_ena | ctrl_usb_on) & ~ctrl_usb_off; endmodule // pmu + + +module pmu_button ( + // System interface + output wire [1:0] si_stat, + input wire si_ack, + input wire si_clk, + input wire si_rst, + + // Button side + input wire btn_pad, + output wire btn_trigged, + output wire btn_verylong, + input wire btn_clk +); + + // Signals + // ------- + + // Basic state + wire btn_iob; + reg [2:0] btn_sync = 3'b111; + + reg [4:0] btn_cnt = 5'd0; + wire btn_val; + + reg [16:0] btn_long_cnt = 16'd0; + wire btn_long; + + // System Interface + // System side + reg sis_ack_toggle = 1'b0; + reg [1:0] sis_trig_sync; + reg [1:0] sis_state; + + localparam [1:0] + STS_IDLE = 2'b00, + STS_TRIG_SHORT = 2'b10, + STS_TRIG_LONG = 2'b11, + STS_WAIT_REL = 2'b01; + + // Button side + reg [2:0] sib_ack_sync; + wire sib_ack; + reg [3:0] sib_state; + + localparam [2:0] + STB_IDLE = 3'b000, + STB_ARMED = 3'b101, + STB_TRIG_SHORT = 3'b110, + STB_TRIG_LONG = 3'b111, + STB_WAIT_REL = 3'b100; + + + // Basic state + // ----------- + + // IOBs + SB_IO #( + .PIN_TYPE(6'b000001), // PIN_OUTPUT_NONE, PIN_INPUT + .PULLUP(1'b1), + .IO_STANDARD("SB_LVCMOS") + ) btn_iob_I ( + .PACKAGE_PIN (btn_pad), + .D_IN_0 (btn_iob) + ); + + // Synchronizer + always @(posedge btn_clk) + btn_sync <= { btn_sync[1:0], btn_iob }; + + // Small counter for debounce + always @(posedge btn_clk) + btn_cnt <= btn_sync[2] ? (btn_cnt + {4'h0, btn_cnt[4]}) : 5'h10; + + assign btn_val = btn_cnt[4]; + + // Long counter to detect long and very long presses + // ( ~6.5 sec ) + always @(posedge btn_clk) + btn_long_cnt <= (btn_long_cnt + btn_val) & {17{btn_val}}; + + assign btn_long = btn_long_cnt[13]; + assign btn_verylong = btn_long_cnt[16]; + + + // System Interface [Sys Domain] + // ---------------- + + // Ack toggle + always @(posedge si_clk) + sis_ack_toggle <= sis_ack_toggle ^ (si_ack & sis_state[1]); + + // Synchronizer for trig signal + always @(posedge si_clk) + sis_trig_sync <= { sis_trig_sync[0], sib_state[3] }; + + // State + always @(posedge si_clk) + begin + case (sis_state) + STS_IDLE: + if (sis_trig_sync[1]) + sis_state <= sib_state[0] ? STS_TRIG_LONG : STS_TRIG_SHORT; + STS_TRIG_SHORT: + if (si_ack) + sis_state <= STS_WAIT_REL; + STS_TRIG_LONG: + if (si_ack) + sis_state <= STS_WAIT_REL; + STS_WAIT_REL: + if (~sis_trig_sync[1]) + sis_state <= STS_IDLE; + endcase + + if (si_rst) + sis_state <= STS_IDLE; + end + + assign si_stat = { sis_state[1], sis_state[1] & sis_state[0] }; + + + // System Interface [Button Domain] + // ---------------- + + // Ack sync / detect + always @(posedge btn_clk) + sib_ack_sync <= { ^sib_ack_sync[1:0], sib_ack_sync[0], sis_ack_toggle }; + + assign sib_ack = sib_ack_sync[2]; + + // State + always @(posedge btn_clk) + begin + // 3rd bit is always delayed version of trigger + sib_state[3] <= sib_state[1]; + + // Next state + case (sib_state[2:0]) + STB_IDLE: + if (btn_val) + sib_state[2:0] <= STB_ARMED; + + STB_ARMED: + if (btn_long) + sib_state[2:0] <= STB_TRIG_LONG; + else if (!btn_val) + sib_state[2:0] <= STB_TRIG_SHORT; + + STB_TRIG_SHORT: + if (sib_ack) + sib_state[2:0] <= STB_WAIT_REL; + + STB_TRIG_LONG: + if (sib_ack) + sib_state[2:0] <= STB_WAIT_REL; + + STB_WAIT_REL: + if (!btn_val) + sib_state[2:0] <= STB_IDLE; + endcase + end + + // External trig indicator + assign btn_trigged = sib_state[3]; + +endmodule // pmu_button