aspeed/i2c: Add support for DMA transfers

The I2C controller of the Aspeed AST2500 and AST2600 SoCs supports DMA
transfers to and from DRAM.

A pair of registers defines the buffer address and the length of the
DMA transfer. The address should be aligned on 4 bytes and the maximum
length should not exceed 4K. The receive or transmit DMA transfer can
then be initiated with specific bits in the Command/Status register of
the controller.

Signed-off-by: Cédric Le Goater <clg@kaod.org>
Reviewed-by: Joel Stanley <joel@jms.id.au>
Tested-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Signed-off-by: Cédric Le Goater <clg@kaod.org>
Message-id: 20191119141211.25716-5-clg@kaod.org
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Cédric Le Goater 2019-11-19 15:11:58 +01:00 committed by Peter Maydell
parent 95b56e173e
commit 545d6bef70
4 changed files with 138 additions and 3 deletions

View File

@ -343,6 +343,11 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev, Error **errp)
}
/* I2C */
object_property_set_link(OBJECT(&s->i2c), OBJECT(s->dram_mr), "dram", &err);
if (err) {
error_propagate(errp, err);
return;
}
object_property_set_bool(OBJECT(&s->i2c), true, "realized", &err);
if (err) {
error_propagate(errp, err);

View File

@ -311,6 +311,11 @@ static void aspeed_soc_realize(DeviceState *dev, Error **errp)
}
/* I2C */
object_property_set_link(OBJECT(&s->i2c), OBJECT(s->dram_mr), "dram", &err);
if (err) {
error_propagate(errp, err);
return;
}
object_property_set_bool(OBJECT(&s->i2c), true, "realized", &err);
if (err) {
error_propagate(errp, err);

View File

@ -23,8 +23,11 @@
#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
#include "hw/i2c/aspeed_i2c.h"
#include "hw/irq.h"
#include "hw/qdev-properties.h"
/* I2C Global Register */
@ -138,7 +141,8 @@
#define I2CD_BYTE_BUF_TX_MASK 0xff
#define I2CD_BYTE_BUF_RX_SHIFT 8
#define I2CD_BYTE_BUF_RX_MASK 0xff
#define I2CD_DMA_ADDR 0x24 /* DMA Buffer Address */
#define I2CD_DMA_LEN 0x28 /* DMA Transfer Length < 4KB */
static inline bool aspeed_i2c_bus_is_master(AspeedI2CBus *bus)
{
@ -165,6 +169,7 @@ static uint64_t aspeed_i2c_bus_read(void *opaque, hwaddr offset,
unsigned size)
{
AspeedI2CBus *bus = opaque;
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
switch (offset) {
case I2CD_FUN_CTRL_REG:
@ -183,6 +188,18 @@ static uint64_t aspeed_i2c_bus_read(void *opaque, hwaddr offset,
return bus->buf;
case I2CD_CMD_REG:
return bus->cmd | (i2c_bus_busy(bus->bus) << 16);
case I2CD_DMA_ADDR:
if (!aic->has_dma) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
return -1;
}
return bus->dma_addr;
case I2CD_DMA_LEN:
if (!aic->has_dma) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
return -1;
}
return bus->dma_len;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, offset);
@ -201,6 +218,24 @@ static uint8_t aspeed_i2c_get_state(AspeedI2CBus *bus)
return (bus->cmd >> I2CD_TX_STATE_SHIFT) & I2CD_TX_STATE_MASK;
}
static int aspeed_i2c_dma_read(AspeedI2CBus *bus, uint8_t *data)
{
MemTxResult result;
AspeedI2CState *s = bus->controller;
result = address_space_read(&s->dram_as, bus->dma_addr,
MEMTXATTRS_UNSPECIFIED, data, 1);
if (result != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: DRAM read failed @%08x\n",
__func__, bus->dma_addr);
return -1;
}
bus->dma_addr++;
bus->dma_len--;
return 0;
}
static int aspeed_i2c_bus_send(AspeedI2CBus *bus, uint8_t pool_start)
{
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
@ -217,6 +252,16 @@ static int aspeed_i2c_bus_send(AspeedI2CBus *bus, uint8_t pool_start)
}
}
bus->cmd &= ~I2CD_TX_BUFF_ENABLE;
} else if (bus->cmd & I2CD_TX_DMA_ENABLE) {
while (bus->dma_len) {
uint8_t data;
aspeed_i2c_dma_read(bus, &data);
ret = i2c_send(bus->bus, data);
if (ret) {
break;
}
}
bus->cmd &= ~I2CD_TX_DMA_ENABLE;
} else {
ret = i2c_send(bus->bus, bus->buf);
}
@ -242,6 +287,24 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
bus->pool_ctrl &= ~(0xff << 24);
bus->pool_ctrl |= (i & 0xff) << 24;
bus->cmd &= ~I2CD_RX_BUFF_ENABLE;
} else if (bus->cmd & I2CD_RX_DMA_ENABLE) {
uint8_t data;
while (bus->dma_len) {
MemTxResult result;
data = i2c_recv(bus->bus);
result = address_space_write(&s->dram_as, bus->dma_addr,
MEMTXATTRS_UNSPECIFIED, &data, 1);
if (result != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: DRAM write failed @%08x\n",
__func__, bus->dma_addr);
return;
}
bus->dma_addr++;
bus->dma_len--;
}
bus->cmd &= ~I2CD_RX_DMA_ENABLE;
} else {
data = i2c_recv(bus->bus);
bus->buf = (data & I2CD_BYTE_BUF_RX_MASK) << I2CD_BYTE_BUF_RX_SHIFT;
@ -268,6 +331,11 @@ static uint8_t aspeed_i2c_get_addr(AspeedI2CBus *bus)
uint8_t *pool_base = aic->bus_pool_base(bus);
return pool_base[0];
} else if (bus->cmd & I2CD_TX_DMA_ENABLE) {
uint8_t data;
aspeed_i2c_dma_read(bus, &data);
return data;
} else {
return bus->buf;
}
@ -344,6 +412,10 @@ static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value)
*/
pool_start++;
}
} else if (bus->cmd & I2CD_TX_DMA_ENABLE) {
if (bus->dma_len == 0) {
bus->cmd &= ~I2CD_M_TX_CMD;
}
} else {
bus->cmd &= ~I2CD_M_TX_CMD;
}
@ -447,9 +519,35 @@ static void aspeed_i2c_bus_write(void *opaque, hwaddr offset,
break;
}
if (!aic->has_dma &&
value & (I2CD_RX_DMA_ENABLE | I2CD_TX_DMA_ENABLE)) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
break;
}
aspeed_i2c_bus_handle_cmd(bus, value);
aspeed_i2c_bus_raise_interrupt(bus);
break;
case I2CD_DMA_ADDR:
if (!aic->has_dma) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
break;
}
bus->dma_addr = value & 0xfffffffc;
break;
case I2CD_DMA_LEN:
if (!aic->has_dma) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
break;
}
bus->dma_len = value & 0xfff;
if (!bus->dma_len) {
qemu_log_mask(LOG_UNIMP, "%s: invalid DMA length\n", __func__);
}
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
@ -542,8 +640,8 @@ static const MemoryRegionOps aspeed_i2c_pool_ops = {
static const VMStateDescription aspeed_i2c_bus_vmstate = {
.name = TYPE_ASPEED_I2C,
.version_id = 2,
.minimum_version_id = 2,
.version_id = 3,
.minimum_version_id = 3,
.fields = (VMStateField[]) {
VMSTATE_UINT8(id, AspeedI2CBus),
VMSTATE_UINT32(ctrl, AspeedI2CBus),
@ -553,6 +651,8 @@ static const VMStateDescription aspeed_i2c_bus_vmstate = {
VMSTATE_UINT32(cmd, AspeedI2CBus),
VMSTATE_UINT32(buf, AspeedI2CBus),
VMSTATE_UINT32(pool_ctrl, AspeedI2CBus),
VMSTATE_UINT32(dma_addr, AspeedI2CBus),
VMSTATE_UINT32(dma_len, AspeedI2CBus),
VMSTATE_END_OF_LIST()
}
};
@ -584,6 +684,8 @@ static void aspeed_i2c_reset(DeviceState *dev)
s->busses[i].intr_status = 0;
s->busses[i].cmd = 0;
s->busses[i].buf = 0;
s->busses[i].dma_addr = 0;
s->busses[i].dma_len = 0;
i2c_end_transfer(s->busses[i].bus);
}
}
@ -640,14 +742,30 @@ static void aspeed_i2c_realize(DeviceState *dev, Error **errp)
memory_region_init_io(&s->pool_iomem, OBJECT(s), &aspeed_i2c_pool_ops, s,
"aspeed.i2c-pool", aic->pool_size);
memory_region_add_subregion(&s->iomem, aic->pool_base, &s->pool_iomem);
if (aic->has_dma) {
if (!s->dram_mr) {
error_setg(errp, TYPE_ASPEED_I2C ": 'dram' link not set");
return;
}
address_space_init(&s->dram_as, s->dram_mr, "dma-dram");
}
}
static Property aspeed_i2c_properties[] = {
DEFINE_PROP_LINK("dram", AspeedI2CState, dram_mr,
TYPE_MEMORY_REGION, MemoryRegion *),
DEFINE_PROP_END_OF_LIST(),
};
static void aspeed_i2c_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->vmsd = &aspeed_i2c_vmstate;
dc->reset = aspeed_i2c_reset;
dc->props = aspeed_i2c_properties;
dc->realize = aspeed_i2c_realize;
dc->desc = "Aspeed I2C Controller";
}
@ -721,6 +839,7 @@ static void aspeed_2500_i2c_class_init(ObjectClass *klass, void *data)
aic->pool_base = 0x200;
aic->bus_pool_base = aspeed_2500_i2c_bus_pool_base;
aic->check_sram = true;
aic->has_dma = true;
}
static const TypeInfo aspeed_2500_i2c_info = {
@ -753,6 +872,7 @@ static void aspeed_2600_i2c_class_init(ObjectClass *klass, void *data)
aic->pool_size = 0x200;
aic->pool_base = 0xC00;
aic->bus_pool_base = aspeed_2600_i2c_bus_pool_base;
aic->has_dma = true;
}
static const TypeInfo aspeed_2600_i2c_info = {

View File

@ -52,6 +52,8 @@ typedef struct AspeedI2CBus {
uint32_t cmd;
uint32_t buf;
uint32_t pool_ctrl;
uint32_t dma_addr;
uint32_t dma_len;
} AspeedI2CBus;
typedef struct AspeedI2CState {
@ -66,6 +68,8 @@ typedef struct AspeedI2CState {
uint8_t pool[ASPEED_I2C_MAX_POOL_SIZE];
AspeedI2CBus busses[ASPEED_I2C_NR_BUSSES];
MemoryRegion *dram_mr;
AddressSpace dram_as;
} AspeedI2CState;
#define ASPEED_I2C_CLASS(klass) \
@ -85,6 +89,7 @@ typedef struct AspeedI2CClass {
hwaddr pool_base;
uint8_t *(*bus_pool_base)(AspeedI2CBus *);
bool check_sram;
bool has_dma;
} AspeedI2CClass;