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 <tnt@246tNt.com>
This commit is contained in:
Sylvain Munaut 2023-03-12 21:19:43 +01:00
parent 0aa54bc297
commit 27f35141de
3 changed files with 252 additions and 92 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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