[PATCH v1 1/8] hw/i2c/aspeed_i2c: Support the AST2700 master buffer mode
Jamin Lin <[email protected]> Tue, 4 Aug 2026 08:19:57 +0000
| Newsgroups | org.nongnu.qemu-arm,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <[email protected]> |
The AST2700 I2C controller can move master DMA payloads through its=0A= internal SRAM pool rather than DRAM. The Linux driver calls this "buffer=0A= mode" and selects it by default. Buffer mode reuses the master DMA=0A= command bits (TX/RX_DMA_EN) and the DMA length registers, so the only=0A= difference from a DRAM transfer is where the data comes from and goes=0A= to: an offset into the pool programmed in I2CM_DMA_TX/RX_ADDR. The=0A= I2CC_VERSION_CTRL FUNC_CFG_DMA_EN bit selects between the two.=0A= =0A= Implement I2CC_VERSION_CTRL and, when FUNC_CFG_DMA_EN is clear, move the=0A= payload through the pool buffer instead of DRAM.=0A= =0A= I2CC_VERSION_CTRL resets to all ones, so guests that never program it=0A= keep targeting DRAM and behave as before. The register sits above the=0A= register window of the earlier SoCs, which are therefore unaffected.=0A= =0A= Signed-off-by: Jamin Lin <[email protected]>=0A= ---=0A= include/hw/i2c/aspeed_i2c.h | 2 +=0A= hw/i2c/aspeed_i2c.c | 79 +++++++++++++++++++++++++++++++++++++=0A= 2 files changed, 81 insertions(+)=0A= =0A= diff --git a/include/hw/i2c/aspeed_i2c.h b/include/hw/i2c/aspeed_i2c.h=0A= index 156998e7c1..05937a7a0b 100644=0A= --- a/include/hw/i2c/aspeed_i2c.h=0A= +++ b/include/hw/i2c/aspeed_i2c.h=0A= @@ -231,6 +231,8 @@ REG32(I2CS_DMA_TX_ADDR_HI, 0x68)=0A= FIELD(I2CS_DMA_TX_ADDR_HI, ADDR_HI, 0, 7)=0A= REG32(I2CS_DMA_RX_ADDR_HI, 0x6c)=0A= FIELD(I2CS_DMA_RX_ADDR_HI, ADDR_HI, 0, 7)=0A= +REG32(I2CC_VERSION_CTRL, 0x94)=0A= + FIELD(I2CC_VERSION_CTRL, FUNC_CFG_DMA_EN, 2, 1)=0A= =0A= struct AspeedI2CState;=0A= =0A= diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c=0A= index 27afcaecee..68bdcd0e25 100644=0A= --- a/hw/i2c/aspeed_i2c.c=0A= +++ b/hw/i2c/aspeed_i2c.c=0A= @@ -159,6 +159,7 @@ static uint64_t aspeed_i2c_bus_new_read(AspeedI2CBus *b= us, hwaddr offset,=0A= case A_I2CS_INTR_CTRL:=0A= case A_I2CS_DMA_LEN_STS:=0A= case A_I2CS_INTR_STS:=0A= + case A_I2CC_VERSION_CTRL:=0A= value =3D bus->regs[offset / sizeof(*bus->regs)];=0A= break;=0A= case A_I2CC_DMA_ADDR:=0A= @@ -295,6 +296,65 @@ static int aspeed_i2c_dma_read(AspeedI2CBus *bus, uint= 8_t *data)=0A= return 0;=0A= }=0A= =0A= +/*=0A= + * In AST2700 buffer mode the master DMA command bits (TX/RX_DMA_EN) and t= he=0A= + * DMA length registers are reused, but data is moved through the controll= er=0A= + * internal SRAM pool at the offset programmed in I2CM_DMA_TX/RX_ADDR inst= ead=0A= + * of DRAM. FUNC_CFG_DMA_EN selects between the two (set =3D DRAM).=0A= + */=0A= +static bool aspeed_i2c_bus_dma_to_pool(AspeedI2CBus *bus)=0A= +{=0A= + return aspeed_i2c_is_new_mode(bus->controller) &&=0A= + !ARRAY_FIELD_EX32(bus->regs, I2CC_VERSION_CTRL, FUNC_CFG_DMA_EN= );=0A= +}=0A= +=0A= +static int aspeed_i2c_bus_send_dma_pool(AspeedI2CBus *bus)=0A= +{=0A= + AspeedI2CClass *aic =3D ASPEED_I2C_GET_CLASS(bus->controller);=0A= + uint32_t reg_dma_len =3D aspeed_i2c_bus_dma_len_offset(bus);=0A= + uint32_t reg_cmd =3D aspeed_i2c_bus_cmd_offset(bus);=0A= + uint32_t offset =3D bus->regs[R_I2CM_DMA_TX_ADDR];=0A= + uint8_t *pool_base =3D aic->bus_pool_base(bus);=0A= + int ret =3D -1;=0A= + int i;=0A= +=0A= + ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, 0);=0A= + for (i =3D 0; bus->regs[reg_dma_len] &&=0A= + offset + i < ASPEED_I2C_BUS_POOL_SIZE; i++) {=0A= + trace_aspeed_i2c_bus_send("BUFF", i + 1, bus->regs[reg_dma_len],= =0A= + pool_base[offset + i]);=0A= + ret =3D i2c_send(bus->bus, pool_base[offset + i]);=0A= + bus->regs[reg_dma_len]--;=0A= + ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, i + 1);=0A= + if (ret) {=0A= + break;=0A= + }=0A= + }=0A= + SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, TX_DMA_EN, 0);=0A= + return ret;=0A= +}=0A= +=0A= +static void aspeed_i2c_bus_recv_dma_pool(AspeedI2CBus *bus)=0A= +{=0A= + AspeedI2CClass *aic =3D ASPEED_I2C_GET_CLASS(bus->controller);=0A= + uint32_t reg_dma_len =3D aspeed_i2c_bus_dma_len_offset(bus);=0A= + uint32_t reg_cmd =3D aspeed_i2c_bus_cmd_offset(bus);=0A= + uint32_t offset =3D bus->regs[R_I2CM_DMA_RX_ADDR];=0A= + uint8_t *pool_base =3D aic->bus_pool_base(bus);=0A= + int i;=0A= +=0A= + ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, 0);=0A= + for (i =3D 0; bus->regs[reg_dma_len] &&=0A= + offset + i < ASPEED_I2C_BUS_POOL_SIZE; i++) {=0A= + pool_base[offset + i] =3D i2c_recv(bus->bus);=0A= + trace_aspeed_i2c_bus_recv("BUFF", i + 1, bus->regs[reg_dma_len],= =0A= + pool_base[offset + i]);=0A= + bus->regs[reg_dma_len]--;=0A= + ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, i + 1);=0A= + }=0A= + SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, RX_DMA_EN, 0);=0A= +}=0A= +=0A= static int aspeed_i2c_bus_send(AspeedI2CBus *bus)=0A= {=0A= AspeedI2CClass *aic =3D ASPEED_I2C_GET_CLASS(bus->controller);=0A= @@ -320,6 +380,10 @@ static int aspeed_i2c_bus_send(AspeedI2CBus *bus)=0A= }=0A= SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, TX_BUFF_EN, 0);=0A= } else if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_DMA_EN)) {= =0A= + /* In buffer mode the DMA moves data through the pool, not DRAM */= =0A= + if (aspeed_i2c_bus_dma_to_pool(bus)) {=0A= + return aspeed_i2c_bus_send_dma_pool(bus);=0A= + }=0A= /* In new mode, clear how many bytes we TXed */=0A= if (aspeed_i2c_is_new_mode(bus->controller)) {=0A= ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, 0);=0A= @@ -385,6 +449,11 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)=0A= SHARED_ARRAY_FIELD_DP32(bus->regs, reg_pool_ctrl, RX_COUNT, i & 0x= ff);=0A= SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, RX_BUFF_EN, 0);=0A= } else if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_DMA_EN)) {= =0A= + /* In buffer mode the DMA moves data through the pool, not DRAM */= =0A= + if (aspeed_i2c_bus_dma_to_pool(bus)) {=0A= + aspeed_i2c_bus_recv_dma_pool(bus);=0A= + return;=0A= + }=0A= /* In new mode, clear how many bytes we RXed */=0A= if (aspeed_i2c_is_new_mode(bus->controller)) {=0A= ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, 0);=0A= @@ -854,6 +923,9 @@ static void aspeed_i2c_bus_new_write(AspeedI2CBus *bus,= hwaddr offset,=0A= I2CS_DMA_RX_ADDR_HI,= =0A= ADDR_HI);=0A= break;=0A= + case A_I2CC_VERSION_CTRL:=0A= + bus->regs[R_I2CC_VERSION_CTRL] =3D value;=0A= + break;=0A= default:=0A= qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\= n",=0A= __func__, offset);=0A= @@ -1497,6 +1569,13 @@ static void aspeed_i2c_bus_reset_hold(Object *obj, R= esetType type)=0A= memset(s->regs, 0, sizeof(s->regs));=0A= s->pending_intr_sts =3D 0;=0A= i2c_end_transfer(s->bus);=0A= + /*=0A= + * I2CC_VERSION_CTRL resets to all-ones. FUNC_CFG_DMA_EN is therefore = set,=0A= + * so master DMA targets DRAM unless the guest clears it to select buf= fer=0A= + * mode. Guests unaware of buffer mode never touch this register and k= eep=0A= + * doing DRAM DMA.=0A= + */=0A= + s->regs[R_I2CC_VERSION_CTRL] =3D 0xffffffff;=0A= }=0A= =0A= static void aspeed_i2c_bus_realize(DeviceState *dev, Error **errp)=0A= -- =0A= 2.43.0=0A=