summaryrefslogtreecommitdiff
path: root/drivers/i2c
diff options
context:
space:
mode:
authorJae Hyun Yoo <jae.hyun.yoo@intel.com>2019-06-18 18:47:50 +0300
committerJae Hyun Yoo <jae.hyun.yoo@linux.intel.com>2021-11-05 10:22:08 +0300
commit9ae84a5a6a44cf5625c088fa99876372c613ee63 (patch)
treea7849b4bc5f60400067c5cf4763bdc0a99a26886 /drivers/i2c
parent36b8d0f5c30ff7dea194ec18d32c5f07620202d6 (diff)
downloadlinux-9ae84a5a6a44cf5625c088fa99876372c613ee63.tar.xz
i2c: aspeed: add DMA mode transfer support
This commit adds DMA mode transfer support. Only AST2500 supports DMA mode under some limitations: I2C is sharing the DMA H/W with UHCI host controller and MCTP controller. Since those controllers operate with DMA mode only, I2C has to use buffer mode or byte mode instead if one of those controllers is enabled. Also make sure that if SD/eMMC or Port80 snoop uses DMA mode instead of PIO or FIFO respectively, I2C can't use DMA mode. Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com>
Diffstat (limited to 'drivers/i2c')
-rw-r--r--drivers/i2c/busses/i2c-aspeed.c253
1 files changed, 209 insertions, 44 deletions
diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
index ae9eed02e1df..3323b4f72374 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,
@@ -185,6 +199,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;
@@ -285,7 +305,11 @@ 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);
@@ -301,7 +325,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));
@@ -318,7 +353,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,
@@ -334,7 +376,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));
@@ -464,7 +522,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) |
@@ -487,7 +553,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;
@@ -540,18 +617,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);
@@ -594,39 +672,52 @@ 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;
+ if (bus->dma_buf) {
+ command |= ASPEED_I2CD_TX_DMA_ENABLE;
- command |= ASPEED_I2CD_TX_BUFF_ENABLE;
+ memcpy(bus->dma_buf, msg->buf + bus->buf_index, len);
- 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(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 - 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 {
@@ -643,7 +734,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);
@@ -660,7 +758,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) {
@@ -670,14 +768,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;
@@ -1272,7 +1380,51 @@ static void aspeed_i2c_set_xfer_mode(struct aspeed_i2c_bus *bus)
sram_enabled = false;
}
- if (sram_enabled) {
+ /*
+ * Only AST2500 and AST2600 support DMA mode under some limitations:
+ * I2C is sharing the DMA H/W with UHCI host controller and MCTP
+ * controller. Since those controllers operate with DMA mode only, I2C
+ * has to use buffer mode or byte mode instead if one of those
+ * controllers is enabled. Also make sure that if SD/eMMC or Port80
+ * snoop uses DMA mode instead of PIO or FIFO respectively, I2C can't
+ * use DMA mode.
+ */
+ if (sram_enabled && !IS_ENABLED(CONFIG_USB_UHCI_ASPEED) &&
+ !of_device_is_compatible(pdev->dev.of_node,
+ "aspeed,ast2400-i2c-bus")) {
+ u32 dma_len_max = ASPEED_I2CD_DMA_LEN_MASK >>
+ ASPEED_I2CD_DMA_LEN_SHIFT;
+
+ ret = device_property_read_u32(&pdev->dev,
+ "aspeed,dma-buf-size",
+ &bus->buf_size);
+ if (!ret && bus->buf_size > dma_len_max)
+ bus->buf_size = dma_len_max;
+ }
+
+ if (bus->buf_size) {
+ if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))) {
+ dev_warn(&pdev->dev, "No suitable DMA available\n");
+ } else {
+ 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) {
+ dev_warn(&pdev->dev,
+ "Cannot allocate DMA buffer\n");
+ dma_pool_destroy(bus->dma_pool);
+ }
+ }
+ }
+
+ if (!bus->dma_buf && sram_enabled) {
struct resource *res = platform_get_resource(pdev,
IORESOURCE_MEM, 1);
@@ -1377,24 +1529,33 @@ 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;
platform_set_drvdata(pdev, bus);
dev_info(bus->dev, "i2c bus %d registered (%s mode), irq %d\n",
- bus->adap.nr, bus->buf_base ? "buffer" : "byte", irq);
+ bus->adap.nr, bus->dma_buf ? "dma" :
+ bus->buf_base ? "buffer" : "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)
@@ -1412,6 +1573,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;