diff mbox series

[v4,3/4] i2c: aspeed: add buffer mode transfer support

Message ID 20210224191720.7724-4-jae.hyun.yoo@linux.intel.com
State New
Headers show
Series i2c: aspeed: Add buffer and DMA modes support | expand

Commit Message

Jae Hyun Yoo Feb. 24, 2021, 7:17 p.m. UTC
This driver uses byte mode that makes lots of interrupt calls
which isn't good for performance and it makes the driver very
timing sensitive. To improve performance of the driver, this commit
adds buffer mode transfer support which uses I2C SRAM buffer
instead of using a single byte buffer.

Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Tested-by: Tao Ren <taoren@fb.com>
---
Changes since v3:
- Added a comment to explain SRAM buffer handling logic.

Changes since v2:
- Refined SoC family dependent xfer mode configuration functions.

Changes since v1:
- Updated commit message.
- Refined using abstract functions.

 drivers/i2c/busses/i2c-aspeed.c | 468 ++++++++++++++++++++++++++++----
 1 file changed, 416 insertions(+), 52 deletions(-)

Comments

Brendan Higgins April 13, 2021, 9:24 p.m. UTC | #1
On Wed, Feb 24, 2021 at 11:04 AM Jae Hyun Yoo
<jae.hyun.yoo@linux.intel.com> wrote:
>
> This driver uses byte mode that makes lots of interrupt calls
> which isn't good for performance and it makes the driver very
> timing sensitive. To improve performance of the driver, this commit
> adds buffer mode transfer support which uses I2C SRAM buffer
> instead of using a single byte buffer.
>
> Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
> Tested-by: Tao Ren <taoren@fb.com>

Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
diff mbox series

Patch

diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
index 724bf30600d6..ffc52937df26 100644
--- a/drivers/i2c/busses/i2c-aspeed.c
+++ b/drivers/i2c/busses/i2c-aspeed.c
@@ -7,6 +7,7 @@ 
  *  Copyright 2017 Google, Inc.
  */
 
+#include <linux/bitfield.h>
 #include <linux/clk.h>
 #include <linux/completion.h>
 #include <linux/err.h>
@@ -19,15 +20,24 @@ 
 #include <linux/irqchip/chained_irq.h>
 #include <linux/irqdomain.h>
 #include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
 #include <linux/module.h>
 #include <linux/of_address.h>
 #include <linux/of_irq.h>
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
+#include <linux/regmap.h>
 #include <linux/reset.h>
 #include <linux/slab.h>
 
-/* I2C Register */
+/* I2C Global Registers */
+/* 0x00 : I2CG Interrupt Status Register  */
+/* 0x08 : I2CG Interrupt Target Assignment  */
+/* 0x0c : I2CG Global Control Register (AST2500)  */
+#define ASPEED_I2CG_GLOBAL_CTRL_REG			0x0c
+#define  ASPEED_I2CG_SRAM_BUFFER_EN			BIT(0)
+
+/* I2C Bus Registers */
 #define ASPEED_I2C_FUN_CTRL_REG				0x00
 #define ASPEED_I2C_AC_TIMING_REG1			0x04
 #define ASPEED_I2C_AC_TIMING_REG2			0x08
@@ -35,14 +45,12 @@ 
 #define ASPEED_I2C_INTR_STS_REG				0x10
 #define ASPEED_I2C_CMD_REG				0x14
 #define ASPEED_I2C_DEV_ADDR_REG				0x18
+#define ASPEED_I2C_BUF_CTRL_REG				0x1c
 #define ASPEED_I2C_BYTE_BUF_REG				0x20
 
-/* Global Register Definition */
-/* 0x00 : I2C Interrupt Status Register  */
-/* 0x08 : I2C Interrupt Target Assignment  */
-
 /* Device Register Definition */
 /* 0x00 : I2CD Function Control Register  */
+#define ASPEED_I2CD_BUFFER_PAGE_SEL_MASK		GENMASK(22, 20)
 #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)
@@ -103,6 +111,8 @@ 
 #define ASPEED_I2CD_BUS_RECOVER_CMD			BIT(11)
 
 /* Command Bit */
+#define ASPEED_I2CD_RX_BUFF_ENABLE			BIT(7)
+#define ASPEED_I2CD_TX_BUFF_ENABLE			BIT(6)
 #define ASPEED_I2CD_M_STOP_CMD				BIT(5)
 #define ASPEED_I2CD_M_S_RX_CMD_LAST			BIT(4)
 #define ASPEED_I2CD_M_RX_CMD				BIT(3)
@@ -119,6 +129,13 @@ 
 /* 0x18 : I2CD Slave Device Address Register   */
 #define ASPEED_I2CD_DEV_ADDR_MASK			GENMASK(6, 0)
 
+/* 0x1c : I2CD Buffer Control Register */
+/* Use 8-bits or 6-bits wide bit fileds to support both AST2400 and AST2500 */
+#define ASPEED_I2CD_BUF_RX_COUNT_MASK			GENMASK(31, 24)
+#define ASPEED_I2CD_BUF_RX_SIZE_MASK			GENMASK(23, 16)
+#define ASPEED_I2CD_BUF_TX_COUNT_MASK			GENMASK(15, 8)
+#define ASPEED_I2CD_BUF_OFFSET_MASK			GENMASK(5, 0)
+
 enum aspeed_i2c_master_state {
 	ASPEED_I2C_MASTER_INACTIVE,
 	ASPEED_I2C_MASTER_PENDING,
@@ -140,6 +157,12 @@  enum aspeed_i2c_slave_state {
 	ASPEED_I2C_SLAVE_STOP,
 };
 
+struct aspeed_i2c_config {
+	u32 (*get_clk_reg_val)(struct device *dev, u32 divisor);
+	int (*enable_sram)(void);
+	int (*set_buf_xfer_mode)(struct device *dev);
+};
+
 struct aspeed_i2c_bus {
 	struct i2c_adapter		adap;
 	struct device			*dev;
@@ -148,8 +171,7 @@  struct aspeed_i2c_bus {
 	/* Synchronizes I/O mem access to base. */
 	spinlock_t			lock;
 	struct completion		cmd_complete;
-	u32				(*get_clk_reg_val)(struct device *dev,
-							   u32 divisor);
+	struct aspeed_i2c_config	*config;
 	unsigned long			parent_clk_frequency;
 	u32				bus_frequency;
 	/* Transaction state. */
@@ -164,6 +186,11 @@  struct aspeed_i2c_bus {
 	int				master_xfer_result;
 	/* Multi-master */
 	bool				multi_master;
+	/* Buffer mode */
+	void __iomem			*buf_base;
+	u8				buf_offset;
+	u8				buf_page;
+	size_t				buf_size;
 #if IS_ENABLED(CONFIG_I2C_SLAVE)
 	struct i2c_client		*slave;
 	enum aspeed_i2c_slave_state	slave_state;
@@ -241,6 +268,77 @@  static int aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus)
 }
 
 #if IS_ENABLED(CONFIG_I2C_SLAVE)
+static inline void
+aspeed_i2c_slave_handle_rx_done(struct aspeed_i2c_bus *bus, u32 irq_status,
+				u8 *value)
+{
+	if (bus->buf_base &&
+	    bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED &&
+	    !(irq_status & ASPEED_I2CD_INTR_NORMAL_STOP))
+		*value = readb(bus->buf_base);
+	else
+		*value = readl(bus->base + ASPEED_I2C_BYTE_BUF_REG) >> 8;
+}
+
+static inline void
+aspeed_i2c_slave_handle_normal_stop(struct aspeed_i2c_bus *bus, u32 irq_status,
+				    u8 *value)
+{
+	int i, len;
+
+	if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED &&
+	    irq_status & ASPEED_I2CD_INTR_RX_DONE) {
+		if (bus->buf_base) {
+			len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK,
+					readl(bus->base +
+					      ASPEED_I2C_BUF_CTRL_REG));
+			for (i = 0; i < len; i++) {
+				*value = readb(bus->buf_base + i);
+				i2c_slave_event(bus->slave,
+						I2C_SLAVE_WRITE_RECEIVED,
+						value);
+			}
+		}
+	}
+}
+
+static inline void
+aspeed_i2c_slave_handle_write_requested(struct aspeed_i2c_bus *bus, u8 *value)
+{
+	if (bus->buf_base) {
+		writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK,
+				  bus->buf_size - 1) |
+		       FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK,
+				  bus->buf_offset),
+		       bus->base + ASPEED_I2C_BUF_CTRL_REG);
+		writel(ASPEED_I2CD_RX_BUFF_ENABLE,
+		       bus->base + ASPEED_I2C_CMD_REG);
+	}
+}
+
+static inline void
+aspeed_i2c_slave_handle_write_received(struct aspeed_i2c_bus *bus, u8 *value)
+{
+	int i, len;
+
+	if (bus->buf_base) {
+		len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK,
+				readl(bus->base +
+				      ASPEED_I2C_BUF_CTRL_REG));
+		for (i = 1; i < len; i++) {
+			*value = readb(bus->buf_base + i);
+			i2c_slave_event(bus->slave, I2C_SLAVE_WRITE_RECEIVED,
+					value);
+		}
+		writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK,
+				  bus->buf_size - 1) |
+		       FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK, bus->buf_offset),
+		       bus->base + ASPEED_I2C_BUF_CTRL_REG);
+		writel(ASPEED_I2CD_RX_BUFF_ENABLE,
+		       bus->base + ASPEED_I2C_CMD_REG);
+	}
+}
+
 static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
 {
 	u32 command, irq_handled = 0;
@@ -267,7 +365,7 @@  static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
 
 	/* Slave was sent something. */
 	if (irq_status & ASPEED_I2CD_INTR_RX_DONE) {
-		value = readl(bus->base + ASPEED_I2C_BYTE_BUF_REG) >> 8;
+		aspeed_i2c_slave_handle_rx_done(bus, irq_status, &value);
 		/* Handle address frame. */
 		if (bus->slave_state == ASPEED_I2C_SLAVE_START) {
 			if (value & 0x1)
@@ -282,9 +380,11 @@  static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
 
 	/* Slave was asked to stop. */
 	if (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) {
+		aspeed_i2c_slave_handle_normal_stop(bus, irq_status, &value);
 		irq_handled |= ASPEED_I2CD_INTR_NORMAL_STOP;
 		bus->slave_state = ASPEED_I2C_SLAVE_STOP;
 	}
+
 	if (irq_status & ASPEED_I2CD_INTR_TX_NAK &&
 	    bus->slave_state == ASPEED_I2C_SLAVE_READ_PROCESSED) {
 		irq_handled |= ASPEED_I2CD_INTR_TX_NAK;
@@ -314,9 +414,11 @@  static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
 	case ASPEED_I2C_SLAVE_WRITE_REQUESTED:
 		bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED;
 		i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value);
+		aspeed_i2c_slave_handle_write_requested(bus, &value);
 		break;
 	case ASPEED_I2C_SLAVE_WRITE_RECEIVED:
 		i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value);
+		aspeed_i2c_slave_handle_write_received(bus, &value);
 		break;
 	case ASPEED_I2C_SLAVE_STOP:
 		i2c_slave_event(slave, I2C_SLAVE_STOP, &value);
@@ -336,12 +438,76 @@  static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
 }
 #endif /* CONFIG_I2C_SLAVE */
 
+static inline u32
+aspeed_i2c_prepare_rx_buf(struct aspeed_i2c_bus *bus, struct i2c_msg *msg)
+{
+	u32 command = 0;
+	int len;
+
+	if (msg->len > bus->buf_size) {
+		len = bus->buf_size;
+	} else {
+		len = msg->len;
+		command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
+	}
+
+	if (bus->buf_base) {
+		command |= ASPEED_I2CD_RX_BUFF_ENABLE;
+
+		writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK, len - 1) |
+		       FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK, bus->buf_offset),
+		       bus->base + ASPEED_I2C_BUF_CTRL_REG);
+	}
+
+	return command;
+}
+
+static inline u32
+aspeed_i2c_prepare_tx_buf(struct aspeed_i2c_bus *bus, struct i2c_msg *msg)
+{
+	u8 slave_addr = i2c_8bit_addr_from_msg(msg);
+	u32 command = 0;
+	int len;
+
+	if (msg->len + 1 > bus->buf_size)
+		len = bus->buf_size;
+	else
+		len = msg->len + 1;
+
+	if (bus->buf_base) {
+		u8 wbuf[4];
+		int i;
+
+		command |= ASPEED_I2CD_TX_BUFF_ENABLE;
+
+		/*
+		 * Yeah, it looks bad but byte writing on remapped I2C SRAM
+		 * causes corruption so use this way to make dword writings.
+		 */
+		wbuf[0] = slave_addr;
+		for (i = 1; i < len; i++) {
+			wbuf[i % 4] = msg->buf[i - 1];
+			if (i % 4 == 3)
+				writel(*(u32 *)wbuf, bus->buf_base + i - 3);
+		}
+		if (--i % 4 != 3)
+			writel(*(u32 *)wbuf, bus->buf_base + i - (i % 4));
+
+		writel(FIELD_PREP(ASPEED_I2CD_BUF_TX_COUNT_MASK, len - 1) |
+		       FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK, bus->buf_offset),
+		       bus->base + ASPEED_I2C_BUF_CTRL_REG);
+	}
+
+	bus->buf_index = len - 1;
+
+	return command;
+}
+
 /* precondition: bus.lock has been acquired. */
 static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus)
 {
 	u32 command = ASPEED_I2CD_M_START_CMD | ASPEED_I2CD_M_TX_CMD;
 	struct i2c_msg *msg = &bus->msgs[bus->msgs_index];
-	u8 slave_addr = i2c_8bit_addr_from_msg(msg);
 
 #if IS_ENABLED(CONFIG_I2C_SLAVE)
 	/*
@@ -360,12 +526,21 @@  static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus)
 
 	if (msg->flags & I2C_M_RD) {
 		command |= ASPEED_I2CD_M_RX_CMD;
-		/* Need to let the hardware know to NACK after RX. */
-		if (msg->len == 1 && !(msg->flags & I2C_M_RECV_LEN))
-			command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
+		if (!(msg->flags & I2C_M_RECV_LEN)) {
+			if (msg->len && bus->buf_base)
+				command |= aspeed_i2c_prepare_rx_buf(bus, msg);
+
+			/* Need to let the hardware know to NACK after RX. */
+			if (msg->len <= 1)
+				command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
+		}
+	} else if (msg->len && bus->buf_base) {
+		command |= aspeed_i2c_prepare_tx_buf(bus, msg);
 	}
 
-	writel(slave_addr, bus->base + ASPEED_I2C_BYTE_BUF_REG);
+	if (!(command & ASPEED_I2CD_TX_BUFF_ENABLE))
+		writel(i2c_8bit_addr_from_msg(msg),
+		       bus->base + ASPEED_I2C_BYTE_BUF_REG);
 	writel(command, bus->base + ASPEED_I2C_CMD_REG);
 }
 
@@ -400,6 +575,107 @@  static int aspeed_i2c_is_irq_error(u32 irq_status)
 	return 0;
 }
 
+static inline u32
+aspeed_i2c_master_handle_tx_first(struct aspeed_i2c_bus *bus,
+				  struct i2c_msg *msg)
+{
+	u32 command = 0;
+
+	if (bus->buf_base) {
+		u8 wbuf[4];
+		int len;
+		int i;
+
+		if (msg->len - bus->buf_index > bus->buf_size)
+			len = bus->buf_size;
+		else
+			len = msg->len - bus->buf_index;
+
+		command |= ASPEED_I2CD_TX_BUFF_ENABLE;
+
+		if (msg->len - bus->buf_index > bus->buf_size)
+			len = bus->buf_size;
+		else
+			len = msg->len - bus->buf_index;
+
+		/*
+		 * Looks bad here again but use dword writings to avoid data
+		 * corruption of byte writing on remapped I2C SRAM.
+		 */
+		for (i = 0; i < len; i++) {
+			wbuf[i % 4] = msg->buf[bus->buf_index + i];
+			if (i % 4 == 3)
+				writel(*(u32 *)wbuf,
+				       bus->buf_base + i - 3);
+		}
+		if (--i % 4 != 3)
+			writel(*(u32 *)wbuf,
+			       bus->buf_base + i - (i % 4));
+
+		writel(FIELD_PREP(ASPEED_I2CD_BUF_TX_COUNT_MASK,
+				  len - 1) |
+		       FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK,
+				  bus->buf_offset),
+		       bus->base + ASPEED_I2C_BUF_CTRL_REG);
+
+		bus->buf_index += len;
+	} else {
+		writel(msg->buf[bus->buf_index++],
+		       bus->base + ASPEED_I2C_BYTE_BUF_REG);
+	}
+
+	return command;
+}
+
+static inline void
+aspeed_i2c_master_handle_rx(struct aspeed_i2c_bus *bus, struct i2c_msg *msg)
+{
+	u8 recv_byte;
+	int len;
+
+	if (bus->buf_base) {
+		len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK,
+				readl(bus->base + ASPEED_I2C_BUF_CTRL_REG));
+		memcpy_fromio(msg->buf + bus->buf_index, bus->buf_base, len);
+		bus->buf_index += len;
+	} else {
+		recv_byte = readl(bus->base + ASPEED_I2C_BYTE_BUF_REG) >> 8;
+		msg->buf[bus->buf_index++] = recv_byte;
+	}
+}
+
+static inline u32
+aspeed_i2c_master_handle_rx_next(struct aspeed_i2c_bus *bus,
+				 struct i2c_msg *msg)
+{
+	u32 command = 0;
+
+	if (bus->buf_base) {
+		int len;
+
+		if (msg->len - bus->buf_index > bus->buf_size) {
+			len = bus->buf_size;
+		} else {
+			len = msg->len - bus->buf_index;
+			command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
+		}
+
+		command |= ASPEED_I2CD_RX_BUFF_ENABLE;
+
+		writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK,
+				  len - 1) |
+		       FIELD_PREP(ASPEED_I2CD_BUF_TX_COUNT_MASK, 0) |
+		       FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK,
+				  bus->buf_offset),
+		       bus->base + ASPEED_I2C_BUF_CTRL_REG);
+	} else {
+		if (bus->buf_index + 1 == msg->len)
+			command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
+	}
+
+	return command;
+}
+
 static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
 {
 	u32 irq_handled = 0, command = 0;
@@ -508,11 +784,10 @@  static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
 		fallthrough;
 	case ASPEED_I2C_MASTER_TX_FIRST:
 		if (bus->buf_index < msg->len) {
+			command = ASPEED_I2CD_M_TX_CMD;
+			command |= aspeed_i2c_master_handle_tx_first(bus, msg);
+			writel(command, bus->base + ASPEED_I2C_CMD_REG);
 			bus->master_state = ASPEED_I2C_MASTER_TX;
-			writel(msg->buf[bus->buf_index++],
-			       bus->base + ASPEED_I2C_BYTE_BUF_REG);
-			writel(ASPEED_I2CD_M_TX_CMD,
-			       bus->base + ASPEED_I2C_CMD_REG);
 		} else {
 			aspeed_i2c_next_msg_or_stop(bus);
 		}
@@ -529,26 +804,26 @@  static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
 		}
 		irq_handled |= ASPEED_I2CD_INTR_RX_DONE;
 
-		recv_byte = readl(bus->base + ASPEED_I2C_BYTE_BUF_REG) >> 8;
-		msg->buf[bus->buf_index++] = recv_byte;
-
 		if (msg->flags & I2C_M_RECV_LEN) {
+			recv_byte = readl(bus->base +
+					ASPEED_I2C_BYTE_BUF_REG) >> 8;
 			if (unlikely(recv_byte > I2C_SMBUS_BLOCK_MAX)) {
 				bus->cmd_err = -EPROTO;
 				aspeed_i2c_do_stop(bus);
 				goto out_no_complete;
 			}
-			msg->len = recv_byte +
-					((msg->flags & I2C_CLIENT_PEC) ? 2 : 1);
+			msg->len = recv_byte + ((msg->flags & I2C_CLIENT_PEC) ?
+						2 : 1);
 			msg->flags &= ~I2C_M_RECV_LEN;
+		} else if (msg->len) {
+			aspeed_i2c_master_handle_rx(bus, msg);
 		}
 
 		if (bus->buf_index < msg->len) {
-			bus->master_state = ASPEED_I2C_MASTER_RX;
 			command = ASPEED_I2CD_M_RX_CMD;
-			if (bus->buf_index + 1 == msg->len)
-				command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
+			command |= aspeed_i2c_master_handle_rx_next(bus, msg);
 			writel(command, bus->base + ASPEED_I2C_CMD_REG);
+			bus->master_state = ASPEED_I2C_MASTER_RX;
 		} else {
 			aspeed_i2c_next_msg_or_stop(bus);
 		}
@@ -887,7 +1162,7 @@  static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus)
 	clk_reg_val &= (ASPEED_I2CD_TIME_TBUF_MASK |
 			ASPEED_I2CD_TIME_THDSTA_MASK |
 			ASPEED_I2CD_TIME_TACST_MASK);
-	clk_reg_val |= bus->get_clk_reg_val(bus->dev, divisor);
+	clk_reg_val |= bus->config->get_clk_reg_val(bus->dev, divisor);
 	writel(clk_reg_val, bus->base + ASPEED_I2C_AC_TIMING_REG1);
 	writel(ASPEED_NO_TIMEOUT_CTRL, bus->base + ASPEED_I2C_AC_TIMING_REG2);
 
@@ -908,6 +1183,9 @@  static int aspeed_i2c_init(struct aspeed_i2c_bus *bus,
 	if (ret < 0)
 		return ret;
 
+	fun_ctrl_reg |= FIELD_PREP(ASPEED_I2CD_BUFFER_PAGE_SEL_MASK,
+				   bus->buf_page);
+
 	if (of_property_read_bool(pdev->dev.of_node, "multi-master"))
 		bus->multi_master = true;
 	else
@@ -948,40 +1226,129 @@  static int aspeed_i2c_reset(struct aspeed_i2c_bus *bus)
 	return ret;
 }
 
+static int aspeed_i2c_24xx_enable_sram(void)
+{
+	/* SRAM is enabled by default */
+
+	return 0;
+}
+
+static int aspeed_i2c_25xx_enable_sram(void)
+{
+	struct regmap *gr_regmap;
+	int ret;
+
+	gr_regmap = syscon_regmap_lookup_by_compatible("aspeed,ast2500-i2c-gr");
+
+	if (IS_ERR(gr_regmap))
+		ret = PTR_ERR(gr_regmap);
+	else
+		ret = regmap_update_bits(gr_regmap,
+					 ASPEED_I2CG_GLOBAL_CTRL_REG,
+					 ASPEED_I2CG_SRAM_BUFFER_EN,
+					 ASPEED_I2CG_SRAM_BUFFER_EN);
+
+	return ret;
+}
+
+static int aspeed_i2c_24xx_set_buf_xfer_mode(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct aspeed_i2c_bus *bus = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev,
+						     IORESOURCE_MEM, 1);
+
+	if (res && resource_size(res) >= 2)
+		bus->buf_base = devm_ioremap_resource(&pdev->dev, res);
+
+	if (!IS_ERR_OR_NULL(bus->buf_base)) {
+		bus->buf_size = resource_size(res);
+		bus->buf_page = ((res->start >> 8) & GENMASK(3, 0)) - 8;
+		bus->buf_offset = (res->start >> 2) &
+				  ASPEED_I2CD_BUF_OFFSET_MASK;
+	}
+
+	return bus->buf_size ? 0 : -EINVAL;
+}
+
+static int aspeed_i2c_25xx_set_buf_xfer_mode(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct aspeed_i2c_bus *bus = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev,
+						     IORESOURCE_MEM, 1);
+
+	if (res && resource_size(res) >= 2)
+		bus->buf_base = devm_ioremap_resource(&pdev->dev, res);
+
+	if (!IS_ERR_OR_NULL(bus->buf_base))
+		bus->buf_size = resource_size(res);
+
+	return bus->buf_size ? 0 : -EINVAL;
+}
+
+static const struct aspeed_i2c_config ast24xx_config = {
+	.get_clk_reg_val = aspeed_i2c_24xx_get_clk_reg_val,
+	.enable_sram = aspeed_i2c_24xx_enable_sram,
+	.set_buf_xfer_mode = aspeed_i2c_24xx_set_buf_xfer_mode,
+};
+
+static const struct aspeed_i2c_config ast25xx_config = {
+	.get_clk_reg_val = aspeed_i2c_25xx_get_clk_reg_val,
+	.enable_sram = aspeed_i2c_25xx_enable_sram,
+	.set_buf_xfer_mode = aspeed_i2c_25xx_set_buf_xfer_mode,
+};
+
+static const struct aspeed_i2c_config ast26xx_config = {
+	.get_clk_reg_val = aspeed_i2c_25xx_get_clk_reg_val,
+	.enable_sram = aspeed_i2c_24xx_enable_sram,
+	.set_buf_xfer_mode = aspeed_i2c_25xx_set_buf_xfer_mode,
+};
+
 static const struct of_device_id aspeed_i2c_bus_of_table[] = {
-	{
-		.compatible = "aspeed,ast2400-i2c-bus",
-		.data = aspeed_i2c_24xx_get_clk_reg_val,
-	},
-	{
-		.compatible = "aspeed,ast2500-i2c-bus",
-		.data = aspeed_i2c_25xx_get_clk_reg_val,
-	},
-	{
-		.compatible = "aspeed,ast2600-i2c-bus",
-		.data = aspeed_i2c_25xx_get_clk_reg_val,
-	},
-	{ },
+	{ .compatible = "aspeed,ast2400-i2c-bus", .data = &ast24xx_config, },
+	{ .compatible = "aspeed,ast2500-i2c-bus", .data = &ast25xx_config, },
+	{ .compatible = "aspeed,ast2600-i2c-bus", .data = &ast26xx_config, },
+	{}
 };
 MODULE_DEVICE_TABLE(of, aspeed_i2c_bus_of_table);
 
+static void aspeed_i2c_set_xfer_mode(struct aspeed_i2c_bus *bus)
+{
+	struct platform_device *pdev = to_platform_device(bus->dev);
+	const char *mode;
+	int ret;
+
+	mode = of_get_property(pdev->dev.of_node, "aspeed,i2c-xfer-mode", NULL);
+	if (!mode || strncasecmp(mode, "byte", 4) == 0)
+		return;
+
+	ret = bus->config->enable_sram();
+	if (!ret && !strncasecmp(mode, "buf", 3))
+		ret = bus->config->set_buf_xfer_mode(bus->dev);
+
+	if (ret)
+		dev_dbg(&pdev->dev, "Use default (byte) xfer mode\n");
+}
+
 static int aspeed_i2c_probe_bus(struct platform_device *pdev)
 {
 	const struct of_device_id *match;
 	struct aspeed_i2c_bus *bus;
 	struct clk *parent_clk;
-	struct resource *res;
 	int irq, ret;
 
 	bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
 	if (!bus)
 		return -ENOMEM;
 
-	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	bus->base = devm_ioremap_resource(&pdev->dev, res);
+	bus->base = devm_platform_ioremap_resource(pdev, 0);
 	if (IS_ERR(bus->base))
 		return PTR_ERR(bus->base);
 
+	bus->dev = &pdev->dev;
+	platform_set_drvdata(pdev, bus);
+
 	parent_clk = devm_clk_get(&pdev->dev, NULL);
 	if (IS_ERR(parent_clk))
 		return PTR_ERR(parent_clk);
@@ -1007,10 +1374,11 @@  static int aspeed_i2c_probe_bus(struct platform_device *pdev)
 
 	match = of_match_node(aspeed_i2c_bus_of_table, pdev->dev.of_node);
 	if (!match)
-		bus->get_clk_reg_val = aspeed_i2c_24xx_get_clk_reg_val;
-	else
-		bus->get_clk_reg_val = (u32 (*)(struct device *, u32))
-				match->data;
+		return -EINVAL;
+
+	bus->config = (struct aspeed_i2c_config *)match->data;
+
+	aspeed_i2c_set_xfer_mode(bus);
 
 	/* Initialize the I2C adapter */
 	spin_lock_init(&bus->lock);
@@ -1023,8 +1391,6 @@  static int aspeed_i2c_probe_bus(struct platform_device *pdev)
 	strlcpy(bus->adap.name, pdev->name, sizeof(bus->adap.name));
 	i2c_set_adapdata(&bus->adap, bus);
 
-	bus->dev = &pdev->dev;
-
 	/* Clean up any left over interrupt state. */
 	writel(0, bus->base + ASPEED_I2C_INTR_CTRL_REG);
 	writel(0xffffffff, bus->base + ASPEED_I2C_INTR_STS_REG);
@@ -1046,10 +1412,8 @@  static int aspeed_i2c_probe_bus(struct platform_device *pdev)
 	if (ret < 0)
 		return ret;
 
-	platform_set_drvdata(pdev, bus);
-
-	dev_info(bus->dev, "i2c bus %d registered, irq %d\n",
-		 bus->adap.nr, irq);
+	dev_info(bus->dev, "i2c bus %d registered (%s mode), irq %d\n",
+		 bus->adap.nr, bus->buf_base ? "buf" : "byte", irq);
 
 	return 0;
 }