//
// Copyright 2015 Ettus Research LLC
//

module axi_pmu
#(
  parameter DEPTH = 64
)
(
  // sys connect
  input         s_axi_aclk,
  input         s_axi_areset,

  // spi slave port
  input         ss,
  input         mosi,
  input         sck,
  output        miso,

  // axi4 lite slave port
  input [31:0]  s_axi_awaddr,
  input         s_axi_awvalid,
  output        s_axi_awready,

  input [31:0]  s_axi_wdata,
  input [3:0]   s_axi_wstrb,
  input         s_axi_wvalid,
  output        s_axi_wready,

  output [1:0]  s_axi_bresp,
  output        s_axi_bvalid,
  input         s_axi_bready,

  input [31:0]  s_axi_araddr,
  input         s_axi_arvalid,
  output        s_axi_arready,

  output [31:0] s_axi_rdata,
  output [1:0]  s_axi_rresp,
  output        s_axi_rvalid,
  input         s_axi_rready,

  output        s_axi_irq
);

  wire             spi_stb;
  wire [DEPTH-1:0] spi_rx;
  wire [DEPTH-1:0] spi_tx;

  spi_slave inst_spi_slave0
  (
    .clk(s_axi_aclk),
    .rst(s_axi_areset),

    .ss(ss),
    .mosi(mosi),
    .miso(miso),
    .sck(sck),

    .parallel_stb(spi_stb),
    .parallel_din(spi_tx),
    .parallel_dout(spi_rx)
  );

  wire [7:0] rx_type = spi_rx[7:0];

  reg [DEPTH-1:0] spi_rx_r0, spi_rx_r1, spi_rx_r2;
  always @ (posedge s_axi_aclk)
    if (s_axi_areset) begin
      spi_rx_r0 <= 64'h0000_0000_0000_0000;
      spi_rx_r1 <= 64'h0000_0000_0000_0000;
      spi_rx_r2 <= 64'h0000_0000_0000_0000;
    end else begin
      spi_rx_r0 <= spi_stb && (rx_type == 0) ? spi_rx : spi_rx_r0;
      spi_rx_r1 <= spi_stb && (rx_type == 1) ? spi_rx : spi_rx_r1;
      spi_rx_r2 <= spi_stb && (rx_type == 2) ? spi_rx : spi_rx_r2;
    end

  localparam IDLE              = 3'b001;
  localparam READ_IN_PROGRESS  = 3'b010;
  localparam WRITE_IN_PROGRESS = 3'b100;

  reg [2:0] state;
  reg [7:0] addr;

  always @ (posedge s_axi_aclk) begin
    if (s_axi_areset) begin
      state <= IDLE;
    end
    else case (state)

    IDLE: begin
      if (s_axi_arvalid) begin
        state <= READ_IN_PROGRESS;
        addr  <= s_axi_araddr[7:0];
      end
      else if (s_axi_awvalid) begin
        state <= WRITE_IN_PROGRESS;
        addr  <= s_axi_awaddr[7:0];
      end
    end

    READ_IN_PROGRESS: begin
      if (s_axi_rready)
        state <= IDLE;
    end

    WRITE_IN_PROGRESS: begin
      if (s_axi_bready)
        state <= IDLE;
    end

    default: begin
      state <= IDLE;
    end

    endcase
  end

  // write mux
  reg write_shutdown;
  reg write_irq_mask;

  always @(*) begin
    write_shutdown = 1'b0;
    write_irq_mask = 1'b0;

    if (state == WRITE_IN_PROGRESS)
      case (addr)
        8'h00: write_shutdown = 1'b1;
        8'h04: write_irq_mask = 1'b1;
      endcase
  end

  reg [31:0] shutdown = 32'h0000_0000;
  always @ (posedge s_axi_aclk) begin
    if (s_axi_areset)
      shutdown <= 32'h0000_0000;
    else if (write_shutdown)
      shutdown <= s_axi_wdata;
  end

  wire [31:0] spi_tx_tdata;
  wire        spi_tx_tvalid;
  wire [5:0] spi_tx_occupied;
  wire [5:0] spi_tx_space;

  wire [31:0] tmux = write_shutdown ? {s_axi_wdata[23:0], 8'h00}
                                    : {s_axi_wdata[7:0], s_axi_wdata[15:8], addr[7:0], 8'h01};

  wire is_spi_cmd = (addr[7:0] == 8'h00) || (addr[7:0] > 8'h04);

  axi_fifo_short #(.WIDTH(32)) axi_fifo_short_inst
  (
    .clk(s_axi_aclk),
    .reset(s_axi_areset),
    .clear(1'b0),
    .i_tdata(tmux),
    .i_tvalid(state == WRITE_IN_PROGRESS && is_spi_cmd),
    .i_tready(),
    .o_tdata(spi_tx_tdata),
    .o_tvalid(spi_tx_tvalid),
    .o_tready(spi_stb),
    .occupied(spi_tx_occupied),
    .space(spi_tx_space)
  );

  reg [63:0] spi_tx_reg;

  always @ (posedge s_axi_aclk)
    if(s_axi_areset)
      spi_tx_reg <= 64'h0000_0000_0000_0000;
    else if (spi_stb)
      spi_tx_reg <= {spi_tx_tvalid, 31'h00, spi_tx_tdata};

  assign spi_tx = spi_tx_reg;

  /* battery stuff */
  wire [15:0] battery_voltage      = {spi_rx_r0[55:48], spi_rx_r0[63:56]};
  wire [1:0]  battery_temp_alert   =  spi_rx_r0[47:46];
  wire        battery_online       =  spi_rx_r0[45];
  wire [2:0]  battery_health       =  spi_rx_r0[44:42];
  wire [1:0]  battery_status       =  spi_rx_r0[41:40];

  /* charger stuff */
  /* unused [39:38] */
  wire [1:0]  charger_health       = spi_rx_r0[37:36];
  wire        charger_online       = spi_rx_r0[35];
  /* unused bit 34 */
  wire [1:0]  charger_charge_type  = spi_rx_r0[33:32];

  /* gauge stuff */
  wire [7:0]  gauge_status         =  spi_rx_r1[63:56];
  wire [15:0] voltage              = {spi_rx_r1[47:40], spi_rx_r1[55:48]};
  wire [15:0] temp                 = {spi_rx_r1[31:24], spi_rx_r1[39:32]};
  wire [15:0] charge_acc           = {spi_rx_r1[15:8] , spi_rx_r1[23:16]};

  /* charge last full */
  wire [15:0] charge_last_full     = {spi_rx_r2[15:8], spi_rx_r2[23:16]};

  /* settings flags */
  wire [7:0] settings = spi_rx_r2[31:24];

  reg [7:0] irq_enable;
  always @ (posedge s_axi_aclk) begin
    if (s_axi_areset)
      irq_enable <= 8'h00;
    else if (write_irq_mask)
      irq_enable <= s_axi_wdata[15:8];
  end

  wire [7:0] irq_status = gauge_status;
  assign s_axi_irq = |(irq_status & irq_enable);

  wire [3:0] version_maj = spi_rx_r0[15:12];
  wire [3:0] version_min = spi_rx_r0[11:8];

  reg [31:0] rdata;
  // read mux
  always @(*) begin
    rdata = 32'hdead_beef;

    if (state == READ_IN_PROGRESS)
      case (addr)
        8'h00: rdata = shutdown;
        8'h04: rdata = {16'h0000, irq_enable, version_maj, version_min};
        8'h08: rdata = {8'h0, battery_voltage, battery_temp_alert, battery_online, battery_health, battery_status};
        8'h0c: rdata = {27'd0, charger_charge_type, charger_online, charger_health};
        8'h10: rdata = {temp, charge_acc};
        8'h14: rdata = {8'h00, gauge_status, voltage};
        8'h18: rdata = {16'h0000, charge_last_full};
        8'h1c: rdata = {24'd0, settings};
      endcase
  end

  assign s_axi_arready = (state == IDLE);
  assign s_axi_rvalid  = (state == READ_IN_PROGRESS);
  assign s_axi_rresp   = 2'b00;

  assign s_axi_rdata   = rdata;

  assign s_axi_awready = (state == IDLE);
  assign s_axi_wready  = (state == WRITE_IN_PROGRESS);
  assign s_axi_bresp   = 2'b00;
  assign s_axi_bvalid  = (state == WRITE_IN_PROGRESS);

endmodule
