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

	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;

	// Control signals
	reg       ctrl_sys_shutdown;
	reg       ctrl_sys_suspend;
	reg       ctrl_usb_off;
	reg       ctrl_usb_on;

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

	// 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[6];

	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,
				stat_pwr_usb,
				stat_pwr_chg,
				btn_val_wb[1],
				btn_val_wb[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;
		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];
		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[2];
			boot_wb_sel <= wb_wdata[1:0];
		end


	// Buttons
	// -------

	// Oscillator for button logic
	(* ROUTE_THROUGH_FABRIC=1 *)
	SB_LFOSC btn_osc (
		.CLKLFPU (1'b1),
		.CLKLFEN (1'b1),
		.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)
	);

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

	// Command from wb or button force
	always @(*)
	begin
		if (btn_long[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;

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

	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
