5d337540c4
According to the datasheet of ASPEED SOCs, each I2C bus has their own pool buffer since AST2500. Only AST2400 utilized a pool buffer share to all I2C bus. And firmware required to set the offset of pool buffer by writing "Function Control Register(I2CD 00)" To make this model more readable, will change to introduce a new bus pool buffer attribute in AspeedI2Cbus. So, it does not need to calculate the pool buffer offset for different I2C bus. This patch rename the I2C class pool attribute to share_pool. It make user more understand share pool and bus pool are different. Incrementing the version of aspeed_i2c_vmstate to 3. Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com> Reviewed-by: Cédric Le Goater <clg@redhat.com>
1419 lines
47 KiB
C
1419 lines
47 KiB
C
/*
|
|
* ARM Aspeed I2C controller
|
|
*
|
|
* Copyright (C) 2016 IBM Corp.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "hw/sysbus.h"
|
|
#include "migration/vmstate.h"
|
|
#include "qemu/cutils.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"
|
|
#include "hw/registerfields.h"
|
|
#include "trace.h"
|
|
|
|
/* Enable SLAVE_ADDR_RX_MATCH always */
|
|
#define R_I2CD_INTR_STS_ALWAYS_ENABLE R_I2CD_INTR_STS_SLAVE_ADDR_RX_MATCH_MASK
|
|
|
|
static inline void aspeed_i2c_bus_raise_interrupt(AspeedI2CBus *bus)
|
|
{
|
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
|
|
uint32_t reg_intr_sts = aspeed_i2c_bus_intr_sts_offset(bus);
|
|
uint32_t intr_ctrl_reg = aspeed_i2c_bus_intr_ctrl_offset(bus);
|
|
uint32_t intr_ctrl_mask = bus->regs[intr_ctrl_reg] |
|
|
R_I2CD_INTR_STS_ALWAYS_ENABLE;
|
|
bool raise_irq;
|
|
|
|
if (trace_event_get_state_backends(TRACE_ASPEED_I2C_BUS_RAISE_INTERRUPT)) {
|
|
g_autofree char *buf = g_strdup_printf("%s%s%s%s%s%s%s",
|
|
aspeed_i2c_bus_pkt_mode_en(bus) &&
|
|
ARRAY_FIELD_EX32(bus->regs, I2CM_INTR_STS, PKT_CMD_DONE) ?
|
|
"pktdone|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_intr_sts, TX_NAK) ?
|
|
"nak|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_intr_sts, TX_ACK) ?
|
|
"ack|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_intr_sts, RX_DONE) ?
|
|
"done|" : "",
|
|
ARRAY_FIELD_EX32(bus->regs, I2CD_INTR_STS, SLAVE_ADDR_RX_MATCH) ?
|
|
"slave-match|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_intr_sts, NORMAL_STOP) ?
|
|
"stop|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_intr_sts, ABNORMAL) ?
|
|
"abnormal" : "");
|
|
|
|
trace_aspeed_i2c_bus_raise_interrupt(bus->regs[reg_intr_sts], buf);
|
|
}
|
|
|
|
raise_irq = bus->regs[reg_intr_sts] & intr_ctrl_mask ;
|
|
|
|
/* In packet mode we don't mask off INTR_STS */
|
|
if (!aspeed_i2c_bus_pkt_mode_en(bus)) {
|
|
bus->regs[reg_intr_sts] &= intr_ctrl_mask;
|
|
}
|
|
|
|
if (raise_irq) {
|
|
bus->controller->intr_status |= 1 << bus->id;
|
|
qemu_irq_raise(aic->bus_get_irq(bus));
|
|
}
|
|
}
|
|
|
|
static inline void aspeed_i2c_bus_raise_slave_interrupt(AspeedI2CBus *bus)
|
|
{
|
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
|
|
|
|
if (!bus->regs[R_I2CS_INTR_STS]) {
|
|
return;
|
|
}
|
|
|
|
bus->controller->intr_status |= 1 << bus->id;
|
|
qemu_irq_raise(aic->bus_get_irq(bus));
|
|
}
|
|
|
|
static uint64_t aspeed_i2c_bus_old_read(AspeedI2CBus *bus, hwaddr offset,
|
|
unsigned size)
|
|
{
|
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
|
|
uint64_t value = bus->regs[offset / sizeof(*bus->regs)];
|
|
|
|
switch (offset) {
|
|
case A_I2CD_FUN_CTRL:
|
|
case A_I2CD_AC_TIMING1:
|
|
case A_I2CD_AC_TIMING2:
|
|
case A_I2CD_INTR_CTRL:
|
|
case A_I2CD_INTR_STS:
|
|
case A_I2CD_DEV_ADDR:
|
|
case A_I2CD_POOL_CTRL:
|
|
case A_I2CD_BYTE_BUF:
|
|
/* Value is already set, don't do anything. */
|
|
break;
|
|
case A_I2CD_CMD:
|
|
value = SHARED_FIELD_DP32(value, BUS_BUSY_STS, i2c_bus_busy(bus->bus));
|
|
break;
|
|
case A_I2CD_DMA_ADDR:
|
|
if (!aic->has_dma) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
|
|
value = -1;
|
|
}
|
|
break;
|
|
case A_I2CD_DMA_LEN:
|
|
if (!aic->has_dma) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
|
|
value = -1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, offset);
|
|
value = -1;
|
|
break;
|
|
}
|
|
|
|
trace_aspeed_i2c_bus_read(bus->id, offset, size, value);
|
|
return value;
|
|
}
|
|
|
|
static uint64_t aspeed_i2c_bus_new_read(AspeedI2CBus *bus, hwaddr offset,
|
|
unsigned size)
|
|
{
|
|
uint64_t value = bus->regs[offset / sizeof(*bus->regs)];
|
|
|
|
switch (offset) {
|
|
case A_I2CC_FUN_CTRL:
|
|
case A_I2CC_AC_TIMING:
|
|
case A_I2CC_POOL_CTRL:
|
|
case A_I2CM_INTR_CTRL:
|
|
case A_I2CM_INTR_STS:
|
|
case A_I2CC_MS_TXRX_BYTE_BUF:
|
|
case A_I2CM_DMA_LEN:
|
|
case A_I2CM_DMA_TX_ADDR:
|
|
case A_I2CM_DMA_RX_ADDR:
|
|
case A_I2CM_DMA_LEN_STS:
|
|
case A_I2CC_DMA_ADDR:
|
|
case A_I2CC_DMA_LEN:
|
|
|
|
case A_I2CS_DEV_ADDR:
|
|
case A_I2CS_DMA_RX_ADDR:
|
|
case A_I2CS_DMA_LEN:
|
|
case A_I2CS_CMD:
|
|
case A_I2CS_INTR_CTRL:
|
|
case A_I2CS_DMA_LEN_STS:
|
|
/* Value is already set, don't do anything. */
|
|
break;
|
|
case A_I2CS_INTR_STS:
|
|
break;
|
|
case A_I2CM_CMD:
|
|
value = SHARED_FIELD_DP32(value, BUS_BUSY_STS, i2c_bus_busy(bus->bus));
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, offset);
|
|
value = -1;
|
|
break;
|
|
}
|
|
|
|
trace_aspeed_i2c_bus_read(bus->id, offset, size, value);
|
|
return value;
|
|
}
|
|
|
|
static uint64_t aspeed_i2c_bus_read(void *opaque, hwaddr offset,
|
|
unsigned size)
|
|
{
|
|
AspeedI2CBus *bus = opaque;
|
|
if (aspeed_i2c_is_new_mode(bus->controller)) {
|
|
return aspeed_i2c_bus_new_read(bus, offset, size);
|
|
}
|
|
return aspeed_i2c_bus_old_read(bus, offset, size);
|
|
}
|
|
|
|
static void aspeed_i2c_set_state(AspeedI2CBus *bus, uint8_t state)
|
|
{
|
|
if (aspeed_i2c_is_new_mode(bus->controller)) {
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, R_I2CC_MS_TXRX_BYTE_BUF, TX_STATE,
|
|
state);
|
|
} else {
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, R_I2CD_CMD, TX_STATE, state);
|
|
}
|
|
}
|
|
|
|
static uint8_t aspeed_i2c_get_state(AspeedI2CBus *bus)
|
|
{
|
|
if (aspeed_i2c_is_new_mode(bus->controller)) {
|
|
return SHARED_ARRAY_FIELD_EX32(bus->regs, R_I2CC_MS_TXRX_BYTE_BUF,
|
|
TX_STATE);
|
|
}
|
|
return SHARED_ARRAY_FIELD_EX32(bus->regs, R_I2CD_CMD, TX_STATE);
|
|
}
|
|
|
|
static int aspeed_i2c_dma_read(AspeedI2CBus *bus, uint8_t *data)
|
|
{
|
|
MemTxResult result;
|
|
AspeedI2CState *s = bus->controller;
|
|
uint32_t reg_dma_addr = aspeed_i2c_bus_dma_addr_offset(bus);
|
|
uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
|
|
|
|
result = address_space_read(&s->dram_as, bus->regs[reg_dma_addr],
|
|
MEMTXATTRS_UNSPECIFIED, data, 1);
|
|
if (result != MEMTX_OK) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: DRAM read failed @%08x\n",
|
|
__func__, bus->regs[reg_dma_addr]);
|
|
return -1;
|
|
}
|
|
|
|
bus->regs[reg_dma_addr]++;
|
|
bus->regs[reg_dma_len]--;
|
|
return 0;
|
|
}
|
|
|
|
static int aspeed_i2c_bus_send(AspeedI2CBus *bus)
|
|
{
|
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
|
|
int ret = -1;
|
|
int i;
|
|
uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus);
|
|
uint32_t reg_pool_ctrl = aspeed_i2c_bus_pool_ctrl_offset(bus);
|
|
uint32_t reg_byte_buf = aspeed_i2c_bus_byte_buf_offset(bus);
|
|
uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
|
|
int pool_tx_count = SHARED_ARRAY_FIELD_EX32(bus->regs, reg_pool_ctrl,
|
|
TX_COUNT) + 1;
|
|
|
|
if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_BUFF_EN)) {
|
|
for (i = 0; i < pool_tx_count; i++) {
|
|
uint8_t *pool_base = aic->bus_pool_base(bus);
|
|
|
|
trace_aspeed_i2c_bus_send("BUF", i + 1, pool_tx_count,
|
|
pool_base[i]);
|
|
ret = i2c_send(bus->bus, pool_base[i]);
|
|
if (ret) {
|
|
break;
|
|
}
|
|
}
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, TX_BUFF_EN, 0);
|
|
} else if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_DMA_EN)) {
|
|
/* In new mode, clear how many bytes we TXed */
|
|
if (aspeed_i2c_is_new_mode(bus->controller)) {
|
|
ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, 0);
|
|
}
|
|
while (bus->regs[reg_dma_len]) {
|
|
uint8_t data;
|
|
aspeed_i2c_dma_read(bus, &data);
|
|
trace_aspeed_i2c_bus_send("DMA", bus->regs[reg_dma_len],
|
|
bus->regs[reg_dma_len], data);
|
|
ret = i2c_send(bus->bus, data);
|
|
if (ret) {
|
|
break;
|
|
}
|
|
/* In new mode, keep track of how many bytes we TXed */
|
|
if (aspeed_i2c_is_new_mode(bus->controller)) {
|
|
ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN,
|
|
ARRAY_FIELD_EX32(bus->regs, I2CM_DMA_LEN_STS,
|
|
TX_LEN) + 1);
|
|
}
|
|
}
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, TX_DMA_EN, 0);
|
|
} else {
|
|
trace_aspeed_i2c_bus_send("BYTE", 0, 1,
|
|
bus->regs[reg_byte_buf]);
|
|
ret = i2c_send(bus->bus, bus->regs[reg_byte_buf]);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
|
|
{
|
|
AspeedI2CState *s = bus->controller;
|
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(s);
|
|
uint8_t data;
|
|
int i;
|
|
uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus);
|
|
uint32_t reg_pool_ctrl = aspeed_i2c_bus_pool_ctrl_offset(bus);
|
|
uint32_t reg_byte_buf = aspeed_i2c_bus_byte_buf_offset(bus);
|
|
uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
|
|
uint32_t reg_dma_addr = aspeed_i2c_bus_dma_addr_offset(bus);
|
|
int pool_rx_count = SHARED_ARRAY_FIELD_EX32(bus->regs, reg_pool_ctrl,
|
|
RX_SIZE) + 1;
|
|
|
|
if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_BUFF_EN)) {
|
|
uint8_t *pool_base = aic->bus_pool_base(bus);
|
|
if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_pool_ctrl,
|
|
BUF_ORGANIZATION)) {
|
|
pool_base += 16;
|
|
}
|
|
|
|
for (i = 0; i < pool_rx_count; i++) {
|
|
pool_base[i] = i2c_recv(bus->bus);
|
|
trace_aspeed_i2c_bus_recv("BUF", i + 1, pool_rx_count,
|
|
pool_base[i]);
|
|
}
|
|
|
|
/* Update RX count */
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_pool_ctrl, RX_COUNT, i & 0xff);
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, RX_BUFF_EN, 0);
|
|
} else if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_DMA_EN)) {
|
|
/* In new mode, clear how many bytes we RXed */
|
|
if (aspeed_i2c_is_new_mode(bus->controller)) {
|
|
ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, 0);
|
|
}
|
|
|
|
while (bus->regs[reg_dma_len]) {
|
|
MemTxResult result;
|
|
|
|
data = i2c_recv(bus->bus);
|
|
trace_aspeed_i2c_bus_recv("DMA", bus->regs[reg_dma_len],
|
|
bus->regs[reg_dma_len], data);
|
|
result = address_space_write(&s->dram_as, bus->regs[reg_dma_addr],
|
|
MEMTXATTRS_UNSPECIFIED, &data, 1);
|
|
if (result != MEMTX_OK) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: DRAM write failed @%08x\n",
|
|
__func__, bus->regs[reg_dma_addr]);
|
|
return;
|
|
}
|
|
bus->regs[reg_dma_addr]++;
|
|
bus->regs[reg_dma_len]--;
|
|
/* In new mode, keep track of how many bytes we RXed */
|
|
if (aspeed_i2c_is_new_mode(bus->controller)) {
|
|
ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN,
|
|
ARRAY_FIELD_EX32(bus->regs, I2CM_DMA_LEN_STS,
|
|
RX_LEN) + 1);
|
|
}
|
|
}
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, RX_DMA_EN, 0);
|
|
} else {
|
|
data = i2c_recv(bus->bus);
|
|
trace_aspeed_i2c_bus_recv("BYTE", 1, 1, bus->regs[reg_byte_buf]);
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_byte_buf, RX_BUF, data);
|
|
}
|
|
}
|
|
|
|
static void aspeed_i2c_handle_rx_cmd(AspeedI2CBus *bus)
|
|
{
|
|
uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus);
|
|
uint32_t reg_intr_sts = aspeed_i2c_bus_intr_sts_offset(bus);
|
|
|
|
aspeed_i2c_set_state(bus, I2CD_MRXD);
|
|
aspeed_i2c_bus_recv(bus);
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_intr_sts, RX_DONE, 1);
|
|
if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, M_S_RX_CMD_LAST)) {
|
|
i2c_nack(bus->bus);
|
|
}
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, M_RX_CMD, 0);
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, M_S_RX_CMD_LAST, 0);
|
|
aspeed_i2c_set_state(bus, I2CD_MACTIVE);
|
|
}
|
|
|
|
static uint8_t aspeed_i2c_get_addr(AspeedI2CBus *bus)
|
|
{
|
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
|
|
uint32_t reg_byte_buf = aspeed_i2c_bus_byte_buf_offset(bus);
|
|
uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus);
|
|
|
|
if (aspeed_i2c_bus_pkt_mode_en(bus)) {
|
|
return (ARRAY_FIELD_EX32(bus->regs, I2CM_CMD, PKT_DEV_ADDR) << 1) |
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, M_RX_CMD);
|
|
}
|
|
if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_BUFF_EN)) {
|
|
uint8_t *pool_base = aic->bus_pool_base(bus);
|
|
|
|
return pool_base[0];
|
|
} else if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_DMA_EN)) {
|
|
uint8_t data;
|
|
|
|
aspeed_i2c_dma_read(bus, &data);
|
|
return data;
|
|
} else {
|
|
return bus->regs[reg_byte_buf];
|
|
}
|
|
}
|
|
|
|
static bool aspeed_i2c_check_sram(AspeedI2CBus *bus)
|
|
{
|
|
AspeedI2CState *s = bus->controller;
|
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(s);
|
|
uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus);
|
|
bool dma_en = SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_DMA_EN) ||
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_DMA_EN) ||
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_BUFF_EN) ||
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_BUFF_EN);
|
|
if (!aic->check_sram) {
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* AST2500: SRAM must be enabled before using the Buffer Pool or
|
|
* DMA mode.
|
|
*/
|
|
if (!FIELD_EX32(s->ctrl_global, I2C_CTRL_GLOBAL, SRAM_EN) && dma_en) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: SRAM is not enabled\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void aspeed_i2c_bus_cmd_dump(AspeedI2CBus *bus)
|
|
{
|
|
g_autofree char *cmd_flags = NULL;
|
|
uint32_t count;
|
|
uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus);
|
|
uint32_t reg_pool_ctrl = aspeed_i2c_bus_pool_ctrl_offset(bus);
|
|
uint32_t reg_intr_sts = aspeed_i2c_bus_intr_sts_offset(bus);
|
|
uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
|
|
if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_BUFF_EN)) {
|
|
count = SHARED_ARRAY_FIELD_EX32(bus->regs, reg_pool_ctrl, TX_COUNT) + 1;
|
|
} else if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_DMA_EN)) {
|
|
count = bus->regs[reg_dma_len];
|
|
} else { /* BYTE mode */
|
|
count = 1;
|
|
}
|
|
|
|
cmd_flags = g_strdup_printf("%s%s%s%s%s%s%s%s%s",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, M_START_CMD) ? "start|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_DMA_EN) ? "rxdma|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_DMA_EN) ? "txdma|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_BUFF_EN) ? "rxbuf|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_BUFF_EN) ? "txbuf|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, M_TX_CMD) ? "tx|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, M_RX_CMD) ? "rx|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, M_S_RX_CMD_LAST) ? "last|" : "",
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, M_STOP_CMD) ? "stop|" : "");
|
|
|
|
trace_aspeed_i2c_bus_cmd(bus->regs[reg_cmd], cmd_flags, count,
|
|
bus->regs[reg_intr_sts]);
|
|
}
|
|
|
|
/*
|
|
* The state machine needs some refinement. It is only used to track
|
|
* invalid STOP commands for the moment.
|
|
*/
|
|
static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value)
|
|
{
|
|
uint32_t reg_intr_sts = aspeed_i2c_bus_intr_sts_offset(bus);
|
|
uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus);
|
|
uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
|
|
|
|
if (!aspeed_i2c_check_sram(bus)) {
|
|
return;
|
|
}
|
|
|
|
if (trace_event_get_state_backends(TRACE_ASPEED_I2C_BUS_CMD)) {
|
|
aspeed_i2c_bus_cmd_dump(bus);
|
|
}
|
|
|
|
if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, M_START_CMD)) {
|
|
uint8_t state = aspeed_i2c_get_state(bus) & I2CD_MACTIVE ?
|
|
I2CD_MSTARTR : I2CD_MSTART;
|
|
uint8_t addr;
|
|
|
|
aspeed_i2c_set_state(bus, state);
|
|
|
|
addr = aspeed_i2c_get_addr(bus);
|
|
if (i2c_start_transfer(bus->bus, extract32(addr, 1, 7),
|
|
extract32(addr, 0, 1))) {
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_intr_sts, TX_NAK, 1);
|
|
if (aspeed_i2c_bus_pkt_mode_en(bus)) {
|
|
ARRAY_FIELD_DP32(bus->regs, I2CM_INTR_STS, PKT_CMD_FAIL, 1);
|
|
}
|
|
} else {
|
|
/* START doesn't set TX_ACK in packet mode */
|
|
if (!aspeed_i2c_bus_pkt_mode_en(bus)) {
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_intr_sts, TX_ACK, 1);
|
|
}
|
|
}
|
|
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, M_START_CMD, 0);
|
|
|
|
if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_DMA_EN)) {
|
|
if (bus->regs[reg_dma_len] == 0) {
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, M_TX_CMD, 0);
|
|
}
|
|
} else if (!SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_BUFF_EN)) {
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, M_TX_CMD, 0);
|
|
}
|
|
|
|
/* No slave found */
|
|
if (!i2c_bus_busy(bus->bus)) {
|
|
if (aspeed_i2c_bus_pkt_mode_en(bus)) {
|
|
ARRAY_FIELD_DP32(bus->regs, I2CM_INTR_STS, PKT_CMD_FAIL, 1);
|
|
ARRAY_FIELD_DP32(bus->regs, I2CM_INTR_STS, PKT_CMD_DONE, 1);
|
|
}
|
|
return;
|
|
}
|
|
aspeed_i2c_set_state(bus, I2CD_MACTIVE);
|
|
}
|
|
|
|
if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, M_TX_CMD)) {
|
|
aspeed_i2c_set_state(bus, I2CD_MTXD);
|
|
if (aspeed_i2c_bus_send(bus)) {
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_intr_sts, TX_NAK, 1);
|
|
i2c_end_transfer(bus->bus);
|
|
} else {
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_intr_sts, TX_ACK, 1);
|
|
}
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, M_TX_CMD, 0);
|
|
aspeed_i2c_set_state(bus, I2CD_MACTIVE);
|
|
}
|
|
|
|
if ((SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, M_RX_CMD) ||
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, M_S_RX_CMD_LAST)) &&
|
|
!SHARED_ARRAY_FIELD_EX32(bus->regs, reg_intr_sts, RX_DONE)) {
|
|
aspeed_i2c_handle_rx_cmd(bus);
|
|
}
|
|
|
|
if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, M_STOP_CMD)) {
|
|
if (!(aspeed_i2c_get_state(bus) & I2CD_MACTIVE)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: abnormal stop\n", __func__);
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_intr_sts, ABNORMAL, 1);
|
|
if (aspeed_i2c_bus_pkt_mode_en(bus)) {
|
|
ARRAY_FIELD_DP32(bus->regs, I2CM_INTR_STS, PKT_CMD_FAIL, 1);
|
|
}
|
|
} else {
|
|
aspeed_i2c_set_state(bus, I2CD_MSTOP);
|
|
i2c_end_transfer(bus->bus);
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_intr_sts, NORMAL_STOP, 1);
|
|
}
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, M_STOP_CMD, 0);
|
|
aspeed_i2c_set_state(bus, I2CD_IDLE);
|
|
|
|
i2c_schedule_pending_master(bus->bus);
|
|
}
|
|
|
|
if (aspeed_i2c_bus_pkt_mode_en(bus)) {
|
|
ARRAY_FIELD_DP32(bus->regs, I2CM_INTR_STS, PKT_CMD_DONE, 1);
|
|
}
|
|
}
|
|
|
|
static void aspeed_i2c_bus_new_write(AspeedI2CBus *bus, hwaddr offset,
|
|
uint64_t value, unsigned size)
|
|
{
|
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
|
|
bool handle_rx;
|
|
bool w1t;
|
|
|
|
trace_aspeed_i2c_bus_write(bus->id, offset, size, value);
|
|
|
|
switch (offset) {
|
|
case A_I2CC_FUN_CTRL:
|
|
bus->regs[R_I2CC_FUN_CTRL] = value;
|
|
break;
|
|
case A_I2CC_AC_TIMING:
|
|
bus->regs[R_I2CC_AC_TIMING] = value & 0x1ffff0ff;
|
|
break;
|
|
case A_I2CC_MS_TXRX_BYTE_BUF:
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, R_I2CC_MS_TXRX_BYTE_BUF, TX_BUF,
|
|
value);
|
|
break;
|
|
case A_I2CC_POOL_CTRL:
|
|
bus->regs[R_I2CC_POOL_CTRL] &= ~0xffffff;
|
|
bus->regs[R_I2CC_POOL_CTRL] |= (value & 0xffffff);
|
|
break;
|
|
case A_I2CM_INTR_CTRL:
|
|
bus->regs[R_I2CM_INTR_CTRL] = value & 0x0007f07f;
|
|
break;
|
|
case A_I2CM_INTR_STS:
|
|
handle_rx = SHARED_ARRAY_FIELD_EX32(bus->regs, R_I2CM_INTR_STS, RX_DONE)
|
|
&& SHARED_FIELD_EX32(value, RX_DONE);
|
|
|
|
/* In packet mode, clearing PKT_CMD_DONE clears other interrupts. */
|
|
if (aspeed_i2c_bus_pkt_mode_en(bus) &&
|
|
FIELD_EX32(value, I2CM_INTR_STS, PKT_CMD_DONE)) {
|
|
bus->regs[R_I2CM_INTR_STS] &= 0xf0001000;
|
|
if (!bus->regs[R_I2CM_INTR_STS]) {
|
|
bus->controller->intr_status &= ~(1 << bus->id);
|
|
qemu_irq_lower(aic->bus_get_irq(bus));
|
|
}
|
|
aspeed_i2c_bus_raise_slave_interrupt(bus);
|
|
break;
|
|
}
|
|
bus->regs[R_I2CM_INTR_STS] &= ~(value & 0xf007f07f);
|
|
if (!bus->regs[R_I2CM_INTR_STS]) {
|
|
bus->controller->intr_status &= ~(1 << bus->id);
|
|
qemu_irq_lower(aic->bus_get_irq(bus));
|
|
}
|
|
if (handle_rx && (SHARED_ARRAY_FIELD_EX32(bus->regs, R_I2CM_CMD,
|
|
M_RX_CMD) ||
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, R_I2CM_CMD,
|
|
M_S_RX_CMD_LAST))) {
|
|
aspeed_i2c_handle_rx_cmd(bus);
|
|
aspeed_i2c_bus_raise_interrupt(bus);
|
|
}
|
|
break;
|
|
case A_I2CM_CMD:
|
|
if (!aspeed_i2c_bus_is_enabled(bus)) {
|
|
break;
|
|
}
|
|
|
|
if (!aspeed_i2c_bus_is_master(bus)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Master mode is not enabled\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
|
|
if (!aic->has_dma &&
|
|
(SHARED_FIELD_EX32(value, RX_DMA_EN) ||
|
|
SHARED_FIELD_EX32(value, TX_DMA_EN))) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
|
|
break;
|
|
}
|
|
|
|
if (bus->regs[R_I2CM_INTR_STS] & 0xffff0000) {
|
|
qemu_log_mask(LOG_UNIMP, "%s: Packet mode is not implemented\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
|
|
value &= 0xff0ffbfb;
|
|
if (ARRAY_FIELD_EX32(bus->regs, I2CM_CMD, W1_CTRL)) {
|
|
bus->regs[R_I2CM_CMD] |= value;
|
|
} else {
|
|
bus->regs[R_I2CM_CMD] = value;
|
|
}
|
|
|
|
aspeed_i2c_bus_handle_cmd(bus, value);
|
|
aspeed_i2c_bus_raise_interrupt(bus);
|
|
break;
|
|
case A_I2CM_DMA_TX_ADDR:
|
|
bus->regs[R_I2CM_DMA_TX_ADDR] = FIELD_EX32(value, I2CM_DMA_TX_ADDR,
|
|
ADDR);
|
|
bus->regs[R_I2CC_DMA_ADDR] = FIELD_EX32(value, I2CM_DMA_TX_ADDR, ADDR);
|
|
bus->regs[R_I2CC_DMA_LEN] = ARRAY_FIELD_EX32(bus->regs, I2CM_DMA_LEN,
|
|
TX_BUF_LEN) + 1;
|
|
break;
|
|
case A_I2CM_DMA_RX_ADDR:
|
|
bus->regs[R_I2CM_DMA_RX_ADDR] = FIELD_EX32(value, I2CM_DMA_RX_ADDR,
|
|
ADDR);
|
|
bus->regs[R_I2CC_DMA_ADDR] = FIELD_EX32(value, I2CM_DMA_RX_ADDR, ADDR);
|
|
bus->regs[R_I2CC_DMA_LEN] = ARRAY_FIELD_EX32(bus->regs, I2CM_DMA_LEN,
|
|
RX_BUF_LEN) + 1;
|
|
break;
|
|
case A_I2CM_DMA_LEN:
|
|
w1t = FIELD_EX32(value, I2CM_DMA_LEN, RX_BUF_LEN_W1T) ||
|
|
FIELD_EX32(value, I2CM_DMA_LEN, TX_BUF_LEN_W1T);
|
|
/* If none of the w1t bits are set, just write to the reg as normal. */
|
|
if (!w1t) {
|
|
bus->regs[R_I2CM_DMA_LEN] = value;
|
|
break;
|
|
}
|
|
if (FIELD_EX32(value, I2CM_DMA_LEN, RX_BUF_LEN_W1T)) {
|
|
ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN, RX_BUF_LEN,
|
|
FIELD_EX32(value, I2CM_DMA_LEN, RX_BUF_LEN));
|
|
}
|
|
if (FIELD_EX32(value, I2CM_DMA_LEN, TX_BUF_LEN_W1T)) {
|
|
ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN, TX_BUF_LEN,
|
|
FIELD_EX32(value, I2CM_DMA_LEN, TX_BUF_LEN));
|
|
}
|
|
break;
|
|
case A_I2CM_DMA_LEN_STS:
|
|
/* Writes clear to 0 */
|
|
bus->regs[R_I2CM_DMA_LEN_STS] = 0;
|
|
break;
|
|
case A_I2CC_DMA_ADDR:
|
|
case A_I2CC_DMA_LEN:
|
|
/* RO */
|
|
break;
|
|
case A_I2CS_DEV_ADDR:
|
|
bus->regs[R_I2CS_DEV_ADDR] = value;
|
|
break;
|
|
case A_I2CS_DMA_RX_ADDR:
|
|
bus->regs[R_I2CS_DMA_RX_ADDR] = value;
|
|
break;
|
|
case A_I2CS_DMA_LEN:
|
|
assert(FIELD_EX32(value, I2CS_DMA_LEN, TX_BUF_LEN) == 0);
|
|
if (FIELD_EX32(value, I2CS_DMA_LEN, RX_BUF_LEN_W1T)) {
|
|
ARRAY_FIELD_DP32(bus->regs, I2CS_DMA_LEN, RX_BUF_LEN,
|
|
FIELD_EX32(value, I2CS_DMA_LEN, RX_BUF_LEN));
|
|
} else {
|
|
bus->regs[R_I2CS_DMA_LEN] = value;
|
|
}
|
|
break;
|
|
case A_I2CS_CMD:
|
|
if (FIELD_EX32(value, I2CS_CMD, W1_CTRL)) {
|
|
bus->regs[R_I2CS_CMD] |= value;
|
|
} else {
|
|
bus->regs[R_I2CS_CMD] = value;
|
|
}
|
|
i2c_slave_set_address(bus->slave, bus->regs[R_I2CS_DEV_ADDR]);
|
|
break;
|
|
case A_I2CS_INTR_CTRL:
|
|
bus->regs[R_I2CS_INTR_CTRL] = value;
|
|
break;
|
|
|
|
case A_I2CS_INTR_STS:
|
|
if (ARRAY_FIELD_EX32(bus->regs, I2CS_INTR_CTRL, PKT_CMD_DONE)) {
|
|
if (ARRAY_FIELD_EX32(bus->regs, I2CS_INTR_STS, PKT_CMD_DONE) &&
|
|
FIELD_EX32(value, I2CS_INTR_STS, PKT_CMD_DONE)) {
|
|
bus->regs[R_I2CS_INTR_STS] &= 0xfffc0000;
|
|
}
|
|
} else {
|
|
bus->regs[R_I2CS_INTR_STS] &= ~value;
|
|
}
|
|
if (!bus->regs[R_I2CS_INTR_STS]) {
|
|
bus->controller->intr_status &= ~(1 << bus->id);
|
|
qemu_irq_lower(aic->bus_get_irq(bus));
|
|
}
|
|
aspeed_i2c_bus_raise_interrupt(bus);
|
|
break;
|
|
case A_I2CS_DMA_LEN_STS:
|
|
bus->regs[R_I2CS_DMA_LEN_STS] = 0;
|
|
break;
|
|
case A_I2CS_DMA_TX_ADDR:
|
|
qemu_log_mask(LOG_UNIMP, "%s: Slave mode DMA TX is not implemented\n",
|
|
__func__);
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
|
|
__func__, offset);
|
|
}
|
|
}
|
|
|
|
static void aspeed_i2c_bus_old_write(AspeedI2CBus *bus, hwaddr offset,
|
|
uint64_t value, unsigned size)
|
|
{
|
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
|
|
bool handle_rx;
|
|
|
|
trace_aspeed_i2c_bus_write(bus->id, offset, size, value);
|
|
|
|
switch (offset) {
|
|
case A_I2CD_FUN_CTRL:
|
|
if (SHARED_FIELD_EX32(value, SLAVE_EN)) {
|
|
i2c_slave_set_address(bus->slave, bus->regs[R_I2CD_DEV_ADDR]);
|
|
}
|
|
bus->regs[R_I2CD_FUN_CTRL] = value & 0x0071C3FF;
|
|
break;
|
|
case A_I2CD_AC_TIMING1:
|
|
bus->regs[R_I2CD_AC_TIMING1] = value & 0xFFFFF0F;
|
|
break;
|
|
case A_I2CD_AC_TIMING2:
|
|
bus->regs[R_I2CD_AC_TIMING2] = value & 0x7;
|
|
break;
|
|
case A_I2CD_INTR_CTRL:
|
|
bus->regs[R_I2CD_INTR_CTRL] = value & 0x7FFF;
|
|
break;
|
|
case A_I2CD_INTR_STS:
|
|
handle_rx = SHARED_ARRAY_FIELD_EX32(bus->regs, R_I2CD_INTR_STS, RX_DONE)
|
|
&& SHARED_FIELD_EX32(value, RX_DONE);
|
|
bus->regs[R_I2CD_INTR_STS] &= ~(value & 0x7FFF);
|
|
if (!bus->regs[R_I2CD_INTR_STS]) {
|
|
bus->controller->intr_status &= ~(1 << bus->id);
|
|
qemu_irq_lower(aic->bus_get_irq(bus));
|
|
}
|
|
if (handle_rx) {
|
|
if (SHARED_ARRAY_FIELD_EX32(bus->regs, R_I2CD_CMD, M_RX_CMD) ||
|
|
SHARED_ARRAY_FIELD_EX32(bus->regs, R_I2CD_CMD,
|
|
M_S_RX_CMD_LAST)) {
|
|
aspeed_i2c_handle_rx_cmd(bus);
|
|
aspeed_i2c_bus_raise_interrupt(bus);
|
|
} else if (aspeed_i2c_get_state(bus) == I2CD_STXD) {
|
|
i2c_ack(bus->bus);
|
|
}
|
|
}
|
|
break;
|
|
case A_I2CD_DEV_ADDR:
|
|
bus->regs[R_I2CD_DEV_ADDR] = value;
|
|
break;
|
|
case A_I2CD_POOL_CTRL:
|
|
bus->regs[R_I2CD_POOL_CTRL] &= ~0xffffff;
|
|
bus->regs[R_I2CD_POOL_CTRL] |= (value & 0xffffff);
|
|
break;
|
|
|
|
case A_I2CD_BYTE_BUF:
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, R_I2CD_BYTE_BUF, TX_BUF, value);
|
|
break;
|
|
case A_I2CD_CMD:
|
|
if (!aspeed_i2c_bus_is_enabled(bus)) {
|
|
break;
|
|
}
|
|
|
|
if (!aspeed_i2c_bus_is_master(bus)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Master mode is not enabled\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
|
|
if (!aic->has_dma &&
|
|
(SHARED_FIELD_EX32(value, RX_DMA_EN) ||
|
|
SHARED_FIELD_EX32(value, TX_DMA_EN))) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
|
|
break;
|
|
}
|
|
|
|
bus->regs[R_I2CD_CMD] &= ~0xFFFF;
|
|
bus->regs[R_I2CD_CMD] |= value & 0xFFFF;
|
|
|
|
aspeed_i2c_bus_handle_cmd(bus, value);
|
|
aspeed_i2c_bus_raise_interrupt(bus);
|
|
break;
|
|
case A_I2CD_DMA_ADDR:
|
|
if (!aic->has_dma) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
|
|
break;
|
|
}
|
|
|
|
bus->regs[R_I2CD_DMA_ADDR] = value & 0x3ffffffc;
|
|
break;
|
|
|
|
case A_I2CD_DMA_LEN:
|
|
if (!aic->has_dma) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
|
|
break;
|
|
}
|
|
|
|
bus->regs[R_I2CD_DMA_LEN] = value & 0xfff;
|
|
if (!bus->regs[R_I2CD_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",
|
|
__func__, offset);
|
|
}
|
|
}
|
|
|
|
static void aspeed_i2c_bus_write(void *opaque, hwaddr offset,
|
|
uint64_t value, unsigned size)
|
|
{
|
|
AspeedI2CBus *bus = opaque;
|
|
if (aspeed_i2c_is_new_mode(bus->controller)) {
|
|
aspeed_i2c_bus_new_write(bus, offset, value, size);
|
|
} else {
|
|
aspeed_i2c_bus_old_write(bus, offset, value, size);
|
|
}
|
|
}
|
|
|
|
static uint64_t aspeed_i2c_ctrl_read(void *opaque, hwaddr offset,
|
|
unsigned size)
|
|
{
|
|
AspeedI2CState *s = opaque;
|
|
|
|
switch (offset) {
|
|
case A_I2C_CTRL_STATUS:
|
|
return s->intr_status;
|
|
case A_I2C_CTRL_GLOBAL:
|
|
return s->ctrl_global;
|
|
case A_I2C_CTRL_NEW_CLK_DIVIDER:
|
|
if (aspeed_i2c_is_new_mode(s)) {
|
|
return s->new_clk_divider;
|
|
}
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
|
|
__func__, offset);
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
|
|
__func__, offset);
|
|
break;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void aspeed_i2c_ctrl_write(void *opaque, hwaddr offset,
|
|
uint64_t value, unsigned size)
|
|
{
|
|
AspeedI2CState *s = opaque;
|
|
|
|
switch (offset) {
|
|
case A_I2C_CTRL_GLOBAL:
|
|
s->ctrl_global = value;
|
|
break;
|
|
case A_I2C_CTRL_NEW_CLK_DIVIDER:
|
|
if (aspeed_i2c_is_new_mode(s)) {
|
|
s->new_clk_divider = value;
|
|
} else {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx
|
|
"\n", __func__, offset);
|
|
}
|
|
break;
|
|
case A_I2C_CTRL_STATUS:
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
|
|
__func__, offset);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps aspeed_i2c_bus_ops = {
|
|
.read = aspeed_i2c_bus_read,
|
|
.write = aspeed_i2c_bus_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
static const MemoryRegionOps aspeed_i2c_ctrl_ops = {
|
|
.read = aspeed_i2c_ctrl_read,
|
|
.write = aspeed_i2c_ctrl_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
static uint64_t aspeed_i2c_share_pool_read(void *opaque, hwaddr offset,
|
|
unsigned size)
|
|
{
|
|
AspeedI2CState *s = opaque;
|
|
uint64_t ret = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
ret |= (uint64_t) s->share_pool[offset + i] << (8 * i);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void aspeed_i2c_share_pool_write(void *opaque, hwaddr offset,
|
|
uint64_t value, unsigned size)
|
|
{
|
|
AspeedI2CState *s = opaque;
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
s->share_pool[offset + i] = (value >> (8 * i)) & 0xFF;
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps aspeed_i2c_share_pool_ops = {
|
|
.read = aspeed_i2c_share_pool_read,
|
|
.write = aspeed_i2c_share_pool_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 1,
|
|
.max_access_size = 4,
|
|
},
|
|
};
|
|
|
|
static const VMStateDescription aspeed_i2c_bus_vmstate = {
|
|
.name = TYPE_ASPEED_I2C,
|
|
.version_id = 5,
|
|
.minimum_version_id = 5,
|
|
.fields = (const VMStateField[]) {
|
|
VMSTATE_UINT32_ARRAY(regs, AspeedI2CBus, ASPEED_I2C_NEW_NUM_REG),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription aspeed_i2c_vmstate = {
|
|
.name = TYPE_ASPEED_I2C,
|
|
.version_id = 3,
|
|
.minimum_version_id = 3,
|
|
.fields = (const VMStateField[]) {
|
|
VMSTATE_UINT32(intr_status, AspeedI2CState),
|
|
VMSTATE_STRUCT_ARRAY(busses, AspeedI2CState,
|
|
ASPEED_I2C_NR_BUSSES, 1, aspeed_i2c_bus_vmstate,
|
|
AspeedI2CBus),
|
|
VMSTATE_UINT8_ARRAY(share_pool, AspeedI2CState,
|
|
ASPEED_I2C_SHARE_POOL_SIZE),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static void aspeed_i2c_reset(DeviceState *dev)
|
|
{
|
|
AspeedI2CState *s = ASPEED_I2C(dev);
|
|
|
|
s->intr_status = 0;
|
|
}
|
|
|
|
static void aspeed_i2c_instance_init(Object *obj)
|
|
{
|
|
AspeedI2CState *s = ASPEED_I2C(obj);
|
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(s);
|
|
int i;
|
|
|
|
for (i = 0; i < aic->num_busses; i++) {
|
|
object_initialize_child(obj, "bus[*]", &s->busses[i],
|
|
TYPE_ASPEED_I2C_BUS);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Address Definitions (AST2400 and AST2500)
|
|
*
|
|
* 0x000 ... 0x03F: Global Register
|
|
* 0x040 ... 0x07F: Device 1
|
|
* 0x080 ... 0x0BF: Device 2
|
|
* 0x0C0 ... 0x0FF: Device 3
|
|
* 0x100 ... 0x13F: Device 4
|
|
* 0x140 ... 0x17F: Device 5
|
|
* 0x180 ... 0x1BF: Device 6
|
|
* 0x1C0 ... 0x1FF: Device 7
|
|
* 0x200 ... 0x2FF: Buffer Pool (AST2500 unused in linux driver)
|
|
* 0x300 ... 0x33F: Device 8
|
|
* 0x340 ... 0x37F: Device 9
|
|
* 0x380 ... 0x3BF: Device 10
|
|
* 0x3C0 ... 0x3FF: Device 11
|
|
* 0x400 ... 0x43F: Device 12
|
|
* 0x440 ... 0x47F: Device 13
|
|
* 0x480 ... 0x4BF: Device 14
|
|
* 0x800 ... 0xFFF: Buffer Pool (AST2400 unused in linux driver)
|
|
*/
|
|
static void aspeed_i2c_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
int i;
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
|
AspeedI2CState *s = ASPEED_I2C(dev);
|
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(s);
|
|
|
|
sysbus_init_irq(sbd, &s->irq);
|
|
memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_i2c_ctrl_ops, s,
|
|
"aspeed.i2c", aic->mem_size);
|
|
sysbus_init_mmio(sbd, &s->iomem);
|
|
|
|
for (i = 0; i < aic->num_busses; i++) {
|
|
Object *bus = OBJECT(&s->busses[i]);
|
|
int offset = i < aic->gap ? 1 : 5;
|
|
|
|
if (!object_property_set_link(bus, "controller", OBJECT(s), errp)) {
|
|
return;
|
|
}
|
|
|
|
if (!object_property_set_uint(bus, "bus-id", i, errp)) {
|
|
return;
|
|
}
|
|
|
|
if (!sysbus_realize(SYS_BUS_DEVICE(bus), errp)) {
|
|
return;
|
|
}
|
|
|
|
memory_region_add_subregion(&s->iomem, aic->reg_size * (i + offset),
|
|
&s->busses[i].mr);
|
|
}
|
|
|
|
memory_region_init_io(&s->pool_iomem, OBJECT(s),
|
|
&aspeed_i2c_share_pool_ops, s,
|
|
"aspeed.i2c-share-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,
|
|
TYPE_ASPEED_I2C "-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;
|
|
device_class_set_props(dc, aspeed_i2c_properties);
|
|
dc->realize = aspeed_i2c_realize;
|
|
dc->desc = "Aspeed I2C Controller";
|
|
}
|
|
|
|
static const TypeInfo aspeed_i2c_info = {
|
|
.name = TYPE_ASPEED_I2C,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_init = aspeed_i2c_instance_init,
|
|
.instance_size = sizeof(AspeedI2CState),
|
|
.class_init = aspeed_i2c_class_init,
|
|
.class_size = sizeof(AspeedI2CClass),
|
|
.abstract = true,
|
|
};
|
|
|
|
static int aspeed_i2c_bus_new_slave_event(AspeedI2CBus *bus,
|
|
enum i2c_event event)
|
|
{
|
|
switch (event) {
|
|
case I2C_START_SEND_ASYNC:
|
|
if (!SHARED_ARRAY_FIELD_EX32(bus->regs, R_I2CS_CMD, RX_DMA_EN)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: Slave mode RX DMA is not enabled\n", __func__);
|
|
return -1;
|
|
}
|
|
ARRAY_FIELD_DP32(bus->regs, I2CS_DMA_LEN_STS, RX_LEN, 0);
|
|
bus->regs[R_I2CC_DMA_ADDR] =
|
|
ARRAY_FIELD_EX32(bus->regs, I2CS_DMA_RX_ADDR, ADDR);
|
|
bus->regs[R_I2CC_DMA_LEN] =
|
|
ARRAY_FIELD_EX32(bus->regs, I2CS_DMA_LEN, RX_BUF_LEN) + 1;
|
|
i2c_ack(bus->bus);
|
|
break;
|
|
case I2C_FINISH:
|
|
ARRAY_FIELD_DP32(bus->regs, I2CS_INTR_STS, PKT_CMD_DONE, 1);
|
|
ARRAY_FIELD_DP32(bus->regs, I2CS_INTR_STS, SLAVE_ADDR_RX_MATCH, 1);
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, R_I2CS_INTR_STS, NORMAL_STOP, 1);
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, R_I2CS_INTR_STS, RX_DONE, 1);
|
|
aspeed_i2c_bus_raise_slave_interrupt(bus);
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_UNIMP, "%s: i2c event %d unimplemented\n",
|
|
__func__, event);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aspeed_i2c_bus_slave_event(I2CSlave *slave, enum i2c_event event)
|
|
{
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(slave));
|
|
AspeedI2CBus *bus = ASPEED_I2C_BUS(qbus->parent);
|
|
uint32_t reg_intr_sts = aspeed_i2c_bus_intr_sts_offset(bus);
|
|
uint32_t reg_byte_buf = aspeed_i2c_bus_byte_buf_offset(bus);
|
|
uint32_t reg_dev_addr = aspeed_i2c_bus_dev_addr_offset(bus);
|
|
uint32_t dev_addr = SHARED_ARRAY_FIELD_EX32(bus->regs, reg_dev_addr,
|
|
SLAVE_DEV_ADDR1);
|
|
|
|
if (aspeed_i2c_is_new_mode(bus->controller)) {
|
|
return aspeed_i2c_bus_new_slave_event(bus, event);
|
|
}
|
|
|
|
switch (event) {
|
|
case I2C_START_SEND_ASYNC:
|
|
/* Bit[0] == 0 indicates "send". */
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_byte_buf, RX_BUF, dev_addr << 1);
|
|
|
|
ARRAY_FIELD_DP32(bus->regs, I2CD_INTR_STS, SLAVE_ADDR_RX_MATCH, 1);
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_intr_sts, RX_DONE, 1);
|
|
|
|
aspeed_i2c_set_state(bus, I2CD_STXD);
|
|
|
|
break;
|
|
|
|
case I2C_FINISH:
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_intr_sts, NORMAL_STOP, 1);
|
|
|
|
aspeed_i2c_set_state(bus, I2CD_IDLE);
|
|
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
aspeed_i2c_bus_raise_interrupt(bus);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void aspeed_i2c_bus_new_slave_send_async(AspeedI2CBus *bus, uint8_t data)
|
|
{
|
|
assert(address_space_write(&bus->controller->dram_as,
|
|
bus->regs[R_I2CC_DMA_ADDR],
|
|
MEMTXATTRS_UNSPECIFIED, &data, 1) == MEMTX_OK);
|
|
|
|
bus->regs[R_I2CC_DMA_ADDR]++;
|
|
bus->regs[R_I2CC_DMA_LEN]--;
|
|
ARRAY_FIELD_DP32(bus->regs, I2CS_DMA_LEN_STS, RX_LEN,
|
|
ARRAY_FIELD_EX32(bus->regs, I2CS_DMA_LEN_STS, RX_LEN) + 1);
|
|
i2c_ack(bus->bus);
|
|
}
|
|
|
|
static void aspeed_i2c_bus_slave_send_async(I2CSlave *slave, uint8_t data)
|
|
{
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(slave));
|
|
AspeedI2CBus *bus = ASPEED_I2C_BUS(qbus->parent);
|
|
uint32_t reg_intr_sts = aspeed_i2c_bus_intr_sts_offset(bus);
|
|
uint32_t reg_byte_buf = aspeed_i2c_bus_byte_buf_offset(bus);
|
|
|
|
if (aspeed_i2c_is_new_mode(bus->controller)) {
|
|
return aspeed_i2c_bus_new_slave_send_async(bus, data);
|
|
}
|
|
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_byte_buf, RX_BUF, data);
|
|
SHARED_ARRAY_FIELD_DP32(bus->regs, reg_intr_sts, RX_DONE, 1);
|
|
|
|
aspeed_i2c_bus_raise_interrupt(bus);
|
|
}
|
|
|
|
static void aspeed_i2c_bus_slave_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass);
|
|
|
|
dc->desc = "Aspeed I2C Bus Slave";
|
|
|
|
sc->event = aspeed_i2c_bus_slave_event;
|
|
sc->send_async = aspeed_i2c_bus_slave_send_async;
|
|
}
|
|
|
|
static const TypeInfo aspeed_i2c_bus_slave_info = {
|
|
.name = TYPE_ASPEED_I2C_BUS_SLAVE,
|
|
.parent = TYPE_I2C_SLAVE,
|
|
.instance_size = sizeof(AspeedI2CBusSlave),
|
|
.class_init = aspeed_i2c_bus_slave_class_init,
|
|
};
|
|
|
|
static void aspeed_i2c_bus_reset(DeviceState *dev)
|
|
{
|
|
AspeedI2CBus *s = ASPEED_I2C_BUS(dev);
|
|
|
|
memset(s->regs, 0, sizeof(s->regs));
|
|
i2c_end_transfer(s->bus);
|
|
}
|
|
|
|
static void aspeed_i2c_bus_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
AspeedI2CBus *s = ASPEED_I2C_BUS(dev);
|
|
AspeedI2CClass *aic;
|
|
g_autofree char *name = g_strdup_printf(TYPE_ASPEED_I2C_BUS ".%d", s->id);
|
|
|
|
if (!s->controller) {
|
|
error_setg(errp, TYPE_ASPEED_I2C_BUS ": 'controller' link not set");
|
|
return;
|
|
}
|
|
|
|
aic = ASPEED_I2C_GET_CLASS(s->controller);
|
|
|
|
sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
|
|
|
|
s->bus = i2c_init_bus(dev, name);
|
|
s->slave = i2c_slave_create_simple(s->bus, TYPE_ASPEED_I2C_BUS_SLAVE,
|
|
0xff);
|
|
|
|
memory_region_init_io(&s->mr, OBJECT(s), &aspeed_i2c_bus_ops,
|
|
s, name, aic->reg_size);
|
|
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mr);
|
|
}
|
|
|
|
static Property aspeed_i2c_bus_properties[] = {
|
|
DEFINE_PROP_UINT8("bus-id", AspeedI2CBus, id, 0),
|
|
DEFINE_PROP_LINK("controller", AspeedI2CBus, controller, TYPE_ASPEED_I2C,
|
|
AspeedI2CState *),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void aspeed_i2c_bus_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->desc = "Aspeed I2C Bus";
|
|
dc->realize = aspeed_i2c_bus_realize;
|
|
dc->reset = aspeed_i2c_bus_reset;
|
|
device_class_set_props(dc, aspeed_i2c_bus_properties);
|
|
}
|
|
|
|
static const TypeInfo aspeed_i2c_bus_info = {
|
|
.name = TYPE_ASPEED_I2C_BUS,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(AspeedI2CBus),
|
|
.class_init = aspeed_i2c_bus_class_init,
|
|
};
|
|
|
|
static qemu_irq aspeed_2400_i2c_bus_get_irq(AspeedI2CBus *bus)
|
|
{
|
|
return bus->controller->irq;
|
|
}
|
|
|
|
static uint8_t *aspeed_2400_i2c_bus_pool_base(AspeedI2CBus *bus)
|
|
{
|
|
uint8_t *pool_page =
|
|
&bus->controller->share_pool[ARRAY_FIELD_EX32(bus->regs,
|
|
I2CD_FUN_CTRL,
|
|
POOL_PAGE_SEL) * 0x100];
|
|
|
|
return &pool_page[ARRAY_FIELD_EX32(bus->regs, I2CD_POOL_CTRL, OFFSET)];
|
|
}
|
|
|
|
static void aspeed_2400_i2c_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
AspeedI2CClass *aic = ASPEED_I2C_CLASS(klass);
|
|
|
|
dc->desc = "ASPEED 2400 I2C Controller";
|
|
|
|
aic->num_busses = 14;
|
|
aic->reg_size = 0x40;
|
|
aic->gap = 7;
|
|
aic->bus_get_irq = aspeed_2400_i2c_bus_get_irq;
|
|
aic->pool_size = 0x800;
|
|
aic->pool_base = 0x800;
|
|
aic->bus_pool_base = aspeed_2400_i2c_bus_pool_base;
|
|
aic->mem_size = 0x1000;
|
|
}
|
|
|
|
static const TypeInfo aspeed_2400_i2c_info = {
|
|
.name = TYPE_ASPEED_2400_I2C,
|
|
.parent = TYPE_ASPEED_I2C,
|
|
.class_init = aspeed_2400_i2c_class_init,
|
|
};
|
|
|
|
static qemu_irq aspeed_2500_i2c_bus_get_irq(AspeedI2CBus *bus)
|
|
{
|
|
return bus->controller->irq;
|
|
}
|
|
|
|
static uint8_t *aspeed_2500_i2c_bus_pool_base(AspeedI2CBus *bus)
|
|
{
|
|
return &bus->controller->share_pool[bus->id * 0x10];
|
|
}
|
|
|
|
static void aspeed_2500_i2c_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
AspeedI2CClass *aic = ASPEED_I2C_CLASS(klass);
|
|
|
|
dc->desc = "ASPEED 2500 I2C Controller";
|
|
|
|
aic->num_busses = 14;
|
|
aic->reg_size = 0x40;
|
|
aic->gap = 7;
|
|
aic->bus_get_irq = aspeed_2500_i2c_bus_get_irq;
|
|
aic->pool_size = 0x100;
|
|
aic->pool_base = 0x200;
|
|
aic->bus_pool_base = aspeed_2500_i2c_bus_pool_base;
|
|
aic->check_sram = true;
|
|
aic->has_dma = true;
|
|
aic->mem_size = 0x1000;
|
|
}
|
|
|
|
static const TypeInfo aspeed_2500_i2c_info = {
|
|
.name = TYPE_ASPEED_2500_I2C,
|
|
.parent = TYPE_ASPEED_I2C,
|
|
.class_init = aspeed_2500_i2c_class_init,
|
|
};
|
|
|
|
static qemu_irq aspeed_2600_i2c_bus_get_irq(AspeedI2CBus *bus)
|
|
{
|
|
return bus->irq;
|
|
}
|
|
|
|
static uint8_t *aspeed_2600_i2c_bus_pool_base(AspeedI2CBus *bus)
|
|
{
|
|
return &bus->controller->share_pool[bus->id * 0x20];
|
|
}
|
|
|
|
static void aspeed_2600_i2c_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
AspeedI2CClass *aic = ASPEED_I2C_CLASS(klass);
|
|
|
|
dc->desc = "ASPEED 2600 I2C Controller";
|
|
|
|
aic->num_busses = 16;
|
|
aic->reg_size = 0x80;
|
|
aic->gap = -1; /* no gap */
|
|
aic->bus_get_irq = aspeed_2600_i2c_bus_get_irq;
|
|
aic->pool_size = 0x200;
|
|
aic->pool_base = 0xC00;
|
|
aic->bus_pool_base = aspeed_2600_i2c_bus_pool_base;
|
|
aic->has_dma = true;
|
|
aic->mem_size = 0x1000;
|
|
}
|
|
|
|
static const TypeInfo aspeed_2600_i2c_info = {
|
|
.name = TYPE_ASPEED_2600_I2C,
|
|
.parent = TYPE_ASPEED_I2C,
|
|
.class_init = aspeed_2600_i2c_class_init,
|
|
};
|
|
|
|
static void aspeed_1030_i2c_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
AspeedI2CClass *aic = ASPEED_I2C_CLASS(klass);
|
|
|
|
dc->desc = "ASPEED 1030 I2C Controller";
|
|
|
|
aic->num_busses = 14;
|
|
aic->reg_size = 0x80;
|
|
aic->gap = -1; /* no gap */
|
|
aic->bus_get_irq = aspeed_2600_i2c_bus_get_irq;
|
|
aic->pool_size = 0x200;
|
|
aic->pool_base = 0xC00;
|
|
aic->bus_pool_base = aspeed_2600_i2c_bus_pool_base;
|
|
aic->has_dma = true;
|
|
aic->mem_size = 0x10000;
|
|
}
|
|
|
|
static const TypeInfo aspeed_1030_i2c_info = {
|
|
.name = TYPE_ASPEED_1030_I2C,
|
|
.parent = TYPE_ASPEED_I2C,
|
|
.class_init = aspeed_1030_i2c_class_init,
|
|
};
|
|
|
|
static void aspeed_i2c_register_types(void)
|
|
{
|
|
type_register_static(&aspeed_i2c_bus_info);
|
|
type_register_static(&aspeed_i2c_bus_slave_info);
|
|
type_register_static(&aspeed_i2c_info);
|
|
type_register_static(&aspeed_2400_i2c_info);
|
|
type_register_static(&aspeed_2500_i2c_info);
|
|
type_register_static(&aspeed_2600_i2c_info);
|
|
type_register_static(&aspeed_1030_i2c_info);
|
|
}
|
|
|
|
type_init(aspeed_i2c_register_types)
|
|
|
|
|
|
I2CBus *aspeed_i2c_get_bus(AspeedI2CState *s, int busnr)
|
|
{
|
|
AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(s);
|
|
I2CBus *bus = NULL;
|
|
|
|
if (busnr >= 0 && busnr < aic->num_busses) {
|
|
bus = s->busses[busnr].bus;
|
|
}
|
|
|
|
return bus;
|
|
}
|