Browse Source

rtc: issue dummy clock cycles if SDA appears stuck

In case the I2C state machine gets out of sync, the device might end
up pulling SDA low. Issue dummy clocks until it either releases SDA,
or we give up...
H. Peter Anvin 3 years ago
parent
commit
4970fb6ef6
10 changed files with 4625 additions and 4604 deletions
  1. 24 14
      fpga/i2c.sv
  2. 1 1
      fpga/iodevs.vh
  3. BIN
      fpga/output_files/max80.jbc
  4. BIN
      fpga/output_files/max80.jic
  5. BIN
      fpga/output_files/max80.pof
  6. BIN
      fpga/output_files/max80.sof
  7. 4575 4575
      fw/boot.mif
  8. 7 1
      fw/ioregs.h
  9. 17 12
      fw/rtc.c
  10. 1 1
      iodevs.conf

+ 24 - 14
fpga/i2c.sv

@@ -5,17 +5,24 @@
 // 0 - write data/command
 //     [15:8] - data bits (must be 1 for read)
 //     [7]    - ACK bit (must be 1 for write)
-//     [2]    - follow with P condition
-//     [1]    - follow with Sr condition
+//     [2:1]  - 00 = normal byte (S if needed, no following Sr/P)
+//              01 = follow with Sr (S if needed)
+//              10 = follow with P (S if needed)
+//              11 = dummy clocks (no S, for synchronization)
 //
 // 1 - read data/status
 //     [15:8] - data bits
 //     [7]    - ack bit
-//     [1]    - bus idle (after P condition)
+//     [6]    - SDA
+//     [5]    - SCL
+//     [4]    - "started" (no S will be issued before next byte)
+//     [2:1]  - bits [2:1] from last command
 //     [0]    - busy
 //
 // 2 - baud rate divisor (f = clk/(4*(divisor+1)))
 //
+// 3 - bit [0] - send dummy clocks on SCL when idle
+//
 // This unit handles S(r) and P conditions by considering two classes of
 // symbols: "normal", when SCL is asserted at the end of the
 
@@ -46,7 +53,6 @@ module i2c (
    reg			  busy;		// Data received, running
    reg			  end_s, end_p; // Trailing S(r) or P
    reg			  started;	// S sent, but not P
-   reg			  is_sr;	// Sending an S(r)
 
    reg [1:0]		  outsymb; // Output symbol [abnormal, data]
 
@@ -68,6 +74,8 @@ module i2c (
 	  sda_out <= 1'b1;
 	  phase   <= 2'b00;
 	  do_read <= 1'b0;
+	  end_s   <= 1'bx;
+	  end_p   <= 1'bx;
 	  started <= 1'b0;
        end
      else
@@ -128,9 +136,9 @@ module i2c (
 				do_read <= 1'b1;
 				if (bitctr[3])
 				  begin
-				     bitctr  <= end_p ? 4'd12 :
-						end_s ? 4'd14 : 4'd0;
-				     busy    <= end_p;
+				     bitctr  <= end_s ? 4'd14 :
+						end_p ? 4'd12 : 4'b0;
+				     busy    <= end_p & ~end_s;
 				     // If we are to be followed by
 				     // an S(r) condition, we are not
 				     // really "started".
@@ -152,13 +160,13 @@ module i2c (
 
 			   // Start condition
 			   4'd14: begin
-			      started <= 1'b1;
+			      started <= ~(end_s & end_p);
 			      outsymb <= 2'b11; // A1
 			   end
 
 			   4'd15: begin
-			      started <= 1'b1;
-			      outsymb <= 2'b00; // N0
+			      // N0, unless dummy in which case N1
+			      outsymb <= { 1'b0, ~started };
 			   end
 
 			   default: begin
@@ -202,14 +210,16 @@ module i2c (
    //
    always_comb
      case (addr)
-       2'b00: rdata = { 16'b0, wreg, 4'b0, end_p, end_s, busy | do_read };
-       2'b01: rdata = { 16'b0, rreg, 4'b0, end_p, end_s, busy | do_read };
+       2'b00: rdata = { 16'b0, wreg, i2c_sda, i2c_scl, started, 1'b0,
+			end_p, end_s, busy | do_read };
+       2'b01: rdata = { 16'b0, rreg, i2c_sda, i2c_scl, started, 1'b0,
+			end_p, end_s, busy | do_read };
        2'b10: rdata = { 24'b0, divisor };
-       default: rdata = 32'b0;
+       default: rdata = 32'bx;
      endcase // casez (addr)
 
    //
-   // IRQ (edge) when unit idle
+   // IRQ (level) when unit idle
    //
    assign irq = ~(busy | do_read);
 

+ 1 - 1
fpga/iodevs.vh

@@ -74,7 +74,7 @@
 	assign sys_irq[ 8] = iodev_irq_esp[0];
 	assign sys_irq[ 9] = iodev_irq_abc[0];
 
-	localparam [31:0] irq_edge_mask =  32'h00000088;
+	localparam [31:0] irq_edge_mask =  32'h00000008;
 	localparam [31:0] irq_masked    = ~32'h000003ff;
 
 	wire iodev_wait_n = (&iodev_wait_n_sys) & 

BIN
fpga/output_files/max80.jbc


BIN
fpga/output_files/max80.jic


BIN
fpga/output_files/max80.pof


BIN
fpga/output_files/max80.sof


File diff suppressed because it is too large
+ 4575 - 4575
fw/boot.mif


+ 7 - 1
fw/ioregs.h

@@ -88,12 +88,18 @@
 #define SD_50MHZ	1	/* Really 42 MHz */
 
 #define I2C_WDATA		IODEVL(I2C,0)
+#define I2C_WDATA_DATA		IODEVB1(I2C,0)
 #define I2C_RDATA		IODEVL(I2C,1)
-#define I2C_DIVISOR		IODEVL(I2C,2)
+#define I2C_RDATA_DATA		IODEVB1(I2C,1)
 #define I2C_BUSY		1
 #define I2C_SR			2
 #define I2C_P			4
+#define I2C_DUMMY		6
+#define I2C_STARTED		0x10
+#define I2C_SCL			0x20
+#define I2C_SDA			0x40
 #define I2C_NAK			0x80
+#define I2C_DIVISOR		IODEVL(I2C,2)
 
 #define SYSCLOCK_DATETIME	IODEVL(SYSCLOCK,0)
 #define SYSCLOCK_TICK		IODEVL(SYSCLOCK,1)

+ 17 - 12
fw/rtc.c

@@ -10,36 +10,30 @@
 
 static inline uint32_t i2c_wait(void)
 {
-    uint32_t rdata;
-
-    while ((rdata = I2C_RDATA) & I2C_BUSY)
-	pause();
-
-    return rdata;
+    waitfor(I2C_IRQ);
 }
 
 static void i2c_send(uint8_t byte, uint8_t ctl)
 {
     i2c_wait();
-
     I2C_WDATA = (byte << 8) | I2C_NAK | ctl;
 }
 
 static bool i2c_acked(void)
 {
-    return !(i2c_wait() & I2C_NAK);
+    i2c_wait();
+    return !(I2C_RDATA & I2C_NAK);
 }
 
-static int i2c_recv(uint8_t ctl)
+static uint8_t i2c_recv(uint8_t ctl)
 {
     uint32_t rdata;
 
     i2c_wait();
-
     I2C_WDATA = (~0xff) | ctl;
 
-    rdata = i2c_wait();
-    return rdata >> 8;
+    i2c_wait();
+    return I2C_RDATA_DATA;
 }
 
 #define RTC_REGS 19
@@ -57,9 +51,20 @@ void read_rtc(void)
     uint8_t rtc_regs[RTC_REGS];
     int i;
     struct tms tms;
+    int sda_retry_count = 16;
 
     i2c_set_speed(400);
 
+    /* SDA held low? */
+    while (!(I2C_RDATA & I2C_SDA)) {
+	i2c_send(0xff, I2C_DUMMY);
+
+	if (!sda_retry_count--) {
+	    con_printf("RTC: I2C SDA stuck low\n");
+	    return;
+	}
+    }
+
     i2c_send(RTC_WCMD, 0);
 
     if (!i2c_acked()) {

+ 1 - 1
iodevs.conf

@@ -19,7 +19,7 @@ our @iodevs = (
     { -name => 'console',   -irq => 'l' },
     { -name => 'romcopy',   -irq => 'l' },
     { -name => 'sdcard',    -irq => 'l' },
-    { -name => 'i2c',       -irq => 'e' },
+    { -name => 'i2c',       -irq => 'l' },
     { -name => 'esp',       -irq => 'l' },
     { -name => 'abc',       -irq => 'l' },
     { -name => 'abcmemmap', -xdev => 1 },

Some files were not shown because too many files changed in this diff