xmas-snoopy/gateware/rtl/pmu.v

407 lines
7.8 KiB
Verilog

/*
* pmu.v
*
* vim: ts=4 sw=4
*
* Platorm Management Unit
*
* Copyright (C) 2023 Sylvain Munaut <tnt@246tNt.com>
* SPDX-License-Identifier: CERN-OHL-P-2.0
*/
`default_nettype none
module pmu (
// Wishbone
input wire [15:0] wb_addr,
output reg [31:0] wb_rdata,
input wire [31:0] wb_wdata,
input wire wb_we,
input wire wb_cyc,
output reg wb_ack,
// Buttons
input wire [ 1:0] btn,
// Power control
input wire pwr_usb_n,
input wire pwr_chg_n,
output wire pwr_off,
// Clock control
output wire sys_start,
output wire sys_stop,
output reg usb_ena,
// Wake up signal
input wire wakeup,
// Clock / Reset
input wire clk,
input wire rst
);
// Signals
// -------
// Bus interface
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 [1:0] ctrl_btn_ack;
wire [1:0] stat_btn [0:1];
// Buttons
wire btn_clk;
wire [1:0] btn_trigged;
wire [1:0] btn_verylong;
// Reboot
reg boot_wb_now;
reg [1:0] boot_wb_sel;
reg boot_now;
reg [1:0] boot_sel;
// Power/Clock Status/Control
wire iob_pwr_usb_n;
wire iob_pwr_chg_n;
reg stat_pwr_usb;
reg stat_pwr_chg;
wire pwr_off_trig;
reg pwr_off_i = 1'b0;
// Bus interface
// -------------
// Clears
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[10];
always @(posedge clk)
if (bus_clr_wr)
bus_ack_dly <= 0;
else
bus_ack_dly <= bus_ack_dly + 1;
// Reads
always @(posedge clk)
if (bus_clr_rd)
wb_rdata <= 32'h00000000;
else
wb_rdata <= {
27'd0,
usb_ena,
2'b00,
stat_pwr_usb,
stat_pwr_chg,
stat_btn[1],
stat_btn[0]
};
// Writes
always @(posedge clk)
if (bus_clr_wr) begin
bus_we <= 1'b0;
ctrl_sys_shutdown <= 1'b0;
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[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)
if (rst) begin
boot_wb_now <= 1'b0;
boot_wb_sel <= 2'b00;
end else if (bus_we) begin
boot_wb_now <= wb_wdata[18];
boot_wb_sel <= wb_wdata[17:16];
end
// Buttons
// -------
// Oscillator for button logic
(* ROUTE_THROUGH_FABRIC=1 *)
SB_LFOSC btn_osc (
.CLKLFPU (1'b1),
.CLKLFEN (1'b1),
.CLKLF (btn_clk)
);
// 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)
);
// Reboot
// ------
// Command from wb or button force
always @(*)
begin
if (btn_verylong[0]) begin
boot_now = 1'b1;
boot_sel = 2'b01;
end else begin
boot_now = boot_wb_now;
boot_sel = boot_wb_sel;
end
end
// Core
SB_WARMBOOT warmboot (
.BOOT (boot_now),
.S0 (boot_sel[0]),
.S1 (boot_sel[1])
);
// Power / Clock control
// ---------------------
// Synchronizers for status
SB_IO #(
.PIN_TYPE(6'b000000), // PIN_OUTPUT_NONE, PIN_INPUT_REGISTERED
.PULLUP(1'b1),
.IO_STANDARD("SB_LVCMOS")
) stat_iob_I[1:0] (
.PACKAGE_PIN ({ pwr_usb_n, pwr_chg_n}),
.INPUT_CLK (clk),
.D_IN_0 ({iob_pwr_usb_n, iob_pwr_chg_n})
);
always @(posedge clk)
begin
stat_pwr_usb <= ~iob_pwr_usb_n;
stat_pwr_chg <= ~iob_pwr_chg_n;
end
// Enable system clock on 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_verylong[1] | ctrl_sys_shutdown;
always @(posedge pwr_off_trig or posedge rst)
if (rst)
pwr_off_i <= 1'b0;
else
pwr_off_i <= 1'b1;
assign pwr_off = pwr_off_i;
// USB ena
always @(posedge clk)
if (rst)
usb_ena <= 1'b0;
else
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