From efb710a6b3a39f28b988af717eefc1b72c4c43bd Mon Sep 17 00:00:00 2001 From: Jae Hyun Yoo Date: Tue, 11 Jun 2019 15:07:08 -0700 Subject: [PATCH] i2c: aspeed: add buffer mode transfer support Byte mode currently this driver uses makes lots of interrupt call 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. AST2400: It has 2 KBytes (256 Bytes x 8 pages) of I2C SRAM buffer pool from 0x1e78a800 to 0x1e78afff that can be used for all busses with buffer pool manipulation. To simplify implementation for supporting both AST2400 and AST2500, it assigns each 128 Bytes per bus without using buffer pool manipulation so total 1792 Bytes of I2C SRAM buffer will used. AST2500: It has 16 Bytes of individual I2C SRAM buffer per each bus and its range is from 0x1e78a200 to 0x1e78a2df, so it doesn't have 'buffer page selection' bit field in the Function control register, and neither 'base address pointer' bit field in the Pool buffer control register it has. To simplify implementation for supporting both AST2400 and AST2500, it writes zeros on those register bit fields but it's okay because it does nothing in AST2500. It provides buffer based master and slave data transfer. Signed-off-by: Jae Hyun Yoo --- arch/arm/boot/dts/aspeed-g4.dtsi | 42 ++++-- arch/arm/boot/dts/aspeed-g5.dtsi | 42 ++++-- drivers/i2c/busses/i2c-aspeed.c | 262 ++++++++++++++++++++++++++++++++---- drivers/irqchip/irq-aspeed-i2c-ic.c | 8 ++ 4 files changed, 301 insertions(+), 53 deletions(-) diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi index 47a5029f5bdb..c1c125add9fa 100644 --- a/arch/arm/boot/dts/aspeed-g4.dtsi +++ b/arch/arm/boot/dts/aspeed-g4.dtsi @@ -482,7 +482,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x40 0x40>; + reg = <0x40 0x40>, <0x800 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -498,7 +499,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x80 0x40>; + reg = <0x80 0x40>, <0x880 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -514,7 +516,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0xc0 0x40>; + reg = <0xc0 0x40>, <0x900 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -531,7 +534,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x100 0x40>; + reg = <0x100 0x40>, <0x980 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -548,7 +552,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x140 0x40>; + reg = <0x140 0x40>, <0xa00 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -565,7 +570,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x180 0x40>; + reg = <0x180 0x40>, <0xa80 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -582,7 +588,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x1c0 0x40>; + reg = <0x1c0 0x40>, <0xb00 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -599,7 +606,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x300 0x40>; + reg = <0x300 0x40>, <0xb80 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -616,7 +624,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x340 0x40>; + reg = <0x340 0x40>, <0xc00 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -633,7 +642,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x380 0x40>; + reg = <0x380 0x40>, <0xc80 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -650,7 +660,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x3c0 0x40>; + reg = <0x3c0 0x40>, <0xd00 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -667,7 +678,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x400 0x40>; + reg = <0x400 0x40>, <0xd80 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -684,7 +696,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x440 0x40>; + reg = <0x440 0x40>, <0xe00 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -701,7 +714,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x480 0x40>; + reg = <0x480 0x40>, <0xe80 0x80>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2400-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi index 9d5ed9499b1f..662249bc15f9 100644 --- a/arch/arm/boot/dts/aspeed-g5.dtsi +++ b/arch/arm/boot/dts/aspeed-g5.dtsi @@ -599,7 +599,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x40 0x40>; + reg = <0x40 0x40>, <0x200 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -615,7 +616,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x80 0x40>; + reg = <0x80 0x40>, <0x210 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -631,7 +633,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0xc0 0x40>; + reg = <0xc0 0x40>, <0x220 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -648,7 +651,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x100 0x40>; + reg = <0x100 0x40>, <0x230 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -665,7 +669,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x140 0x40>; + reg = <0x140 0x40>, <0x240 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -682,7 +687,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x180 0x40>; + reg = <0x180 0x40>, <0x250 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -699,7 +705,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x1c0 0x40>; + reg = <0x1c0 0x40>, <0x260 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -716,7 +723,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x300 0x40>; + reg = <0x300 0x40>, <0x270 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -733,7 +741,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x340 0x40>; + reg = <0x340 0x40>, <0x280 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -750,7 +759,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x380 0x40>; + reg = <0x380 0x40>, <0x290 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -767,7 +777,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x3c0 0x40>; + reg = <0x3c0 0x40>, <0x2a0 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -784,7 +795,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x400 0x40>; + reg = <0x400 0x40>, <0x2b0 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -801,7 +813,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x440 0x40>; + reg = <0x440 0x40>, <0x2c0 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; @@ -818,7 +831,8 @@ #size-cells = <0>; #interrupt-cells = <1>; - reg = <0x480 0x40>; + reg = <0x480 0x40>, <0x2d0 0x10>; + reg-names = "bus-regs", "buf"; compatible = "aspeed,ast2500-i2c-bus"; clocks = <&syscon ASPEED_CLK_APB>; resets = <&syscon ASPEED_RESET_I2C>; diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c index 64bc68eaa88f..b21a4c87853e 100644 --- a/drivers/i2c/busses/i2c-aspeed.c +++ b/drivers/i2c/busses/i2c-aspeed.c @@ -10,6 +10,7 @@ * published by the Free Software Foundation. */ +#include #include #include #include @@ -38,6 +39,7 @@ #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 */ @@ -46,6 +48,7 @@ /* 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) @@ -107,6 +110,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) @@ -117,6 +122,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, @@ -164,6 +176,11 @@ struct aspeed_i2c_bus { int master_xfer_result; /* Multi-master */ bool multi_master; + /* Buffer/DMA mode */ + void __iomem *buf_base; + size_t buf_size; + u8 buf_offset; + u8 buf_page; #if IS_ENABLED(CONFIG_I2C_SLAVE) struct i2c_client *slave; enum aspeed_i2c_slave_state slave_state; @@ -260,6 +277,7 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) { u32 command, irq_handled = 0; struct i2c_client *slave = bus->slave; + int i, len; u8 value; if (!slave) @@ -288,7 +306,11 @@ 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; + if (bus->buf_base && + bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED) + value = readb(bus->buf_base); + else + value = readl(bus->base + ASPEED_I2C_BYTE_BUF_REG) >> 8; /* Handle address frame. */ if (bus->slave_state == ASPEED_I2C_SLAVE_START) { if (value & 0x1) @@ -318,6 +340,18 @@ 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) { + if (bus->buf_base && + bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED) { + 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(slave, + I2C_SLAVE_WRITE_RECEIVED, + &value); + } + } irq_handled |= ASPEED_I2CD_INTR_NORMAL_STOP; bus->slave_state = ASPEED_I2C_SLAVE_STOP; } @@ -350,6 +384,15 @@ 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); + 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); + } break; case ASPEED_I2C_SLAVE_GCALL_REQUESTED: bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED; @@ -357,6 +400,24 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) break; case ASPEED_I2C_SLAVE_WRITE_RECEIVED: i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value); + 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(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); + } break; case ASPEED_I2C_SLAVE_STOP: i2c_slave_event(slave, I2C_SLAVE_STOP, &value); @@ -383,6 +444,8 @@ 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); + u8 wbuf[4]; + int len; #if IS_ENABLED(CONFIG_I2C_SLAVE) /* @@ -401,12 +464,66 @@ 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 (bus->buf_base && !(msg->flags & I2C_M_RECV_LEN)) { + command |= ASPEED_I2CD_TX_BUFF_ENABLE | + ASPEED_I2CD_RX_BUFF_ENABLE; + + wbuf[0] = slave_addr; + writel(*(u32 *)wbuf, bus->buf_base); + + if (msg->len > bus->buf_size) { + len = bus->buf_size; + } else { + len = msg->len; + command |= ASPEED_I2CD_M_S_RX_CMD_LAST; + } + + 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 { + /* 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; + } + } else { + if (bus->buf_base) { + int i; + + command |= ASPEED_I2CD_TX_BUFF_ENABLE; + + if (msg->len + 1 > bus->buf_size) + len = bus->buf_size; + else + len = msg->len + 1; + + 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)); + + bus->buf_index = len - 1; + + 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(slave_addr, bus->base + ASPEED_I2C_BYTE_BUF_REG); + if (!(command & ASPEED_I2CD_TX_BUFF_ENABLE)) + writel(slave_addr, bus->base + ASPEED_I2C_BYTE_BUF_REG); writel(command, bus->base + ASPEED_I2C_CMD_REG); } @@ -441,12 +558,22 @@ static int aspeed_i2c_is_irq_error(u32 irq_status) return 0; } +static inline int aspeed_i2c_is_tx_error(struct aspeed_i2c_bus *bus) +{ + if (FIELD_GET(ASPEED_I2CD_BUF_TX_COUNT_MASK, + readl(bus->base + ASPEED_I2C_BUF_CTRL_REG)) != + bus->buf_size - 1) + return -EIO; + + return 0; +} + static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) { u32 irq_handled = 0, command = 0; struct i2c_msg *msg; u8 recv_byte; - int ret; + int ret, len; if (irq_status & ASPEED_I2CD_INTR_BUS_RECOVER_DONE) { bus->master_state = ASPEED_I2C_MASTER_INACTIVE; @@ -559,11 +686,46 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) /* fall through */ case ASPEED_I2C_MASTER_TX_FIRST: if (bus->buf_index < msg->len) { + if (bus->buf_base) { + u8 wbuf[4]; + int i; + + if (unlikely(aspeed_i2c_is_tx_error(bus))) + goto error_and_stop; + + 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)); + + bus->buf_index += len; + + 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(ASPEED_I2CD_TX_BUFF_ENABLE | + ASPEED_I2CD_M_TX_CMD, + bus->base + ASPEED_I2C_CMD_REG); + } else { + 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); + } 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); } @@ -580,25 +742,57 @@ 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) { - if (unlikely(recv_byte > I2C_SMBUS_BLOCK_MAX)) { - bus->cmd_err = -EPROTO; - aspeed_i2c_do_stop(bus); - goto out_no_complete; + if (bus->buf_base && !(msg->flags & I2C_M_RECV_LEN)) { + 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; + + if (msg->flags & I2C_M_RECV_LEN) { + 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->flags &= ~I2C_M_RECV_LEN; } - msg->len = recv_byte + - ((msg->flags & I2C_CLIENT_PEC) ? 2 : 1); - msg->flags &= ~I2C_M_RECV_LEN; } 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; + bus->master_state = ASPEED_I2C_MASTER_RX; + if (bus->buf_base) { + command |= ASPEED_I2CD_RX_BUFF_ENABLE; + + 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; + } + + 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; + } writel(command, bus->base + ASPEED_I2C_CMD_REG); } else { aspeed_i2c_next_msg_or_stop(bus); @@ -948,6 +1142,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 @@ -1010,17 +1207,32 @@ static int aspeed_i2c_probe_bus(struct platform_device *pdev) struct aspeed_i2c_bus *bus; struct clk *parent_clk; struct resource *res; + void __iomem *gc_reg; int irq, ret; bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL); if (!bus) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "bus-regs"); bus->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(bus->base)) return PTR_ERR(bus->base); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "buf"); + bus->buf_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(bus->buf_base) || resource_size(res) < 2) { + bus->buf_base = NULL; + } else { + bus->buf_size = resource_size(res); + if (of_device_is_compatible(pdev->dev.of_node, + "aspeed,ast2400-i2c-bus")) { + bus->buf_page = (res->start >> 8) & GENMASK(3, 0) - 8; + bus->buf_offset = (res->start >> 2) & + ASPEED_I2CD_BUF_OFFSET_MASK; + } + } + parent_clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(parent_clk)) return PTR_ERR(parent_clk); diff --git a/drivers/irqchip/irq-aspeed-i2c-ic.c b/drivers/irqchip/irq-aspeed-i2c-ic.c index f20200af0992..99985b22a9fa 100644 --- a/drivers/irqchip/irq-aspeed-i2c-ic.c +++ b/drivers/irqchip/irq-aspeed-i2c-ic.c @@ -18,6 +18,9 @@ #include #include +/* I2C Global Control Register (AST2500) */ +#define ASPEED_I2C_GLOBAL_CTRL_REG 0xc +#define ASPEED_I2C_SRAM_BUFFER_EN BIT(0) #define ASPEED_I2C_IC_NUM_BUS 14 @@ -100,6 +103,11 @@ static int __init aspeed_i2c_ic_of_init(struct device_node *node, irq_set_chained_handler_and_data(i2c_ic->parent_irq, aspeed_i2c_ic_irq_handler, i2c_ic); + /* Enable I2C SRAM buffer in case of AST2500 */ + if (of_device_is_compatible(node, "aspeed,ast2500-i2c-ic")) + writel(ASPEED_I2C_SRAM_BUFFER_EN, + i2c_ic->base + ASPEED_I2C_GLOBAL_CTRL_REG); + pr_info("i2c controller registered, irq %d\n", i2c_ic->parent_irq); return 0; -- 2.7.4