407 lines
7.8 KiB
Verilog
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
|