diff mbox series

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

Message ID 20210224191720.7724-5-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 commit adds DMA mode transfer support.

Only AST2500 and later versions support DMA mode.

AST2500 has these restrictions:
  - If one of these controllers is enabled
      * UHCI host controller
      * MCTP controller
    I2C has to use buffer mode or byte mode instead
    since these controllers run only in DMA mode and
    I2C is sharing the same DMA H/W with them.
  - If one of these controllers uses DMA mode, I2C
    can't use DMA mode
      * SD/eMMC
      * Port80 snoop

Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
---
Changes since v3:
- None

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

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

 drivers/i2c/busses/i2c-aspeed.c | 265 ++++++++++++++++++++++++++------
 1 file changed, 216 insertions(+), 49 deletions(-)

Comments

Brendan Higgins April 13, 2021, 9:32 p.m. UTC | #1
On Wed, Feb 24, 2021 at 11:04 AM Jae Hyun Yoo
<jae.hyun.yoo@linux.intel.com> wrote:
>

> This commit adds DMA mode transfer support.

>

> Only AST2500 and later versions support DMA mode.

>

> AST2500 has these restrictions:

>   - If one of these controllers is enabled

>       * UHCI host controller

>       * MCTP controller

>     I2C has to use buffer mode or byte mode instead

>     since these controllers run only in DMA mode and

>     I2C is sharing the same DMA H/W with them.

>   - If one of these controllers uses DMA mode, I2C

>     can't use DMA mode

>       * SD/eMMC

>       * Port80 snoop

>

> Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>

> ---

> Changes since v3:

> - None

>

> Changes since v2:

> - Refined SoC family dependent xfer mode configuration functions.

>

> Changes since v1:

> - Updated commit message and comments.

> - Refined using abstract functions.

>

>  drivers/i2c/busses/i2c-aspeed.c | 265 ++++++++++++++++++++++++++------

>  1 file changed, 216 insertions(+), 49 deletions(-)

>

> diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c

> index ffc52937df26..3e3bb014b027 100644

> --- a/drivers/i2c/busses/i2c-aspeed.c

> +++ b/drivers/i2c/busses/i2c-aspeed.c

> @@ -10,6 +10,8 @@

>  #include <linux/bitfield.h>

>  #include <linux/clk.h>

>  #include <linux/completion.h>

> +#include <linux/dma-mapping.h>

> +#include <linux/dmapool.h>

>  #include <linux/err.h>

>  #include <linux/errno.h>

>  #include <linux/i2c.h>

> @@ -47,6 +49,8 @@

>  #define ASPEED_I2C_DEV_ADDR_REG                                0x18

>  #define ASPEED_I2C_BUF_CTRL_REG                                0x1c

>  #define ASPEED_I2C_BYTE_BUF_REG                                0x20

> +#define ASPEED_I2C_DMA_ADDR_REG                                0x24

> +#define ASPEED_I2C_DMA_LEN_REG                         0x28

>

>  /* Device Register Definition */

>  /* 0x00 : I2CD Function Control Register  */

> @@ -111,6 +115,8 @@

>  #define ASPEED_I2CD_BUS_RECOVER_CMD                    BIT(11)

>

>  /* Command Bit */

> +#define ASPEED_I2CD_RX_DMA_ENABLE                      BIT(9)

> +#define ASPEED_I2CD_TX_DMA_ENABLE                      BIT(8)

>  #define ASPEED_I2CD_RX_BUFF_ENABLE                     BIT(7)

>  #define ASPEED_I2CD_TX_BUFF_ENABLE                     BIT(6)

>  #define ASPEED_I2CD_M_STOP_CMD                         BIT(5)

> @@ -136,6 +142,14 @@

>  #define ASPEED_I2CD_BUF_TX_COUNT_MASK                  GENMASK(15, 8)

>  #define ASPEED_I2CD_BUF_OFFSET_MASK                    GENMASK(5, 0)

>

> +/* 0x24 : I2CD DMA Mode Buffer Address Register */

> +#define ASPEED_I2CD_DMA_ADDR_MASK                      GENMASK(31, 2)

> +#define ASPEED_I2CD_DMA_ALIGN                          4

> +

> +/* 0x28 : I2CD DMA Transfer Length Register */

> +#define ASPEED_I2CD_DMA_LEN_SHIFT                      0

> +#define ASPEED_I2CD_DMA_LEN_MASK                       GENMASK(11, 0)

> +

>  enum aspeed_i2c_master_state {

>         ASPEED_I2C_MASTER_INACTIVE,

>         ASPEED_I2C_MASTER_PENDING,

> @@ -161,6 +175,7 @@ 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);

> +       int (*set_dma_xfer_mode)(struct device *dev);

>  };

>

>  struct aspeed_i2c_bus {

> @@ -190,6 +205,12 @@ struct aspeed_i2c_bus {

>         void __iomem                    *buf_base;

>         u8                              buf_offset;

>         u8                              buf_page;

> +       /* DMA mode */

> +       struct dma_pool                 *dma_pool;

> +       dma_addr_t                      dma_handle;

> +       u8                              *dma_buf;

> +       size_t                          dma_len;

> +       /* Buffer/DMA mode */

>         size_t                          buf_size;

>  #if IS_ENABLED(CONFIG_I2C_SLAVE)

>         struct i2c_client               *slave;

> @@ -272,9 +293,13 @@ static inline void

>  aspeed_i2c_slave_handle_rx_done(struct aspeed_i2c_bus *bus, u32 irq_status,

>                                 u8 *value)

>  {

> -       if (bus->buf_base &&

> +       if (bus->dma_buf &&

>             bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED &&

>             !(irq_status & ASPEED_I2CD_INTR_NORMAL_STOP))

> +               *value = bus->dma_buf[0];

> +       else 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;

> @@ -288,7 +313,18 @@ aspeed_i2c_slave_handle_normal_stop(struct aspeed_i2c_bus *bus, u32 irq_status,

>

>         if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED &&

>             irq_status & ASPEED_I2CD_INTR_RX_DONE) {

> -               if (bus->buf_base) {

> +               if (bus->dma_buf) {

> +                       len = bus->buf_size -

> +                             FIELD_GET(ASPEED_I2CD_DMA_LEN_MASK,

> +                                       readl(bus->base +

> +                                             ASPEED_I2C_DMA_LEN_REG));

> +                       for (i = 0; i < len; i++) {

> +                               *value = bus->dma_buf[i];

> +                               i2c_slave_event(bus->slave,

> +                                               I2C_SLAVE_WRITE_RECEIVED,

> +                                               value);

> +                       }

> +               } else if (bus->buf_base) {

>                         len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK,

>                                         readl(bus->base +

>                                               ASPEED_I2C_BUF_CTRL_REG));

> @@ -305,7 +341,14 @@ aspeed_i2c_slave_handle_normal_stop(struct aspeed_i2c_bus *bus, u32 irq_status,

>  static inline void

>  aspeed_i2c_slave_handle_write_requested(struct aspeed_i2c_bus *bus, u8 *value)

>  {

> -       if (bus->buf_base) {

> +       if (bus->dma_buf) {

> +               writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,

> +                      bus->base + ASPEED_I2C_DMA_ADDR_REG);

> +               writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, bus->buf_size),

> +                      bus->base + ASPEED_I2C_DMA_LEN_REG);

> +               writel(ASPEED_I2CD_RX_DMA_ENABLE,

> +                      bus->base + ASPEED_I2C_CMD_REG);

> +       } else if (bus->buf_base) {

>                 writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK,

>                                   bus->buf_size - 1) |

>                        FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK,

> @@ -321,7 +364,23 @@ aspeed_i2c_slave_handle_write_received(struct aspeed_i2c_bus *bus, u8 *value)

>  {

>         int i, len;

>

> -       if (bus->buf_base) {

> +       if (bus->dma_buf) {

> +               len = bus->buf_size -

> +                     FIELD_GET(ASPEED_I2CD_DMA_LEN_MASK,

> +                               readl(bus->base +

> +                                     ASPEED_I2C_DMA_LEN_REG));

> +               for (i = 1; i < len; i++) {

> +                       *value = bus->dma_buf[i];

> +                       i2c_slave_event(bus->slave, I2C_SLAVE_WRITE_RECEIVED,

> +                                       value);

> +               }

> +               writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,

> +                      bus->base + ASPEED_I2C_DMA_ADDR_REG);

> +               writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, bus->buf_size),

> +                      bus->base + ASPEED_I2C_DMA_LEN_REG);

> +               writel(ASPEED_I2CD_RX_DMA_ENABLE,

> +                      bus->base + ASPEED_I2C_CMD_REG);

> +       } else if (bus->buf_base) {

>                 len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK,

>                                 readl(bus->base +

>                                       ASPEED_I2C_BUF_CTRL_REG));

> @@ -451,7 +510,15 @@ aspeed_i2c_prepare_rx_buf(struct aspeed_i2c_bus *bus, struct i2c_msg *msg)

>                 command |= ASPEED_I2CD_M_S_RX_CMD_LAST;

>         }

>

> -       if (bus->buf_base) {

> +       if (bus->dma_buf) {

> +               command |= ASPEED_I2CD_RX_DMA_ENABLE;

> +

> +               writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,

> +                      bus->base + ASPEED_I2C_DMA_ADDR_REG);

> +               writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, len),

> +                      bus->base + ASPEED_I2C_DMA_LEN_REG);

> +               bus->dma_len = len;

> +       } else {

>                 command |= ASPEED_I2CD_RX_BUFF_ENABLE;

>

>                 writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK, len - 1) |

> @@ -474,7 +541,18 @@ aspeed_i2c_prepare_tx_buf(struct aspeed_i2c_bus *bus, struct i2c_msg *msg)

>         else

>                 len = msg->len + 1;

>

> -       if (bus->buf_base) {

> +       if (bus->dma_buf) {

> +               command |= ASPEED_I2CD_TX_DMA_ENABLE;

> +

> +               bus->dma_buf[0] = slave_addr;

> +               memcpy(bus->dma_buf + 1, msg->buf, len);

> +

> +               writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,

> +                      bus->base + ASPEED_I2C_DMA_ADDR_REG);

> +               writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, len),

> +                      bus->base + ASPEED_I2C_DMA_LEN_REG);

> +               bus->dma_len = len;

> +       } else {

>                 u8 wbuf[4];

>                 int i;

>

> @@ -527,18 +605,19 @@ static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus)

>         if (msg->flags & I2C_M_RD) {

>                 command |= ASPEED_I2CD_M_RX_CMD;

>                 if (!(msg->flags & I2C_M_RECV_LEN)) {

> -                       if (msg->len && bus->buf_base)

> +                       if (msg->len && (bus->dma_buf || 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) {

> +       } else if (msg->len && (bus->dma_buf || bus->buf_base)) {

>                 command |= aspeed_i2c_prepare_tx_buf(bus, msg);

>         }

>

> -       if (!(command & ASPEED_I2CD_TX_BUFF_ENABLE))

> +       if (!(command & (ASPEED_I2CD_TX_BUFF_ENABLE |

> +                        ASPEED_I2CD_TX_DMA_ENABLE)))

>                 writel(i2c_8bit_addr_from_msg(msg),

>                        bus->base + ASPEED_I2C_BYTE_BUF_REG);

>         writel(command, bus->base + ASPEED_I2C_CMD_REG);

> @@ -581,42 +660,55 @@ aspeed_i2c_master_handle_tx_first(struct aspeed_i2c_bus *bus,

>  {

>         u32 command = 0;

>

> -       if (bus->buf_base) {

> -               u8 wbuf[4];

> +       if (bus->dma_buf || bus->buf_base) {

>                 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 (bus->dma_buf) {

> +                       command |= ASPEED_I2CD_TX_DMA_ENABLE;

>

> -               if (msg->len - bus->buf_index > bus->buf_size)

> -                       len = bus->buf_size;

> -               else

> -                       len = msg->len - bus->buf_index;

> +                       memcpy(bus->dma_buf, msg->buf + bus->buf_index, len);

>

> -               /*

> -                * 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(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,

> +                              bus->base + ASPEED_I2C_DMA_ADDR_REG);

> +                       writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, len),

> +                              bus->base + ASPEED_I2C_DMA_LEN_REG);

> +                       bus->dma_len = len;

> +               } else {

> +                       u8 wbuf[4];

> +                       int i;

> +

> +                       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 - 3);

> -               }

> -               if (--i % 4 != 3)

> -                       writel(*(u32 *)wbuf,

> -                              bus->buf_base + i - (i % 4));

> +                                      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);

> +                       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 {


Some of these functions are getting really complex and most of the
logic for the different modes is in different if-else blocks. Could
you look into splitting this into separate functions based on which
mode is being used?

Otherwise, this patch looks good.

> @@ -633,7 +725,14 @@ aspeed_i2c_master_handle_rx(struct aspeed_i2c_bus *bus, struct i2c_msg *msg)

>         u8 recv_byte;

>         int len;

>

> -       if (bus->buf_base) {

> +       if (bus->dma_buf) {

> +               len = bus->dma_len -

> +                     FIELD_GET(ASPEED_I2CD_DMA_LEN_MASK,

> +                               readl(bus->base + ASPEED_I2C_DMA_LEN_REG));

> +

> +               memcpy(msg->buf + bus->buf_index, bus->dma_buf, len);

> +               bus->buf_index += len;

> +       } else 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);

> @@ -650,7 +749,7 @@ aspeed_i2c_master_handle_rx_next(struct aspeed_i2c_bus *bus,

>  {

>         u32 command = 0;

>

> -       if (bus->buf_base) {

> +       if (bus->dma_buf || bus->buf_base) {

>                 int len;

>

>                 if (msg->len - bus->buf_index > bus->buf_size) {

> @@ -660,14 +759,24 @@ aspeed_i2c_master_handle_rx_next(struct aspeed_i2c_bus *bus,

>                         command |= ASPEED_I2CD_M_S_RX_CMD_LAST;

>                 }

>

> -               command |= ASPEED_I2CD_RX_BUFF_ENABLE;

> +               if (bus->dma_buf) {

> +                       command |= ASPEED_I2CD_RX_DMA_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);

> +                       writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,

> +                              bus->base + ASPEED_I2C_DMA_ADDR_REG);

> +                       writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, len),

> +                              bus->base + ASPEED_I2C_DMA_LEN_REG);

> +                       bus->dma_len = len;

> +               } else {

> +                       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;

> @@ -1287,22 +1396,63 @@ static int aspeed_i2c_25xx_set_buf_xfer_mode(struct device *dev)

>         return bus->buf_size ? 0 : -EINVAL;

>  }

>

> +static int aspeed_i2c_24xx_set_dma_xfer_mode(struct device *dev)

> +{

> +       /* AST24xx doesn't support DMA mode */

> +

> +       return -EBADR;

> +}

> +

> +static int aspeed_i2c_25xx_set_dma_xfer_mode(struct device *dev)

> +{

> +       struct platform_device *pdev = to_platform_device(dev);

> +       struct aspeed_i2c_bus *bus = platform_get_drvdata(pdev);

> +       int ret;

> +

> +       ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));

> +       if (!ret) {

> +               bus->buf_size = ASPEED_I2CD_DMA_LEN_MASK >>

> +                               ASPEED_I2CD_DMA_LEN_SHIFT;

> +               bus->dma_pool = dma_pool_create("i2c-aspeed",

> +                                               &pdev->dev,

> +                                               bus->buf_size,

> +                                               ASPEED_I2CD_DMA_ALIGN,

> +                                               0);

> +               if (bus->dma_pool)

> +                       bus->dma_buf = dma_pool_alloc(bus->dma_pool,

> +                                                     GFP_KERNEL,

> +                                                     &bus->dma_handle);

> +

> +               if (!bus->dma_buf) {

> +                       ret = -ENOMEM;

> +                       bus->buf_size = 0;

> +                       dev_dbg(&pdev->dev, "Cannot allocate DMA buffer\n");

> +                       dma_pool_destroy(bus->dma_pool);

> +               }

> +       }

> +

> +       return ret;

> +}

> +

>  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,

> +       .set_dma_xfer_mode = aspeed_i2c_24xx_set_dma_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,

> +       .set_dma_xfer_mode = aspeed_i2c_25xx_set_dma_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,

> +       .set_dma_xfer_mode = aspeed_i2c_25xx_set_dma_xfer_mode,

>  };

>

>  static const struct of_device_id aspeed_i2c_bus_of_table[] = {

> @@ -1324,8 +1474,12 @@ static void aspeed_i2c_set_xfer_mode(struct aspeed_i2c_bus *bus)

>                 return;

>

>         ret = bus->config->enable_sram();

> -       if (!ret && !strncasecmp(mode, "buf", 3))

> -               ret = bus->config->set_buf_xfer_mode(bus->dev);

> +       if (!ret) {

> +               if (!strncasecmp(mode, "buf", 3))

> +                       ret = bus->config->set_buf_xfer_mode(bus->dev);

> +               else if (!strncasecmp(mode, "dma", 3))

> +                       ret = bus->config->set_dma_xfer_mode(bus->dev);

> +       }

>

>         if (ret)

>                 dev_dbg(&pdev->dev, "Use default (byte) xfer mode\n");

> @@ -1400,22 +1554,31 @@ static int aspeed_i2c_probe_bus(struct platform_device *pdev)

>          */

>         ret = aspeed_i2c_init(bus, pdev);

>         if (ret < 0)

> -               return ret;

> +               goto out_free_dma_buf;

>

>         irq = irq_of_parse_and_map(pdev->dev.of_node, 0);

>         ret = devm_request_irq(&pdev->dev, irq, aspeed_i2c_bus_irq,

>                                0, dev_name(&pdev->dev), bus);

>         if (ret < 0)

> -               return ret;

> +               goto out_free_dma_buf;

>

>         ret = i2c_add_adapter(&bus->adap);

>         if (ret < 0)

> -               return ret;

> +               goto out_free_dma_buf;

>

>         dev_info(bus->dev, "i2c bus %d registered (%s mode), irq %d\n",

> -                bus->adap.nr, bus->buf_base ? "buf" : "byte", irq);

> +                bus->adap.nr, bus->dma_buf ? "dma" :

> +                                             bus->buf_base ? "buf" : "byte",

> +                irq);

>

>         return 0;

> +

> +out_free_dma_buf:

> +       if (bus->dma_buf)

> +               dma_pool_free(bus->dma_pool, bus->dma_buf, bus->dma_handle);

> +       dma_pool_destroy(bus->dma_pool);

> +

> +       return ret;

>  }

>

>  static int aspeed_i2c_remove_bus(struct platform_device *pdev)

> @@ -1433,6 +1596,10 @@ static int aspeed_i2c_remove_bus(struct platform_device *pdev)

>

>         reset_control_assert(bus->rst);

>

> +       if (bus->dma_buf)

> +               dma_pool_free(bus->dma_pool, bus->dma_buf, bus->dma_handle);

> +       dma_pool_destroy(bus->dma_pool);

> +

>         i2c_del_adapter(&bus->adap);

>

>         return 0;

> --

> 2.17.1

>
Jae Hyun Yoo May 19, 2021, 6:38 p.m. UTC | #2
Hi Brendan,

On 4/14/2021 8:08 AM, Jae Hyun Yoo wrote:

[....]

>>> @@ -581,42 +660,55 @@ aspeed_i2c_master_handle_tx_first(struct 

>>> aspeed_i2c_bus *bus,

>>>   {

>>>          u32 command = 0;

>>>

>>> -       if (bus->buf_base) {

>>> -               u8 wbuf[4];

>>> +       if (bus->dma_buf || bus->buf_base) {

>>>                  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 (bus->dma_buf) {

>>> +                       command |= ASPEED_I2CD_TX_DMA_ENABLE;

>>>

>>> -               if (msg->len - bus->buf_index > bus->buf_size)

>>> -                       len = bus->buf_size;

>>> -               else

>>> -                       len = msg->len - bus->buf_index;

>>> +                       memcpy(bus->dma_buf, msg->buf + 

>>> bus->buf_index, len);

>>>

>>> -               /*

>>> -                * 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(bus->dma_handle & 

>>> ASPEED_I2CD_DMA_ADDR_MASK,

>>> +                              bus->base + ASPEED_I2C_DMA_ADDR_REG);

>>> +                       writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, 

>>> len),

>>> +                              bus->base + ASPEED_I2C_DMA_LEN_REG);

>>> +                       bus->dma_len = len;

>>> +               } else {

>>> +                       u8 wbuf[4];

>>> +                       int i;

>>> +

>>> +                       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 - 3);

>>> -               }

>>> -               if (--i % 4 != 3)

>>> -                       writel(*(u32 *)wbuf,

>>> -                              bus->buf_base + i - (i % 4));

>>> +                                      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);

>>> +                       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 {

>>

>> Some of these functions are getting really complex and most of the

>> logic for the different modes is in different if-else blocks. Could

>> you look into splitting this into separate functions based on which

>> mode is being used?

>>

>> Otherwise, this patch looks good.

> 

> I already splitted some big chunk of mode dependant logics to address

> your comment on v1. Could you please check again the patched result of

> this function? It's not much complex and it'd be better keep as is for

> consistency in other changes in this patch. I think, splitting it again

> would be not good for readability of the code flow.

> 

> Thanks,

> Jae

> 


This is the patched result of this function:

static inline u32
aspeed_i2c_master_handle_tx_first(struct aspeed_i2c_bus *bus,
				  struct i2c_msg *msg)
{
	u32 command = 0;

	if (bus->dma_buf || 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;

		if (bus->dma_buf) {
			command |= ASPEED_I2CD_TX_DMA_ENABLE;

			memcpy(bus->dma_buf, msg->buf + bus->buf_index, len);


			writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,
			       bus->base + ASPEED_I2C_DMA_ADDR_REG);
			writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, len),
			       bus->base + ASPEED_I2C_DMA_LEN_REG);
			bus->dma_len = len;
		} else {
			u8 wbuf[4];
			int i;

			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;

			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;
}

Do you still think that it should be split into separate functions per
each transfer mode?

Thanks,
Jae

[....]
diff mbox series

Patch

diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
index ffc52937df26..3e3bb014b027 100644
--- a/drivers/i2c/busses/i2c-aspeed.c
+++ b/drivers/i2c/busses/i2c-aspeed.c
@@ -10,6 +10,8 @@ 
 #include <linux/bitfield.h>
 #include <linux/clk.h>
 #include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
 #include <linux/err.h>
 #include <linux/errno.h>
 #include <linux/i2c.h>
@@ -47,6 +49,8 @@ 
 #define ASPEED_I2C_DEV_ADDR_REG				0x18
 #define ASPEED_I2C_BUF_CTRL_REG				0x1c
 #define ASPEED_I2C_BYTE_BUF_REG				0x20
+#define ASPEED_I2C_DMA_ADDR_REG				0x24
+#define ASPEED_I2C_DMA_LEN_REG				0x28
 
 /* Device Register Definition */
 /* 0x00 : I2CD Function Control Register  */
@@ -111,6 +115,8 @@ 
 #define ASPEED_I2CD_BUS_RECOVER_CMD			BIT(11)
 
 /* Command Bit */
+#define ASPEED_I2CD_RX_DMA_ENABLE			BIT(9)
+#define ASPEED_I2CD_TX_DMA_ENABLE			BIT(8)
 #define ASPEED_I2CD_RX_BUFF_ENABLE			BIT(7)
 #define ASPEED_I2CD_TX_BUFF_ENABLE			BIT(6)
 #define ASPEED_I2CD_M_STOP_CMD				BIT(5)
@@ -136,6 +142,14 @@ 
 #define ASPEED_I2CD_BUF_TX_COUNT_MASK			GENMASK(15, 8)
 #define ASPEED_I2CD_BUF_OFFSET_MASK			GENMASK(5, 0)
 
+/* 0x24 : I2CD DMA Mode Buffer Address Register */
+#define ASPEED_I2CD_DMA_ADDR_MASK			GENMASK(31, 2)
+#define ASPEED_I2CD_DMA_ALIGN				4
+
+/* 0x28 : I2CD DMA Transfer Length Register */
+#define ASPEED_I2CD_DMA_LEN_SHIFT			0
+#define ASPEED_I2CD_DMA_LEN_MASK			GENMASK(11, 0)
+
 enum aspeed_i2c_master_state {
 	ASPEED_I2C_MASTER_INACTIVE,
 	ASPEED_I2C_MASTER_PENDING,
@@ -161,6 +175,7 @@  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);
+	int (*set_dma_xfer_mode)(struct device *dev);
 };
 
 struct aspeed_i2c_bus {
@@ -190,6 +205,12 @@  struct aspeed_i2c_bus {
 	void __iomem			*buf_base;
 	u8				buf_offset;
 	u8				buf_page;
+	/* DMA mode */
+	struct dma_pool			*dma_pool;
+	dma_addr_t			dma_handle;
+	u8				*dma_buf;
+	size_t				dma_len;
+	/* Buffer/DMA mode */
 	size_t				buf_size;
 #if IS_ENABLED(CONFIG_I2C_SLAVE)
 	struct i2c_client		*slave;
@@ -272,9 +293,13 @@  static inline void
 aspeed_i2c_slave_handle_rx_done(struct aspeed_i2c_bus *bus, u32 irq_status,
 				u8 *value)
 {
-	if (bus->buf_base &&
+	if (bus->dma_buf &&
 	    bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED &&
 	    !(irq_status & ASPEED_I2CD_INTR_NORMAL_STOP))
+		*value = bus->dma_buf[0];
+	else 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;
@@ -288,7 +313,18 @@  aspeed_i2c_slave_handle_normal_stop(struct aspeed_i2c_bus *bus, u32 irq_status,
 
 	if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED &&
 	    irq_status & ASPEED_I2CD_INTR_RX_DONE) {
-		if (bus->buf_base) {
+		if (bus->dma_buf) {
+			len = bus->buf_size -
+			      FIELD_GET(ASPEED_I2CD_DMA_LEN_MASK,
+					readl(bus->base +
+					      ASPEED_I2C_DMA_LEN_REG));
+			for (i = 0; i < len; i++) {
+				*value = bus->dma_buf[i];
+				i2c_slave_event(bus->slave,
+						I2C_SLAVE_WRITE_RECEIVED,
+						value);
+			}
+		} else if (bus->buf_base) {
 			len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK,
 					readl(bus->base +
 					      ASPEED_I2C_BUF_CTRL_REG));
@@ -305,7 +341,14 @@  aspeed_i2c_slave_handle_normal_stop(struct aspeed_i2c_bus *bus, u32 irq_status,
 static inline void
 aspeed_i2c_slave_handle_write_requested(struct aspeed_i2c_bus *bus, u8 *value)
 {
-	if (bus->buf_base) {
+	if (bus->dma_buf) {
+		writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,
+		       bus->base + ASPEED_I2C_DMA_ADDR_REG);
+		writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, bus->buf_size),
+		       bus->base + ASPEED_I2C_DMA_LEN_REG);
+		writel(ASPEED_I2CD_RX_DMA_ENABLE,
+		       bus->base + ASPEED_I2C_CMD_REG);
+	} else if (bus->buf_base) {
 		writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK,
 				  bus->buf_size - 1) |
 		       FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK,
@@ -321,7 +364,23 @@  aspeed_i2c_slave_handle_write_received(struct aspeed_i2c_bus *bus, u8 *value)
 {
 	int i, len;
 
-	if (bus->buf_base) {
+	if (bus->dma_buf) {
+		len = bus->buf_size -
+		      FIELD_GET(ASPEED_I2CD_DMA_LEN_MASK,
+				readl(bus->base +
+				      ASPEED_I2C_DMA_LEN_REG));
+		for (i = 1; i < len; i++) {
+			*value = bus->dma_buf[i];
+			i2c_slave_event(bus->slave, I2C_SLAVE_WRITE_RECEIVED,
+					value);
+		}
+		writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,
+		       bus->base + ASPEED_I2C_DMA_ADDR_REG);
+		writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, bus->buf_size),
+		       bus->base + ASPEED_I2C_DMA_LEN_REG);
+		writel(ASPEED_I2CD_RX_DMA_ENABLE,
+		       bus->base + ASPEED_I2C_CMD_REG);
+	} else if (bus->buf_base) {
 		len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK,
 				readl(bus->base +
 				      ASPEED_I2C_BUF_CTRL_REG));
@@ -451,7 +510,15 @@  aspeed_i2c_prepare_rx_buf(struct aspeed_i2c_bus *bus, struct i2c_msg *msg)
 		command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
 	}
 
-	if (bus->buf_base) {
+	if (bus->dma_buf) {
+		command |= ASPEED_I2CD_RX_DMA_ENABLE;
+
+		writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,
+		       bus->base + ASPEED_I2C_DMA_ADDR_REG);
+		writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, len),
+		       bus->base + ASPEED_I2C_DMA_LEN_REG);
+		bus->dma_len = len;
+	} else {
 		command |= ASPEED_I2CD_RX_BUFF_ENABLE;
 
 		writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK, len - 1) |
@@ -474,7 +541,18 @@  aspeed_i2c_prepare_tx_buf(struct aspeed_i2c_bus *bus, struct i2c_msg *msg)
 	else
 		len = msg->len + 1;
 
-	if (bus->buf_base) {
+	if (bus->dma_buf) {
+		command |= ASPEED_I2CD_TX_DMA_ENABLE;
+
+		bus->dma_buf[0] = slave_addr;
+		memcpy(bus->dma_buf + 1, msg->buf, len);
+
+		writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,
+		       bus->base + ASPEED_I2C_DMA_ADDR_REG);
+		writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, len),
+		       bus->base + ASPEED_I2C_DMA_LEN_REG);
+		bus->dma_len = len;
+	} else {
 		u8 wbuf[4];
 		int i;
 
@@ -527,18 +605,19 @@  static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus)
 	if (msg->flags & I2C_M_RD) {
 		command |= ASPEED_I2CD_M_RX_CMD;
 		if (!(msg->flags & I2C_M_RECV_LEN)) {
-			if (msg->len && bus->buf_base)
+			if (msg->len && (bus->dma_buf || 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) {
+	} else if (msg->len && (bus->dma_buf || bus->buf_base)) {
 		command |= aspeed_i2c_prepare_tx_buf(bus, msg);
 	}
 
-	if (!(command & ASPEED_I2CD_TX_BUFF_ENABLE))
+	if (!(command & (ASPEED_I2CD_TX_BUFF_ENABLE |
+			 ASPEED_I2CD_TX_DMA_ENABLE)))
 		writel(i2c_8bit_addr_from_msg(msg),
 		       bus->base + ASPEED_I2C_BYTE_BUF_REG);
 	writel(command, bus->base + ASPEED_I2C_CMD_REG);
@@ -581,42 +660,55 @@  aspeed_i2c_master_handle_tx_first(struct aspeed_i2c_bus *bus,
 {
 	u32 command = 0;
 
-	if (bus->buf_base) {
-		u8 wbuf[4];
+	if (bus->dma_buf || bus->buf_base) {
 		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 (bus->dma_buf) {
+			command |= ASPEED_I2CD_TX_DMA_ENABLE;
 
-		if (msg->len - bus->buf_index > bus->buf_size)
-			len = bus->buf_size;
-		else
-			len = msg->len - bus->buf_index;
+			memcpy(bus->dma_buf, msg->buf + bus->buf_index, len);
 
-		/*
-		 * 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(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,
+			       bus->base + ASPEED_I2C_DMA_ADDR_REG);
+			writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, len),
+			       bus->base + ASPEED_I2C_DMA_LEN_REG);
+			bus->dma_len = len;
+		} else {
+			u8 wbuf[4];
+			int i;
+
+			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 - 3);
-		}
-		if (--i % 4 != 3)
-			writel(*(u32 *)wbuf,
-			       bus->buf_base + i - (i % 4));
+				       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);
+			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 {
@@ -633,7 +725,14 @@  aspeed_i2c_master_handle_rx(struct aspeed_i2c_bus *bus, struct i2c_msg *msg)
 	u8 recv_byte;
 	int len;
 
-	if (bus->buf_base) {
+	if (bus->dma_buf) {
+		len = bus->dma_len -
+		      FIELD_GET(ASPEED_I2CD_DMA_LEN_MASK,
+				readl(bus->base + ASPEED_I2C_DMA_LEN_REG));
+
+		memcpy(msg->buf + bus->buf_index, bus->dma_buf, len);
+		bus->buf_index += len;
+	} else 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);
@@ -650,7 +749,7 @@  aspeed_i2c_master_handle_rx_next(struct aspeed_i2c_bus *bus,
 {
 	u32 command = 0;
 
-	if (bus->buf_base) {
+	if (bus->dma_buf || bus->buf_base) {
 		int len;
 
 		if (msg->len - bus->buf_index > bus->buf_size) {
@@ -660,14 +759,24 @@  aspeed_i2c_master_handle_rx_next(struct aspeed_i2c_bus *bus,
 			command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
 		}
 
-		command |= ASPEED_I2CD_RX_BUFF_ENABLE;
+		if (bus->dma_buf) {
+			command |= ASPEED_I2CD_RX_DMA_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);
+			writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK,
+			       bus->base + ASPEED_I2C_DMA_ADDR_REG);
+			writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, len),
+			       bus->base + ASPEED_I2C_DMA_LEN_REG);
+			bus->dma_len = len;
+		} else {
+			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;
@@ -1287,22 +1396,63 @@  static int aspeed_i2c_25xx_set_buf_xfer_mode(struct device *dev)
 	return bus->buf_size ? 0 : -EINVAL;
 }
 
+static int aspeed_i2c_24xx_set_dma_xfer_mode(struct device *dev)
+{
+	/* AST24xx doesn't support DMA mode */
+
+	return -EBADR;
+}
+
+static int aspeed_i2c_25xx_set_dma_xfer_mode(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct aspeed_i2c_bus *bus = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
+	if (!ret) {
+		bus->buf_size = ASPEED_I2CD_DMA_LEN_MASK >>
+				ASPEED_I2CD_DMA_LEN_SHIFT;
+		bus->dma_pool = dma_pool_create("i2c-aspeed",
+						&pdev->dev,
+						bus->buf_size,
+						ASPEED_I2CD_DMA_ALIGN,
+						0);
+		if (bus->dma_pool)
+			bus->dma_buf = dma_pool_alloc(bus->dma_pool,
+						      GFP_KERNEL,
+						      &bus->dma_handle);
+
+		if (!bus->dma_buf) {
+			ret = -ENOMEM;
+			bus->buf_size = 0;
+			dev_dbg(&pdev->dev, "Cannot allocate DMA buffer\n");
+			dma_pool_destroy(bus->dma_pool);
+		}
+	}
+
+	return ret;
+}
+
 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,
+	.set_dma_xfer_mode = aspeed_i2c_24xx_set_dma_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,
+	.set_dma_xfer_mode = aspeed_i2c_25xx_set_dma_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,
+	.set_dma_xfer_mode = aspeed_i2c_25xx_set_dma_xfer_mode,
 };
 
 static const struct of_device_id aspeed_i2c_bus_of_table[] = {
@@ -1324,8 +1474,12 @@  static void aspeed_i2c_set_xfer_mode(struct aspeed_i2c_bus *bus)
 		return;
 
 	ret = bus->config->enable_sram();
-	if (!ret && !strncasecmp(mode, "buf", 3))
-		ret = bus->config->set_buf_xfer_mode(bus->dev);
+	if (!ret) {
+		if (!strncasecmp(mode, "buf", 3))
+			ret = bus->config->set_buf_xfer_mode(bus->dev);
+		else if (!strncasecmp(mode, "dma", 3))
+			ret = bus->config->set_dma_xfer_mode(bus->dev);
+	}
 
 	if (ret)
 		dev_dbg(&pdev->dev, "Use default (byte) xfer mode\n");
@@ -1400,22 +1554,31 @@  static int aspeed_i2c_probe_bus(struct platform_device *pdev)
 	 */
 	ret = aspeed_i2c_init(bus, pdev);
 	if (ret < 0)
-		return ret;
+		goto out_free_dma_buf;
 
 	irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
 	ret = devm_request_irq(&pdev->dev, irq, aspeed_i2c_bus_irq,
 			       0, dev_name(&pdev->dev), bus);
 	if (ret < 0)
-		return ret;
+		goto out_free_dma_buf;
 
 	ret = i2c_add_adapter(&bus->adap);
 	if (ret < 0)
-		return ret;
+		goto out_free_dma_buf;
 
 	dev_info(bus->dev, "i2c bus %d registered (%s mode), irq %d\n",
-		 bus->adap.nr, bus->buf_base ? "buf" : "byte", irq);
+		 bus->adap.nr, bus->dma_buf ? "dma" :
+					      bus->buf_base ? "buf" : "byte",
+		 irq);
 
 	return 0;
+
+out_free_dma_buf:
+	if (bus->dma_buf)
+		dma_pool_free(bus->dma_pool, bus->dma_buf, bus->dma_handle);
+	dma_pool_destroy(bus->dma_pool);
+
+	return ret;
 }
 
 static int aspeed_i2c_remove_bus(struct platform_device *pdev)
@@ -1433,6 +1596,10 @@  static int aspeed_i2c_remove_bus(struct platform_device *pdev)
 
 	reset_control_assert(bus->rst);
 
+	if (bus->dma_buf)
+		dma_pool_free(bus->dma_pool, bus->dma_buf, bus->dma_handle);
+	dma_pool_destroy(bus->dma_pool);
+
 	i2c_del_adapter(&bus->adap);
 
 	return 0;