summaryrefslogtreecommitdiff
path: root/drivers/i2c
diff options
context:
space:
mode:
authorJae Hyun Yoo <jae.hyun.yoo@intel.com>2019-07-11 23:53:34 +0300
committerJae Hyun Yoo <jae.hyun.yoo@linux.intel.com>2021-11-05 10:22:08 +0300
commitba4c2f110fac5748ec409eff41f5c457e68826de (patch)
tree15414ba4696bcb64fe15bb51a79177c502f6e6cf /drivers/i2c
parent9e8b19854f7a64143fc1177f82f185e3051154b3 (diff)
downloadlinux-ba4c2f110fac5748ec409eff41f5c457e68826de.tar.xz
i2c: aspeed: add H/W timeout support
This commit adds I2C H/W timeout support. Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com>
Diffstat (limited to 'drivers/i2c')
-rw-r--r--drivers/i2c/busses/i2c-aspeed.c82
1 files changed, 76 insertions, 6 deletions
diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
index 4ed33ac2045c..f5a7a008270c 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)
@@ -71,10 +72,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
@@ -83,6 +88,7 @@
* status bits.
*/
#define ASPEED_I2CD_INTR_RECV_MASK 0xf000ffff
+#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_GCALL_ADDR BIT(8)
@@ -99,8 +105,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 | \
@@ -187,6 +196,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;
@@ -307,6 +317,14 @@ reset_out:
}
#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 inline void
aspeed_i2c_slave_handle_rx_done(struct aspeed_i2c_bus *bus, u32 irq_status,
u8 *value)
@@ -425,6 +443,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. */
@@ -685,7 +711,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;
@@ -844,9 +870,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) {
@@ -1319,6 +1345,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);
@@ -1327,8 +1354,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;
}
@@ -1343,6 +1408,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;