diff mbox series

[v3,dev-5.2,2/2] i2c: aspeed: add slave inactive timeout support

Message ID 20190909222634.26146-3-jae.hyun.yoo@linux.intel.com
State Not Applicable, archived
Headers show
Series i2c: aspeed: Add H/W timeout support | expand

Commit Message

Jae Hyun Yoo Sept. 9, 2019, 10:26 p.m. UTC
In case of multi-master environment, if a peer master incorrectly handles
a bus in the middle of a transaction, I2C hardware hangs in slave state
and it can't escape from the slave state, so this commit adds slave
inactive timeout support to recover the bus in the case.

Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
---
Changes since v2:
 - Moved 'aspeed,hw-timeout-ms' property parsing logic to make it can
   be done before aspeed_i2c_init_clk.

Changes since v1:
 - Made it use bus auto recovery feature so that bus can recover itself
   automatically.

 drivers/i2c/busses/i2c-aspeed.c | 82 ++++++++++++++++++++++++++++++---
 1 file changed, 76 insertions(+), 6 deletions(-)

Comments

Tao Ren Sept. 9, 2019, 11:40 p.m. UTC | #1
On 9/9/19 3:26 PM, Jae Hyun Yoo wrote:
> In case of multi-master environment, if a peer master incorrectly handles
> a bus in the middle of a transaction, I2C hardware hangs in slave state
> and it can't escape from the slave state, so this commit adds slave
> inactive timeout support to recover the bus in the case.
> 
> Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>

The patch looks good to me. Thank you Jae for making the changes.

Reviewed-by: Tao Ren <taoren@fb.com>

Cheers,

Tao
> ---
> Changes since v2:
>  - Moved 'aspeed,hw-timeout-ms' property parsing logic to make it can
>    be done before aspeed_i2c_init_clk.
> 
> Changes since v1:
>  - Made it use bus auto recovery feature so that bus can recover itself
>    automatically.
> 
>  drivers/i2c/busses/i2c-aspeed.c | 82 ++++++++++++++++++++++++++++++---
>  1 file changed, 76 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
> index 89317929bee4..c66eca9a00f4 100644
> --- a/drivers/i2c/busses/i2c-aspeed.c
> +++ b/drivers/i2c/busses/i2c-aspeed.c
> @@ -55,6 +55,7 @@
>  /* Device Register Definition */
>  /* 0x00 : I2CD Function Control Register  */
>  #define ASPEED_I2CD_BUFFER_PAGE_SEL_MASK		GENMASK(22, 20)
> +#define ASPEED_I2CD_BUS_AUTO_RECOVERY_EN		BIT(17)
>  #define ASPEED_I2CD_MULTI_MASTER_DIS			BIT(15)
>  #define ASPEED_I2CD_SDA_DRIVE_1T_EN			BIT(8)
>  #define ASPEED_I2CD_M_SDA_DRIVE_1T_EN			BIT(7)
> @@ -70,10 +71,14 @@
>  #define ASPEED_I2CD_TIME_SCL_HIGH_MASK			GENMASK(19, 16)
>  #define ASPEED_I2CD_TIME_SCL_LOW_SHIFT			12
>  #define ASPEED_I2CD_TIME_SCL_LOW_MASK			GENMASK(15, 12)
> +#define ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_SHIFT	8
> +#define ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_MASK	GENMASK(9, 8)
>  #define ASPEED_I2CD_TIME_BASE_DIVISOR_MASK		GENMASK(3, 0)
>  #define ASPEED_I2CD_TIME_SCL_REG_MAX			GENMASK(3, 0)
> +
>  /* 0x08 : I2CD Clock and AC Timing Control Register #2 */
> -#define ASPEED_NO_TIMEOUT_CTRL				0
> +#define ASPEED_I2CD_TIMEOUT_CYCLES_SHIFT		0
> +#define ASPEED_I2CD_TIMEOUT_CYCLES_MASK			GENMASK(4, 0)
>  
>  /* 0x0c : I2CD Interrupt Control Register &
>   * 0x10 : I2CD Interrupt Status Register
> @@ -81,6 +86,7 @@
>   * These share bit definitions, so use the same values for the enable &
>   * status bits.
>   */
> +#define ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT		BIT(15)
>  #define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT			BIT(14)
>  #define ASPEED_I2CD_INTR_BUS_RECOVER_DONE		BIT(13)
>  #define ASPEED_I2CD_INTR_SLAVE_MATCH			BIT(7)
> @@ -96,8 +102,11 @@
>  		 ASPEED_I2CD_INTR_SCL_TIMEOUT |				       \
>  		 ASPEED_I2CD_INTR_ABNORMAL |				       \
>  		 ASPEED_I2CD_INTR_ARBIT_LOSS)
> +#define ASPEED_I2CD_INTR_SLAVE_ERRORS					       \
> +		ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT
>  #define ASPEED_I2CD_INTR_ALL						       \
> -		(ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |			       \
> +		(ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT |		       \
> +		 ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |			       \
>  		 ASPEED_I2CD_INTR_BUS_RECOVER_DONE |			       \
>  		 ASPEED_I2CD_INTR_SCL_TIMEOUT |				       \
>  		 ASPEED_I2CD_INTR_ABNORMAL |				       \
> @@ -176,6 +185,7 @@ struct aspeed_i2c_bus {
>  							   u32 divisor);
>  	unsigned long			parent_clk_frequency;
>  	u32				bus_frequency;
> +	u32				hw_timeout_ms;
>  	/* Transaction state. */
>  	enum aspeed_i2c_master_state	master_state;
>  	struct i2c_msg			*msgs;
> @@ -276,6 +286,14 @@ static int aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus)
>  }
>  
>  #if IS_ENABLED(CONFIG_I2C_SLAVE)
> +static int aspeed_i2c_check_slave_error(u32 irq_status)
> +{
> +	if (irq_status & ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT)
> +		return -EIO;
> +
> +	return 0;
> +}
> +
>  static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
>  {
>  	u32 command, irq_handled = 0;
> @@ -286,6 +304,14 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
>  	if (!slave)
>  		return 0;
>  
> +	if (aspeed_i2c_check_slave_error(irq_status)) {
> +		dev_dbg(bus->dev, "received slave error interrupt: 0x%08x\n",
> +			irq_status);
> +		irq_handled |= (irq_status & ASPEED_I2CD_INTR_SLAVE_ERRORS);
> +		bus->slave_state = ASPEED_I2C_SLAVE_INACTIVE;
> +		return irq_handled;
> +	}
> +
>  	command = readl(bus->base + ASPEED_I2C_CMD_REG);
>  
>  	/* Slave was requested, restart state machine. */
> @@ -602,7 +628,7 @@ static void aspeed_i2c_next_msg_or_stop(struct aspeed_i2c_bus *bus)
>  	}
>  }
>  
> -static int aspeed_i2c_is_irq_error(u32 irq_status)
> +static int aspeed_i2c_check_master_error(u32 irq_status)
>  {
>  	if (irq_status & ASPEED_I2CD_INTR_ARBIT_LOSS)
>  		return -EAGAIN;
> @@ -633,9 +659,9 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
>  	 * should clear the command queue effectively taking us back to the
>  	 * INACTIVE state.
>  	 */
> -	ret = aspeed_i2c_is_irq_error(irq_status);
> +	ret = aspeed_i2c_check_master_error(irq_status);
>  	if (ret) {
> -		dev_dbg(bus->dev, "received error interrupt: 0x%08x\n",
> +		dev_dbg(bus->dev, "received master error interrupt: 0x%08x\n",
>  			irq_status);
>  		irq_handled |= (irq_status & ASPEED_I2CD_INTR_MASTER_ERRORS);
>  		if (bus->master_state != ASPEED_I2C_MASTER_INACTIVE) {
> @@ -1194,6 +1220,7 @@ static u32 aspeed_i2c_25xx_get_clk_reg_val(struct device *dev, u32 divisor)
>  /* precondition: bus.lock has been acquired. */
>  static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus)
>  {
> +	u32 timeout_base_divisor, timeout_tick_us, timeout_cycles;
>  	u32 divisor, clk_reg_val;
>  
>  	divisor = DIV_ROUND_UP(bus->parent_clk_frequency, bus->bus_frequency);
> @@ -1202,8 +1229,46 @@ static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus)
>  			ASPEED_I2CD_TIME_THDSTA_MASK |
>  			ASPEED_I2CD_TIME_TACST_MASK);
>  	clk_reg_val |= bus->get_clk_reg_val(bus->dev, divisor);
> +
> +	if (bus->hw_timeout_ms) {
> +		u8 div_max = ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_MASK >>
> +			     ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_SHIFT;
> +		u8 cycles_max = ASPEED_I2CD_TIMEOUT_CYCLES_MASK >>
> +				ASPEED_I2CD_TIMEOUT_CYCLES_SHIFT;
> +
> +		timeout_base_divisor = 0;
> +
> +		do {
> +			timeout_tick_us = 1000 * (16384 <<
> +						  (timeout_base_divisor << 1)) /
> +					  (bus->parent_clk_frequency / 1000);
> +
> +			if (timeout_base_divisor == div_max ||
> +			    timeout_tick_us * ASPEED_I2CD_TIMEOUT_CYCLES_MASK >=
> +			    bus->hw_timeout_ms * 1000)
> +				break;
> +		} while (timeout_base_divisor++ < div_max);
> +
> +		if (timeout_tick_us) {
> +			timeout_cycles = DIV_ROUND_UP(bus->hw_timeout_ms * 1000,
> +						      timeout_tick_us);
> +			if (timeout_cycles == 0)
> +				timeout_cycles = 1;
> +			else if (timeout_cycles > cycles_max)
> +				timeout_cycles = cycles_max;
> +		} else {
> +			timeout_cycles = 0;
> +		}
> +	} else {
> +		timeout_base_divisor = 0;
> +		timeout_cycles = 0;
> +	}
> +
> +	clk_reg_val |= FIELD_PREP(ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_MASK,
> +				  timeout_base_divisor);
> +
>  	writel(clk_reg_val, bus->base + ASPEED_I2C_AC_TIMING_REG1);
> -	writel(ASPEED_NO_TIMEOUT_CTRL, bus->base + ASPEED_I2C_AC_TIMING_REG2);
> +	writel(timeout_cycles, bus->base + ASPEED_I2C_AC_TIMING_REG2);
>  
>  	return 0;
>  }
> @@ -1218,6 +1283,11 @@ static int aspeed_i2c_init(struct aspeed_i2c_bus *bus,
>  	/* Disable everything. */
>  	writel(0, bus->base + ASPEED_I2C_FUN_CTRL_REG);
>  
> +	device_property_read_u32(&pdev->dev, "aspeed,hw-timeout-ms",
> +				 &bus->hw_timeout_ms);
> +	if (bus->hw_timeout_ms)
> +		fun_ctrl_reg |= ASPEED_I2CD_BUS_AUTO_RECOVERY_EN;
> +
>  	ret = aspeed_i2c_init_clk(bus);
>  	if (ret < 0)
>  		return ret;
>
Jae Hyun Yoo Sept. 9, 2019, 11:52 p.m. UTC | #2
On 9/9/2019 4:40 PM, Tao Ren wrote:
> On 9/9/19 3:26 PM, Jae Hyun Yoo wrote:
>> In case of multi-master environment, if a peer master incorrectly handles
>> a bus in the middle of a transaction, I2C hardware hangs in slave state
>> and it can't escape from the slave state, so this commit adds slave
>> inactive timeout support to recover the bus in the case.
>>
>> Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
> 
> The patch looks good to me. Thank you Jae for making the changes.
> 
> Reviewed-by: Tao Ren <taoren@fb.com>

Hi Tao,

Thanks a lot for your code review and your help on testing it.

I'll submit it to linux upstream as well after completing other backlog
I2C patches' upstreaming.

Cheers,
Jae
diff mbox series

Patch

diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
index 89317929bee4..c66eca9a00f4 100644
--- a/drivers/i2c/busses/i2c-aspeed.c
+++ b/drivers/i2c/busses/i2c-aspeed.c
@@ -55,6 +55,7 @@ 
 /* Device Register Definition */
 /* 0x00 : I2CD Function Control Register  */
 #define ASPEED_I2CD_BUFFER_PAGE_SEL_MASK		GENMASK(22, 20)
+#define ASPEED_I2CD_BUS_AUTO_RECOVERY_EN		BIT(17)
 #define ASPEED_I2CD_MULTI_MASTER_DIS			BIT(15)
 #define ASPEED_I2CD_SDA_DRIVE_1T_EN			BIT(8)
 #define ASPEED_I2CD_M_SDA_DRIVE_1T_EN			BIT(7)
@@ -70,10 +71,14 @@ 
 #define ASPEED_I2CD_TIME_SCL_HIGH_MASK			GENMASK(19, 16)
 #define ASPEED_I2CD_TIME_SCL_LOW_SHIFT			12
 #define ASPEED_I2CD_TIME_SCL_LOW_MASK			GENMASK(15, 12)
+#define ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_SHIFT	8
+#define ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_MASK	GENMASK(9, 8)
 #define ASPEED_I2CD_TIME_BASE_DIVISOR_MASK		GENMASK(3, 0)
 #define ASPEED_I2CD_TIME_SCL_REG_MAX			GENMASK(3, 0)
+
 /* 0x08 : I2CD Clock and AC Timing Control Register #2 */
-#define ASPEED_NO_TIMEOUT_CTRL				0
+#define ASPEED_I2CD_TIMEOUT_CYCLES_SHIFT		0
+#define ASPEED_I2CD_TIMEOUT_CYCLES_MASK			GENMASK(4, 0)
 
 /* 0x0c : I2CD Interrupt Control Register &
  * 0x10 : I2CD Interrupt Status Register
@@ -81,6 +86,7 @@ 
  * These share bit definitions, so use the same values for the enable &
  * status bits.
  */
+#define ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT		BIT(15)
 #define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT			BIT(14)
 #define ASPEED_I2CD_INTR_BUS_RECOVER_DONE		BIT(13)
 #define ASPEED_I2CD_INTR_SLAVE_MATCH			BIT(7)
@@ -96,8 +102,11 @@ 
 		 ASPEED_I2CD_INTR_SCL_TIMEOUT |				       \
 		 ASPEED_I2CD_INTR_ABNORMAL |				       \
 		 ASPEED_I2CD_INTR_ARBIT_LOSS)
+#define ASPEED_I2CD_INTR_SLAVE_ERRORS					       \
+		ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT
 #define ASPEED_I2CD_INTR_ALL						       \
-		(ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |			       \
+		(ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT |		       \
+		 ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |			       \
 		 ASPEED_I2CD_INTR_BUS_RECOVER_DONE |			       \
 		 ASPEED_I2CD_INTR_SCL_TIMEOUT |				       \
 		 ASPEED_I2CD_INTR_ABNORMAL |				       \
@@ -176,6 +185,7 @@  struct aspeed_i2c_bus {
 							   u32 divisor);
 	unsigned long			parent_clk_frequency;
 	u32				bus_frequency;
+	u32				hw_timeout_ms;
 	/* Transaction state. */
 	enum aspeed_i2c_master_state	master_state;
 	struct i2c_msg			*msgs;
@@ -276,6 +286,14 @@  static int aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus)
 }
 
 #if IS_ENABLED(CONFIG_I2C_SLAVE)
+static int aspeed_i2c_check_slave_error(u32 irq_status)
+{
+	if (irq_status & ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT)
+		return -EIO;
+
+	return 0;
+}
+
 static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
 {
 	u32 command, irq_handled = 0;
@@ -286,6 +304,14 @@  static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
 	if (!slave)
 		return 0;
 
+	if (aspeed_i2c_check_slave_error(irq_status)) {
+		dev_dbg(bus->dev, "received slave error interrupt: 0x%08x\n",
+			irq_status);
+		irq_handled |= (irq_status & ASPEED_I2CD_INTR_SLAVE_ERRORS);
+		bus->slave_state = ASPEED_I2C_SLAVE_INACTIVE;
+		return irq_handled;
+	}
+
 	command = readl(bus->base + ASPEED_I2C_CMD_REG);
 
 	/* Slave was requested, restart state machine. */
@@ -602,7 +628,7 @@  static void aspeed_i2c_next_msg_or_stop(struct aspeed_i2c_bus *bus)
 	}
 }
 
-static int aspeed_i2c_is_irq_error(u32 irq_status)
+static int aspeed_i2c_check_master_error(u32 irq_status)
 {
 	if (irq_status & ASPEED_I2CD_INTR_ARBIT_LOSS)
 		return -EAGAIN;
@@ -633,9 +659,9 @@  static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
 	 * should clear the command queue effectively taking us back to the
 	 * INACTIVE state.
 	 */
-	ret = aspeed_i2c_is_irq_error(irq_status);
+	ret = aspeed_i2c_check_master_error(irq_status);
 	if (ret) {
-		dev_dbg(bus->dev, "received error interrupt: 0x%08x\n",
+		dev_dbg(bus->dev, "received master error interrupt: 0x%08x\n",
 			irq_status);
 		irq_handled |= (irq_status & ASPEED_I2CD_INTR_MASTER_ERRORS);
 		if (bus->master_state != ASPEED_I2C_MASTER_INACTIVE) {
@@ -1194,6 +1220,7 @@  static u32 aspeed_i2c_25xx_get_clk_reg_val(struct device *dev, u32 divisor)
 /* precondition: bus.lock has been acquired. */
 static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus)
 {
+	u32 timeout_base_divisor, timeout_tick_us, timeout_cycles;
 	u32 divisor, clk_reg_val;
 
 	divisor = DIV_ROUND_UP(bus->parent_clk_frequency, bus->bus_frequency);
@@ -1202,8 +1229,46 @@  static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus)
 			ASPEED_I2CD_TIME_THDSTA_MASK |
 			ASPEED_I2CD_TIME_TACST_MASK);
 	clk_reg_val |= bus->get_clk_reg_val(bus->dev, divisor);
+
+	if (bus->hw_timeout_ms) {
+		u8 div_max = ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_MASK >>
+			     ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_SHIFT;
+		u8 cycles_max = ASPEED_I2CD_TIMEOUT_CYCLES_MASK >>
+				ASPEED_I2CD_TIMEOUT_CYCLES_SHIFT;
+
+		timeout_base_divisor = 0;
+
+		do {
+			timeout_tick_us = 1000 * (16384 <<
+						  (timeout_base_divisor << 1)) /
+					  (bus->parent_clk_frequency / 1000);
+
+			if (timeout_base_divisor == div_max ||
+			    timeout_tick_us * ASPEED_I2CD_TIMEOUT_CYCLES_MASK >=
+			    bus->hw_timeout_ms * 1000)
+				break;
+		} while (timeout_base_divisor++ < div_max);
+
+		if (timeout_tick_us) {
+			timeout_cycles = DIV_ROUND_UP(bus->hw_timeout_ms * 1000,
+						      timeout_tick_us);
+			if (timeout_cycles == 0)
+				timeout_cycles = 1;
+			else if (timeout_cycles > cycles_max)
+				timeout_cycles = cycles_max;
+		} else {
+			timeout_cycles = 0;
+		}
+	} else {
+		timeout_base_divisor = 0;
+		timeout_cycles = 0;
+	}
+
+	clk_reg_val |= FIELD_PREP(ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_MASK,
+				  timeout_base_divisor);
+
 	writel(clk_reg_val, bus->base + ASPEED_I2C_AC_TIMING_REG1);
-	writel(ASPEED_NO_TIMEOUT_CTRL, bus->base + ASPEED_I2C_AC_TIMING_REG2);
+	writel(timeout_cycles, bus->base + ASPEED_I2C_AC_TIMING_REG2);
 
 	return 0;
 }
@@ -1218,6 +1283,11 @@  static int aspeed_i2c_init(struct aspeed_i2c_bus *bus,
 	/* Disable everything. */
 	writel(0, bus->base + ASPEED_I2C_FUN_CTRL_REG);
 
+	device_property_read_u32(&pdev->dev, "aspeed,hw-timeout-ms",
+				 &bus->hw_timeout_ms);
+	if (bus->hw_timeout_ms)
+		fun_ctrl_reg |= ASPEED_I2CD_BUS_AUTO_RECOVERY_EN;
+
 	ret = aspeed_i2c_init_clk(bus);
 	if (ret < 0)
 		return ret;