Skip to content

Commit

Permalink
rtl/i2c_master: Add support for clock stretching
Browse files Browse the repository at this point in the history
If the slave wants, it can hold SCL low to prevent the master
from proceeding. This adds support for detecting this and waiting
for SCL to rise. It also has optional timeout (with error report)
support if you want to abort transactions where the slave seems
unresponsive.

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
  • Loading branch information
smunaut committed May 3, 2022
1 parent 9d1cdfa commit f9d1d47
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 10 deletions.
5 changes: 5 additions & 0 deletions doc/i2c_master_wb.md
Expand Up @@ -58,6 +58,7 @@ is simply dropped and put in the response FIFO.
* [31] - rv : Response Valid
* [30] - cr : Command ready
* [9] - eo : Err Out (Time-out occured)
* [8] - ao : Ack Out (Only for WRITE)
* [7:0] - data : Data Byte (Only for READ)
```
Expand All @@ -72,6 +73,10 @@ The `cr` bit indicates in both cases if a new command can be submitted safely. F
mode that bit will match `rv`, and for FIFO mode, this indicates that the command FIFO is not
full.

The `eo` bit indicates that the core waited too long for SCL to rise, meaning some device
held it low for too long. Only valid if both clock stretching support and timeout counter
were enabled in the core build.


### Response peek (Read Only, addr `0x04`)

Expand Down
61 changes: 59 additions & 2 deletions rtl/i2c_master.v
Expand Up @@ -10,10 +10,13 @@
`default_nettype none

module i2c_master #(
parameter integer DW = 3
parameter integer DW = 3, // i2c_clk = sys_clk / (4 * ((1 << DW) + 1))
parameter integer TW = 0, // Timeout (0 = no timeout)
parameter integer CLOCK_STRETCH = 0
)(
// IOs
output reg scl_oe,
input wire scl_i,
output reg sda_oe,
input wire sda_i,

Expand All @@ -25,6 +28,7 @@ module i2c_master #(

output wire [7:0] data_out,
output wire ack_out,
output wire err_out,

output wire ready,

Expand Down Expand Up @@ -64,6 +68,12 @@ module i2c_master #(

reg [8:0] data_reg;

reg [TW:0] to_cnt;
wire to_now;
reg to_latch;

reg scl_ir;


// State Machine
// -------------
Expand Down Expand Up @@ -91,7 +101,8 @@ module i2c_master #(

ST_RISE_SCL:
if (cyc_now)
state_nxt = ST_HIGH_CYCLE;
if (scl_ir | to_now)
state_nxt = ST_HIGH_CYCLE;

ST_HIGH_CYCLE:
if (cyc_now)
Expand All @@ -103,6 +114,7 @@ module i2c_master #(
endcase
end


// Misc control
// ------------

Expand Down Expand Up @@ -152,6 +164,40 @@ module i2c_master #(
data_reg <= cmd[0] ? { 8'b11111111, ack_in } : { data_in, 1'b1 };


// Time Out (for clock stretching)
// --------

generate
if ((CLOCK_STRETCH > 0) && (TW > 0)) begin

// Count cycles
always @(posedge clk)
if (cyc_now) begin
if (state == ST_LOW_CYCLE)
to_cnt <= 0;
else
to_cnt <= to_cnt + 1;
end

// Error latch
always @(posedge clk)
to_latch <= (to_latch | to_now) & ~stb;

end else begin

// Dummy
initial begin
to_cnt <= 0;
to_latch <= 1'b0;
end

end
endgenerate

// Time out signal
assign to_now = to_cnt[TW];


// IO
// --

Expand Down Expand Up @@ -180,12 +226,23 @@ module i2c_master #(
end
end

generate
// Use input only if clock stretching is enabled
if (CLOCK_STRETCH == 1)
always @(posedge clk)
scl_ir <= scl_i;
else
initial
scl_ir <= 1'b1;
endgenerate


// User IF
// -------

assign data_out = data_reg[8:1];
assign ack_out = data_reg[0];
assign err_out = to_latch;

assign ready = (state == ST_IDLE);

Expand Down
25 changes: 17 additions & 8 deletions rtl/i2c_master_wb.v
Expand Up @@ -10,12 +10,15 @@
`default_nettype none

module i2c_master_wb #(
parameter integer DW = 3,
parameter integer DW = 3, // i2c_clk = sys_clk / (4 * ((1 << DW) + 1))
parameter integer TW = 0, // Timeout (0 = no timeout)
parameter integer CLOCK_STRETCH = 0,
parameter integer FIFO_DEPTH = 0,
parameter FIFO_TYPE = "shift"
)(
// IOs
output wire scl_oe,
input wire scl_i,
output wire sda_oe,
input wire sda_i,

Expand All @@ -41,16 +44,20 @@ module i2c_master_wb #(
wire stb;
wire [7:0] data_out;
wire ack_out;
wire err_out;
wire ready;


// Core
// ----

i2c_master #(
.DW(DW)
.DW(DW),
.TW(TW),
.CLOCK_STRETCH(CLOCK_STRETCH)
) core_I (
.scl_oe (scl_oe),
.scl_i (scl_i),
.sda_oe (sda_oe),
.sda_i (sda_i),
.data_in (data_in),
Expand All @@ -59,6 +66,7 @@ module i2c_master_wb #(
.stb (stb),
.data_out (data_out),
.ack_out (ack_out),
.err_out (err_out),
.ready (ready),
.clk (clk),
.rst (rst)
Expand All @@ -84,7 +92,7 @@ module i2c_master_wb #(
if (bus_clr)
wb_rdata <= 32'h00000000;
else
wb_rdata <= { ready, ready, 21'd0, ack_out, data_out };
wb_rdata <= { ready, ready, 20'd0, err_out, ack_out, data_out };

// Data write
assign cmd = wb_wdata[13:12];
Expand All @@ -108,10 +116,10 @@ module i2c_master_wb #(
wire cf_re;
wire cf_empty;

wire [8:0] rf_wdata;
wire [9:0] rf_wdata;
wire rf_we;
wire rf_full;
wire [8:0] rf_rdata;
wire [9:0] rf_rdata;
wire rf_re;
wire rf_empty;

Expand All @@ -130,7 +138,7 @@ module i2c_master_wb #(
if (bus_clr)
wb_rdata <= 32'h00000000;
else
wb_rdata <= { ~rf_empty, ~cf_full, 21'd0, rf_rdata };
wb_rdata <= { ~rf_empty, ~cf_full, 20'd0, rf_rdata };

assign rf_re = wb_ack & ~wb_we & wb_rdata[31] & ~wb_addr[0];

Expand All @@ -157,6 +165,7 @@ module i2c_master_wb #(

// Responses
assign rf_wdata = {
err_out,
ack_out,
data_out
};
Expand Down Expand Up @@ -187,7 +196,7 @@ module i2c_master_wb #(
// Response FIFO
fifo_sync_shift #(
.DEPTH(FIFO_DEPTH),
.WIDTH(9)
.WIDTH(10)
) fifo_rsp_I (
.wr_data (rf_wdata),
.wr_ena (rf_we),
Expand Down Expand Up @@ -221,7 +230,7 @@ module i2c_master_wb #(
// Response FIFO
fifo_sync_ram #(
.DEPTH(FIFO_DEPTH),
.WIDTH(9)
.WIDTH(10)
) fifo_rsp_I (
.wr_data (rf_wdata),
.wr_ena (rf_we),
Expand Down

0 comments on commit f9d1d47

Please sign in to comment.