mirror of https://gitee.com/openkylin/qemu.git
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:
parent
95b56e173e
commit
545d6bef70
|
@ -343,6 +343,11 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev, Error **errp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* I2C */
|
/* 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);
|
object_property_set_bool(OBJECT(&s->i2c), true, "realized", &err);
|
||||||
if (err) {
|
if (err) {
|
||||||
error_propagate(errp, err);
|
error_propagate(errp, err);
|
||||||
|
|
|
@ -311,6 +311,11 @@ static void aspeed_soc_realize(DeviceState *dev, Error **errp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* I2C */
|
/* 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);
|
object_property_set_bool(OBJECT(&s->i2c), true, "realized", &err);
|
||||||
if (err) {
|
if (err) {
|
||||||
error_propagate(errp, err);
|
error_propagate(errp, err);
|
||||||
|
|
|
@ -23,8 +23,11 @@
|
||||||
#include "migration/vmstate.h"
|
#include "migration/vmstate.h"
|
||||||
#include "qemu/log.h"
|
#include "qemu/log.h"
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
|
#include "qemu/error-report.h"
|
||||||
|
#include "qapi/error.h"
|
||||||
#include "hw/i2c/aspeed_i2c.h"
|
#include "hw/i2c/aspeed_i2c.h"
|
||||||
#include "hw/irq.h"
|
#include "hw/irq.h"
|
||||||
|
#include "hw/qdev-properties.h"
|
||||||
|
|
||||||
/* I2C Global Register */
|
/* I2C Global Register */
|
||||||
|
|
||||||
|
@ -138,7 +141,8 @@
|
||||||
#define I2CD_BYTE_BUF_TX_MASK 0xff
|
#define I2CD_BYTE_BUF_TX_MASK 0xff
|
||||||
#define I2CD_BYTE_BUF_RX_SHIFT 8
|
#define I2CD_BYTE_BUF_RX_SHIFT 8
|
||||||
#define I2CD_BYTE_BUF_RX_MASK 0xff
|
#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)
|
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)
|
unsigned size)
|
||||||
{
|
{
|
||||||
AspeedI2CBus *bus = opaque;
|
AspeedI2CBus *bus = opaque;
|
||||||
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
|
||||||
|
|
||||||
switch (offset) {
|
switch (offset) {
|
||||||
case I2CD_FUN_CTRL_REG:
|
case I2CD_FUN_CTRL_REG:
|
||||||
|
@ -183,6 +188,18 @@ static uint64_t aspeed_i2c_bus_read(void *opaque, hwaddr offset,
|
||||||
return bus->buf;
|
return bus->buf;
|
||||||
case I2CD_CMD_REG:
|
case I2CD_CMD_REG:
|
||||||
return bus->cmd | (i2c_bus_busy(bus->bus) << 16);
|
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:
|
default:
|
||||||
qemu_log_mask(LOG_GUEST_ERROR,
|
qemu_log_mask(LOG_GUEST_ERROR,
|
||||||
"%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, offset);
|
"%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;
|
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)
|
static int aspeed_i2c_bus_send(AspeedI2CBus *bus, uint8_t pool_start)
|
||||||
{
|
{
|
||||||
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
|
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;
|
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 {
|
} else {
|
||||||
ret = i2c_send(bus->bus, bus->buf);
|
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 &= ~(0xff << 24);
|
||||||
bus->pool_ctrl |= (i & 0xff) << 24;
|
bus->pool_ctrl |= (i & 0xff) << 24;
|
||||||
bus->cmd &= ~I2CD_RX_BUFF_ENABLE;
|
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 {
|
} else {
|
||||||
data = i2c_recv(bus->bus);
|
data = i2c_recv(bus->bus);
|
||||||
bus->buf = (data & I2CD_BYTE_BUF_RX_MASK) << I2CD_BYTE_BUF_RX_SHIFT;
|
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);
|
uint8_t *pool_base = aic->bus_pool_base(bus);
|
||||||
|
|
||||||
return pool_base[0];
|
return pool_base[0];
|
||||||
|
} else if (bus->cmd & I2CD_TX_DMA_ENABLE) {
|
||||||
|
uint8_t data;
|
||||||
|
|
||||||
|
aspeed_i2c_dma_read(bus, &data);
|
||||||
|
return data;
|
||||||
} else {
|
} else {
|
||||||
return bus->buf;
|
return bus->buf;
|
||||||
}
|
}
|
||||||
|
@ -344,6 +412,10 @@ static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value)
|
||||||
*/
|
*/
|
||||||
pool_start++;
|
pool_start++;
|
||||||
}
|
}
|
||||||
|
} else if (bus->cmd & I2CD_TX_DMA_ENABLE) {
|
||||||
|
if (bus->dma_len == 0) {
|
||||||
|
bus->cmd &= ~I2CD_M_TX_CMD;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bus->cmd &= ~I2CD_M_TX_CMD;
|
bus->cmd &= ~I2CD_M_TX_CMD;
|
||||||
}
|
}
|
||||||
|
@ -447,9 +519,35 @@ static void aspeed_i2c_bus_write(void *opaque, hwaddr offset,
|
||||||
break;
|
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_handle_cmd(bus, value);
|
||||||
aspeed_i2c_bus_raise_interrupt(bus);
|
aspeed_i2c_bus_raise_interrupt(bus);
|
||||||
break;
|
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:
|
default:
|
||||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
|
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 = {
|
static const VMStateDescription aspeed_i2c_bus_vmstate = {
|
||||||
.name = TYPE_ASPEED_I2C,
|
.name = TYPE_ASPEED_I2C,
|
||||||
.version_id = 2,
|
.version_id = 3,
|
||||||
.minimum_version_id = 2,
|
.minimum_version_id = 3,
|
||||||
.fields = (VMStateField[]) {
|
.fields = (VMStateField[]) {
|
||||||
VMSTATE_UINT8(id, AspeedI2CBus),
|
VMSTATE_UINT8(id, AspeedI2CBus),
|
||||||
VMSTATE_UINT32(ctrl, AspeedI2CBus),
|
VMSTATE_UINT32(ctrl, AspeedI2CBus),
|
||||||
|
@ -553,6 +651,8 @@ static const VMStateDescription aspeed_i2c_bus_vmstate = {
|
||||||
VMSTATE_UINT32(cmd, AspeedI2CBus),
|
VMSTATE_UINT32(cmd, AspeedI2CBus),
|
||||||
VMSTATE_UINT32(buf, AspeedI2CBus),
|
VMSTATE_UINT32(buf, AspeedI2CBus),
|
||||||
VMSTATE_UINT32(pool_ctrl, AspeedI2CBus),
|
VMSTATE_UINT32(pool_ctrl, AspeedI2CBus),
|
||||||
|
VMSTATE_UINT32(dma_addr, AspeedI2CBus),
|
||||||
|
VMSTATE_UINT32(dma_len, AspeedI2CBus),
|
||||||
VMSTATE_END_OF_LIST()
|
VMSTATE_END_OF_LIST()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -584,6 +684,8 @@ static void aspeed_i2c_reset(DeviceState *dev)
|
||||||
s->busses[i].intr_status = 0;
|
s->busses[i].intr_status = 0;
|
||||||
s->busses[i].cmd = 0;
|
s->busses[i].cmd = 0;
|
||||||
s->busses[i].buf = 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);
|
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,
|
memory_region_init_io(&s->pool_iomem, OBJECT(s), &aspeed_i2c_pool_ops, s,
|
||||||
"aspeed.i2c-pool", aic->pool_size);
|
"aspeed.i2c-pool", aic->pool_size);
|
||||||
memory_region_add_subregion(&s->iomem, aic->pool_base, &s->pool_iomem);
|
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)
|
static void aspeed_i2c_class_init(ObjectClass *klass, void *data)
|
||||||
{
|
{
|
||||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||||
|
|
||||||
dc->vmsd = &aspeed_i2c_vmstate;
|
dc->vmsd = &aspeed_i2c_vmstate;
|
||||||
dc->reset = aspeed_i2c_reset;
|
dc->reset = aspeed_i2c_reset;
|
||||||
|
dc->props = aspeed_i2c_properties;
|
||||||
dc->realize = aspeed_i2c_realize;
|
dc->realize = aspeed_i2c_realize;
|
||||||
dc->desc = "Aspeed I2C Controller";
|
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->pool_base = 0x200;
|
||||||
aic->bus_pool_base = aspeed_2500_i2c_bus_pool_base;
|
aic->bus_pool_base = aspeed_2500_i2c_bus_pool_base;
|
||||||
aic->check_sram = true;
|
aic->check_sram = true;
|
||||||
|
aic->has_dma = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const TypeInfo aspeed_2500_i2c_info = {
|
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_size = 0x200;
|
||||||
aic->pool_base = 0xC00;
|
aic->pool_base = 0xC00;
|
||||||
aic->bus_pool_base = aspeed_2600_i2c_bus_pool_base;
|
aic->bus_pool_base = aspeed_2600_i2c_bus_pool_base;
|
||||||
|
aic->has_dma = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const TypeInfo aspeed_2600_i2c_info = {
|
static const TypeInfo aspeed_2600_i2c_info = {
|
||||||
|
|
|
@ -52,6 +52,8 @@ typedef struct AspeedI2CBus {
|
||||||
uint32_t cmd;
|
uint32_t cmd;
|
||||||
uint32_t buf;
|
uint32_t buf;
|
||||||
uint32_t pool_ctrl;
|
uint32_t pool_ctrl;
|
||||||
|
uint32_t dma_addr;
|
||||||
|
uint32_t dma_len;
|
||||||
} AspeedI2CBus;
|
} AspeedI2CBus;
|
||||||
|
|
||||||
typedef struct AspeedI2CState {
|
typedef struct AspeedI2CState {
|
||||||
|
@ -66,6 +68,8 @@ typedef struct AspeedI2CState {
|
||||||
uint8_t pool[ASPEED_I2C_MAX_POOL_SIZE];
|
uint8_t pool[ASPEED_I2C_MAX_POOL_SIZE];
|
||||||
|
|
||||||
AspeedI2CBus busses[ASPEED_I2C_NR_BUSSES];
|
AspeedI2CBus busses[ASPEED_I2C_NR_BUSSES];
|
||||||
|
MemoryRegion *dram_mr;
|
||||||
|
AddressSpace dram_as;
|
||||||
} AspeedI2CState;
|
} AspeedI2CState;
|
||||||
|
|
||||||
#define ASPEED_I2C_CLASS(klass) \
|
#define ASPEED_I2C_CLASS(klass) \
|
||||||
|
@ -85,6 +89,7 @@ typedef struct AspeedI2CClass {
|
||||||
hwaddr pool_base;
|
hwaddr pool_base;
|
||||||
uint8_t *(*bus_pool_base)(AspeedI2CBus *);
|
uint8_t *(*bus_pool_base)(AspeedI2CBus *);
|
||||||
bool check_sram;
|
bool check_sram;
|
||||||
|
bool has_dma;
|
||||||
|
|
||||||
} AspeedI2CClass;
|
} AspeedI2CClass;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue