/* Copyright 2010, Unpublished Work of Technologic Systems
 * All Rights Reserved.
 *
 * THIS WORK IS AN UNPUBLISHED WORK AND CONTAINS CONFIDENTIAL,
 * PROPRIETARY AND TRADE SECRET INFORMATION OF TECHNOLOGIC SYSTEMS.
 * ACCESS TO THIS WORK IS RESTRICTED TO (I) TECHNOLOGIC SYSTEMS 
 * EMPLOYEES WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE
 * OF THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN TECHNOLOGIC
 * SYSTEMS WHO HAVE ENTERED INTO APPROPRIATE LICENSE AGREEMENTS.  NO
 * PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED, COPIED, 
 * DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED, CONDENSED, 
 * EXPANDED, COLLECTED, COMPILED, LINKED, RECAST, TRANSFORMED, ADAPTED
 * IN ANY FORM OR BY ANY MEANS, MANUAL, MECHANICAL, CHEMICAL, 
 * ELECTRICAL, ELECTRONIC, OPTICAL, BIOLOGICAL, OR OTHERWISE WITHOUT
 * THE PRIOR WRITTEN PERMISSION AND CONSENT OF TECHNOLOGIC SYSTEMS.
 * ANY USE OR EXPLOITATION OF THIS WORK WITHOUT THE PRIOR WRITTEN
 * CONSENT OF TECHNOLOGIC SYSTEMS COULD SUBJECT THE PERPETRATOR TO
 * CRIMINAL AND CIVIL LIABILITY.
 */

/* This is a wishbone core to conveniently access the MCP3428.  The
 * MCP3428 is an affordable 16-bit ADC that is used on several different
 * TS-8xxx TS-SOCKET base boards.  This core assumes the standard circuit
 * which allows 2 differential channels and 4 single-ended channels. The
 * single-ended channels are chosen using analog muxes controlled by the 
 * AN_SEL line.  Since different base boards use a different pin for 
 * AN_SEL, a register is also provided to select the correct line.
 * Channels 1 and 2 are differential channels with a range of -2.048V to 
 * +2.048V.  Channels 3-6 are 0 to 10.24V.

 * The channel mask register controls which channels are enabled.  Bits
 * 0-5 enable channels 1-6 respectively.  If a given channel is not enabled,
 * (enable bit == 0) it will not be sampled and its conversion value 
 * register will contain an obsolete and meaningless value.  The more
 * channels that are enabled, the lower the sampling speed on each channel.

 * Register map: (All 16 bit registers)
 * 0x0: Config (RW)
 * 0x2: Channel mask (RW)
 * 0x4: Channel 1 most recent conversion value (RO)
 * 0x6: Channel 2 most recent conversion value (RO)
 * 0x8: Channel 3 most recent conversion value (RO)
 * 0xa: Channel 4 most recent conversion value (RO)
 * 0xc: Channel 5 most recent conversion value (RO)
 * 0xe: Channel 6 most recent conversion value (RO)

 * Config register bits:
 * Bits 15:8 Core ID register: reads 0xad.
 * Bits 7:6 reserved
 * Bits 5:4 Analog select select:
 *   0 = Do not use an AN_SEL
 *   1 = Use CN1 pin 77 for AN_SEL (TS-8100)
 *   2 = Use CN1 pin 74 for AN_SEL (TS-8390)
 *   3 = reserved
 * Bits 3:2 Speed: 
 *   0 = 240Hz, 12 bit resolution
 *   1 = 60Hz, 14 bit resolution
 *   2 = 15Hz, 16 bit resolution
 *   3 = reserved
 * Bits 1:0 Programmable Gain Amplifier:
 *   0 = No gain
 *   1 = 2x gain
 *   2 = 4x gain
 *   3 = 8x gain
 */

/* External to this module, scl and sda are expected to be connected thusly:
 * assign scl_pad = scl ? 1'bz : 1'b0;
 * assign sda_pad = sda ? 1'bz : 1'b0;
 */

module wb_mcp3428(
  wb_clk_i,
  wb_rst_i,

  wb_cyc_i,
  wb_stb_i,
  wb_we_i,
  wb_adr_i,
  wb_dat_i,
  wb_sel_i,
  wb_dat_o,
  wb_ack_o,

  scl_o,
  sda_o,
  sda_i,
  an_sel_o,
  an_sel_sel_o,
  active_o
);

/* No scl_i line is needed as this device does not use clock stretching. */

input wb_clk_i, wb_rst_i, wb_cyc_i, wb_stb_i, wb_we_i;
input [31:0] wb_adr_i;
input [15:0] wb_dat_i;
input [1:0] wb_sel_i;
output [15:0] wb_dat_o;
output wb_ack_o;

output scl_o;
output sda_o;
input sda_i;
output an_sel_o;
output [1:0] an_sel_sel_o;
output active_o;

localparam mcp_adr = 8'b11010000;

/* Registers controlled by wishbone interface: */
reg [5:0] chan_mask;
reg [6:0] adc_config;
reg active;
assign active_o = active;

/* Registers controlled by master state machine: */
reg an_sel;
reg [5:0] chan;
reg [1:0] i2c_action;
/* I2C actions:
 * 0 = idle
 * 1 = send config
 * 2 = read config until sample is ready
 * 3 = read sample
 */
reg [3:0] state;
reg [7:0] data_out;
reg [15:0] chan1;
reg [15:0] chan2;
reg [15:0] chan3;
reg [15:0] chan4;
reg [15:0] chan5;
reg [15:0] chan6;
reg [7:0] timer;
reg tick;

/* Registers controlled by I2C state machine: */
reg scl, sda;
reg [3:0] i2c_bits;
reg [3:0] i2c_state;
reg [3:0] next_i2c;
reg [8:0] i2c_dat;
reg [15:0] data_in;
reg i2c_busy; /* Once busy is asserted, action should be set to 0 by master. */
reg fault;  /* Asserted to indicate there has been an unexpected NACK */

assign scl_o = scl;
assign sda_o = sda;
assign an_sel_o = an_sel;
assign an_sel_sel_o = adc_config[5:4];

always @(posedge wb_clk_i or posedge wb_rst_i) begin
  if (wb_rst_i) begin
    an_sel <= 1'b0;
    chan <= 6'b000000;
    i2c_action <= 2'b00;
    state <= 4'd0;
    data_out <= 8'h00;
    chan1 <= 16'd0;
    chan2 <= 16'd0;
    chan3 <= 16'd0;
    chan4 <= 16'd0;
    chan5 <= 16'd0;
    chan6 <= 16'd0;
    timer <= 8'h00;
    tick <= 1'b0;
    scl <= 1'b1;
    sda <= 1'b1;
    i2c_bits <= 4'd0;
    i2c_state <= 4'd0;
    next_i2c <= 4'd0;
    i2c_dat <= 9'd0;
    data_in <= 16'h0000;
    i2c_busy <= 1'b0;
    fault <= 1'b0;
  end else begin
    tick <= 1'b0;
    timer <= timer + 1'b1;
    if (timer == 8'h0) tick <= 1'b1;

    case (state)
      4'd0: begin // selecting a channel
        chan <= {chan[4:0], 1'b0};
        if (chan == 6'd0) chan <= 6'b000001;
        state <= 4'd1;
      end
      4'd1: begin
        if (chan & chan_mask == 0) state <= 4'd0;
        else state <= 4'd2;
      end
      4'd2: begin // send config
        if (chan[3] || chan[5]) an_sel <= 1'b1;
        else an_sel <= 1'b0;
        data_out[7] <= 1'b1;
        data_out[4:0] <= {1'b0, adc_config[3:0]};
        casex(chan)
          6'bxxxxx1: data_out[6:5] <= 2'b00;
          6'bxxxx1x: data_out[6:5] <= 2'b01;
          6'bxxx1xx: data_out[6:5] <= 2'b10;
          6'bxx1xxx: data_out[6:5] <= 2'b10;
          6'bx1xxxx: data_out[6:5] <= 2'b11;
          6'b1xxxxx: data_out[6:5] <= 2'b11;
        endcase
        i2c_action <= 2'b01;
        if (i2c_busy) begin
          i2c_action <= 2'b00;
          state <= 4'd3;
        end
      end
      4'd3: if (!i2c_busy) state <= 4'd4;
      4'd4: begin // wait for conversion
        i2c_action <= 2'b10;
        if (i2c_busy) begin
          i2c_action <= 2'b00;
          state <= 4'd5;
        end
      end
      4'd5: if (!i2c_busy) state <= 4'd6;
      4'd6: begin // read sample
        i2c_action <= 2'b11;
        if (i2c_busy) begin
          i2c_action <= 2'b00;
          state <= 4'd7;
        end
      end
      4'd7: if (!i2c_busy) state <= 4'd8;
      4'd8: begin // update channel data with new sample
        casex(chan)
          6'bxxxxx1: chan1 <= data_in;
          6'bxxxx1x: chan2 <= data_in;
          6'bxxx1xx: chan3 <= data_in;
          6'bxx1xxx: chan4 <= data_in;
          6'bx1xxxx: chan5 <= data_in;
          6'b1xxxxx: chan6 <= data_in;
        endcase
        state <= 4'd0;
      end
    endcase

    if (tick) case (i2c_state)
      4'd0: begin // idle
        i2c_busy <= 1'b0;
        sda <= 1'b1;
        scl <= 1'b1;
        i2c_bits <= 3'd0;
        if (i2c_action != 2'b00) i2c_state <= 4'd1;
      end
      4'd1: begin
      /* Initialization: MCP3428 seems to require a clock toggle before
       * a start condition is presented.
       */
        scl <= 1'b0;
        i2c_state <= 4'd2;
      end
      4'd2: begin
        scl <= 1'b1;
        i2c_state <= 4'd3;
      end
      4'd3: begin
        sda <= 1'b0;
        i2c_state <= 4'd4;
      end
      4'd4: begin // send address
        i2c_dat <= {mcp_adr, 1'b1};
        if (i2c_action[1]) i2c_dat[1] <= 1'b1;
        i2c_bits <= 4'd9;
        if (i2c_action[1]) next_i2c <= 4'd6;
        else next_i2c <= 4'd5;
        i2c_state <= 4'd12;
      end
      4'd5: begin // send config
        i2c_dat <= {data_out, 1'b1};
        next_i2c <= 4'd11;
        i2c_bits <= 4'd9;
        i2c_state <= 4'd12;
      end
      4'd6: begin // read MSB
        i2c_dat <= 9'b111111110;
        i2c_bits <= 4'd9;
        i2c_state <= 4'd12;
        next_i2c <= 4'd7;
      end
      4'd7: begin // read LSB
        i2c_dat <= 9'b111111110;
        i2c_bits <= 4'd9;
        i2c_state <= 4'd12;
        next_i2c <= 4'd8;
        data_in[15:8] <= i2c_dat[8:1];
      end
      4'd8: begin // read config
        i2c_dat <= 9'b111111110;
        i2c_bits <= 4'd9;
        i2c_state <= 4'd12;
        next_i2c <= 4'd9;
        data_in[7:0] <= i2c_dat[8:1];
        if (i2c_action == 2'b11) i2c_state <= 4'd11;
      end
      4'd9: begin // check status flag
        if (i2c_dat[8]) i2c_state <= 4'd8;
        else i2c_state <= 4'd11;
      end
      4'd11: begin // finished with transaction.
      // there is no reason to bother sending a stop condition.
        i2c_busy <= 1'b1;
        i2c_state <= 4'd0;
      end
      4'd12: begin // drop clock
        scl <= 1'b0;
        i2c_state <= 4'd13;
      end
      4'd13: begin // set data
        sda <= i2c_dat[8];
        i2c_bits <= i2c_bits - 1'b1;
        i2c_state <= 4'd14;
      end
      4'd14: begin // read data, raise clock
        // I think this is valid -- SDA should be set up while clock was low.
        i2c_dat <= {i2c_dat[7:0], sda_i};
        scl <= 1'b1;
        if (i2c_bits == 4'd0) i2c_state <= 4'd15;
        else i2c_state <= 4'd12;
      end
      4'd15: begin // check ACK
        if (i2c_dat[0]) begin // unexpected NACK
          i2c_state <= 4'd0;
          fault <= 1'b1;
        end else i2c_state <= next_i2c;
      end
    endcase
  end
end

/* Wishbone interface: */
always @(posedge wb_clk_i or posedge wb_rst_i) begin
  if (wb_rst_i) begin
    adc_config <= 7'd0;
    chan_mask <= 6'd0;
    active <= 1'b0;
  end else begin
    active <= (chan_mask != 6'd0);
    if (wb_cyc_i && wb_stb_i && wb_we_i && wb_sel_i[0]) begin
      if (wb_adr_i[9:2] == 8'h00) case (wb_adr_i[1:0])
        2'b00: adc_config <= wb_dat_i[6:0];
        2'b10: chan_mask <= wb_dat_i[5:0];
      endcase
    end
  end
end

reg [15:0] wb_dat;
assign wb_dat_o = wb_dat;
reg wb_ack;
assign wb_ack_o = wb_ack;
always @(*) begin
  wb_ack = wb_cyc_i && wb_stb_i;
  wb_dat = 16'hxxxx;
  case (wb_adr_i[3:0])
    4'h0: wb_dat = {8'had, fault, adc_config[6:0]};
    4'h2: wb_dat = {{10{1'bx}}, chan_mask[5:0]};
    4'h4: wb_dat = chan1;
    4'h6: wb_dat = chan2;
    4'h8: wb_dat = chan3;
    4'ha: wb_dat = chan4;
    4'hc: wb_dat = chan5;
    4'he: wb_dat = chan6;
  endcase
end

endmodule
