/*
 * 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
