aboutsummaryrefslogtreecommitdiffstats
path: root/hw/i2c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/i2c')
-rw-r--r--hw/i2c/Kconfig38
-rw-r--r--hw/i2c/aspeed_i2c.c1038
-rw-r--r--hw/i2c/bitbang_i2c.c226
-rw-r--r--hw/i2c/core.c357
-rw-r--r--hw/i2c/exynos4210_i2c.c333
-rw-r--r--hw/i2c/i2c_mux_pca954x.c290
-rw-r--r--hw/i2c/imx_i2c.c333
-rw-r--r--hw/i2c/meson.build19
-rw-r--r--hw/i2c/microbit_i2c.c130
-rw-r--r--hw/i2c/mpc_i2c.c359
-rw-r--r--hw/i2c/npcm7xx_smbus.c1098
-rw-r--r--hw/i2c/omap_i2c.c549
-rw-r--r--hw/i2c/pm_smbus.c497
-rw-r--r--hw/i2c/pmbus_device.c1612
-rw-r--r--hw/i2c/ppc4xx_i2c.c377
-rw-r--r--hw/i2c/smbus_eeprom.c300
-rw-r--r--hw/i2c/smbus_ich9.c155
-rw-r--r--hw/i2c/smbus_master.c164
-rw-r--r--hw/i2c/smbus_slave.c237
-rw-r--r--hw/i2c/trace-events33
-rw-r--r--hw/i2c/trace.h1
-rw-r--r--hw/i2c/versatile_i2c.c112
22 files changed, 8258 insertions, 0 deletions
diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig
new file mode 100644
index 000000000..8217cb504
--- /dev/null
+++ b/hw/i2c/Kconfig
@@ -0,0 +1,38 @@
+config I2C
+ bool
+
+config SMBUS
+ bool
+ select I2C
+
+config SMBUS_EEPROM
+ bool
+ select SMBUS
+
+config VERSATILE_I2C
+ bool
+ select BITBANG_I2C
+
+config ACPI_SMBUS
+ bool
+ select SMBUS
+
+config BITBANG_I2C
+ bool
+ select I2C
+
+config IMX_I2C
+ bool
+ select I2C
+
+config MPC_I2C
+ bool
+ select I2C
+
+config PCA954X
+ bool
+ select I2C
+
+config PMBUS
+ bool
+ select SMBUS
diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c
new file mode 100644
index 000000000..03a4f5a91
--- /dev/null
+++ b/hw/i2c/aspeed_i2c.c
@@ -0,0 +1,1038 @@
+/*
+ * 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/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 "trace.h"
+
+/* I2C Global Register */
+
+#define I2C_CTRL_STATUS 0x00 /* Device Interrupt Status */
+#define I2C_CTRL_ASSIGN 0x08 /* Device Interrupt Target
+ Assignment */
+#define I2C_CTRL_GLOBAL 0x0C /* Global Control Register */
+#define I2C_CTRL_SRAM_EN BIT(0)
+
+/* I2C Device (Bus) Register */
+
+#define I2CD_FUN_CTRL_REG 0x00 /* I2CD Function Control */
+#define I2CD_POOL_PAGE_SEL(x) (((x) >> 20) & 0x7) /* AST2400 */
+#define I2CD_M_SDA_LOCK_EN (0x1 << 16)
+#define I2CD_MULTI_MASTER_DIS (0x1 << 15)
+#define I2CD_M_SCL_DRIVE_EN (0x1 << 14)
+#define I2CD_MSB_STS (0x1 << 9)
+#define I2CD_SDA_DRIVE_1T_EN (0x1 << 8)
+#define I2CD_M_SDA_DRIVE_1T_EN (0x1 << 7)
+#define I2CD_M_HIGH_SPEED_EN (0x1 << 6)
+#define I2CD_DEF_ADDR_EN (0x1 << 5)
+#define I2CD_DEF_ALERT_EN (0x1 << 4)
+#define I2CD_DEF_ARP_EN (0x1 << 3)
+#define I2CD_DEF_GCALL_EN (0x1 << 2)
+#define I2CD_SLAVE_EN (0x1 << 1)
+#define I2CD_MASTER_EN (0x1)
+
+#define I2CD_AC_TIMING_REG1 0x04 /* Clock and AC Timing Control #1 */
+#define I2CD_AC_TIMING_REG2 0x08 /* Clock and AC Timing Control #1 */
+#define I2CD_INTR_CTRL_REG 0x0c /* I2CD Interrupt Control */
+#define I2CD_INTR_STS_REG 0x10 /* I2CD Interrupt Status */
+
+#define I2CD_INTR_SLAVE_ADDR_MATCH (0x1 << 31) /* 0: addr1 1: addr2 */
+#define I2CD_INTR_SLAVE_ADDR_RX_PENDING (0x1 << 30)
+/* bits[19-16] Reserved */
+
+/* All bits below are cleared by writing 1 */
+#define I2CD_INTR_SLAVE_INACTIVE_TIMEOUT (0x1 << 15)
+#define I2CD_INTR_SDA_DL_TIMEOUT (0x1 << 14)
+#define I2CD_INTR_BUS_RECOVER_DONE (0x1 << 13)
+#define I2CD_INTR_SMBUS_ALERT (0x1 << 12) /* Bus [0-3] only */
+#define I2CD_INTR_SMBUS_ARP_ADDR (0x1 << 11) /* Removed */
+#define I2CD_INTR_SMBUS_DEV_ALERT_ADDR (0x1 << 10) /* Removed */
+#define I2CD_INTR_SMBUS_DEF_ADDR (0x1 << 9) /* Removed */
+#define I2CD_INTR_GCALL_ADDR (0x1 << 8) /* Removed */
+#define I2CD_INTR_SLAVE_ADDR_RX_MATCH (0x1 << 7) /* use RX_DONE */
+#define I2CD_INTR_SCL_TIMEOUT (0x1 << 6)
+#define I2CD_INTR_ABNORMAL (0x1 << 5)
+#define I2CD_INTR_NORMAL_STOP (0x1 << 4)
+#define I2CD_INTR_ARBIT_LOSS (0x1 << 3)
+#define I2CD_INTR_RX_DONE (0x1 << 2)
+#define I2CD_INTR_TX_NAK (0x1 << 1)
+#define I2CD_INTR_TX_ACK (0x1 << 0)
+
+#define I2CD_CMD_REG 0x14 /* I2CD Command/Status */
+#define I2CD_SDA_OE (0x1 << 28)
+#define I2CD_SDA_O (0x1 << 27)
+#define I2CD_SCL_OE (0x1 << 26)
+#define I2CD_SCL_O (0x1 << 25)
+#define I2CD_TX_TIMING (0x1 << 24)
+#define I2CD_TX_STATUS (0x1 << 23)
+
+#define I2CD_TX_STATE_SHIFT 19 /* Tx State Machine */
+#define I2CD_TX_STATE_MASK 0xf
+#define I2CD_IDLE 0x0
+#define I2CD_MACTIVE 0x8
+#define I2CD_MSTART 0x9
+#define I2CD_MSTARTR 0xa
+#define I2CD_MSTOP 0xb
+#define I2CD_MTXD 0xc
+#define I2CD_MRXACK 0xd
+#define I2CD_MRXD 0xe
+#define I2CD_MTXACK 0xf
+#define I2CD_SWAIT 0x1
+#define I2CD_SRXD 0x4
+#define I2CD_STXACK 0x5
+#define I2CD_STXD 0x6
+#define I2CD_SRXACK 0x7
+#define I2CD_RECOVER 0x3
+
+#define I2CD_SCL_LINE_STS (0x1 << 18)
+#define I2CD_SDA_LINE_STS (0x1 << 17)
+#define I2CD_BUS_BUSY_STS (0x1 << 16)
+#define I2CD_SDA_OE_OUT_DIR (0x1 << 15)
+#define I2CD_SDA_O_OUT_DIR (0x1 << 14)
+#define I2CD_SCL_OE_OUT_DIR (0x1 << 13)
+#define I2CD_SCL_O_OUT_DIR (0x1 << 12)
+#define I2CD_BUS_RECOVER_CMD_EN (0x1 << 11)
+#define I2CD_S_ALT_EN (0x1 << 10)
+
+/* Command Bit */
+#define I2CD_RX_DMA_ENABLE (0x1 << 9)
+#define I2CD_TX_DMA_ENABLE (0x1 << 8)
+#define I2CD_RX_BUFF_ENABLE (0x1 << 7)
+#define I2CD_TX_BUFF_ENABLE (0x1 << 6)
+#define I2CD_M_STOP_CMD (0x1 << 5)
+#define I2CD_M_S_RX_CMD_LAST (0x1 << 4)
+#define I2CD_M_RX_CMD (0x1 << 3)
+#define I2CD_S_TX_CMD (0x1 << 2)
+#define I2CD_M_TX_CMD (0x1 << 1)
+#define I2CD_M_START_CMD (0x1)
+
+#define I2CD_DEV_ADDR_REG 0x18 /* Slave Device Address */
+#define I2CD_POOL_CTRL_REG 0x1c /* Pool Buffer Control */
+#define I2CD_POOL_RX_COUNT(x) (((x) >> 24) & 0xff)
+#define I2CD_POOL_RX_SIZE(x) ((((x) >> 16) & 0xff) + 1)
+#define I2CD_POOL_TX_COUNT(x) ((((x) >> 8) & 0xff) + 1)
+#define I2CD_POOL_OFFSET(x) (((x) & 0x3f) << 2) /* AST2400 */
+#define I2CD_BYTE_BUF_REG 0x20 /* Transmit/Receive Byte Buffer */
+#define I2CD_BYTE_BUF_TX_SHIFT 0
+#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)
+{
+ return bus->ctrl & I2CD_MASTER_EN;
+}
+
+static inline bool aspeed_i2c_bus_is_enabled(AspeedI2CBus *bus)
+{
+ return bus->ctrl & (I2CD_MASTER_EN | I2CD_SLAVE_EN);
+}
+
+static inline void aspeed_i2c_bus_raise_interrupt(AspeedI2CBus *bus)
+{
+ AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
+
+ trace_aspeed_i2c_bus_raise_interrupt(bus->intr_status,
+ bus->intr_status & I2CD_INTR_TX_NAK ? "nak|" : "",
+ bus->intr_status & I2CD_INTR_TX_ACK ? "ack|" : "",
+ bus->intr_status & I2CD_INTR_RX_DONE ? "done|" : "",
+ bus->intr_status & I2CD_INTR_NORMAL_STOP ? "normal|" : "",
+ bus->intr_status & I2CD_INTR_ABNORMAL ? "abnormal" : "");
+
+ bus->intr_status &= bus->intr_ctrl;
+ if (bus->intr_status) {
+ bus->controller->intr_status |= 1 << bus->id;
+ qemu_irq_raise(aic->bus_get_irq(bus));
+ }
+}
+
+static uint64_t aspeed_i2c_bus_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ AspeedI2CBus *bus = opaque;
+ AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
+ uint64_t value = -1;
+
+ switch (offset) {
+ case I2CD_FUN_CTRL_REG:
+ value = bus->ctrl;
+ break;
+ case I2CD_AC_TIMING_REG1:
+ value = bus->timing[0];
+ break;
+ case I2CD_AC_TIMING_REG2:
+ value = bus->timing[1];
+ break;
+ case I2CD_INTR_CTRL_REG:
+ value = bus->intr_ctrl;
+ break;
+ case I2CD_INTR_STS_REG:
+ value = bus->intr_status;
+ break;
+ case I2CD_POOL_CTRL_REG:
+ value = bus->pool_ctrl;
+ break;
+ case I2CD_BYTE_BUF_REG:
+ value = bus->buf;
+ break;
+ case I2CD_CMD_REG:
+ value = bus->cmd | (i2c_bus_busy(bus->bus) << 16);
+ break;
+ case I2CD_DMA_ADDR:
+ if (!aic->has_dma) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
+ break;
+ }
+ value = bus->dma_addr;
+ break;
+ case I2CD_DMA_LEN:
+ if (!aic->has_dma) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: No DMA support\n", __func__);
+ break;
+ }
+ value = bus->dma_len;
+ 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 void aspeed_i2c_set_state(AspeedI2CBus *bus, uint8_t state)
+{
+ bus->cmd &= ~(I2CD_TX_STATE_MASK << I2CD_TX_STATE_SHIFT);
+ bus->cmd |= (state & I2CD_TX_STATE_MASK) << I2CD_TX_STATE_SHIFT;
+}
+
+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);
+ int ret = -1;
+ int i;
+
+ if (bus->cmd & I2CD_TX_BUFF_ENABLE) {
+ for (i = pool_start; i < I2CD_POOL_TX_COUNT(bus->pool_ctrl); i++) {
+ uint8_t *pool_base = aic->bus_pool_base(bus);
+
+ trace_aspeed_i2c_bus_send("BUF", i + 1,
+ I2CD_POOL_TX_COUNT(bus->pool_ctrl),
+ pool_base[i]);
+ ret = i2c_send(bus->bus, pool_base[i]);
+ if (ret) {
+ break;
+ }
+ }
+ 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);
+ trace_aspeed_i2c_bus_send("DMA", bus->dma_len, bus->dma_len, data);
+ ret = i2c_send(bus->bus, data);
+ if (ret) {
+ break;
+ }
+ }
+ bus->cmd &= ~I2CD_TX_DMA_ENABLE;
+ } else {
+ trace_aspeed_i2c_bus_send("BYTE", pool_start, 1, bus->buf);
+ ret = i2c_send(bus->bus, bus->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;
+
+ if (bus->cmd & I2CD_RX_BUFF_ENABLE) {
+ uint8_t *pool_base = aic->bus_pool_base(bus);
+
+ for (i = 0; i < I2CD_POOL_RX_SIZE(bus->pool_ctrl); i++) {
+ pool_base[i] = i2c_recv(bus->bus);
+ trace_aspeed_i2c_bus_recv("BUF", i + 1,
+ I2CD_POOL_RX_SIZE(bus->pool_ctrl),
+ pool_base[i]);
+ }
+
+ /* Update RX count */
+ 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);
+ trace_aspeed_i2c_bus_recv("DMA", bus->dma_len, bus->dma_len, data);
+ 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);
+ trace_aspeed_i2c_bus_recv("BYTE", 1, 1, bus->buf);
+ bus->buf = (data & I2CD_BYTE_BUF_RX_MASK) << I2CD_BYTE_BUF_RX_SHIFT;
+ }
+}
+
+static void aspeed_i2c_handle_rx_cmd(AspeedI2CBus *bus)
+{
+ aspeed_i2c_set_state(bus, I2CD_MRXD);
+ aspeed_i2c_bus_recv(bus);
+ bus->intr_status |= I2CD_INTR_RX_DONE;
+ if (bus->cmd & I2CD_M_S_RX_CMD_LAST) {
+ i2c_nack(bus->bus);
+ }
+ bus->cmd &= ~(I2CD_M_RX_CMD | I2CD_M_S_RX_CMD_LAST);
+ aspeed_i2c_set_state(bus, I2CD_MACTIVE);
+}
+
+static uint8_t aspeed_i2c_get_addr(AspeedI2CBus *bus)
+{
+ AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
+
+ if (bus->cmd & I2CD_TX_BUFF_ENABLE) {
+ 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;
+ }
+}
+
+static bool aspeed_i2c_check_sram(AspeedI2CBus *bus)
+{
+ AspeedI2CState *s = bus->controller;
+ AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(s);
+
+ if (!aic->check_sram) {
+ return true;
+ }
+
+ /*
+ * AST2500: SRAM must be enabled before using the Buffer Pool or
+ * DMA mode.
+ */
+ if (!(s->ctrl_global & I2C_CTRL_SRAM_EN) &&
+ (bus->cmd & (I2CD_RX_DMA_ENABLE | I2CD_TX_DMA_ENABLE |
+ I2CD_RX_BUFF_ENABLE | I2CD_TX_BUFF_ENABLE))) {
+ 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;
+
+ if (bus->cmd & (I2CD_RX_BUFF_ENABLE | I2CD_RX_BUFF_ENABLE)) {
+ count = I2CD_POOL_TX_COUNT(bus->pool_ctrl);
+ } else if (bus->cmd & (I2CD_RX_DMA_ENABLE | I2CD_RX_DMA_ENABLE)) {
+ count = bus->dma_len;
+ } else { /* BYTE mode */
+ count = 1;
+ }
+
+ cmd_flags = g_strdup_printf("%s%s%s%s%s%s%s%s%s",
+ bus->cmd & I2CD_M_START_CMD ? "start|" : "",
+ bus->cmd & I2CD_RX_DMA_ENABLE ? "rxdma|" : "",
+ bus->cmd & I2CD_TX_DMA_ENABLE ? "txdma|" : "",
+ bus->cmd & I2CD_RX_BUFF_ENABLE ? "rxbuf|" : "",
+ bus->cmd & I2CD_TX_BUFF_ENABLE ? "txbuf|" : "",
+ bus->cmd & I2CD_M_TX_CMD ? "tx|" : "",
+ bus->cmd & I2CD_M_RX_CMD ? "rx|" : "",
+ bus->cmd & I2CD_M_S_RX_CMD_LAST ? "last|" : "",
+ bus->cmd & I2CD_M_STOP_CMD ? "stop" : "");
+
+ trace_aspeed_i2c_bus_cmd(bus->cmd, cmd_flags, count, bus->intr_status);
+}
+
+/*
+ * 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)
+{
+ uint8_t pool_start = 0;
+
+ bus->cmd &= ~0xFFFF;
+ bus->cmd |= value & 0xFFFF;
+
+ 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 (bus->cmd & I2CD_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))) {
+ bus->intr_status |= I2CD_INTR_TX_NAK;
+ } else {
+ bus->intr_status |= I2CD_INTR_TX_ACK;
+ }
+
+ bus->cmd &= ~I2CD_M_START_CMD;
+
+ /*
+ * The START command is also a TX command, as the slave
+ * address is sent on the bus. Drop the TX flag if nothing
+ * else needs to be sent in this sequence.
+ */
+ if (bus->cmd & I2CD_TX_BUFF_ENABLE) {
+ if (I2CD_POOL_TX_COUNT(bus->pool_ctrl) == 1) {
+ bus->cmd &= ~I2CD_M_TX_CMD;
+ } else {
+ /*
+ * Increase the start index in the TX pool buffer to
+ * skip the address byte.
+ */
+ 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;
+ }
+
+ /* No slave found */
+ if (!i2c_bus_busy(bus->bus)) {
+ return;
+ }
+ aspeed_i2c_set_state(bus, I2CD_MACTIVE);
+ }
+
+ if (bus->cmd & I2CD_M_TX_CMD) {
+ aspeed_i2c_set_state(bus, I2CD_MTXD);
+ if (aspeed_i2c_bus_send(bus, pool_start)) {
+ bus->intr_status |= (I2CD_INTR_TX_NAK);
+ i2c_end_transfer(bus->bus);
+ } else {
+ bus->intr_status |= I2CD_INTR_TX_ACK;
+ }
+ bus->cmd &= ~I2CD_M_TX_CMD;
+ aspeed_i2c_set_state(bus, I2CD_MACTIVE);
+ }
+
+ if ((bus->cmd & (I2CD_M_RX_CMD | I2CD_M_S_RX_CMD_LAST)) &&
+ !(bus->intr_status & I2CD_INTR_RX_DONE)) {
+ aspeed_i2c_handle_rx_cmd(bus);
+ }
+
+ if (bus->cmd & I2CD_M_STOP_CMD) {
+ if (!(aspeed_i2c_get_state(bus) & I2CD_MACTIVE)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: abnormal stop\n", __func__);
+ bus->intr_status |= I2CD_INTR_ABNORMAL;
+ } else {
+ aspeed_i2c_set_state(bus, I2CD_MSTOP);
+ i2c_end_transfer(bus->bus);
+ bus->intr_status |= I2CD_INTR_NORMAL_STOP;
+ }
+ bus->cmd &= ~I2CD_M_STOP_CMD;
+ aspeed_i2c_set_state(bus, I2CD_IDLE);
+ }
+}
+
+static void aspeed_i2c_bus_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ AspeedI2CBus *bus = opaque;
+ AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
+ bool handle_rx;
+
+ trace_aspeed_i2c_bus_write(bus->id, offset, size, value);
+
+ switch (offset) {
+ case I2CD_FUN_CTRL_REG:
+ if (value & I2CD_SLAVE_EN) {
+ qemu_log_mask(LOG_UNIMP, "%s: slave mode not implemented\n",
+ __func__);
+ break;
+ }
+ bus->ctrl = value & 0x0071C3FF;
+ break;
+ case I2CD_AC_TIMING_REG1:
+ bus->timing[0] = value & 0xFFFFF0F;
+ break;
+ case I2CD_AC_TIMING_REG2:
+ bus->timing[1] = value & 0x7;
+ break;
+ case I2CD_INTR_CTRL_REG:
+ bus->intr_ctrl = value & 0x7FFF;
+ break;
+ case I2CD_INTR_STS_REG:
+ handle_rx = (bus->intr_status & I2CD_INTR_RX_DONE) &&
+ (value & I2CD_INTR_RX_DONE);
+ bus->intr_status &= ~(value & 0x7FFF);
+ if (!bus->intr_status) {
+ bus->controller->intr_status &= ~(1 << bus->id);
+ qemu_irq_lower(aic->bus_get_irq(bus));
+ }
+ if (handle_rx && (bus->cmd & (I2CD_M_RX_CMD | I2CD_M_S_RX_CMD_LAST))) {
+ aspeed_i2c_handle_rx_cmd(bus);
+ aspeed_i2c_bus_raise_interrupt(bus);
+ }
+ break;
+ case I2CD_DEV_ADDR_REG:
+ qemu_log_mask(LOG_UNIMP, "%s: slave mode not implemented\n",
+ __func__);
+ break;
+ case I2CD_POOL_CTRL_REG:
+ bus->pool_ctrl &= ~0xffffff;
+ bus->pool_ctrl |= (value & 0xffffff);
+ break;
+
+ case I2CD_BYTE_BUF_REG:
+ bus->buf = (value & I2CD_BYTE_BUF_TX_MASK) << I2CD_BYTE_BUF_TX_SHIFT;
+ break;
+ case I2CD_CMD_REG:
+ if (!aspeed_i2c_bus_is_enabled(bus)) {
+ break;
+ }
+
+ if (!aspeed_i2c_bus_is_master(bus)) {
+ qemu_log_mask(LOG_UNIMP, "%s: slave mode not implemented\n",
+ __func__);
+ 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 & 0x3ffffffc;
+ 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",
+ __func__, offset);
+ }
+}
+
+static uint64_t aspeed_i2c_ctrl_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ AspeedI2CState *s = opaque;
+
+ switch (offset) {
+ case I2C_CTRL_STATUS:
+ return s->intr_status;
+ case I2C_CTRL_GLOBAL:
+ return s->ctrl_global;
+ 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 I2C_CTRL_GLOBAL:
+ s->ctrl_global = value;
+ break;
+ case 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_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->pool[offset + i] << (8 * i);
+ }
+
+ return ret;
+}
+
+static void aspeed_i2c_pool_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ AspeedI2CState *s = opaque;
+ int i;
+
+ for (i = 0; i < size; i++) {
+ s->pool[offset + i] = (value >> (8 * i)) & 0xFF;
+ }
+}
+
+static const MemoryRegionOps aspeed_i2c_pool_ops = {
+ .read = aspeed_i2c_pool_read,
+ .write = aspeed_i2c_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 = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(id, AspeedI2CBus),
+ VMSTATE_UINT32(ctrl, AspeedI2CBus),
+ VMSTATE_UINT32_ARRAY(timing, AspeedI2CBus, 2),
+ VMSTATE_UINT32(intr_ctrl, AspeedI2CBus),
+ VMSTATE_UINT32(intr_status, AspeedI2CBus),
+ 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()
+ }
+};
+
+static const VMStateDescription aspeed_i2c_vmstate = {
+ .name = TYPE_ASPEED_I2C,
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(intr_status, AspeedI2CState),
+ VMSTATE_STRUCT_ARRAY(busses, AspeedI2CState,
+ ASPEED_I2C_NR_BUSSES, 1, aspeed_i2c_bus_vmstate,
+ AspeedI2CBus),
+ VMSTATE_UINT8_ARRAY(pool, AspeedI2CState, ASPEED_I2C_MAX_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 (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 (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", 0x1000);
+ 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_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,
+ 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 void aspeed_i2c_bus_reset(DeviceState *dev)
+{
+ AspeedI2CBus *s = ASPEED_I2C_BUS(dev);
+
+ s->intr_ctrl = 0;
+ s->intr_status = 0;
+ s->cmd = 0;
+ s->buf = 0;
+ s->dma_addr = 0;
+ s->dma_len = 0;
+ 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);
+
+ 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->pool[I2CD_POOL_PAGE_SEL(bus->ctrl) * 0x100];
+
+ return &pool_page[I2CD_POOL_OFFSET(bus->pool_ctrl)];
+}
+
+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;
+}
+
+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->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;
+}
+
+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->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;
+}
+
+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_i2c_register_types(void)
+{
+ type_register_static(&aspeed_i2c_bus_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_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;
+}
diff --git a/hw/i2c/bitbang_i2c.c b/hw/i2c/bitbang_i2c.c
new file mode 100644
index 000000000..e9a0612a0
--- /dev/null
+++ b/hw/i2c/bitbang_i2c.c
@@ -0,0 +1,226 @@
+/*
+ * Bit-Bang i2c emulation extracted from
+ * Marvell MV88W8618 / Freecom MusicPal emulation.
+ *
+ * Copyright (c) 2008 Jan Kiszka
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/i2c/bitbang_i2c.h"
+#include "hw/sysbus.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+//#define DEBUG_BITBANG_I2C
+
+#ifdef DEBUG_BITBANG_I2C
+#define DPRINTF(fmt, ...) \
+do { printf("bitbang_i2c: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+static void bitbang_i2c_enter_stop(bitbang_i2c_interface *i2c)
+{
+ DPRINTF("STOP\n");
+ if (i2c->current_addr >= 0)
+ i2c_end_transfer(i2c->bus);
+ i2c->current_addr = -1;
+ i2c->state = STOPPED;
+}
+
+/* Set device data pin. */
+static int bitbang_i2c_ret(bitbang_i2c_interface *i2c, int level)
+{
+ i2c->device_out = level;
+ //DPRINTF("%d %d %d\n", i2c->last_clock, i2c->last_data, i2c->device_out);
+ return level & i2c->last_data;
+}
+
+/* Leave device data pin unodified. */
+static int bitbang_i2c_nop(bitbang_i2c_interface *i2c)
+{
+ return bitbang_i2c_ret(i2c, i2c->device_out);
+}
+
+/* Returns data line level. */
+int bitbang_i2c_set(bitbang_i2c_interface *i2c, int line, int level)
+{
+ int data;
+
+ if (level != 0 && level != 1) {
+ abort();
+ }
+
+ if (line == BITBANG_I2C_SDA) {
+ if (level == i2c->last_data) {
+ return bitbang_i2c_nop(i2c);
+ }
+ i2c->last_data = level;
+ if (i2c->last_clock == 0) {
+ return bitbang_i2c_nop(i2c);
+ }
+ if (level == 0) {
+ DPRINTF("START\n");
+ /* START condition. */
+ i2c->state = SENDING_BIT7;
+ i2c->current_addr = -1;
+ } else {
+ /* STOP condition. */
+ bitbang_i2c_enter_stop(i2c);
+ }
+ return bitbang_i2c_ret(i2c, 1);
+ }
+
+ data = i2c->last_data;
+ if (i2c->last_clock == level) {
+ return bitbang_i2c_nop(i2c);
+ }
+ i2c->last_clock = level;
+ if (level == 0) {
+ /* State is set/read at the start of the clock pulse.
+ release the data line at the end. */
+ return bitbang_i2c_ret(i2c, 1);
+ }
+ switch (i2c->state) {
+ case STOPPED:
+ case SENT_NACK:
+ return bitbang_i2c_ret(i2c, 1);
+
+ case SENDING_BIT7 ... SENDING_BIT0:
+ i2c->buffer = (i2c->buffer << 1) | data;
+ /* will end up in WAITING_FOR_ACK */
+ i2c->state++;
+ return bitbang_i2c_ret(i2c, 1);
+
+ case WAITING_FOR_ACK:
+ {
+ int ret;
+
+ if (i2c->current_addr < 0) {
+ i2c->current_addr = i2c->buffer;
+ DPRINTF("Address 0x%02x\n", i2c->current_addr);
+ ret = i2c_start_transfer(i2c->bus, i2c->current_addr >> 1,
+ i2c->current_addr & 1);
+ } else {
+ DPRINTF("Sent 0x%02x\n", i2c->buffer);
+ ret = i2c_send(i2c->bus, i2c->buffer);
+ }
+ if (ret) {
+ /* NACK (either addressing a nonexistent device, or the
+ * device we were sending to decided to NACK us).
+ */
+ DPRINTF("Got NACK\n");
+ bitbang_i2c_enter_stop(i2c);
+ return bitbang_i2c_ret(i2c, 1);
+ }
+ if (i2c->current_addr & 1) {
+ i2c->state = RECEIVING_BIT7;
+ } else {
+ i2c->state = SENDING_BIT7;
+ }
+ return bitbang_i2c_ret(i2c, 0);
+ }
+ case RECEIVING_BIT7:
+ i2c->buffer = i2c_recv(i2c->bus);
+ DPRINTF("RX byte 0x%02x\n", i2c->buffer);
+ /* Fall through... */
+ case RECEIVING_BIT6 ... RECEIVING_BIT0:
+ data = i2c->buffer >> 7;
+ /* will end up in SENDING_ACK */
+ i2c->state++;
+ i2c->buffer <<= 1;
+ return bitbang_i2c_ret(i2c, data);
+
+ case SENDING_ACK:
+ i2c->state = RECEIVING_BIT7;
+ if (data != 0) {
+ DPRINTF("NACKED\n");
+ i2c->state = SENT_NACK;
+ i2c_nack(i2c->bus);
+ } else {
+ DPRINTF("ACKED\n");
+ }
+ return bitbang_i2c_ret(i2c, 1);
+ }
+ abort();
+}
+
+void bitbang_i2c_init(bitbang_i2c_interface *s, I2CBus *bus)
+{
+ s->bus = bus;
+ s->last_data = 1;
+ s->last_clock = 1;
+ s->device_out = 1;
+}
+
+/* GPIO interface. */
+
+#define TYPE_GPIO_I2C "gpio_i2c"
+OBJECT_DECLARE_SIMPLE_TYPE(GPIOI2CState, GPIO_I2C)
+
+struct GPIOI2CState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion dummy_iomem;
+ bitbang_i2c_interface bitbang;
+ int last_level;
+ qemu_irq out;
+};
+
+static void bitbang_i2c_gpio_set(void *opaque, int irq, int level)
+{
+ GPIOI2CState *s = opaque;
+
+ level = bitbang_i2c_set(&s->bitbang, irq, level);
+ if (level != s->last_level) {
+ s->last_level = level;
+ qemu_set_irq(s->out, level);
+ }
+}
+
+static void gpio_i2c_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ GPIOI2CState *s = GPIO_I2C(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ I2CBus *bus;
+
+ memory_region_init(&s->dummy_iomem, obj, "gpio_i2c", 0);
+ sysbus_init_mmio(sbd, &s->dummy_iomem);
+
+ bus = i2c_init_bus(dev, "i2c");
+ bitbang_i2c_init(&s->bitbang, bus);
+
+ qdev_init_gpio_in(dev, bitbang_i2c_gpio_set, 2);
+ qdev_init_gpio_out(dev, &s->out, 1);
+}
+
+static void gpio_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->desc = "Virtual GPIO to I2C bridge";
+}
+
+static const TypeInfo gpio_i2c_info = {
+ .name = TYPE_GPIO_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(GPIOI2CState),
+ .instance_init = gpio_i2c_init,
+ .class_init = gpio_i2c_class_init,
+};
+
+static void bitbang_i2c_register_types(void)
+{
+ type_register_static(&gpio_i2c_info);
+}
+
+type_init(bitbang_i2c_register_types)
diff --git a/hw/i2c/core.c b/hw/i2c/core.c
new file mode 100644
index 000000000..0e7d2763b
--- /dev/null
+++ b/hw/i2c/core.c
@@ -0,0 +1,357 @@
+/*
+ * QEMU I2C bus interface.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the LGPL.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+#define I2C_BROADCAST 0x00
+
+static Property i2c_props[] = {
+ DEFINE_PROP_UINT8("address", struct I2CSlave, address, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const TypeInfo i2c_bus_info = {
+ .name = TYPE_I2C_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(I2CBus),
+};
+
+static int i2c_bus_pre_save(void *opaque)
+{
+ I2CBus *bus = opaque;
+
+ bus->saved_address = -1;
+ if (!QLIST_EMPTY(&bus->current_devs)) {
+ if (!bus->broadcast) {
+ bus->saved_address = QLIST_FIRST(&bus->current_devs)->elt->address;
+ } else {
+ bus->saved_address = I2C_BROADCAST;
+ }
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_i2c_bus = {
+ .name = "i2c_bus",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = i2c_bus_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(saved_address, I2CBus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* Create a new I2C bus. */
+I2CBus *i2c_init_bus(DeviceState *parent, const char *name)
+{
+ I2CBus *bus;
+
+ bus = I2C_BUS(qbus_new(TYPE_I2C_BUS, parent, name));
+ QLIST_INIT(&bus->current_devs);
+ vmstate_register(NULL, VMSTATE_INSTANCE_ID_ANY, &vmstate_i2c_bus, bus);
+ return bus;
+}
+
+void i2c_slave_set_address(I2CSlave *dev, uint8_t address)
+{
+ dev->address = address;
+}
+
+/* Return nonzero if bus is busy. */
+int i2c_bus_busy(I2CBus *bus)
+{
+ return !QLIST_EMPTY(&bus->current_devs);
+}
+
+bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
+ I2CNodeList *current_devs)
+{
+ BusChild *kid;
+
+ QTAILQ_FOREACH(kid, &bus->qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ I2CSlave *candidate = I2C_SLAVE(qdev);
+ I2CSlaveClass *sc = I2C_SLAVE_GET_CLASS(candidate);
+
+ if (sc->match_and_add(candidate, address, broadcast, current_devs)) {
+ if (!broadcast) {
+ return true;
+ }
+ }
+ }
+
+ /*
+ * If broadcast was true, and the list was full or empty, return true. If
+ * broadcast was false, return false.
+ */
+ return broadcast;
+}
+
+/* TODO: Make this handle multiple masters. */
+/*
+ * Start or continue an i2c transaction. When this is called for the
+ * first time or after an i2c_end_transfer(), if it returns an error
+ * the bus transaction is terminated (or really never started). If
+ * this is called after another i2c_start_transfer() without an
+ * intervening i2c_end_transfer(), and it returns an error, the
+ * transaction will not be terminated. The caller must do it.
+ *
+ * This corresponds with the way real hardware works. The SMBus
+ * protocol uses a start transfer to switch from write to read mode
+ * without releasing the bus. If that fails, the bus is still
+ * in a transaction.
+ *
+ * @event must be I2C_START_RECV or I2C_START_SEND.
+ */
+static int i2c_do_start_transfer(I2CBus *bus, uint8_t address,
+ enum i2c_event event)
+{
+ I2CSlaveClass *sc;
+ I2CNode *node;
+ bool bus_scanned = false;
+
+ if (address == I2C_BROADCAST) {
+ /*
+ * This is a broadcast, the current_devs will be all the devices of the
+ * bus.
+ */
+ bus->broadcast = true;
+ }
+
+ /*
+ * If there are already devices in the list, that means we are in
+ * the middle of a transaction and we shouldn't rescan the bus.
+ *
+ * This happens with any SMBus transaction, even on a pure I2C
+ * device. The interface does a transaction start without
+ * terminating the previous transaction.
+ */
+ if (QLIST_EMPTY(&bus->current_devs)) {
+ /* Disregard whether devices were found. */
+ (void)i2c_scan_bus(bus, address, bus->broadcast, &bus->current_devs);
+ bus_scanned = true;
+ }
+
+ if (QLIST_EMPTY(&bus->current_devs)) {
+ return 1;
+ }
+
+ QLIST_FOREACH(node, &bus->current_devs, next) {
+ I2CSlave *s = node->elt;
+ int rv;
+
+ sc = I2C_SLAVE_GET_CLASS(s);
+ /* If the bus is already busy, assume this is a repeated
+ start condition. */
+
+ if (sc->event) {
+ trace_i2c_event("start", s->address);
+ rv = sc->event(s, event);
+ if (rv && !bus->broadcast) {
+ if (bus_scanned) {
+ /* First call, terminate the transfer. */
+ i2c_end_transfer(bus);
+ }
+ return rv;
+ }
+ }
+ }
+ return 0;
+}
+
+int i2c_start_transfer(I2CBus *bus, uint8_t address, bool is_recv)
+{
+ return i2c_do_start_transfer(bus, address, is_recv
+ ? I2C_START_RECV
+ : I2C_START_SEND);
+}
+
+int i2c_start_recv(I2CBus *bus, uint8_t address)
+{
+ return i2c_do_start_transfer(bus, address, I2C_START_RECV);
+}
+
+int i2c_start_send(I2CBus *bus, uint8_t address)
+{
+ return i2c_do_start_transfer(bus, address, I2C_START_SEND);
+}
+
+void i2c_end_transfer(I2CBus *bus)
+{
+ I2CSlaveClass *sc;
+ I2CNode *node, *next;
+
+ QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) {
+ I2CSlave *s = node->elt;
+ sc = I2C_SLAVE_GET_CLASS(s);
+ if (sc->event) {
+ trace_i2c_event("finish", s->address);
+ sc->event(s, I2C_FINISH);
+ }
+ QLIST_REMOVE(node, next);
+ g_free(node);
+ }
+ bus->broadcast = false;
+}
+
+int i2c_send(I2CBus *bus, uint8_t data)
+{
+ I2CSlaveClass *sc;
+ I2CSlave *s;
+ I2CNode *node;
+ int ret = 0;
+
+ QLIST_FOREACH(node, &bus->current_devs, next) {
+ s = node->elt;
+ sc = I2C_SLAVE_GET_CLASS(s);
+ if (sc->send) {
+ trace_i2c_send(s->address, data);
+ ret = ret || sc->send(s, data);
+ } else {
+ ret = -1;
+ }
+ }
+
+ return ret ? -1 : 0;
+}
+
+uint8_t i2c_recv(I2CBus *bus)
+{
+ uint8_t data = 0xff;
+ I2CSlaveClass *sc;
+ I2CSlave *s;
+
+ if (!QLIST_EMPTY(&bus->current_devs) && !bus->broadcast) {
+ sc = I2C_SLAVE_GET_CLASS(QLIST_FIRST(&bus->current_devs)->elt);
+ if (sc->recv) {
+ s = QLIST_FIRST(&bus->current_devs)->elt;
+ data = sc->recv(s);
+ trace_i2c_recv(s->address, data);
+ }
+ }
+
+ return data;
+}
+
+void i2c_nack(I2CBus *bus)
+{
+ I2CSlaveClass *sc;
+ I2CNode *node;
+
+ if (QLIST_EMPTY(&bus->current_devs)) {
+ return;
+ }
+
+ QLIST_FOREACH(node, &bus->current_devs, next) {
+ sc = I2C_SLAVE_GET_CLASS(node->elt);
+ if (sc->event) {
+ trace_i2c_event("nack", node->elt->address);
+ sc->event(node->elt, I2C_NACK);
+ }
+ }
+}
+
+static int i2c_slave_post_load(void *opaque, int version_id)
+{
+ I2CSlave *dev = opaque;
+ I2CBus *bus;
+ I2CNode *node;
+
+ bus = I2C_BUS(qdev_get_parent_bus(DEVICE(dev)));
+ if ((bus->saved_address == dev->address) ||
+ (bus->saved_address == I2C_BROADCAST)) {
+ node = g_malloc(sizeof(struct I2CNode));
+ node->elt = dev;
+ QLIST_INSERT_HEAD(&bus->current_devs, node, next);
+ }
+ return 0;
+}
+
+const VMStateDescription vmstate_i2c_slave = {
+ .name = "I2CSlave",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = i2c_slave_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(address, I2CSlave),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+I2CSlave *i2c_slave_new(const char *name, uint8_t addr)
+{
+ DeviceState *dev;
+
+ dev = qdev_new(name);
+ qdev_prop_set_uint8(dev, "address", addr);
+ return I2C_SLAVE(dev);
+}
+
+bool i2c_slave_realize_and_unref(I2CSlave *dev, I2CBus *bus, Error **errp)
+{
+ return qdev_realize_and_unref(&dev->qdev, &bus->qbus, errp);
+}
+
+I2CSlave *i2c_slave_create_simple(I2CBus *bus, const char *name, uint8_t addr)
+{
+ I2CSlave *dev = i2c_slave_new(name, addr);
+
+ i2c_slave_realize_and_unref(dev, bus, &error_abort);
+
+ return dev;
+}
+
+static bool i2c_slave_match(I2CSlave *candidate, uint8_t address,
+ bool broadcast, I2CNodeList *current_devs)
+{
+ if ((candidate->address == address) || (broadcast)) {
+ I2CNode *node = g_malloc(sizeof(struct I2CNode));
+ node->elt = candidate;
+ QLIST_INSERT_HEAD(current_devs, node, next);
+ return true;
+ }
+
+ /* Not found and not broadcast. */
+ return false;
+}
+
+static void i2c_slave_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass);
+ set_bit(DEVICE_CATEGORY_MISC, k->categories);
+ k->bus_type = TYPE_I2C_BUS;
+ device_class_set_props(k, i2c_props);
+ sc->match_and_add = i2c_slave_match;
+}
+
+static const TypeInfo i2c_slave_type_info = {
+ .name = TYPE_I2C_SLAVE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(I2CSlave),
+ .abstract = true,
+ .class_size = sizeof(I2CSlaveClass),
+ .class_init = i2c_slave_class_init,
+};
+
+static void i2c_slave_register_types(void)
+{
+ type_register_static(&i2c_bus_info);
+ type_register_static(&i2c_slave_type_info);
+}
+
+type_init(i2c_slave_register_types)
diff --git a/hw/i2c/exynos4210_i2c.c b/hw/i2c/exynos4210_i2c.c
new file mode 100644
index 000000000..b65a7d022
--- /dev/null
+++ b/hw/i2c/exynos4210_i2c.c
@@ -0,0 +1,333 @@
+/*
+ * Exynos4210 I2C Bus Serial Interface Emulation
+ *
+ * Copyright (C) 2012 Samsung Electronics Co Ltd.
+ * Maksim Kozlov, <m.kozlov@samsung.com>
+ * Igor Mitsyanko, <i.mitsyanko@samsung.com>
+ *
+ * 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 "qemu/module.h"
+#include "qemu/timer.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/i2c/i2c.h"
+#include "hw/irq.h"
+#include "qom/object.h"
+
+#ifndef EXYNOS4_I2C_DEBUG
+#define EXYNOS4_I2C_DEBUG 0
+#endif
+
+#define TYPE_EXYNOS4_I2C "exynos4210.i2c"
+OBJECT_DECLARE_SIMPLE_TYPE(Exynos4210I2CState, EXYNOS4_I2C)
+
+/* Exynos4210 I2C memory map */
+#define EXYNOS4_I2C_MEM_SIZE 0x14
+#define I2CCON_ADDR 0x00 /* control register */
+#define I2CSTAT_ADDR 0x04 /* control/status register */
+#define I2CADD_ADDR 0x08 /* address register */
+#define I2CDS_ADDR 0x0c /* data shift register */
+#define I2CLC_ADDR 0x10 /* line control register */
+
+#define I2CCON_ACK_GEN (1 << 7)
+#define I2CCON_INTRS_EN (1 << 5)
+#define I2CCON_INT_PEND (1 << 4)
+
+#define EXYNOS4_I2C_MODE(reg) (((reg) >> 6) & 3)
+#define I2C_IN_MASTER_MODE(reg) (((reg) >> 6) & 2)
+#define I2CMODE_MASTER_Rx 0x2
+#define I2CMODE_MASTER_Tx 0x3
+#define I2CSTAT_LAST_BIT (1 << 0)
+#define I2CSTAT_OUTPUT_EN (1 << 4)
+#define I2CSTAT_START_BUSY (1 << 5)
+
+
+#if EXYNOS4_I2C_DEBUG
+#define DPRINT(fmt, args...) \
+ do { fprintf(stderr, "QEMU I2C: "fmt, ## args); } while (0)
+
+static const char *exynos4_i2c_get_regname(unsigned offset)
+{
+ switch (offset) {
+ case I2CCON_ADDR:
+ return "I2CCON";
+ case I2CSTAT_ADDR:
+ return "I2CSTAT";
+ case I2CADD_ADDR:
+ return "I2CADD";
+ case I2CDS_ADDR:
+ return "I2CDS";
+ case I2CLC_ADDR:
+ return "I2CLC";
+ default:
+ return "[?]";
+ }
+}
+
+#else
+#define DPRINT(fmt, args...) do { } while (0)
+#endif
+
+struct Exynos4210I2CState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ I2CBus *bus;
+ qemu_irq irq;
+
+ uint8_t i2ccon;
+ uint8_t i2cstat;
+ uint8_t i2cadd;
+ uint8_t i2cds;
+ uint8_t i2clc;
+ bool scl_free;
+};
+
+static inline void exynos4210_i2c_raise_interrupt(Exynos4210I2CState *s)
+{
+ if (s->i2ccon & I2CCON_INTRS_EN) {
+ s->i2ccon |= I2CCON_INT_PEND;
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static void exynos4210_i2c_data_receive(void *opaque)
+{
+ Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+
+ s->i2cstat &= ~I2CSTAT_LAST_BIT;
+ s->scl_free = false;
+ s->i2cds = i2c_recv(s->bus);
+ exynos4210_i2c_raise_interrupt(s);
+}
+
+static void exynos4210_i2c_data_send(void *opaque)
+{
+ Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+
+ s->i2cstat &= ~I2CSTAT_LAST_BIT;
+ s->scl_free = false;
+ if (i2c_send(s->bus, s->i2cds) < 0 && (s->i2ccon & I2CCON_ACK_GEN)) {
+ s->i2cstat |= I2CSTAT_LAST_BIT;
+ }
+ exynos4210_i2c_raise_interrupt(s);
+}
+
+static uint64_t exynos4210_i2c_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+ uint8_t value;
+
+ switch (offset) {
+ case I2CCON_ADDR:
+ value = s->i2ccon;
+ break;
+ case I2CSTAT_ADDR:
+ value = s->i2cstat;
+ break;
+ case I2CADD_ADDR:
+ value = s->i2cadd;
+ break;
+ case I2CDS_ADDR:
+ value = s->i2cds;
+ s->scl_free = true;
+ if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx &&
+ (s->i2cstat & I2CSTAT_START_BUSY) &&
+ !(s->i2ccon & I2CCON_INT_PEND)) {
+ exynos4210_i2c_data_receive(s);
+ }
+ break;
+ case I2CLC_ADDR:
+ value = s->i2clc;
+ break;
+ default:
+ value = 0;
+ DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset);
+ break;
+ }
+
+ DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_get_regname(offset),
+ (unsigned int)offset, value);
+ return value;
+}
+
+static void exynos4210_i2c_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+ uint8_t v = value & 0xff;
+
+ DPRINT("write %s [0x%02x] <- 0x%02x\n", exynos4_i2c_get_regname(offset),
+ (unsigned int)offset, v);
+
+ switch (offset) {
+ case I2CCON_ADDR:
+ s->i2ccon = (v & ~I2CCON_INT_PEND) | (s->i2ccon & I2CCON_INT_PEND);
+ if ((s->i2ccon & I2CCON_INT_PEND) && !(v & I2CCON_INT_PEND)) {
+ s->i2ccon &= ~I2CCON_INT_PEND;
+ qemu_irq_lower(s->irq);
+ if (!(s->i2ccon & I2CCON_INTRS_EN)) {
+ s->i2cstat &= ~I2CSTAT_START_BUSY;
+ }
+
+ if (s->i2cstat & I2CSTAT_START_BUSY) {
+ if (s->scl_free) {
+ if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) {
+ exynos4210_i2c_data_send(s);
+ } else if (EXYNOS4_I2C_MODE(s->i2cstat) ==
+ I2CMODE_MASTER_Rx) {
+ exynos4210_i2c_data_receive(s);
+ }
+ } else {
+ s->i2ccon |= I2CCON_INT_PEND;
+ qemu_irq_raise(s->irq);
+ }
+ }
+ }
+ break;
+ case I2CSTAT_ADDR:
+ s->i2cstat =
+ (s->i2cstat & I2CSTAT_START_BUSY) | (v & ~I2CSTAT_START_BUSY);
+
+ if (!(s->i2cstat & I2CSTAT_OUTPUT_EN)) {
+ s->i2cstat &= ~I2CSTAT_START_BUSY;
+ s->scl_free = true;
+ qemu_irq_lower(s->irq);
+ break;
+ }
+
+ /* Nothing to do if in i2c slave mode */
+ if (!I2C_IN_MASTER_MODE(s->i2cstat)) {
+ break;
+ }
+
+ if (v & I2CSTAT_START_BUSY) {
+ s->i2cstat &= ~I2CSTAT_LAST_BIT;
+ s->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */
+ s->scl_free = false;
+
+ /* Generate start bit and send slave address */
+ if (i2c_start_transfer(s->bus, s->i2cds >> 1, s->i2cds & 0x1) &&
+ (s->i2ccon & I2CCON_ACK_GEN)) {
+ s->i2cstat |= I2CSTAT_LAST_BIT;
+ } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) {
+ exynos4210_i2c_data_receive(s);
+ }
+ exynos4210_i2c_raise_interrupt(s);
+ } else {
+ i2c_end_transfer(s->bus);
+ if (!(s->i2ccon & I2CCON_INT_PEND)) {
+ s->i2cstat &= ~I2CSTAT_START_BUSY;
+ }
+ s->scl_free = true;
+ }
+ break;
+ case I2CADD_ADDR:
+ if ((s->i2cstat & I2CSTAT_OUTPUT_EN) == 0) {
+ s->i2cadd = v;
+ }
+ break;
+ case I2CDS_ADDR:
+ if (s->i2cstat & I2CSTAT_OUTPUT_EN) {
+ s->i2cds = v;
+ s->scl_free = true;
+ if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx &&
+ (s->i2cstat & I2CSTAT_START_BUSY) &&
+ !(s->i2ccon & I2CCON_INT_PEND)) {
+ exynos4210_i2c_data_send(s);
+ }
+ }
+ break;
+ case I2CLC_ADDR:
+ s->i2clc = v;
+ break;
+ default:
+ DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps exynos4210_i2c_ops = {
+ .read = exynos4210_i2c_read,
+ .write = exynos4210_i2c_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription exynos4210_i2c_vmstate = {
+ .name = "exynos4210.i2c",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(i2ccon, Exynos4210I2CState),
+ VMSTATE_UINT8(i2cstat, Exynos4210I2CState),
+ VMSTATE_UINT8(i2cds, Exynos4210I2CState),
+ VMSTATE_UINT8(i2cadd, Exynos4210I2CState),
+ VMSTATE_UINT8(i2clc, Exynos4210I2CState),
+ VMSTATE_BOOL(scl_free, Exynos4210I2CState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void exynos4210_i2c_reset(DeviceState *d)
+{
+ Exynos4210I2CState *s = EXYNOS4_I2C(d);
+
+ s->i2ccon = 0x00;
+ s->i2cstat = 0x00;
+ s->i2cds = 0xFF;
+ s->i2clc = 0x00;
+ s->i2cadd = 0xFF;
+ s->scl_free = true;
+}
+
+static void exynos4210_i2c_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ Exynos4210I2CState *s = EXYNOS4_I2C(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, obj, &exynos4210_i2c_ops, s,
+ TYPE_EXYNOS4_I2C, EXYNOS4_I2C_MEM_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ s->bus = i2c_init_bus(dev, "i2c");
+}
+
+static void exynos4210_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &exynos4210_i2c_vmstate;
+ dc->reset = exynos4210_i2c_reset;
+}
+
+static const TypeInfo exynos4210_i2c_type_info = {
+ .name = TYPE_EXYNOS4_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210I2CState),
+ .instance_init = exynos4210_i2c_init,
+ .class_init = exynos4210_i2c_class_init,
+};
+
+static void exynos4210_i2c_register_types(void)
+{
+ type_register_static(&exynos4210_i2c_type_info);
+}
+
+type_init(exynos4210_i2c_register_types)
diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
new file mode 100644
index 000000000..847c59921
--- /dev/null
+++ b/hw/i2c/i2c_mux_pca954x.c
@@ -0,0 +1,290 @@
+/*
+ * I2C multiplexer for PCA954x series of I2C multiplexer/switch chips.
+ *
+ * Copyright 2021 Google LLC
+ *
+ * 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.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/i2c/i2c.h"
+#include "hw/i2c/i2c_mux_pca954x.h"
+#include "hw/i2c/smbus_slave.h"
+#include "hw/qdev-core.h"
+#include "hw/sysbus.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/queue.h"
+#include "qom/object.h"
+#include "trace.h"
+
+#define PCA9548_CHANNEL_COUNT 8
+#define PCA9546_CHANNEL_COUNT 4
+
+/*
+ * struct Pca954xChannel - The i2c mux device will have N of these states
+ * that own the i2c channel bus.
+ * @bus: The owned channel bus.
+ * @enabled: Is this channel active?
+ */
+typedef struct Pca954xChannel {
+ SysBusDevice parent;
+
+ I2CBus *bus;
+
+ bool enabled;
+} Pca954xChannel;
+
+#define TYPE_PCA954X_CHANNEL "pca954x-channel"
+#define PCA954X_CHANNEL(obj) \
+ OBJECT_CHECK(Pca954xChannel, (obj), TYPE_PCA954X_CHANNEL)
+
+/*
+ * struct Pca954xState - The pca954x state object.
+ * @control: The value written to the mux control.
+ * @channel: The set of i2c channel buses that act as channels which own the
+ * i2c children.
+ */
+typedef struct Pca954xState {
+ SMBusDevice parent;
+
+ uint8_t control;
+
+ /* The channel i2c buses. */
+ Pca954xChannel channel[PCA9548_CHANNEL_COUNT];
+} Pca954xState;
+
+/*
+ * struct Pca954xClass - The pca954x class object.
+ * @nchans: The number of i2c channels this device has.
+ */
+typedef struct Pca954xClass {
+ SMBusDeviceClass parent;
+
+ uint8_t nchans;
+} Pca954xClass;
+
+#define TYPE_PCA954X "pca954x"
+OBJECT_DECLARE_TYPE(Pca954xState, Pca954xClass, PCA954X)
+
+/*
+ * For each channel, if it's enabled, recursively call match on those children.
+ */
+static bool pca954x_match(I2CSlave *candidate, uint8_t address,
+ bool broadcast,
+ I2CNodeList *current_devs)
+{
+ Pca954xState *mux = PCA954X(candidate);
+ Pca954xClass *mc = PCA954X_GET_CLASS(mux);
+ int i;
+
+ /* They are talking to the mux itself (or all devices enabled). */
+ if ((candidate->address == address) || broadcast) {
+ I2CNode *node = g_malloc(sizeof(struct I2CNode));
+ node->elt = candidate;
+ QLIST_INSERT_HEAD(current_devs, node, next);
+ if (!broadcast) {
+ return true;
+ }
+ }
+
+ for (i = 0; i < mc->nchans; i++) {
+ if (!mux->channel[i].enabled) {
+ continue;
+ }
+
+ if (i2c_scan_bus(mux->channel[i].bus, address, broadcast,
+ current_devs)) {
+ if (!broadcast) {
+ return true;
+ }
+ }
+ }
+
+ /* If we arrived here we didn't find a match, return broadcast. */
+ return broadcast;
+}
+
+static void pca954x_enable_channel(Pca954xState *s, uint8_t enable_mask)
+{
+ Pca954xClass *mc = PCA954X_GET_CLASS(s);
+ int i;
+
+ /*
+ * For each channel, check if their bit is set in enable_mask and if yes,
+ * enable it, otherwise disable, hide it.
+ */
+ for (i = 0; i < mc->nchans; i++) {
+ if (enable_mask & (1 << i)) {
+ s->channel[i].enabled = true;
+ } else {
+ s->channel[i].enabled = false;
+ }
+ }
+}
+
+static void pca954x_write(Pca954xState *s, uint8_t data)
+{
+ s->control = data;
+ pca954x_enable_channel(s, data);
+
+ trace_pca954x_write_bytes(data);
+}
+
+static int pca954x_write_data(SMBusDevice *d, uint8_t *buf, uint8_t len)
+{
+ Pca954xState *s = PCA954X(d);
+
+ if (len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+ return -1;
+ }
+
+ /*
+ * len should be 1, because they write one byte to enable/disable channels.
+ */
+ if (len > 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: extra data after channel selection mask\n",
+ __func__);
+ return -1;
+ }
+
+ pca954x_write(s, buf[0]);
+ return 0;
+}
+
+static uint8_t pca954x_read_byte(SMBusDevice *d)
+{
+ Pca954xState *s = PCA954X(d);
+ uint8_t data = s->control;
+ trace_pca954x_read_data(data);
+ return data;
+}
+
+static void pca954x_enter_reset(Object *obj, ResetType type)
+{
+ Pca954xState *s = PCA954X(obj);
+ /* Reset will disable all channels. */
+ pca954x_write(s, 0);
+}
+
+I2CBus *pca954x_i2c_get_bus(I2CSlave *mux, uint8_t channel)
+{
+ Pca954xClass *pc = PCA954X_GET_CLASS(mux);
+ Pca954xState *pca954x = PCA954X(mux);
+
+ g_assert(channel < pc->nchans);
+ return I2C_BUS(qdev_get_child_bus(DEVICE(&pca954x->channel[channel]),
+ "i2c-bus"));
+}
+
+static void pca954x_channel_init(Object *obj)
+{
+ Pca954xChannel *s = PCA954X_CHANNEL(obj);
+ s->bus = i2c_init_bus(DEVICE(s), "i2c-bus");
+
+ /* Start all channels as disabled. */
+ s->enabled = false;
+}
+
+static void pca954x_channel_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->desc = "Pca954x Channel";
+}
+
+static void pca9546_class_init(ObjectClass *klass, void *data)
+{
+ Pca954xClass *s = PCA954X_CLASS(klass);
+ s->nchans = PCA9546_CHANNEL_COUNT;
+}
+
+static void pca9548_class_init(ObjectClass *klass, void *data)
+{
+ Pca954xClass *s = PCA954X_CLASS(klass);
+ s->nchans = PCA9548_CHANNEL_COUNT;
+}
+
+static void pca954x_realize(DeviceState *dev, Error **errp)
+{
+ Pca954xState *s = PCA954X(dev);
+ Pca954xClass *c = PCA954X_GET_CLASS(s);
+ int i;
+
+ /* SMBus modules. Cannot fail. */
+ for (i = 0; i < c->nchans; i++) {
+ sysbus_realize(SYS_BUS_DEVICE(&s->channel[i]), &error_abort);
+ }
+}
+
+static void pca954x_init(Object *obj)
+{
+ Pca954xState *s = PCA954X(obj);
+ Pca954xClass *c = PCA954X_GET_CLASS(obj);
+ int i;
+
+ /* Only initialize the children we expect. */
+ for (i = 0; i < c->nchans; i++) {
+ object_initialize_child(obj, "channel[*]", &s->channel[i],
+ TYPE_PCA954X_CHANNEL);
+ }
+}
+
+static void pca954x_class_init(ObjectClass *klass, void *data)
+{
+ I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SMBusDeviceClass *k = SMBUS_DEVICE_CLASS(klass);
+
+ sc->match_and_add = pca954x_match;
+
+ rc->phases.enter = pca954x_enter_reset;
+
+ dc->desc = "Pca954x i2c-mux";
+ dc->realize = pca954x_realize;
+
+ k->write_data = pca954x_write_data;
+ k->receive_byte = pca954x_read_byte;
+}
+
+static const TypeInfo pca954x_info[] = {
+ {
+ .name = TYPE_PCA954X,
+ .parent = TYPE_SMBUS_DEVICE,
+ .instance_size = sizeof(Pca954xState),
+ .instance_init = pca954x_init,
+ .class_size = sizeof(Pca954xClass),
+ .class_init = pca954x_class_init,
+ .abstract = true,
+ },
+ {
+ .name = TYPE_PCA9546,
+ .parent = TYPE_PCA954X,
+ .class_init = pca9546_class_init,
+ },
+ {
+ .name = TYPE_PCA9548,
+ .parent = TYPE_PCA954X,
+ .class_init = pca9548_class_init,
+ },
+ {
+ .name = TYPE_PCA954X_CHANNEL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .class_init = pca954x_channel_class_init,
+ .instance_size = sizeof(Pca954xChannel),
+ .instance_init = pca954x_channel_init,
+ }
+};
+
+DEFINE_TYPES(pca954x_info)
diff --git a/hw/i2c/imx_i2c.c b/hw/i2c/imx_i2c.c
new file mode 100644
index 000000000..9792583fe
--- /dev/null
+++ b/hw/i2c/imx_i2c.c
@@ -0,0 +1,333 @@
+/*
+ * i.MX I2C Bus Serial Interface Emulation
+ *
+ * Copyright (C) 2013 Jean-Christophe Dubois. <jcd@tribudubois.net>
+ *
+ * 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/i2c/imx_i2c.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "hw/i2c/i2c.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#ifndef DEBUG_IMX_I2C
+#define DEBUG_IMX_I2C 0
+#endif
+
+#define DPRINTF(fmt, args...) \
+ do { \
+ if (DEBUG_IMX_I2C) { \
+ fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX_I2C, \
+ __func__, ##args); \
+ } \
+ } while (0)
+
+static const char *imx_i2c_get_regname(unsigned offset)
+{
+ switch (offset) {
+ case IADR_ADDR:
+ return "IADR";
+ case IFDR_ADDR:
+ return "IFDR";
+ case I2CR_ADDR:
+ return "I2CR";
+ case I2SR_ADDR:
+ return "I2SR";
+ case I2DR_ADDR:
+ return "I2DR";
+ default:
+ return "[?]";
+ }
+}
+
+static inline bool imx_i2c_is_enabled(IMXI2CState *s)
+{
+ return s->i2cr & I2CR_IEN;
+}
+
+static inline bool imx_i2c_interrupt_is_enabled(IMXI2CState *s)
+{
+ return s->i2cr & I2CR_IIEN;
+}
+
+static inline bool imx_i2c_is_master(IMXI2CState *s)
+{
+ return s->i2cr & I2CR_MSTA;
+}
+
+static void imx_i2c_reset(DeviceState *dev)
+{
+ IMXI2CState *s = IMX_I2C(dev);
+
+ if (s->address != ADDR_RESET) {
+ i2c_end_transfer(s->bus);
+ }
+
+ s->address = ADDR_RESET;
+ s->iadr = IADR_RESET;
+ s->ifdr = IFDR_RESET;
+ s->i2cr = I2CR_RESET;
+ s->i2sr = I2SR_RESET;
+ s->i2dr_read = I2DR_RESET;
+ s->i2dr_write = I2DR_RESET;
+}
+
+static inline void imx_i2c_raise_interrupt(IMXI2CState *s)
+{
+ /*
+ * raise an interrupt if the device is enabled and it is configured
+ * to generate some interrupts.
+ */
+ if (imx_i2c_is_enabled(s) && imx_i2c_interrupt_is_enabled(s)) {
+ s->i2sr |= I2SR_IIF;
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static uint64_t imx_i2c_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint16_t value;
+ IMXI2CState *s = IMX_I2C(opaque);
+
+ switch (offset) {
+ case IADR_ADDR:
+ value = s->iadr;
+ break;
+ case IFDR_ADDR:
+ value = s->ifdr;
+ break;
+ case I2CR_ADDR:
+ value = s->i2cr;
+ break;
+ case I2SR_ADDR:
+ value = s->i2sr;
+ break;
+ case I2DR_ADDR:
+ value = s->i2dr_read;
+
+ if (imx_i2c_is_master(s)) {
+ uint8_t ret = 0xff;
+
+ if (s->address == ADDR_RESET) {
+ /* something is wrong as the address is not set */
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Trying to read "
+ "without specifying the slave address\n",
+ TYPE_IMX_I2C, __func__);
+ } else if (s->i2cr & I2CR_MTX) {
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Trying to read "
+ "but MTX is set\n", TYPE_IMX_I2C, __func__);
+ } else {
+ /* get the next byte */
+ ret = i2c_recv(s->bus);
+ imx_i2c_raise_interrupt(s);
+ }
+
+ s->i2dr_read = ret;
+ } else {
+ qemu_log_mask(LOG_UNIMP, "[%s]%s: slave mode not implemented\n",
+ TYPE_IMX_I2C, __func__);
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad address at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_IMX_I2C, __func__, offset);
+ value = 0;
+ break;
+ }
+
+ DPRINTF("read %s [0x%" HWADDR_PRIx "] -> 0x%02x\n",
+ imx_i2c_get_regname(offset), offset, value);
+
+ return (uint64_t)value;
+}
+
+static void imx_i2c_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ IMXI2CState *s = IMX_I2C(opaque);
+
+ DPRINTF("write %s [0x%" HWADDR_PRIx "] <- 0x%02x\n",
+ imx_i2c_get_regname(offset), offset, (int)value);
+
+ value &= 0xff;
+
+ switch (offset) {
+ case IADR_ADDR:
+ s->iadr = value & IADR_MASK;
+ /* i2c_slave_set_address(s->bus, (uint8_t)s->iadr); */
+ break;
+ case IFDR_ADDR:
+ s->ifdr = value & IFDR_MASK;
+ break;
+ case I2CR_ADDR:
+ if (imx_i2c_is_enabled(s) && ((value & I2CR_IEN) == 0)) {
+ /* This is a soft reset. IADR is preserved during soft resets */
+ uint16_t iadr = s->iadr;
+ imx_i2c_reset(DEVICE(s));
+ s->iadr = iadr;
+ } else { /* normal write */
+ s->i2cr = value & I2CR_MASK;
+
+ if (imx_i2c_is_master(s)) {
+ /* set the bus to busy */
+ s->i2sr |= I2SR_IBB;
+ } else { /* slave mode */
+ /* bus is not busy anymore */
+ s->i2sr &= ~I2SR_IBB;
+
+ /*
+ * if we unset the master mode then it ends the ongoing
+ * transfer if any
+ */
+ if (s->address != ADDR_RESET) {
+ i2c_end_transfer(s->bus);
+ s->address = ADDR_RESET;
+ }
+ }
+
+ if (s->i2cr & I2CR_RSTA) { /* Restart */
+ /* if this is a restart then it ends the ongoing transfer */
+ if (s->address != ADDR_RESET) {
+ i2c_end_transfer(s->bus);
+ s->address = ADDR_RESET;
+ s->i2cr &= ~I2CR_RSTA;
+ }
+ }
+ }
+ break;
+ case I2SR_ADDR:
+ /*
+ * if the user writes 0 to IIF then lower the interrupt and
+ * reset the bit
+ */
+ if ((s->i2sr & I2SR_IIF) && !(value & I2SR_IIF)) {
+ s->i2sr &= ~I2SR_IIF;
+ qemu_irq_lower(s->irq);
+ }
+
+ /*
+ * if the user writes 0 to IAL, reset the bit
+ */
+ if ((s->i2sr & I2SR_IAL) && !(value & I2SR_IAL)) {
+ s->i2sr &= ~I2SR_IAL;
+ }
+
+ break;
+ case I2DR_ADDR:
+ /* if the device is not enabled, nothing to do */
+ if (!imx_i2c_is_enabled(s)) {
+ break;
+ }
+
+ s->i2dr_write = value & I2DR_MASK;
+
+ if (imx_i2c_is_master(s)) {
+ /* If this is the first write cycle then it is the slave addr */
+ if (s->address == ADDR_RESET) {
+ if (i2c_start_transfer(s->bus, extract32(s->i2dr_write, 1, 7),
+ extract32(s->i2dr_write, 0, 1))) {
+ /* if non zero is returned, the address is not valid */
+ s->i2sr |= I2SR_RXAK;
+ } else {
+ s->address = s->i2dr_write;
+ s->i2sr &= ~I2SR_RXAK;
+ imx_i2c_raise_interrupt(s);
+ }
+ } else { /* This is a normal data write */
+ if (i2c_send(s->bus, s->i2dr_write)) {
+ /* if the target return non zero then end the transfer */
+ s->i2sr |= I2SR_RXAK;
+ s->address = ADDR_RESET;
+ i2c_end_transfer(s->bus);
+ } else {
+ s->i2sr &= ~I2SR_RXAK;
+ imx_i2c_raise_interrupt(s);
+ }
+ }
+ } else {
+ qemu_log_mask(LOG_UNIMP, "[%s]%s: slave mode not implemented\n",
+ TYPE_IMX_I2C, __func__);
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad address at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_IMX_I2C, __func__, offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps imx_i2c_ops = {
+ .read = imx_i2c_read,
+ .write = imx_i2c_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 2,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription imx_i2c_vmstate = {
+ .name = TYPE_IMX_I2C,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(address, IMXI2CState),
+ VMSTATE_UINT16(iadr, IMXI2CState),
+ VMSTATE_UINT16(ifdr, IMXI2CState),
+ VMSTATE_UINT16(i2cr, IMXI2CState),
+ VMSTATE_UINT16(i2sr, IMXI2CState),
+ VMSTATE_UINT16(i2dr_read, IMXI2CState),
+ VMSTATE_UINT16(i2dr_write, IMXI2CState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void imx_i2c_realize(DeviceState *dev, Error **errp)
+{
+ IMXI2CState *s = IMX_I2C(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &imx_i2c_ops, s, TYPE_IMX_I2C,
+ IMX_I2C_MEM_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+ s->bus = i2c_init_bus(dev, NULL);
+}
+
+static void imx_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &imx_i2c_vmstate;
+ dc->reset = imx_i2c_reset;
+ dc->realize = imx_i2c_realize;
+ dc->desc = "i.MX I2C Controller";
+}
+
+static const TypeInfo imx_i2c_type_info = {
+ .name = TYPE_IMX_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMXI2CState),
+ .class_init = imx_i2c_class_init,
+};
+
+static void imx_i2c_register_types(void)
+{
+ type_register_static(&imx_i2c_type_info);
+}
+
+type_init(imx_i2c_register_types)
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
new file mode 100644
index 000000000..d3df27325
--- /dev/null
+++ b/hw/i2c/meson.build
@@ -0,0 +1,19 @@
+i2c_ss = ss.source_set()
+i2c_ss.add(when: 'CONFIG_I2C', if_true: files('core.c'))
+i2c_ss.add(when: 'CONFIG_SMBUS', if_true: files('smbus_slave.c', 'smbus_master.c'))
+i2c_ss.add(when: 'CONFIG_ACPI_SMBUS', if_true: files('pm_smbus.c'))
+i2c_ss.add(when: 'CONFIG_ACPI_X86_ICH', if_true: files('smbus_ich9.c'))
+i2c_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_i2c.c'))
+i2c_ss.add(when: 'CONFIG_BITBANG_I2C', if_true: files('bitbang_i2c.c'))
+i2c_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_i2c.c'))
+i2c_ss.add(when: 'CONFIG_IMX_I2C', if_true: files('imx_i2c.c'))
+i2c_ss.add(when: 'CONFIG_MPC_I2C', if_true: files('mpc_i2c.c'))
+i2c_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('microbit_i2c.c'))
+i2c_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_smbus.c'))
+i2c_ss.add(when: 'CONFIG_SMBUS_EEPROM', if_true: files('smbus_eeprom.c'))
+i2c_ss.add(when: 'CONFIG_VERSATILE_I2C', if_true: files('versatile_i2c.c'))
+i2c_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_i2c.c'))
+i2c_ss.add(when: 'CONFIG_PPC4XX', if_true: files('ppc4xx_i2c.c'))
+i2c_ss.add(when: 'CONFIG_PCA954X', if_true: files('i2c_mux_pca954x.c'))
+i2c_ss.add(when: 'CONFIG_PMBUS', if_true: files('pmbus_device.c'))
+softmmu_ss.add_all(when: 'CONFIG_I2C', if_true: i2c_ss)
diff --git a/hw/i2c/microbit_i2c.c b/hw/i2c/microbit_i2c.c
new file mode 100644
index 000000000..e92f9f84e
--- /dev/null
+++ b/hw/i2c/microbit_i2c.c
@@ -0,0 +1,130 @@
+/*
+ * Microbit stub for Nordic Semiconductor nRF51 SoC Two-Wire Interface
+ * http://infocenter.nordicsemi.com/pdf/nRF51_RM_v3.0.1.pdf
+ *
+ * This is a microbit-specific stub for the TWI controller on the nRF51 SoC.
+ * We don't emulate I2C devices but the firmware probes the
+ * accelerometer/magnetometer on startup and panics if they are not found.
+ * Therefore we stub out the probing.
+ *
+ * In the future this file could evolve into a full nRF51 TWI controller
+ * device.
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/i2c/microbit_i2c.h"
+#include "migration/vmstate.h"
+
+static const uint32_t twi_read_sequence[] = {0x5A, 0x5A, 0x40};
+
+static uint64_t microbit_i2c_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ MicrobitI2CState *s = opaque;
+ uint64_t data = 0x00;
+
+ switch (addr) {
+ case NRF51_TWI_EVENT_STOPPED:
+ data = 0x01;
+ break;
+ case NRF51_TWI_EVENT_RXDREADY:
+ data = 0x01;
+ break;
+ case NRF51_TWI_EVENT_TXDSENT:
+ data = 0x01;
+ break;
+ case NRF51_TWI_REG_RXD:
+ data = twi_read_sequence[s->read_idx];
+ if (s->read_idx < G_N_ELEMENTS(twi_read_sequence)) {
+ s->read_idx++;
+ }
+ break;
+ default:
+ data = s->regs[addr / sizeof(s->regs[0])];
+ break;
+ }
+
+ qemu_log_mask(LOG_UNIMP, "%s: 0x%" HWADDR_PRIx " [%u] = %" PRIx32 "\n",
+ __func__, addr, size, (uint32_t)data);
+
+
+ return data;
+}
+
+static void microbit_i2c_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ MicrobitI2CState *s = opaque;
+
+ qemu_log_mask(LOG_UNIMP, "%s: 0x%" HWADDR_PRIx " <- 0x%" PRIx64 " [%u]\n",
+ __func__, addr, data, size);
+ s->regs[addr / sizeof(s->regs[0])] = data;
+}
+
+static const MemoryRegionOps microbit_i2c_ops = {
+ .read = microbit_i2c_read,
+ .write = microbit_i2c_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
+static const VMStateDescription microbit_i2c_vmstate = {
+ .name = TYPE_MICROBIT_I2C,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MicrobitI2CState, MICROBIT_I2C_NREGS),
+ VMSTATE_UINT32(read_idx, MicrobitI2CState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void microbit_i2c_reset(DeviceState *dev)
+{
+ MicrobitI2CState *s = MICROBIT_I2C(dev);
+
+ memset(s->regs, 0, sizeof(s->regs));
+ s->read_idx = 0;
+}
+
+static void microbit_i2c_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ MicrobitI2CState *s = MICROBIT_I2C(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &microbit_i2c_ops, s,
+ "microbit.twi", NRF51_PERIPHERAL_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void microbit_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &microbit_i2c_vmstate;
+ dc->reset = microbit_i2c_reset;
+ dc->realize = microbit_i2c_realize;
+ dc->desc = "Microbit I2C controller";
+}
+
+static const TypeInfo microbit_i2c_info = {
+ .name = TYPE_MICROBIT_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MicrobitI2CState),
+ .class_init = microbit_i2c_class_init,
+};
+
+static void microbit_i2c_register_types(void)
+{
+ type_register_static(&microbit_i2c_info);
+}
+
+type_init(microbit_i2c_register_types)
diff --git a/hw/i2c/mpc_i2c.c b/hw/i2c/mpc_i2c.c
new file mode 100644
index 000000000..845392505
--- /dev/null
+++ b/hw/i2c/mpc_i2c.c
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2014 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Amit Tomar, <Amit.Tomar@freescale.com>
+ *
+ * Description:
+ * This file is derived from IMX I2C controller,
+ * by Jean-Christophe DUBOIS .
+ *
+ * Thanks to Scott Wood and Alexander Graf for their kind help on this.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2 or later,
+ * as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "hw/irq.h"
+#include "qemu/module.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qom/object.h"
+
+/* #define DEBUG_I2C */
+
+#ifdef DEBUG_I2C
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, "mpc_i2c[%s]: " fmt, __func__, ## __VA_ARGS__); \
+ } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define TYPE_MPC_I2C "mpc-i2c"
+OBJECT_DECLARE_SIMPLE_TYPE(MPCI2CState, MPC_I2C)
+
+#define MPC_I2C_ADR 0x00
+#define MPC_I2C_FDR 0x04
+#define MPC_I2C_CR 0x08
+#define MPC_I2C_SR 0x0c
+#define MPC_I2C_DR 0x10
+#define MPC_I2C_DFSRR 0x14
+
+#define CCR_MEN (1 << 7)
+#define CCR_MIEN (1 << 6)
+#define CCR_MSTA (1 << 5)
+#define CCR_MTX (1 << 4)
+#define CCR_TXAK (1 << 3)
+#define CCR_RSTA (1 << 2)
+#define CCR_BCST (1 << 0)
+
+#define CSR_MCF (1 << 7)
+#define CSR_MAAS (1 << 6)
+#define CSR_MBB (1 << 5)
+#define CSR_MAL (1 << 4)
+#define CSR_SRW (1 << 2)
+#define CSR_MIF (1 << 1)
+#define CSR_RXAK (1 << 0)
+
+#define CADR_MASK 0xFE
+#define CFDR_MASK 0x3F
+#define CCR_MASK 0xFC
+#define CSR_MASK 0xED
+#define CDR_MASK 0xFF
+
+#define CYCLE_RESET 0xFF
+
+struct MPCI2CState {
+ SysBusDevice parent_obj;
+
+ I2CBus *bus;
+ qemu_irq irq;
+ MemoryRegion iomem;
+
+ uint8_t address;
+ uint8_t adr;
+ uint8_t fdr;
+ uint8_t cr;
+ uint8_t sr;
+ uint8_t dr;
+ uint8_t dfssr;
+};
+
+static bool mpc_i2c_is_enabled(MPCI2CState *s)
+{
+ return s->cr & CCR_MEN;
+}
+
+static bool mpc_i2c_is_master(MPCI2CState *s)
+{
+ return s->cr & CCR_MSTA;
+}
+
+static bool mpc_i2c_direction_is_tx(MPCI2CState *s)
+{
+ return s->cr & CCR_MTX;
+}
+
+static bool mpc_i2c_irq_pending(MPCI2CState *s)
+{
+ return s->sr & CSR_MIF;
+}
+
+static bool mpc_i2c_irq_is_enabled(MPCI2CState *s)
+{
+ return s->cr & CCR_MIEN;
+}
+
+static void mpc_i2c_reset(DeviceState *dev)
+{
+ MPCI2CState *i2c = MPC_I2C(dev);
+
+ i2c->address = 0xFF;
+ i2c->adr = 0x00;
+ i2c->fdr = 0x00;
+ i2c->cr = 0x00;
+ i2c->sr = 0x81;
+ i2c->dr = 0x00;
+}
+
+static void mpc_i2c_irq(MPCI2CState *s)
+{
+ bool irq_active = false;
+
+ if (mpc_i2c_is_enabled(s) && mpc_i2c_irq_is_enabled(s)
+ && mpc_i2c_irq_pending(s)) {
+ irq_active = true;
+ }
+
+ if (irq_active) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static void mpc_i2c_soft_reset(MPCI2CState *s)
+{
+ /* This is a soft reset. ADR is preserved during soft resets */
+ uint8_t adr = s->adr;
+ mpc_i2c_reset(DEVICE(s));
+ s->adr = adr;
+}
+
+static void mpc_i2c_address_send(MPCI2CState *s)
+{
+ /* if returns non zero slave address is not right */
+ if (i2c_start_transfer(s->bus, s->dr >> 1, s->dr & (0x01))) {
+ s->sr |= CSR_RXAK;
+ } else {
+ s->address = s->dr;
+ s->sr &= ~CSR_RXAK;
+ s->sr |= CSR_MCF; /* Set after Byte Transfer is completed */
+ s->sr |= CSR_MIF; /* Set after Byte Transfer is completed */
+ mpc_i2c_irq(s);
+ }
+}
+
+static void mpc_i2c_data_send(MPCI2CState *s)
+{
+ if (i2c_send(s->bus, s->dr)) {
+ /* End of transfer */
+ s->sr |= CSR_RXAK;
+ i2c_end_transfer(s->bus);
+ } else {
+ s->sr &= ~CSR_RXAK;
+ s->sr |= CSR_MCF; /* Set after Byte Transfer is completed */
+ s->sr |= CSR_MIF; /* Set after Byte Transfer is completed */
+ mpc_i2c_irq(s);
+ }
+}
+
+static void mpc_i2c_data_recive(MPCI2CState *s)
+{
+ int ret;
+ /* get the next byte */
+ ret = i2c_recv(s->bus);
+ if (ret >= 0) {
+ s->sr |= CSR_MCF; /* Set after Byte Transfer is completed */
+ s->sr |= CSR_MIF; /* Set after Byte Transfer is completed */
+ mpc_i2c_irq(s);
+ } else {
+ DPRINTF("read failed for device");
+ ret = 0xff;
+ }
+ s->dr = ret;
+}
+
+static uint64_t mpc_i2c_read(void *opaque, hwaddr addr, unsigned size)
+{
+ MPCI2CState *s = opaque;
+ uint8_t value;
+
+ switch (addr) {
+ case MPC_I2C_ADR:
+ value = s->adr;
+ break;
+ case MPC_I2C_FDR:
+ value = s->fdr;
+ break;
+ case MPC_I2C_CR:
+ value = s->cr;
+ break;
+ case MPC_I2C_SR:
+ value = s->sr;
+ break;
+ case MPC_I2C_DR:
+ value = s->dr;
+ if (mpc_i2c_is_master(s)) { /* master mode */
+ if (mpc_i2c_direction_is_tx(s)) {
+ DPRINTF("MTX is set not in recv mode\n");
+ } else {
+ mpc_i2c_data_recive(s);
+ }
+ }
+ break;
+ default:
+ value = 0;
+ DPRINTF("ERROR: Bad read addr 0x%x\n", (unsigned int)addr);
+ break;
+ }
+
+ DPRINTF("%s: addr " TARGET_FMT_plx " %02" PRIx32 "\n", __func__,
+ addr, value);
+ return (uint64_t)value;
+}
+
+static void mpc_i2c_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ MPCI2CState *s = opaque;
+
+ DPRINTF("%s: addr " TARGET_FMT_plx " val %08" PRIx64 "\n", __func__,
+ addr, value);
+ switch (addr) {
+ case MPC_I2C_ADR:
+ s->adr = value & CADR_MASK;
+ break;
+ case MPC_I2C_FDR:
+ s->fdr = value & CFDR_MASK;
+ break;
+ case MPC_I2C_CR:
+ if (mpc_i2c_is_enabled(s) && ((value & CCR_MEN) == 0)) {
+ mpc_i2c_soft_reset(s);
+ break;
+ }
+ /* normal write */
+ s->cr = value & CCR_MASK;
+ if (mpc_i2c_is_master(s)) { /* master mode */
+ /* set the bus to busy after master is set as per RM */
+ s->sr |= CSR_MBB;
+ } else {
+ /* bus is not busy anymore */
+ s->sr &= ~CSR_MBB;
+ /* Reset the address for fresh write/read cycle */
+ if (s->address != CYCLE_RESET) {
+ i2c_end_transfer(s->bus);
+ s->address = CYCLE_RESET;
+ }
+ }
+ /* For restart end the onging transfer */
+ if (s->cr & CCR_RSTA) {
+ if (s->address != CYCLE_RESET) {
+ s->address = CYCLE_RESET;
+ i2c_end_transfer(s->bus);
+ s->cr &= ~CCR_RSTA;
+ }
+ }
+ break;
+ case MPC_I2C_SR:
+ s->sr = value & CSR_MASK;
+ /* Lower the interrupt */
+ if (!(s->sr & CSR_MIF) || !(s->sr & CSR_MAL)) {
+ mpc_i2c_irq(s);
+ }
+ break;
+ case MPC_I2C_DR:
+ /* if the device is not enabled, nothing to do */
+ if (!mpc_i2c_is_enabled(s)) {
+ break;
+ }
+ s->dr = value & CDR_MASK;
+ if (mpc_i2c_is_master(s)) { /* master mode */
+ if (s->address == CYCLE_RESET) {
+ mpc_i2c_address_send(s);
+ } else {
+ mpc_i2c_data_send(s);
+ }
+ }
+ break;
+ case MPC_I2C_DFSRR:
+ s->dfssr = value;
+ break;
+ default:
+ DPRINTF("ERROR: Bad write addr 0x%x\n", (unsigned int)addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps i2c_ops = {
+ .read = mpc_i2c_read,
+ .write = mpc_i2c_write,
+ .valid.max_access_size = 1,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription mpc_i2c_vmstate = {
+ .name = TYPE_MPC_I2C,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(address, MPCI2CState),
+ VMSTATE_UINT8(adr, MPCI2CState),
+ VMSTATE_UINT8(fdr, MPCI2CState),
+ VMSTATE_UINT8(cr, MPCI2CState),
+ VMSTATE_UINT8(sr, MPCI2CState),
+ VMSTATE_UINT8(dr, MPCI2CState),
+ VMSTATE_UINT8(dfssr, MPCI2CState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mpc_i2c_realize(DeviceState *dev, Error **errp)
+{
+ MPCI2CState *i2c = MPC_I2C(dev);
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &i2c->irq);
+ memory_region_init_io(&i2c->iomem, OBJECT(i2c), &i2c_ops, i2c,
+ "mpc-i2c", 0x14);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &i2c->iomem);
+ i2c->bus = i2c_init_bus(dev, "i2c");
+}
+
+static void mpc_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &mpc_i2c_vmstate ;
+ dc->reset = mpc_i2c_reset;
+ dc->realize = mpc_i2c_realize;
+ dc->desc = "MPC I2C Controller";
+}
+
+static const TypeInfo mpc_i2c_type_info = {
+ .name = TYPE_MPC_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MPCI2CState),
+ .class_init = mpc_i2c_class_init,
+};
+
+static void mpc_i2c_register_types(void)
+{
+ type_register_static(&mpc_i2c_type_info);
+}
+
+type_init(mpc_i2c_register_types)
diff --git a/hw/i2c/npcm7xx_smbus.c b/hw/i2c/npcm7xx_smbus.c
new file mode 100644
index 000000000..e7e0ba66f
--- /dev/null
+++ b/hw/i2c/npcm7xx_smbus.c
@@ -0,0 +1,1098 @@
+/*
+ * Nuvoton NPCM7xx SMBus Module.
+ *
+ * Copyright 2020 Google LLC
+ *
+ * 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.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/i2c/npcm7xx_smbus.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/guest-random.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+
+#include "trace.h"
+
+enum NPCM7xxSMBusCommonRegister {
+ NPCM7XX_SMB_SDA = 0x0,
+ NPCM7XX_SMB_ST = 0x2,
+ NPCM7XX_SMB_CST = 0x4,
+ NPCM7XX_SMB_CTL1 = 0x6,
+ NPCM7XX_SMB_ADDR1 = 0x8,
+ NPCM7XX_SMB_CTL2 = 0xa,
+ NPCM7XX_SMB_ADDR2 = 0xc,
+ NPCM7XX_SMB_CTL3 = 0xe,
+ NPCM7XX_SMB_CST2 = 0x18,
+ NPCM7XX_SMB_CST3 = 0x19,
+ NPCM7XX_SMB_VER = 0x1f,
+};
+
+enum NPCM7xxSMBusBank0Register {
+ NPCM7XX_SMB_ADDR3 = 0x10,
+ NPCM7XX_SMB_ADDR7 = 0x11,
+ NPCM7XX_SMB_ADDR4 = 0x12,
+ NPCM7XX_SMB_ADDR8 = 0x13,
+ NPCM7XX_SMB_ADDR5 = 0x14,
+ NPCM7XX_SMB_ADDR9 = 0x15,
+ NPCM7XX_SMB_ADDR6 = 0x16,
+ NPCM7XX_SMB_ADDR10 = 0x17,
+ NPCM7XX_SMB_CTL4 = 0x1a,
+ NPCM7XX_SMB_CTL5 = 0x1b,
+ NPCM7XX_SMB_SCLLT = 0x1c,
+ NPCM7XX_SMB_FIF_CTL = 0x1d,
+ NPCM7XX_SMB_SCLHT = 0x1e,
+};
+
+enum NPCM7xxSMBusBank1Register {
+ NPCM7XX_SMB_FIF_CTS = 0x10,
+ NPCM7XX_SMB_FAIR_PER = 0x11,
+ NPCM7XX_SMB_TXF_CTL = 0x12,
+ NPCM7XX_SMB_T_OUT = 0x14,
+ NPCM7XX_SMB_TXF_STS = 0x1a,
+ NPCM7XX_SMB_RXF_STS = 0x1c,
+ NPCM7XX_SMB_RXF_CTL = 0x1e,
+};
+
+/* ST fields */
+#define NPCM7XX_SMBST_STP BIT(7)
+#define NPCM7XX_SMBST_SDAST BIT(6)
+#define NPCM7XX_SMBST_BER BIT(5)
+#define NPCM7XX_SMBST_NEGACK BIT(4)
+#define NPCM7XX_SMBST_STASTR BIT(3)
+#define NPCM7XX_SMBST_NMATCH BIT(2)
+#define NPCM7XX_SMBST_MODE BIT(1)
+#define NPCM7XX_SMBST_XMIT BIT(0)
+
+/* CST fields */
+#define NPCM7XX_SMBCST_ARPMATCH BIT(7)
+#define NPCM7XX_SMBCST_MATCHAF BIT(6)
+#define NPCM7XX_SMBCST_TGSCL BIT(5)
+#define NPCM7XX_SMBCST_TSDA BIT(4)
+#define NPCM7XX_SMBCST_GCMATCH BIT(3)
+#define NPCM7XX_SMBCST_MATCH BIT(2)
+#define NPCM7XX_SMBCST_BB BIT(1)
+#define NPCM7XX_SMBCST_BUSY BIT(0)
+
+/* CST2 fields */
+#define NPCM7XX_SMBCST2_INTSTS BIT(7)
+#define NPCM7XX_SMBCST2_MATCH7F BIT(6)
+#define NPCM7XX_SMBCST2_MATCH6F BIT(5)
+#define NPCM7XX_SMBCST2_MATCH5F BIT(4)
+#define NPCM7XX_SMBCST2_MATCH4F BIT(3)
+#define NPCM7XX_SMBCST2_MATCH3F BIT(2)
+#define NPCM7XX_SMBCST2_MATCH2F BIT(1)
+#define NPCM7XX_SMBCST2_MATCH1F BIT(0)
+
+/* CST3 fields */
+#define NPCM7XX_SMBCST3_EO_BUSY BIT(7)
+#define NPCM7XX_SMBCST3_MATCH10F BIT(2)
+#define NPCM7XX_SMBCST3_MATCH9F BIT(1)
+#define NPCM7XX_SMBCST3_MATCH8F BIT(0)
+
+/* CTL1 fields */
+#define NPCM7XX_SMBCTL1_STASTRE BIT(7)
+#define NPCM7XX_SMBCTL1_NMINTE BIT(6)
+#define NPCM7XX_SMBCTL1_GCMEN BIT(5)
+#define NPCM7XX_SMBCTL1_ACK BIT(4)
+#define NPCM7XX_SMBCTL1_EOBINTE BIT(3)
+#define NPCM7XX_SMBCTL1_INTEN BIT(2)
+#define NPCM7XX_SMBCTL1_STOP BIT(1)
+#define NPCM7XX_SMBCTL1_START BIT(0)
+
+/* CTL2 fields */
+#define NPCM7XX_SMBCTL2_SCLFRQ(rv) extract8((rv), 1, 6)
+#define NPCM7XX_SMBCTL2_ENABLE BIT(0)
+
+/* CTL3 fields */
+#define NPCM7XX_SMBCTL3_SCL_LVL BIT(7)
+#define NPCM7XX_SMBCTL3_SDA_LVL BIT(6)
+#define NPCM7XX_SMBCTL3_BNK_SEL BIT(5)
+#define NPCM7XX_SMBCTL3_400K_MODE BIT(4)
+#define NPCM7XX_SMBCTL3_IDL_START BIT(3)
+#define NPCM7XX_SMBCTL3_ARPMEN BIT(2)
+#define NPCM7XX_SMBCTL3_SCLFRQ(rv) extract8((rv), 0, 2)
+
+/* ADDR fields */
+#define NPCM7XX_ADDR_EN BIT(7)
+#define NPCM7XX_ADDR_A(rv) extract8((rv), 0, 6)
+
+/* FIFO Mode Register Fields */
+/* FIF_CTL fields */
+#define NPCM7XX_SMBFIF_CTL_FIFO_EN BIT(4)
+#define NPCM7XX_SMBFIF_CTL_FAIR_RDY_IE BIT(2)
+#define NPCM7XX_SMBFIF_CTL_FAIR_RDY BIT(1)
+#define NPCM7XX_SMBFIF_CTL_FAIR_BUSY BIT(0)
+/* FIF_CTS fields */
+#define NPCM7XX_SMBFIF_CTS_STR BIT(7)
+#define NPCM7XX_SMBFIF_CTS_CLR_FIFO BIT(6)
+#define NPCM7XX_SMBFIF_CTS_RFTE_IE BIT(3)
+#define NPCM7XX_SMBFIF_CTS_RXF_TXE BIT(1)
+/* TXF_CTL fields */
+#define NPCM7XX_SMBTXF_CTL_THR_TXIE BIT(6)
+#define NPCM7XX_SMBTXF_CTL_TX_THR(rv) extract8((rv), 0, 5)
+/* T_OUT fields */
+#define NPCM7XX_SMBT_OUT_ST BIT(7)
+#define NPCM7XX_SMBT_OUT_IE BIT(6)
+#define NPCM7XX_SMBT_OUT_CLKDIV(rv) extract8((rv), 0, 6)
+/* TXF_STS fields */
+#define NPCM7XX_SMBTXF_STS_TX_THST BIT(6)
+#define NPCM7XX_SMBTXF_STS_TX_BYTES(rv) extract8((rv), 0, 5)
+/* RXF_STS fields */
+#define NPCM7XX_SMBRXF_STS_RX_THST BIT(6)
+#define NPCM7XX_SMBRXF_STS_RX_BYTES(rv) extract8((rv), 0, 5)
+/* RXF_CTL fields */
+#define NPCM7XX_SMBRXF_CTL_THR_RXIE BIT(6)
+#define NPCM7XX_SMBRXF_CTL_LAST BIT(5)
+#define NPCM7XX_SMBRXF_CTL_RX_THR(rv) extract8((rv), 0, 5)
+
+#define KEEP_OLD_BIT(o, n, b) (((n) & (~(b))) | ((o) & (b)))
+#define WRITE_ONE_CLEAR(o, n, b) ((n) & (b) ? (o) & (~(b)) : (o))
+
+#define NPCM7XX_SMBUS_ENABLED(s) ((s)->ctl2 & NPCM7XX_SMBCTL2_ENABLE)
+#define NPCM7XX_SMBUS_FIFO_ENABLED(s) ((s)->fif_ctl & \
+ NPCM7XX_SMBFIF_CTL_FIFO_EN)
+
+/* VERSION fields values, read-only. */
+#define NPCM7XX_SMBUS_VERSION_NUMBER 1
+#define NPCM7XX_SMBUS_VERSION_FIFO_SUPPORTED 1
+
+/* Reset values */
+#define NPCM7XX_SMB_ST_INIT_VAL 0x00
+#define NPCM7XX_SMB_CST_INIT_VAL 0x10
+#define NPCM7XX_SMB_CST2_INIT_VAL 0x00
+#define NPCM7XX_SMB_CST3_INIT_VAL 0x00
+#define NPCM7XX_SMB_CTL1_INIT_VAL 0x00
+#define NPCM7XX_SMB_CTL2_INIT_VAL 0x00
+#define NPCM7XX_SMB_CTL3_INIT_VAL 0xc0
+#define NPCM7XX_SMB_CTL4_INIT_VAL 0x07
+#define NPCM7XX_SMB_CTL5_INIT_VAL 0x00
+#define NPCM7XX_SMB_ADDR_INIT_VAL 0x00
+#define NPCM7XX_SMB_SCLLT_INIT_VAL 0x00
+#define NPCM7XX_SMB_SCLHT_INIT_VAL 0x00
+#define NPCM7XX_SMB_FIF_CTL_INIT_VAL 0x00
+#define NPCM7XX_SMB_FIF_CTS_INIT_VAL 0x00
+#define NPCM7XX_SMB_FAIR_PER_INIT_VAL 0x00
+#define NPCM7XX_SMB_TXF_CTL_INIT_VAL 0x00
+#define NPCM7XX_SMB_T_OUT_INIT_VAL 0x3f
+#define NPCM7XX_SMB_TXF_STS_INIT_VAL 0x00
+#define NPCM7XX_SMB_RXF_STS_INIT_VAL 0x00
+#define NPCM7XX_SMB_RXF_CTL_INIT_VAL 0x01
+
+static uint8_t npcm7xx_smbus_get_version(void)
+{
+ return NPCM7XX_SMBUS_VERSION_FIFO_SUPPORTED << 7 |
+ NPCM7XX_SMBUS_VERSION_NUMBER;
+}
+
+static void npcm7xx_smbus_update_irq(NPCM7xxSMBusState *s)
+{
+ int level;
+
+ if (s->ctl1 & NPCM7XX_SMBCTL1_INTEN) {
+ level = !!((s->ctl1 & NPCM7XX_SMBCTL1_NMINTE &&
+ s->st & NPCM7XX_SMBST_NMATCH) ||
+ (s->st & NPCM7XX_SMBST_BER) ||
+ (s->st & NPCM7XX_SMBST_NEGACK) ||
+ (s->st & NPCM7XX_SMBST_SDAST) ||
+ (s->ctl1 & NPCM7XX_SMBCTL1_STASTRE &&
+ s->st & NPCM7XX_SMBST_SDAST) ||
+ (s->ctl1 & NPCM7XX_SMBCTL1_EOBINTE &&
+ s->cst3 & NPCM7XX_SMBCST3_EO_BUSY) ||
+ (s->rxf_ctl & NPCM7XX_SMBRXF_CTL_THR_RXIE &&
+ s->rxf_sts & NPCM7XX_SMBRXF_STS_RX_THST) ||
+ (s->txf_ctl & NPCM7XX_SMBTXF_CTL_THR_TXIE &&
+ s->txf_sts & NPCM7XX_SMBTXF_STS_TX_THST) ||
+ (s->fif_cts & NPCM7XX_SMBFIF_CTS_RFTE_IE &&
+ s->fif_cts & NPCM7XX_SMBFIF_CTS_RXF_TXE));
+
+ if (level) {
+ s->cst2 |= NPCM7XX_SMBCST2_INTSTS;
+ } else {
+ s->cst2 &= ~NPCM7XX_SMBCST2_INTSTS;
+ }
+ qemu_set_irq(s->irq, level);
+ }
+}
+
+static void npcm7xx_smbus_nack(NPCM7xxSMBusState *s)
+{
+ s->st &= ~NPCM7XX_SMBST_SDAST;
+ s->st |= NPCM7XX_SMBST_NEGACK;
+ s->status = NPCM7XX_SMBUS_STATUS_NEGACK;
+}
+
+static void npcm7xx_smbus_clear_buffer(NPCM7xxSMBusState *s)
+{
+ s->fif_cts &= ~NPCM7XX_SMBFIF_CTS_RXF_TXE;
+ s->txf_sts = 0;
+ s->rxf_sts = 0;
+}
+
+static void npcm7xx_smbus_send_byte(NPCM7xxSMBusState *s, uint8_t value)
+{
+ int rv = i2c_send(s->bus, value);
+
+ if (rv) {
+ npcm7xx_smbus_nack(s);
+ } else {
+ s->st |= NPCM7XX_SMBST_SDAST;
+ if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) {
+ s->fif_cts |= NPCM7XX_SMBFIF_CTS_RXF_TXE;
+ if (NPCM7XX_SMBTXF_STS_TX_BYTES(s->txf_sts) ==
+ NPCM7XX_SMBTXF_CTL_TX_THR(s->txf_ctl)) {
+ s->txf_sts = NPCM7XX_SMBTXF_STS_TX_THST;
+ } else {
+ s->txf_sts = 0;
+ }
+ }
+ }
+ trace_npcm7xx_smbus_send_byte((DEVICE(s)->canonical_path), value, !rv);
+ npcm7xx_smbus_update_irq(s);
+}
+
+static void npcm7xx_smbus_recv_byte(NPCM7xxSMBusState *s)
+{
+ s->sda = i2c_recv(s->bus);
+ s->st |= NPCM7XX_SMBST_SDAST;
+ if (s->st & NPCM7XX_SMBCTL1_ACK) {
+ trace_npcm7xx_smbus_nack(DEVICE(s)->canonical_path);
+ i2c_nack(s->bus);
+ s->st &= NPCM7XX_SMBCTL1_ACK;
+ }
+ trace_npcm7xx_smbus_recv_byte((DEVICE(s)->canonical_path), s->sda);
+ npcm7xx_smbus_update_irq(s);
+}
+
+static void npcm7xx_smbus_recv_fifo(NPCM7xxSMBusState *s)
+{
+ uint8_t expected_bytes = NPCM7XX_SMBRXF_CTL_RX_THR(s->rxf_ctl);
+ uint8_t received_bytes = NPCM7XX_SMBRXF_STS_RX_BYTES(s->rxf_sts);
+ uint8_t pos;
+
+ if (received_bytes == expected_bytes) {
+ return;
+ }
+
+ while (received_bytes < expected_bytes &&
+ received_bytes < NPCM7XX_SMBUS_FIFO_SIZE) {
+ pos = (s->rx_cur + received_bytes) % NPCM7XX_SMBUS_FIFO_SIZE;
+ s->rx_fifo[pos] = i2c_recv(s->bus);
+ trace_npcm7xx_smbus_recv_byte((DEVICE(s)->canonical_path),
+ s->rx_fifo[pos]);
+ ++received_bytes;
+ }
+
+ trace_npcm7xx_smbus_recv_fifo((DEVICE(s)->canonical_path),
+ received_bytes, expected_bytes);
+ s->rxf_sts = received_bytes;
+ if (unlikely(received_bytes < expected_bytes)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid rx_thr value: 0x%02x\n",
+ DEVICE(s)->canonical_path, expected_bytes);
+ return;
+ }
+
+ s->rxf_sts |= NPCM7XX_SMBRXF_STS_RX_THST;
+ if (s->rxf_ctl & NPCM7XX_SMBRXF_CTL_LAST) {
+ trace_npcm7xx_smbus_nack(DEVICE(s)->canonical_path);
+ i2c_nack(s->bus);
+ s->rxf_ctl &= ~NPCM7XX_SMBRXF_CTL_LAST;
+ }
+ if (received_bytes == NPCM7XX_SMBUS_FIFO_SIZE) {
+ s->st |= NPCM7XX_SMBST_SDAST;
+ s->fif_cts |= NPCM7XX_SMBFIF_CTS_RXF_TXE;
+ } else if (!(s->rxf_ctl & NPCM7XX_SMBRXF_CTL_THR_RXIE)) {
+ s->st |= NPCM7XX_SMBST_SDAST;
+ } else {
+ s->st &= ~NPCM7XX_SMBST_SDAST;
+ }
+ npcm7xx_smbus_update_irq(s);
+}
+
+static void npcm7xx_smbus_read_byte_fifo(NPCM7xxSMBusState *s)
+{
+ uint8_t received_bytes = NPCM7XX_SMBRXF_STS_RX_BYTES(s->rxf_sts);
+
+ if (received_bytes == 0) {
+ npcm7xx_smbus_recv_fifo(s);
+ return;
+ }
+
+ s->sda = s->rx_fifo[s->rx_cur];
+ s->rx_cur = (s->rx_cur + 1u) % NPCM7XX_SMBUS_FIFO_SIZE;
+ --s->rxf_sts;
+ npcm7xx_smbus_update_irq(s);
+}
+
+static void npcm7xx_smbus_start(NPCM7xxSMBusState *s)
+{
+ /*
+ * We can start the bus if one of these is true:
+ * 1. The bus is idle (so we can request it)
+ * 2. We are the occupier (it's a repeated start condition.)
+ */
+ int available = !i2c_bus_busy(s->bus) ||
+ s->status != NPCM7XX_SMBUS_STATUS_IDLE;
+
+ if (available) {
+ s->st |= NPCM7XX_SMBST_MODE | NPCM7XX_SMBST_XMIT | NPCM7XX_SMBST_SDAST;
+ s->cst |= NPCM7XX_SMBCST_BUSY;
+ if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) {
+ s->fif_cts |= NPCM7XX_SMBFIF_CTS_RXF_TXE;
+ }
+ } else {
+ s->st &= ~NPCM7XX_SMBST_MODE;
+ s->cst &= ~NPCM7XX_SMBCST_BUSY;
+ s->st |= NPCM7XX_SMBST_BER;
+ }
+
+ trace_npcm7xx_smbus_start(DEVICE(s)->canonical_path, available);
+ s->cst |= NPCM7XX_SMBCST_BB;
+ s->status = NPCM7XX_SMBUS_STATUS_IDLE;
+ npcm7xx_smbus_update_irq(s);
+}
+
+static void npcm7xx_smbus_send_address(NPCM7xxSMBusState *s, uint8_t value)
+{
+ int recv;
+ int rv;
+
+ recv = value & BIT(0);
+ rv = i2c_start_transfer(s->bus, value >> 1, recv);
+ trace_npcm7xx_smbus_send_address(DEVICE(s)->canonical_path,
+ value >> 1, recv, !rv);
+ if (rv) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: requesting i2c bus for 0x%02x failed: %d\n",
+ DEVICE(s)->canonical_path, value, rv);
+ /* Failed to start transfer. NACK to reject.*/
+ if (recv) {
+ s->st &= ~NPCM7XX_SMBST_XMIT;
+ } else {
+ s->st |= NPCM7XX_SMBST_XMIT;
+ }
+ npcm7xx_smbus_nack(s);
+ npcm7xx_smbus_update_irq(s);
+ return;
+ }
+
+ s->st &= ~NPCM7XX_SMBST_NEGACK;
+ if (recv) {
+ s->status = NPCM7XX_SMBUS_STATUS_RECEIVING;
+ s->st &= ~NPCM7XX_SMBST_XMIT;
+ } else {
+ s->status = NPCM7XX_SMBUS_STATUS_SENDING;
+ s->st |= NPCM7XX_SMBST_XMIT;
+ }
+
+ if (s->ctl1 & NPCM7XX_SMBCTL1_STASTRE) {
+ s->st |= NPCM7XX_SMBST_STASTR;
+ if (!recv) {
+ s->st |= NPCM7XX_SMBST_SDAST;
+ }
+ } else if (recv) {
+ s->st |= NPCM7XX_SMBST_SDAST;
+ if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) {
+ npcm7xx_smbus_recv_fifo(s);
+ } else {
+ npcm7xx_smbus_recv_byte(s);
+ }
+ } else if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) {
+ s->st |= NPCM7XX_SMBST_SDAST;
+ s->fif_cts |= NPCM7XX_SMBFIF_CTS_RXF_TXE;
+ }
+ npcm7xx_smbus_update_irq(s);
+}
+
+static void npcm7xx_smbus_execute_stop(NPCM7xxSMBusState *s)
+{
+ i2c_end_transfer(s->bus);
+ s->st = 0;
+ s->cst = 0;
+ s->status = NPCM7XX_SMBUS_STATUS_IDLE;
+ s->cst3 |= NPCM7XX_SMBCST3_EO_BUSY;
+ trace_npcm7xx_smbus_stop(DEVICE(s)->canonical_path);
+ npcm7xx_smbus_update_irq(s);
+}
+
+
+static void npcm7xx_smbus_stop(NPCM7xxSMBusState *s)
+{
+ if (s->st & NPCM7XX_SMBST_MODE) {
+ switch (s->status) {
+ case NPCM7XX_SMBUS_STATUS_RECEIVING:
+ case NPCM7XX_SMBUS_STATUS_STOPPING_LAST_RECEIVE:
+ s->status = NPCM7XX_SMBUS_STATUS_STOPPING_LAST_RECEIVE;
+ break;
+
+ case NPCM7XX_SMBUS_STATUS_NEGACK:
+ s->status = NPCM7XX_SMBUS_STATUS_STOPPING_NEGACK;
+ break;
+
+ default:
+ npcm7xx_smbus_execute_stop(s);
+ break;
+ }
+ }
+}
+
+static uint8_t npcm7xx_smbus_read_sda(NPCM7xxSMBusState *s)
+{
+ uint8_t value = s->sda;
+
+ switch (s->status) {
+ case NPCM7XX_SMBUS_STATUS_STOPPING_LAST_RECEIVE:
+ if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) {
+ if (NPCM7XX_SMBRXF_STS_RX_BYTES(s->rxf_sts) <= 1) {
+ npcm7xx_smbus_execute_stop(s);
+ }
+ if (NPCM7XX_SMBRXF_STS_RX_BYTES(s->rxf_sts) == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: read to SDA with an empty rx-fifo buffer, "
+ "result undefined: %u\n",
+ DEVICE(s)->canonical_path, s->sda);
+ break;
+ }
+ npcm7xx_smbus_read_byte_fifo(s);
+ value = s->sda;
+ } else {
+ npcm7xx_smbus_execute_stop(s);
+ }
+ break;
+
+ case NPCM7XX_SMBUS_STATUS_RECEIVING:
+ if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) {
+ npcm7xx_smbus_read_byte_fifo(s);
+ value = s->sda;
+ } else {
+ npcm7xx_smbus_recv_byte(s);
+ }
+ break;
+
+ default:
+ /* Do nothing */
+ break;
+ }
+
+ return value;
+}
+
+static void npcm7xx_smbus_write_sda(NPCM7xxSMBusState *s, uint8_t value)
+{
+ s->sda = value;
+ if (s->st & NPCM7XX_SMBST_MODE) {
+ switch (s->status) {
+ case NPCM7XX_SMBUS_STATUS_IDLE:
+ npcm7xx_smbus_send_address(s, value);
+ break;
+ case NPCM7XX_SMBUS_STATUS_SENDING:
+ npcm7xx_smbus_send_byte(s, value);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to SDA in invalid status %d: %u\n",
+ DEVICE(s)->canonical_path, s->status, value);
+ break;
+ }
+ }
+}
+
+static void npcm7xx_smbus_write_st(NPCM7xxSMBusState *s, uint8_t value)
+{
+ s->st = WRITE_ONE_CLEAR(s->st, value, NPCM7XX_SMBST_STP);
+ s->st = WRITE_ONE_CLEAR(s->st, value, NPCM7XX_SMBST_BER);
+ s->st = WRITE_ONE_CLEAR(s->st, value, NPCM7XX_SMBST_STASTR);
+ s->st = WRITE_ONE_CLEAR(s->st, value, NPCM7XX_SMBST_NMATCH);
+
+ if (value & NPCM7XX_SMBST_NEGACK) {
+ s->st &= ~NPCM7XX_SMBST_NEGACK;
+ if (s->status == NPCM7XX_SMBUS_STATUS_STOPPING_NEGACK) {
+ npcm7xx_smbus_execute_stop(s);
+ }
+ }
+
+ if (value & NPCM7XX_SMBST_STASTR &&
+ s->status == NPCM7XX_SMBUS_STATUS_RECEIVING) {
+ if (NPCM7XX_SMBUS_FIFO_ENABLED(s)) {
+ npcm7xx_smbus_recv_fifo(s);
+ } else {
+ npcm7xx_smbus_recv_byte(s);
+ }
+ }
+
+ npcm7xx_smbus_update_irq(s);
+}
+
+static void npcm7xx_smbus_write_cst(NPCM7xxSMBusState *s, uint8_t value)
+{
+ uint8_t new_value = s->cst;
+
+ s->cst = WRITE_ONE_CLEAR(new_value, value, NPCM7XX_SMBCST_BB);
+ npcm7xx_smbus_update_irq(s);
+}
+
+static void npcm7xx_smbus_write_cst3(NPCM7xxSMBusState *s, uint8_t value)
+{
+ s->cst3 = WRITE_ONE_CLEAR(s->cst3, value, NPCM7XX_SMBCST3_EO_BUSY);
+ npcm7xx_smbus_update_irq(s);
+}
+
+static void npcm7xx_smbus_write_ctl1(NPCM7xxSMBusState *s, uint8_t value)
+{
+ s->ctl1 = KEEP_OLD_BIT(s->ctl1, value,
+ NPCM7XX_SMBCTL1_START | NPCM7XX_SMBCTL1_STOP | NPCM7XX_SMBCTL1_ACK);
+
+ if (value & NPCM7XX_SMBCTL1_START) {
+ npcm7xx_smbus_start(s);
+ }
+
+ if (value & NPCM7XX_SMBCTL1_STOP) {
+ npcm7xx_smbus_stop(s);
+ }
+
+ npcm7xx_smbus_update_irq(s);
+}
+
+static void npcm7xx_smbus_write_ctl2(NPCM7xxSMBusState *s, uint8_t value)
+{
+ s->ctl2 = value;
+
+ if (!NPCM7XX_SMBUS_ENABLED(s)) {
+ /* Disable this SMBus module. */
+ s->ctl1 = 0;
+ s->st = 0;
+ s->cst3 = s->cst3 & (~NPCM7XX_SMBCST3_EO_BUSY);
+ s->cst = 0;
+ npcm7xx_smbus_clear_buffer(s);
+ }
+}
+
+static void npcm7xx_smbus_write_ctl3(NPCM7xxSMBusState *s, uint8_t value)
+{
+ uint8_t old_ctl3 = s->ctl3;
+
+ /* Write to SDA and SCL bits are ignored. */
+ s->ctl3 = KEEP_OLD_BIT(old_ctl3, value,
+ NPCM7XX_SMBCTL3_SCL_LVL | NPCM7XX_SMBCTL3_SDA_LVL);
+}
+
+static void npcm7xx_smbus_write_fif_ctl(NPCM7xxSMBusState *s, uint8_t value)
+{
+ uint8_t new_ctl = value;
+
+ new_ctl = KEEP_OLD_BIT(s->fif_ctl, new_ctl, NPCM7XX_SMBFIF_CTL_FAIR_RDY);
+ new_ctl = WRITE_ONE_CLEAR(new_ctl, value, NPCM7XX_SMBFIF_CTL_FAIR_RDY);
+ new_ctl = KEEP_OLD_BIT(s->fif_ctl, new_ctl, NPCM7XX_SMBFIF_CTL_FAIR_BUSY);
+ s->fif_ctl = new_ctl;
+}
+
+static void npcm7xx_smbus_write_fif_cts(NPCM7xxSMBusState *s, uint8_t value)
+{
+ s->fif_cts = WRITE_ONE_CLEAR(s->fif_cts, value, NPCM7XX_SMBFIF_CTS_STR);
+ s->fif_cts = WRITE_ONE_CLEAR(s->fif_cts, value, NPCM7XX_SMBFIF_CTS_RXF_TXE);
+ s->fif_cts = KEEP_OLD_BIT(value, s->fif_cts, NPCM7XX_SMBFIF_CTS_RFTE_IE);
+
+ if (value & NPCM7XX_SMBFIF_CTS_CLR_FIFO) {
+ npcm7xx_smbus_clear_buffer(s);
+ }
+}
+
+static void npcm7xx_smbus_write_txf_ctl(NPCM7xxSMBusState *s, uint8_t value)
+{
+ s->txf_ctl = value;
+}
+
+static void npcm7xx_smbus_write_t_out(NPCM7xxSMBusState *s, uint8_t value)
+{
+ uint8_t new_t_out = value;
+
+ if ((value & NPCM7XX_SMBT_OUT_ST) || (!(s->t_out & NPCM7XX_SMBT_OUT_ST))) {
+ new_t_out &= ~NPCM7XX_SMBT_OUT_ST;
+ } else {
+ new_t_out |= NPCM7XX_SMBT_OUT_ST;
+ }
+
+ s->t_out = new_t_out;
+}
+
+static void npcm7xx_smbus_write_txf_sts(NPCM7xxSMBusState *s, uint8_t value)
+{
+ s->txf_sts = WRITE_ONE_CLEAR(s->txf_sts, value, NPCM7XX_SMBTXF_STS_TX_THST);
+}
+
+static void npcm7xx_smbus_write_rxf_sts(NPCM7xxSMBusState *s, uint8_t value)
+{
+ if (value & NPCM7XX_SMBRXF_STS_RX_THST) {
+ s->rxf_sts &= ~NPCM7XX_SMBRXF_STS_RX_THST;
+ if (s->status == NPCM7XX_SMBUS_STATUS_RECEIVING) {
+ npcm7xx_smbus_recv_fifo(s);
+ }
+ }
+}
+
+static void npcm7xx_smbus_write_rxf_ctl(NPCM7xxSMBusState *s, uint8_t value)
+{
+ uint8_t new_ctl = value;
+
+ if (!(value & NPCM7XX_SMBRXF_CTL_LAST)) {
+ new_ctl = KEEP_OLD_BIT(s->rxf_ctl, new_ctl, NPCM7XX_SMBRXF_CTL_LAST);
+ }
+ s->rxf_ctl = new_ctl;
+}
+
+static uint64_t npcm7xx_smbus_read(void *opaque, hwaddr offset, unsigned size)
+{
+ NPCM7xxSMBusState *s = opaque;
+ uint64_t value = 0;
+ uint8_t bank = s->ctl3 & NPCM7XX_SMBCTL3_BNK_SEL;
+
+ /* The order of the registers are their order in memory. */
+ switch (offset) {
+ case NPCM7XX_SMB_SDA:
+ value = npcm7xx_smbus_read_sda(s);
+ break;
+
+ case NPCM7XX_SMB_ST:
+ value = s->st;
+ break;
+
+ case NPCM7XX_SMB_CST:
+ value = s->cst;
+ break;
+
+ case NPCM7XX_SMB_CTL1:
+ value = s->ctl1;
+ break;
+
+ case NPCM7XX_SMB_ADDR1:
+ value = s->addr[0];
+ break;
+
+ case NPCM7XX_SMB_CTL2:
+ value = s->ctl2;
+ break;
+
+ case NPCM7XX_SMB_ADDR2:
+ value = s->addr[1];
+ break;
+
+ case NPCM7XX_SMB_CTL3:
+ value = s->ctl3;
+ break;
+
+ case NPCM7XX_SMB_CST2:
+ value = s->cst2;
+ break;
+
+ case NPCM7XX_SMB_CST3:
+ value = s->cst3;
+ break;
+
+ case NPCM7XX_SMB_VER:
+ value = npcm7xx_smbus_get_version();
+ break;
+
+ /* This register is either invalid or banked at this point. */
+ default:
+ if (bank) {
+ /* Bank 1 */
+ switch (offset) {
+ case NPCM7XX_SMB_FIF_CTS:
+ value = s->fif_cts;
+ break;
+
+ case NPCM7XX_SMB_FAIR_PER:
+ value = s->fair_per;
+ break;
+
+ case NPCM7XX_SMB_TXF_CTL:
+ value = s->txf_ctl;
+ break;
+
+ case NPCM7XX_SMB_T_OUT:
+ value = s->t_out;
+ break;
+
+ case NPCM7XX_SMB_TXF_STS:
+ value = s->txf_sts;
+ break;
+
+ case NPCM7XX_SMB_RXF_STS:
+ value = s->rxf_sts;
+ break;
+
+ case NPCM7XX_SMB_RXF_CTL:
+ value = s->rxf_ctl;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: read from invalid offset 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, offset);
+ break;
+ }
+ } else {
+ /* Bank 0 */
+ switch (offset) {
+ case NPCM7XX_SMB_ADDR3:
+ value = s->addr[2];
+ break;
+
+ case NPCM7XX_SMB_ADDR7:
+ value = s->addr[6];
+ break;
+
+ case NPCM7XX_SMB_ADDR4:
+ value = s->addr[3];
+ break;
+
+ case NPCM7XX_SMB_ADDR8:
+ value = s->addr[7];
+ break;
+
+ case NPCM7XX_SMB_ADDR5:
+ value = s->addr[4];
+ break;
+
+ case NPCM7XX_SMB_ADDR9:
+ value = s->addr[8];
+ break;
+
+ case NPCM7XX_SMB_ADDR6:
+ value = s->addr[5];
+ break;
+
+ case NPCM7XX_SMB_ADDR10:
+ value = s->addr[9];
+ break;
+
+ case NPCM7XX_SMB_CTL4:
+ value = s->ctl4;
+ break;
+
+ case NPCM7XX_SMB_CTL5:
+ value = s->ctl5;
+ break;
+
+ case NPCM7XX_SMB_SCLLT:
+ value = s->scllt;
+ break;
+
+ case NPCM7XX_SMB_FIF_CTL:
+ value = s->fif_ctl;
+ break;
+
+ case NPCM7XX_SMB_SCLHT:
+ value = s->sclht;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: read from invalid offset 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, offset);
+ break;
+ }
+ }
+ break;
+ }
+
+ trace_npcm7xx_smbus_read(DEVICE(s)->canonical_path, offset, value, size);
+
+ return value;
+}
+
+static void npcm7xx_smbus_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ NPCM7xxSMBusState *s = opaque;
+ uint8_t bank = s->ctl3 & NPCM7XX_SMBCTL3_BNK_SEL;
+
+ trace_npcm7xx_smbus_write(DEVICE(s)->canonical_path, offset, value, size);
+
+ /* The order of the registers are their order in memory. */
+ switch (offset) {
+ case NPCM7XX_SMB_SDA:
+ npcm7xx_smbus_write_sda(s, value);
+ break;
+
+ case NPCM7XX_SMB_ST:
+ npcm7xx_smbus_write_st(s, value);
+ break;
+
+ case NPCM7XX_SMB_CST:
+ npcm7xx_smbus_write_cst(s, value);
+ break;
+
+ case NPCM7XX_SMB_CTL1:
+ npcm7xx_smbus_write_ctl1(s, value);
+ break;
+
+ case NPCM7XX_SMB_ADDR1:
+ s->addr[0] = value;
+ break;
+
+ case NPCM7XX_SMB_CTL2:
+ npcm7xx_smbus_write_ctl2(s, value);
+ break;
+
+ case NPCM7XX_SMB_ADDR2:
+ s->addr[1] = value;
+ break;
+
+ case NPCM7XX_SMB_CTL3:
+ npcm7xx_smbus_write_ctl3(s, value);
+ break;
+
+ case NPCM7XX_SMB_CST2:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to read-only reg: offset 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, offset);
+ break;
+
+ case NPCM7XX_SMB_CST3:
+ npcm7xx_smbus_write_cst3(s, value);
+ break;
+
+ case NPCM7XX_SMB_VER:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to read-only reg: offset 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, offset);
+ break;
+
+ /* This register is either invalid or banked at this point. */
+ default:
+ if (bank) {
+ /* Bank 1 */
+ switch (offset) {
+ case NPCM7XX_SMB_FIF_CTS:
+ npcm7xx_smbus_write_fif_cts(s, value);
+ break;
+
+ case NPCM7XX_SMB_FAIR_PER:
+ s->fair_per = value;
+ break;
+
+ case NPCM7XX_SMB_TXF_CTL:
+ npcm7xx_smbus_write_txf_ctl(s, value);
+ break;
+
+ case NPCM7XX_SMB_T_OUT:
+ npcm7xx_smbus_write_t_out(s, value);
+ break;
+
+ case NPCM7XX_SMB_TXF_STS:
+ npcm7xx_smbus_write_txf_sts(s, value);
+ break;
+
+ case NPCM7XX_SMB_RXF_STS:
+ npcm7xx_smbus_write_rxf_sts(s, value);
+ break;
+
+ case NPCM7XX_SMB_RXF_CTL:
+ npcm7xx_smbus_write_rxf_ctl(s, value);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to invalid offset 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, offset);
+ break;
+ }
+ } else {
+ /* Bank 0 */
+ switch (offset) {
+ case NPCM7XX_SMB_ADDR3:
+ s->addr[2] = value;
+ break;
+
+ case NPCM7XX_SMB_ADDR7:
+ s->addr[6] = value;
+ break;
+
+ case NPCM7XX_SMB_ADDR4:
+ s->addr[3] = value;
+ break;
+
+ case NPCM7XX_SMB_ADDR8:
+ s->addr[7] = value;
+ break;
+
+ case NPCM7XX_SMB_ADDR5:
+ s->addr[4] = value;
+ break;
+
+ case NPCM7XX_SMB_ADDR9:
+ s->addr[8] = value;
+ break;
+
+ case NPCM7XX_SMB_ADDR6:
+ s->addr[5] = value;
+ break;
+
+ case NPCM7XX_SMB_ADDR10:
+ s->addr[9] = value;
+ break;
+
+ case NPCM7XX_SMB_CTL4:
+ s->ctl4 = value;
+ break;
+
+ case NPCM7XX_SMB_CTL5:
+ s->ctl5 = value;
+ break;
+
+ case NPCM7XX_SMB_SCLLT:
+ s->scllt = value;
+ break;
+
+ case NPCM7XX_SMB_FIF_CTL:
+ npcm7xx_smbus_write_fif_ctl(s, value);
+ break;
+
+ case NPCM7XX_SMB_SCLHT:
+ s->sclht = value;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to invalid offset 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, offset);
+ break;
+ }
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps npcm7xx_smbus_ops = {
+ .read = npcm7xx_smbus_read,
+ .write = npcm7xx_smbus_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ .unaligned = false,
+ },
+};
+
+static void npcm7xx_smbus_enter_reset(Object *obj, ResetType type)
+{
+ NPCM7xxSMBusState *s = NPCM7XX_SMBUS(obj);
+
+ s->st = NPCM7XX_SMB_ST_INIT_VAL;
+ s->cst = NPCM7XX_SMB_CST_INIT_VAL;
+ s->cst2 = NPCM7XX_SMB_CST2_INIT_VAL;
+ s->cst3 = NPCM7XX_SMB_CST3_INIT_VAL;
+ s->ctl1 = NPCM7XX_SMB_CTL1_INIT_VAL;
+ s->ctl2 = NPCM7XX_SMB_CTL2_INIT_VAL;
+ s->ctl3 = NPCM7XX_SMB_CTL3_INIT_VAL;
+ s->ctl4 = NPCM7XX_SMB_CTL4_INIT_VAL;
+ s->ctl5 = NPCM7XX_SMB_CTL5_INIT_VAL;
+
+ for (int i = 0; i < NPCM7XX_SMBUS_NR_ADDRS; ++i) {
+ s->addr[i] = NPCM7XX_SMB_ADDR_INIT_VAL;
+ }
+ s->scllt = NPCM7XX_SMB_SCLLT_INIT_VAL;
+ s->sclht = NPCM7XX_SMB_SCLHT_INIT_VAL;
+
+ s->fif_ctl = NPCM7XX_SMB_FIF_CTL_INIT_VAL;
+ s->fif_cts = NPCM7XX_SMB_FIF_CTS_INIT_VAL;
+ s->fair_per = NPCM7XX_SMB_FAIR_PER_INIT_VAL;
+ s->txf_ctl = NPCM7XX_SMB_TXF_CTL_INIT_VAL;
+ s->t_out = NPCM7XX_SMB_T_OUT_INIT_VAL;
+ s->txf_sts = NPCM7XX_SMB_TXF_STS_INIT_VAL;
+ s->rxf_sts = NPCM7XX_SMB_RXF_STS_INIT_VAL;
+ s->rxf_ctl = NPCM7XX_SMB_RXF_CTL_INIT_VAL;
+
+ npcm7xx_smbus_clear_buffer(s);
+ s->status = NPCM7XX_SMBUS_STATUS_IDLE;
+ s->rx_cur = 0;
+}
+
+static void npcm7xx_smbus_hold_reset(Object *obj)
+{
+ NPCM7xxSMBusState *s = NPCM7XX_SMBUS(obj);
+
+ qemu_irq_lower(s->irq);
+}
+
+static void npcm7xx_smbus_init(Object *obj)
+{
+ NPCM7xxSMBusState *s = NPCM7XX_SMBUS(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ sysbus_init_irq(sbd, &s->irq);
+ memory_region_init_io(&s->iomem, obj, &npcm7xx_smbus_ops, s,
+ "regs", 4 * KiB);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ s->bus = i2c_init_bus(DEVICE(s), "i2c-bus");
+}
+
+static const VMStateDescription vmstate_npcm7xx_smbus = {
+ .name = "npcm7xx-smbus",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(sda, NPCM7xxSMBusState),
+ VMSTATE_UINT8(st, NPCM7xxSMBusState),
+ VMSTATE_UINT8(cst, NPCM7xxSMBusState),
+ VMSTATE_UINT8(cst2, NPCM7xxSMBusState),
+ VMSTATE_UINT8(cst3, NPCM7xxSMBusState),
+ VMSTATE_UINT8(ctl1, NPCM7xxSMBusState),
+ VMSTATE_UINT8(ctl2, NPCM7xxSMBusState),
+ VMSTATE_UINT8(ctl3, NPCM7xxSMBusState),
+ VMSTATE_UINT8(ctl4, NPCM7xxSMBusState),
+ VMSTATE_UINT8(ctl5, NPCM7xxSMBusState),
+ VMSTATE_UINT8_ARRAY(addr, NPCM7xxSMBusState, NPCM7XX_SMBUS_NR_ADDRS),
+ VMSTATE_UINT8(scllt, NPCM7xxSMBusState),
+ VMSTATE_UINT8(sclht, NPCM7xxSMBusState),
+ VMSTATE_UINT8(fif_ctl, NPCM7xxSMBusState),
+ VMSTATE_UINT8(fif_cts, NPCM7xxSMBusState),
+ VMSTATE_UINT8(fair_per, NPCM7xxSMBusState),
+ VMSTATE_UINT8(txf_ctl, NPCM7xxSMBusState),
+ VMSTATE_UINT8(t_out, NPCM7xxSMBusState),
+ VMSTATE_UINT8(txf_sts, NPCM7xxSMBusState),
+ VMSTATE_UINT8(rxf_sts, NPCM7xxSMBusState),
+ VMSTATE_UINT8(rxf_ctl, NPCM7xxSMBusState),
+ VMSTATE_UINT8_ARRAY(rx_fifo, NPCM7xxSMBusState,
+ NPCM7XX_SMBUS_FIFO_SIZE),
+ VMSTATE_UINT8(rx_cur, NPCM7xxSMBusState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void npcm7xx_smbus_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "NPCM7xx System Management Bus";
+ dc->vmsd = &vmstate_npcm7xx_smbus;
+ rc->phases.enter = npcm7xx_smbus_enter_reset;
+ rc->phases.hold = npcm7xx_smbus_hold_reset;
+}
+
+static const TypeInfo npcm7xx_smbus_types[] = {
+ {
+ .name = TYPE_NPCM7XX_SMBUS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NPCM7xxSMBusState),
+ .class_init = npcm7xx_smbus_class_init,
+ .instance_init = npcm7xx_smbus_init,
+ },
+};
+DEFINE_TYPES(npcm7xx_smbus_types);
diff --git a/hw/i2c/omap_i2c.c b/hw/i2c/omap_i2c.c
new file mode 100644
index 000000000..e5d205dda
--- /dev/null
+++ b/hw/i2c/omap_i2c.c
@@ -0,0 +1,549 @@
+/*
+ * TI OMAP on-chip I2C controller. Only "new I2C" mode supported.
+ *
+ * Copyright (C) 2007 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * 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 "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/i2c/i2c.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/arm/omap.h"
+#include "hw/sysbus.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+
+struct OMAPI2CState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq drq[2];
+ I2CBus *bus;
+
+ uint8_t revision;
+ void *iclk;
+ void *fclk;
+
+ uint8_t mask;
+ uint16_t stat;
+ uint16_t dma;
+ uint16_t count;
+ int count_cur;
+ uint32_t fifo;
+ int rxlen;
+ int txlen;
+ uint16_t control;
+ uint16_t addr[2];
+ uint8_t divider;
+ uint8_t times[2];
+ uint16_t test;
+};
+
+#define OMAP2_INTR_REV 0x34
+#define OMAP2_GC_REV 0x34
+
+static void omap_i2c_interrupts_update(OMAPI2CState *s)
+{
+ qemu_set_irq(s->irq, s->stat & s->mask);
+ if ((s->dma >> 15) & 1) /* RDMA_EN */
+ qemu_set_irq(s->drq[0], (s->stat >> 3) & 1); /* RRDY */
+ if ((s->dma >> 7) & 1) /* XDMA_EN */
+ qemu_set_irq(s->drq[1], (s->stat >> 4) & 1); /* XRDY */
+}
+
+static void omap_i2c_fifo_run(OMAPI2CState *s)
+{
+ int ack = 1;
+
+ if (!i2c_bus_busy(s->bus))
+ return;
+
+ if ((s->control >> 2) & 1) { /* RM */
+ if ((s->control >> 1) & 1) { /* STP */
+ i2c_end_transfer(s->bus);
+ s->control &= ~(1 << 1); /* STP */
+ s->count_cur = s->count;
+ s->txlen = 0;
+ } else if ((s->control >> 9) & 1) { /* TRX */
+ while (ack && s->txlen)
+ ack = (i2c_send(s->bus,
+ (s->fifo >> ((-- s->txlen) << 3)) &
+ 0xff) >= 0);
+ s->stat |= 1 << 4; /* XRDY */
+ } else {
+ while (s->rxlen < 4)
+ s->fifo |= i2c_recv(s->bus) << ((s->rxlen ++) << 3);
+ s->stat |= 1 << 3; /* RRDY */
+ }
+ } else {
+ if ((s->control >> 9) & 1) { /* TRX */
+ while (ack && s->count_cur && s->txlen) {
+ ack = (i2c_send(s->bus,
+ (s->fifo >> ((-- s->txlen) << 3)) &
+ 0xff) >= 0);
+ s->count_cur --;
+ }
+ if (ack && s->count_cur)
+ s->stat |= 1 << 4; /* XRDY */
+ else
+ s->stat &= ~(1 << 4); /* XRDY */
+ if (!s->count_cur) {
+ s->stat |= 1 << 2; /* ARDY */
+ s->control &= ~(1 << 10); /* MST */
+ }
+ } else {
+ while (s->count_cur && s->rxlen < 4) {
+ s->fifo |= i2c_recv(s->bus) << ((s->rxlen ++) << 3);
+ s->count_cur --;
+ }
+ if (s->rxlen)
+ s->stat |= 1 << 3; /* RRDY */
+ else
+ s->stat &= ~(1 << 3); /* RRDY */
+ }
+ if (!s->count_cur) {
+ if ((s->control >> 1) & 1) { /* STP */
+ i2c_end_transfer(s->bus);
+ s->control &= ~(1 << 1); /* STP */
+ s->count_cur = s->count;
+ s->txlen = 0;
+ } else {
+ s->stat |= 1 << 2; /* ARDY */
+ s->control &= ~(1 << 10); /* MST */
+ }
+ }
+ }
+
+ s->stat |= (!ack) << 1; /* NACK */
+ if (!ack)
+ s->control &= ~(1 << 1); /* STP */
+}
+
+static void omap_i2c_reset(DeviceState *dev)
+{
+ OMAPI2CState *s = OMAP_I2C(dev);
+
+ s->mask = 0;
+ s->stat = 0;
+ s->dma = 0;
+ s->count = 0;
+ s->count_cur = 0;
+ s->fifo = 0;
+ s->rxlen = 0;
+ s->txlen = 0;
+ s->control = 0;
+ s->addr[0] = 0;
+ s->addr[1] = 0;
+ s->divider = 0;
+ s->times[0] = 0;
+ s->times[1] = 0;
+ s->test = 0;
+}
+
+static uint32_t omap_i2c_read(void *opaque, hwaddr addr)
+{
+ OMAPI2CState *s = opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+ uint16_t ret;
+
+ switch (offset) {
+ case 0x00: /* I2C_REV */
+ return s->revision; /* REV */
+
+ case 0x04: /* I2C_IE */
+ return s->mask;
+
+ case 0x08: /* I2C_STAT */
+ return s->stat | (i2c_bus_busy(s->bus) << 12);
+
+ case 0x0c: /* I2C_IV */
+ if (s->revision >= OMAP2_INTR_REV)
+ break;
+ ret = ctz32(s->stat & s->mask);
+ if (ret != 32) {
+ s->stat ^= 1 << ret;
+ ret++;
+ } else {
+ ret = 0;
+ }
+ omap_i2c_interrupts_update(s);
+ return ret;
+
+ case 0x10: /* I2C_SYSS */
+ return (s->control >> 15) & 1; /* I2C_EN */
+
+ case 0x14: /* I2C_BUF */
+ return s->dma;
+
+ case 0x18: /* I2C_CNT */
+ return s->count_cur; /* DCOUNT */
+
+ case 0x1c: /* I2C_DATA */
+ ret = 0;
+ if (s->control & (1 << 14)) { /* BE */
+ ret |= ((s->fifo >> 0) & 0xff) << 8;
+ ret |= ((s->fifo >> 8) & 0xff) << 0;
+ } else {
+ ret |= ((s->fifo >> 8) & 0xff) << 8;
+ ret |= ((s->fifo >> 0) & 0xff) << 0;
+ }
+ if (s->rxlen == 1) {
+ s->stat |= 1 << 15; /* SBD */
+ s->rxlen = 0;
+ } else if (s->rxlen > 1) {
+ if (s->rxlen > 2)
+ s->fifo >>= 16;
+ s->rxlen -= 2;
+ } else {
+ /* XXX: remote access (qualifier) error - what's that? */
+ }
+ if (!s->rxlen) {
+ s->stat &= ~(1 << 3); /* RRDY */
+ if (((s->control >> 10) & 1) && /* MST */
+ ((~s->control >> 9) & 1)) { /* TRX */
+ s->stat |= 1 << 2; /* ARDY */
+ s->control &= ~(1 << 10); /* MST */
+ }
+ }
+ s->stat &= ~(1 << 11); /* ROVR */
+ omap_i2c_fifo_run(s);
+ omap_i2c_interrupts_update(s);
+ return ret;
+
+ case 0x20: /* I2C_SYSC */
+ return 0;
+
+ case 0x24: /* I2C_CON */
+ return s->control;
+
+ case 0x28: /* I2C_OA */
+ return s->addr[0];
+
+ case 0x2c: /* I2C_SA */
+ return s->addr[1];
+
+ case 0x30: /* I2C_PSC */
+ return s->divider;
+
+ case 0x34: /* I2C_SCLL */
+ return s->times[0];
+
+ case 0x38: /* I2C_SCLH */
+ return s->times[1];
+
+ case 0x3c: /* I2C_SYSTEST */
+ if (s->test & (1 << 15)) { /* ST_EN */
+ s->test ^= 0xa;
+ return s->test;
+ } else
+ return s->test & ~0x300f;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_i2c_write(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ OMAPI2CState *s = opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+ int nack;
+
+ switch (offset) {
+ case 0x00: /* I2C_REV */
+ case 0x0c: /* I2C_IV */
+ case 0x10: /* I2C_SYSS */
+ OMAP_RO_REG(addr);
+ return;
+
+ case 0x04: /* I2C_IE */
+ s->mask = value & (s->revision < OMAP2_GC_REV ? 0x1f : 0x3f);
+ break;
+
+ case 0x08: /* I2C_STAT */
+ if (s->revision < OMAP2_INTR_REV) {
+ OMAP_RO_REG(addr);
+ return;
+ }
+
+ /* RRDY and XRDY are reset by hardware. (in all versions???) */
+ s->stat &= ~(value & 0x27);
+ omap_i2c_interrupts_update(s);
+ break;
+
+ case 0x14: /* I2C_BUF */
+ s->dma = value & 0x8080;
+ if (value & (1 << 15)) /* RDMA_EN */
+ s->mask &= ~(1 << 3); /* RRDY_IE */
+ if (value & (1 << 7)) /* XDMA_EN */
+ s->mask &= ~(1 << 4); /* XRDY_IE */
+ break;
+
+ case 0x18: /* I2C_CNT */
+ s->count = value; /* DCOUNT */
+ break;
+
+ case 0x1c: /* I2C_DATA */
+ if (s->txlen > 2) {
+ /* XXX: remote access (qualifier) error - what's that? */
+ break;
+ }
+ s->fifo <<= 16;
+ s->txlen += 2;
+ if (s->control & (1 << 14)) { /* BE */
+ s->fifo |= ((value >> 8) & 0xff) << 8;
+ s->fifo |= ((value >> 0) & 0xff) << 0;
+ } else {
+ s->fifo |= ((value >> 0) & 0xff) << 8;
+ s->fifo |= ((value >> 8) & 0xff) << 0;
+ }
+ s->stat &= ~(1 << 10); /* XUDF */
+ if (s->txlen > 2)
+ s->stat &= ~(1 << 4); /* XRDY */
+ omap_i2c_fifo_run(s);
+ omap_i2c_interrupts_update(s);
+ break;
+
+ case 0x20: /* I2C_SYSC */
+ if (s->revision < OMAP2_INTR_REV) {
+ OMAP_BAD_REG(addr);
+ return;
+ }
+
+ if (value & 2) {
+ omap_i2c_reset(DEVICE(s));
+ }
+ break;
+
+ case 0x24: /* I2C_CON */
+ s->control = value & 0xcf87;
+ if (~value & (1 << 15)) { /* I2C_EN */
+ if (s->revision < OMAP2_INTR_REV) {
+ omap_i2c_reset(DEVICE(s));
+ }
+ break;
+ }
+ if ((value & (1 << 15)) && !(value & (1 << 10))) { /* MST */
+ qemu_log_mask(LOG_UNIMP, "%s: I^2C slave mode not supported\n",
+ __func__);
+ break;
+ }
+ if ((value & (1 << 15)) && value & (1 << 8)) { /* XA */
+ qemu_log_mask(LOG_UNIMP,
+ "%s: 10-bit addressing mode not supported\n",
+ __func__);
+ break;
+ }
+ if ((value & (1 << 15)) && value & (1 << 0)) { /* STT */
+ nack = !!i2c_start_transfer(s->bus, s->addr[1], /* SA */
+ (~value >> 9) & 1); /* TRX */
+ s->stat |= nack << 1; /* NACK */
+ s->control &= ~(1 << 0); /* STT */
+ s->fifo = 0;
+ if (nack)
+ s->control &= ~(1 << 1); /* STP */
+ else {
+ s->count_cur = s->count;
+ omap_i2c_fifo_run(s);
+ }
+ omap_i2c_interrupts_update(s);
+ }
+ break;
+
+ case 0x28: /* I2C_OA */
+ s->addr[0] = value & 0x3ff;
+ break;
+
+ case 0x2c: /* I2C_SA */
+ s->addr[1] = value & 0x3ff;
+ break;
+
+ case 0x30: /* I2C_PSC */
+ s->divider = value;
+ break;
+
+ case 0x34: /* I2C_SCLL */
+ s->times[0] = value;
+ break;
+
+ case 0x38: /* I2C_SCLH */
+ s->times[1] = value;
+ break;
+
+ case 0x3c: /* I2C_SYSTEST */
+ s->test = value & 0xf80f;
+ if (value & (1 << 11)) /* SBB */
+ if (s->revision >= OMAP2_INTR_REV) {
+ s->stat |= 0x3f;
+ omap_i2c_interrupts_update(s);
+ }
+ if (value & (1 << 15)) { /* ST_EN */
+ qemu_log_mask(LOG_UNIMP,
+ "%s: System Test not supported\n", __func__);
+ }
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static void omap_i2c_writeb(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ OMAPI2CState *s = opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ switch (offset) {
+ case 0x1c: /* I2C_DATA */
+ if (s->txlen > 2) {
+ /* XXX: remote access (qualifier) error - what's that? */
+ break;
+ }
+ s->fifo <<= 8;
+ s->txlen += 1;
+ s->fifo |= value & 0xff;
+ s->stat &= ~(1 << 10); /* XUDF */
+ if (s->txlen > 2)
+ s->stat &= ~(1 << 4); /* XRDY */
+ omap_i2c_fifo_run(s);
+ omap_i2c_interrupts_update(s);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static uint64_t omap_i2c_readfn(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ switch (size) {
+ case 2:
+ return omap_i2c_read(opaque, addr);
+ default:
+ return omap_badwidth_read16(opaque, addr);
+ }
+}
+
+static void omap_i2c_writefn(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ switch (size) {
+ case 1:
+ /* Only the last fifo write can be 8 bit. */
+ omap_i2c_writeb(opaque, addr, value);
+ break;
+ case 2:
+ omap_i2c_write(opaque, addr, value);
+ break;
+ default:
+ omap_badwidth_write16(opaque, addr, value);
+ break;
+ }
+}
+
+static const MemoryRegionOps omap_i2c_ops = {
+ .read = omap_i2c_readfn,
+ .write = omap_i2c_writefn,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_i2c_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ OMAPI2CState *s = OMAP_I2C(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_irq(sbd, &s->drq[0]);
+ sysbus_init_irq(sbd, &s->drq[1]);
+ sysbus_init_mmio(sbd, &s->iomem);
+ s->bus = i2c_init_bus(dev, NULL);
+}
+
+static void omap_i2c_realize(DeviceState *dev, Error **errp)
+{
+ OMAPI2CState *s = OMAP_I2C(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &omap_i2c_ops, s, "omap.i2c",
+ (s->revision < OMAP2_INTR_REV) ? 0x800 : 0x1000);
+
+ if (!s->fclk) {
+ error_setg(errp, "omap_i2c: fclk not connected");
+ return;
+ }
+ if (s->revision >= OMAP2_INTR_REV && !s->iclk) {
+ /* Note that OMAP1 doesn't have a separate interface clock */
+ error_setg(errp, "omap_i2c: iclk not connected");
+ return;
+ }
+}
+
+void omap_i2c_set_iclk(OMAPI2CState *i2c, omap_clk clk)
+{
+ i2c->iclk = clk;
+}
+
+void omap_i2c_set_fclk(OMAPI2CState *i2c, omap_clk clk)
+{
+ i2c->fclk = clk;
+}
+
+static Property omap_i2c_properties[] = {
+ DEFINE_PROP_UINT8("revision", OMAPI2CState, revision, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void omap_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, omap_i2c_properties);
+ dc->reset = omap_i2c_reset;
+ /* Reason: pointer properties "iclk", "fclk" */
+ dc->user_creatable = false;
+ dc->realize = omap_i2c_realize;
+}
+
+static const TypeInfo omap_i2c_info = {
+ .name = TYPE_OMAP_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(OMAPI2CState),
+ .instance_init = omap_i2c_init,
+ .class_init = omap_i2c_class_init,
+};
+
+static void omap_i2c_register_types(void)
+{
+ type_register_static(&omap_i2c_info);
+}
+
+I2CBus *omap_i2c_bus(DeviceState *omap_i2c)
+{
+ OMAPI2CState *s = OMAP_I2C(omap_i2c);
+ return s->bus;
+}
+
+type_init(omap_i2c_register_types)
diff --git a/hw/i2c/pm_smbus.c b/hw/i2c/pm_smbus.c
new file mode 100644
index 000000000..d7eae548c
--- /dev/null
+++ b/hw/i2c/pm_smbus.c
@@ -0,0 +1,497 @@
+/*
+ * PC SMBus implementation
+ * splitted from acpi.c
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/boards.h"
+#include "hw/i2c/pm_smbus.h"
+#include "hw/i2c/smbus_master.h"
+#include "migration/vmstate.h"
+
+#define SMBHSTSTS 0x00
+#define SMBHSTCNT 0x02
+#define SMBHSTCMD 0x03
+#define SMBHSTADD 0x04
+#define SMBHSTDAT0 0x05
+#define SMBHSTDAT1 0x06
+#define SMBBLKDAT 0x07
+#define SMBAUXCTL 0x0d
+
+#define STS_HOST_BUSY (1 << 0)
+#define STS_INTR (1 << 1)
+#define STS_DEV_ERR (1 << 2)
+#define STS_BUS_ERR (1 << 3)
+#define STS_FAILED (1 << 4)
+#define STS_SMBALERT (1 << 5)
+#define STS_INUSE_STS (1 << 6)
+#define STS_BYTE_DONE (1 << 7)
+/* Signs of successfully transaction end :
+* ByteDoneStatus = 1 (STS_BYTE_DONE) and INTR = 1 (STS_INTR )
+*/
+
+#define CTL_INTREN (1 << 0)
+#define CTL_KILL (1 << 1)
+#define CTL_LAST_BYTE (1 << 5)
+#define CTL_START (1 << 6)
+#define CTL_PEC_EN (1 << 7)
+#define CTL_RETURN_MASK 0x1f
+
+#define PROT_QUICK 0
+#define PROT_BYTE 1
+#define PROT_BYTE_DATA 2
+#define PROT_WORD_DATA 3
+#define PROT_PROC_CALL 4
+#define PROT_BLOCK_DATA 5
+#define PROT_I2C_BLOCK_READ 6
+
+#define AUX_PEC (1 << 0)
+#define AUX_BLK (1 << 1)
+#define AUX_MASK 0x3
+
+/*#define DEBUG*/
+
+#ifdef DEBUG
+# define SMBUS_DPRINTF(format, ...) printf(format, ## __VA_ARGS__)
+#else
+# define SMBUS_DPRINTF(format, ...) do { } while (0)
+#endif
+
+
+static void smb_transaction(PMSMBus *s)
+{
+ uint8_t prot = (s->smb_ctl >> 2) & 0x07;
+ uint8_t read = s->smb_addr & 0x01;
+ uint8_t cmd = s->smb_cmd;
+ uint8_t addr = s->smb_addr >> 1;
+ I2CBus *bus = s->smbus;
+ int ret;
+
+ SMBUS_DPRINTF("SMBus trans addr=0x%02x prot=0x%02x\n", addr, prot);
+ /* Transaction isn't exec if STS_DEV_ERR bit set */
+ if ((s->smb_stat & STS_DEV_ERR) != 0) {
+ goto error;
+ }
+
+ switch(prot) {
+ case PROT_QUICK:
+ ret = smbus_quick_command(bus, addr, read);
+ goto done;
+ case PROT_BYTE:
+ if (read) {
+ ret = smbus_receive_byte(bus, addr);
+ goto data8;
+ } else {
+ ret = smbus_send_byte(bus, addr, cmd);
+ goto done;
+ }
+ case PROT_BYTE_DATA:
+ if (read) {
+ ret = smbus_read_byte(bus, addr, cmd);
+ goto data8;
+ } else {
+ ret = smbus_write_byte(bus, addr, cmd, s->smb_data0);
+ goto done;
+ }
+ break;
+ case PROT_WORD_DATA:
+ if (read) {
+ ret = smbus_read_word(bus, addr, cmd);
+ goto data16;
+ } else {
+ ret = smbus_write_word(bus, addr, cmd,
+ (s->smb_data1 << 8) | s->smb_data0);
+ goto done;
+ }
+ break;
+ case PROT_I2C_BLOCK_READ:
+ /* According to the Linux i2c-i801 driver:
+ * NB: page 240 of ICH5 datasheet shows that the R/#W
+ * bit should be cleared here, even when reading.
+ * However if SPD Write Disable is set (Lynx Point and later),
+ * the read will fail if we don't set the R/#W bit.
+ * So at least Linux may or may not set the read bit here.
+ * So just ignore the read bit for this command.
+ */
+ if (i2c_start_send(bus, addr)) {
+ goto error;
+ }
+ ret = i2c_send(bus, s->smb_data1);
+ if (ret) {
+ goto error;
+ }
+ if (i2c_start_recv(bus, addr)) {
+ goto error;
+ }
+ s->in_i2c_block_read = true;
+ s->smb_blkdata = i2c_recv(s->smbus);
+ s->op_done = false;
+ s->smb_stat |= STS_HOST_BUSY | STS_BYTE_DONE;
+ goto out;
+
+ case PROT_BLOCK_DATA:
+ if (read) {
+ ret = smbus_read_block(bus, addr, cmd, s->smb_data,
+ sizeof(s->smb_data), !s->i2c_enable,
+ !s->i2c_enable);
+ if (ret < 0) {
+ goto error;
+ }
+ s->smb_index = 0;
+ s->op_done = false;
+ if (s->smb_auxctl & AUX_BLK) {
+ s->smb_stat |= STS_INTR;
+ } else {
+ s->smb_blkdata = s->smb_data[0];
+ s->smb_stat |= STS_HOST_BUSY | STS_BYTE_DONE;
+ }
+ s->smb_data0 = ret;
+ goto out;
+ } else {
+ if (s->smb_auxctl & AUX_BLK) {
+ if (s->smb_index != s->smb_data0) {
+ s->smb_index = 0;
+ goto error;
+ }
+ /* Data is already all written to the queue, just do
+ the operation. */
+ s->smb_index = 0;
+ ret = smbus_write_block(bus, addr, cmd, s->smb_data,
+ s->smb_data0, !s->i2c_enable);
+ if (ret < 0) {
+ goto error;
+ }
+ s->op_done = true;
+ s->smb_stat |= STS_INTR;
+ s->smb_stat &= ~STS_HOST_BUSY;
+ } else {
+ s->op_done = false;
+ s->smb_stat |= STS_HOST_BUSY | STS_BYTE_DONE;
+ s->smb_data[0] = s->smb_blkdata;
+ s->smb_index = 0;
+ }
+ goto out;
+ }
+ break;
+ default:
+ goto error;
+ }
+ abort();
+
+data16:
+ if (ret < 0) {
+ goto error;
+ }
+ s->smb_data1 = ret >> 8;
+data8:
+ if (ret < 0) {
+ goto error;
+ }
+ s->smb_data0 = ret;
+done:
+ if (ret < 0) {
+ goto error;
+ }
+ s->smb_stat |= STS_INTR;
+out:
+ return;
+
+error:
+ s->smb_stat |= STS_DEV_ERR;
+ return;
+}
+
+static void smb_transaction_start(PMSMBus *s)
+{
+ if (s->smb_ctl & CTL_INTREN) {
+ smb_transaction(s);
+ s->start_transaction_on_status_read = false;
+ } else {
+ /* Do not execute immediately the command; it will be
+ * executed when guest will read SMB_STAT register. This
+ * is to work around a bug in AMIBIOS (that is working
+ * around another bug in some specific hardware) where
+ * it waits for STS_HOST_BUSY to be set before waiting
+ * checking for status. If STS_HOST_BUSY doesn't get
+ * set, it gets stuck. */
+ s->smb_stat |= STS_HOST_BUSY;
+ s->start_transaction_on_status_read = true;
+ }
+}
+
+static bool
+smb_irq_value(PMSMBus *s)
+{
+ return ((s->smb_stat & ~STS_HOST_BUSY) != 0) && (s->smb_ctl & CTL_INTREN);
+}
+
+static bool
+smb_byte_by_byte(PMSMBus *s)
+{
+ if (s->op_done) {
+ return false;
+ }
+ if (s->in_i2c_block_read) {
+ return true;
+ }
+ return !(s->smb_auxctl & AUX_BLK);
+}
+
+static void smb_ioport_writeb(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ PMSMBus *s = opaque;
+ uint8_t clear_byte_done;
+
+ SMBUS_DPRINTF("SMB writeb port=0x%04" HWADDR_PRIx
+ " val=0x%02" PRIx64 "\n", addr, val);
+ switch(addr) {
+ case SMBHSTSTS:
+ clear_byte_done = s->smb_stat & val & STS_BYTE_DONE;
+ s->smb_stat &= ~(val & ~STS_HOST_BUSY);
+ if (clear_byte_done && smb_byte_by_byte(s)) {
+ uint8_t read = s->smb_addr & 0x01;
+
+ if (s->in_i2c_block_read) {
+ /* See comment below PROT_I2C_BLOCK_READ above. */
+ read = 1;
+ }
+
+ s->smb_index++;
+ if (s->smb_index >= PM_SMBUS_MAX_MSG_SIZE) {
+ s->smb_index = 0;
+ }
+ if (!read && s->smb_index == s->smb_data0) {
+ uint8_t prot = (s->smb_ctl >> 2) & 0x07;
+ uint8_t cmd = s->smb_cmd;
+ uint8_t addr = s->smb_addr >> 1;
+ int ret;
+
+ if (prot == PROT_I2C_BLOCK_READ) {
+ s->smb_stat |= STS_DEV_ERR;
+ goto out;
+ }
+
+ ret = smbus_write_block(s->smbus, addr, cmd, s->smb_data,
+ s->smb_data0, !s->i2c_enable);
+ if (ret < 0) {
+ s->smb_stat |= STS_DEV_ERR;
+ goto out;
+ }
+ s->op_done = true;
+ s->smb_stat |= STS_INTR;
+ s->smb_stat &= ~STS_HOST_BUSY;
+ } else if (!read) {
+ s->smb_data[s->smb_index] = s->smb_blkdata;
+ s->smb_stat |= STS_BYTE_DONE;
+ } else if (s->smb_ctl & CTL_LAST_BYTE) {
+ s->op_done = true;
+ if (s->in_i2c_block_read) {
+ s->in_i2c_block_read = false;
+ s->smb_blkdata = i2c_recv(s->smbus);
+ i2c_nack(s->smbus);
+ i2c_end_transfer(s->smbus);
+ } else {
+ s->smb_blkdata = s->smb_data[s->smb_index];
+ }
+ s->smb_index = 0;
+ s->smb_stat |= STS_INTR;
+ s->smb_stat &= ~STS_HOST_BUSY;
+ } else {
+ if (s->in_i2c_block_read) {
+ s->smb_blkdata = i2c_recv(s->smbus);
+ } else {
+ s->smb_blkdata = s->smb_data[s->smb_index];
+ }
+ s->smb_stat |= STS_BYTE_DONE;
+ }
+ }
+ break;
+ case SMBHSTCNT:
+ s->smb_ctl = val & ~CTL_START; /* CTL_START always reads 0 */
+ if (val & CTL_START) {
+ if (!s->op_done) {
+ s->smb_index = 0;
+ s->op_done = true;
+ if (s->in_i2c_block_read) {
+ s->in_i2c_block_read = false;
+ i2c_end_transfer(s->smbus);
+ }
+ }
+ smb_transaction_start(s);
+ }
+ if (s->smb_ctl & CTL_KILL) {
+ s->op_done = true;
+ s->smb_index = 0;
+ s->smb_stat |= STS_FAILED;
+ s->smb_stat &= ~STS_HOST_BUSY;
+ }
+ break;
+ case SMBHSTCMD:
+ s->smb_cmd = val;
+ break;
+ case SMBHSTADD:
+ s->smb_addr = val;
+ break;
+ case SMBHSTDAT0:
+ s->smb_data0 = val;
+ break;
+ case SMBHSTDAT1:
+ s->smb_data1 = val;
+ break;
+ case SMBBLKDAT:
+ if (s->smb_index >= PM_SMBUS_MAX_MSG_SIZE) {
+ s->smb_index = 0;
+ }
+ if (s->smb_auxctl & AUX_BLK) {
+ s->smb_data[s->smb_index++] = val;
+ } else {
+ s->smb_blkdata = val;
+ }
+ break;
+ case SMBAUXCTL:
+ s->smb_auxctl = val & AUX_MASK;
+ break;
+ default:
+ break;
+ }
+
+ out:
+ if (s->set_irq) {
+ s->set_irq(s, smb_irq_value(s));
+ }
+}
+
+static uint64_t smb_ioport_readb(void *opaque, hwaddr addr, unsigned width)
+{
+ PMSMBus *s = opaque;
+ uint32_t val;
+
+ switch(addr) {
+ case SMBHSTSTS:
+ val = s->smb_stat;
+ if (s->start_transaction_on_status_read) {
+ /* execute command now */
+ s->start_transaction_on_status_read = false;
+ s->smb_stat &= ~STS_HOST_BUSY;
+ smb_transaction(s);
+ }
+ break;
+ case SMBHSTCNT:
+ val = s->smb_ctl & CTL_RETURN_MASK;
+ break;
+ case SMBHSTCMD:
+ val = s->smb_cmd;
+ break;
+ case SMBHSTADD:
+ val = s->smb_addr;
+ break;
+ case SMBHSTDAT0:
+ val = s->smb_data0;
+ break;
+ case SMBHSTDAT1:
+ val = s->smb_data1;
+ break;
+ case SMBBLKDAT:
+ if (s->smb_auxctl & AUX_BLK && !s->in_i2c_block_read) {
+ if (s->smb_index >= PM_SMBUS_MAX_MSG_SIZE) {
+ s->smb_index = 0;
+ }
+ val = s->smb_data[s->smb_index++];
+ if (!s->op_done && s->smb_index == s->smb_data0) {
+ s->op_done = true;
+ s->smb_index = 0;
+ s->smb_stat &= ~STS_HOST_BUSY;
+ }
+ } else {
+ val = s->smb_blkdata;
+ }
+ break;
+ case SMBAUXCTL:
+ val = s->smb_auxctl;
+ break;
+ default:
+ val = 0;
+ break;
+ }
+ SMBUS_DPRINTF("SMB readb port=0x%04" HWADDR_PRIx " val=0x%02x\n",
+ addr, val);
+
+ if (s->set_irq) {
+ s->set_irq(s, smb_irq_value(s));
+ }
+
+ return val;
+}
+
+static void pm_smbus_reset(PMSMBus *s)
+{
+ s->op_done = true;
+ s->smb_index = 0;
+ s->smb_stat = 0;
+}
+
+static const MemoryRegionOps pm_smbus_ops = {
+ .read = smb_ioport_readb,
+ .write = smb_ioport_writeb,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+bool pm_smbus_vmstate_needed(void)
+{
+ MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine());
+
+ return !mc->smbus_no_migration_support;
+}
+
+const VMStateDescription pmsmb_vmstate = {
+ .name = "pmsmb",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(smb_stat, PMSMBus),
+ VMSTATE_UINT8(smb_ctl, PMSMBus),
+ VMSTATE_UINT8(smb_cmd, PMSMBus),
+ VMSTATE_UINT8(smb_addr, PMSMBus),
+ VMSTATE_UINT8(smb_data0, PMSMBus),
+ VMSTATE_UINT8(smb_data1, PMSMBus),
+ VMSTATE_UINT32(smb_index, PMSMBus),
+ VMSTATE_UINT8_ARRAY(smb_data, PMSMBus, PM_SMBUS_MAX_MSG_SIZE),
+ VMSTATE_UINT8(smb_auxctl, PMSMBus),
+ VMSTATE_UINT8(smb_blkdata, PMSMBus),
+ VMSTATE_BOOL(i2c_enable, PMSMBus),
+ VMSTATE_BOOL(op_done, PMSMBus),
+ VMSTATE_BOOL(in_i2c_block_read, PMSMBus),
+ VMSTATE_BOOL(start_transaction_on_status_read, PMSMBus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+void pm_smbus_init(DeviceState *parent, PMSMBus *smb, bool force_aux_blk)
+{
+ smb->op_done = true;
+ smb->reset = pm_smbus_reset;
+ smb->smbus = i2c_init_bus(parent, "i2c");
+ if (force_aux_blk) {
+ smb->smb_auxctl |= AUX_BLK;
+ }
+ memory_region_init_io(&smb->io, OBJECT(parent), &pm_smbus_ops, smb,
+ "pm-smbus", 64);
+}
diff --git a/hw/i2c/pmbus_device.c b/hw/i2c/pmbus_device.c
new file mode 100644
index 000000000..24f8f522d
--- /dev/null
+++ b/hw/i2c/pmbus_device.c
@@ -0,0 +1,1612 @@
+/*
+ * PMBus wrapper over SMBus
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include <math.h>
+#include <string.h>
+#include "hw/i2c/pmbus_device.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+
+uint16_t pmbus_data2direct_mode(PMBusCoefficients c, uint32_t value)
+{
+ /* R is usually negative to fit large readings into 16 bits */
+ uint16_t y = (c.m * value + c.b) * pow(10, c.R);
+ return y;
+}
+
+uint32_t pmbus_direct_mode2data(PMBusCoefficients c, uint16_t value)
+{
+ /* X = (Y * 10^-R - b) / m */
+ uint32_t x = (value / pow(10, c.R) - c.b) / c.m;
+ return x;
+}
+
+void pmbus_send(PMBusDevice *pmdev, const uint8_t *data, uint16_t len)
+{
+ if (pmdev->out_buf_len + len > SMBUS_DATA_MAX_LEN) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMBus device tried to send too much data");
+ len = 0;
+ }
+
+ for (int i = len - 1; i >= 0; i--) {
+ pmdev->out_buf[i + pmdev->out_buf_len] = data[len - i - 1];
+ }
+ pmdev->out_buf_len += len;
+}
+
+/* Internal only, convert unsigned ints to the little endian bus */
+static void pmbus_send_uint(PMBusDevice *pmdev, uint64_t data, uint8_t size)
+{
+ uint8_t bytes[8];
+ g_assert(size <= 8);
+
+ for (int i = 0; i < size; i++) {
+ bytes[i] = data & 0xFF;
+ data = data >> 8;
+ }
+ pmbus_send(pmdev, bytes, size);
+}
+
+void pmbus_send8(PMBusDevice *pmdev, uint8_t data)
+{
+ pmbus_send_uint(pmdev, data, 1);
+}
+
+void pmbus_send16(PMBusDevice *pmdev, uint16_t data)
+{
+ pmbus_send_uint(pmdev, data, 2);
+}
+
+void pmbus_send32(PMBusDevice *pmdev, uint32_t data)
+{
+ pmbus_send_uint(pmdev, data, 4);
+}
+
+void pmbus_send64(PMBusDevice *pmdev, uint64_t data)
+{
+ pmbus_send_uint(pmdev, data, 8);
+}
+
+void pmbus_send_string(PMBusDevice *pmdev, const char *data)
+{
+ size_t len = strlen(data);
+ g_assert(len > 0);
+ g_assert(len + pmdev->out_buf_len < SMBUS_DATA_MAX_LEN);
+ pmdev->out_buf[len + pmdev->out_buf_len] = len;
+
+ for (int i = len - 1; i >= 0; i--) {
+ pmdev->out_buf[i + pmdev->out_buf_len] = data[len - 1 - i];
+ }
+ pmdev->out_buf_len += len + 1;
+}
+
+
+static uint64_t pmbus_receive_uint(const uint8_t *buf, uint8_t len)
+{
+ uint64_t ret = 0;
+
+ /* Exclude command code from return value */
+ buf++;
+ len--;
+
+ for (int i = len - 1; i >= 0; i--) {
+ ret = ret << 8 | buf[i];
+ }
+ return ret;
+}
+
+uint8_t pmbus_receive8(PMBusDevice *pmdev)
+{
+ if (pmdev->in_buf_len - 1 != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: length mismatch. Expected 1 byte, got %d bytes\n",
+ __func__, pmdev->in_buf_len - 1);
+ }
+ return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
+}
+
+uint16_t pmbus_receive16(PMBusDevice *pmdev)
+{
+ if (pmdev->in_buf_len - 1 != 2) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: length mismatch. Expected 2 bytes, got %d bytes\n",
+ __func__, pmdev->in_buf_len - 1);
+ }
+ return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
+}
+
+uint32_t pmbus_receive32(PMBusDevice *pmdev)
+{
+ if (pmdev->in_buf_len - 1 != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: length mismatch. Expected 4 bytes, got %d bytes\n",
+ __func__, pmdev->in_buf_len - 1);
+ }
+ return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
+}
+
+uint64_t pmbus_receive64(PMBusDevice *pmdev)
+{
+ if (pmdev->in_buf_len - 1 != 8) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: length mismatch. Expected 8 bytes, got %d bytes\n",
+ __func__, pmdev->in_buf_len - 1);
+ }
+ return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
+}
+
+static uint8_t pmbus_out_buf_pop(PMBusDevice *pmdev)
+{
+ if (pmdev->out_buf_len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: tried to read from empty buffer",
+ __func__);
+ return 0xFF;
+ }
+ uint8_t data = pmdev->out_buf[pmdev->out_buf_len - 1];
+ pmdev->out_buf_len--;
+ return data;
+}
+
+static void pmbus_quick_cmd(SMBusDevice *smd, uint8_t read)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(smd);
+ PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
+
+ if (pmdc->quick_cmd) {
+ pmdc->quick_cmd(pmdev, read);
+ }
+}
+
+static void pmbus_pages_alloc(PMBusDevice *pmdev)
+{
+ /* some PMBus devices don't use the PAGE command, so they get 1 page */
+ PMBusDeviceClass *k = PMBUS_DEVICE_GET_CLASS(pmdev);
+ if (k->device_num_pages == 0) {
+ k->device_num_pages = 1;
+ }
+ pmdev->num_pages = k->device_num_pages;
+ pmdev->pages = g_new0(PMBusPage, k->device_num_pages);
+}
+
+void pmbus_check_limits(PMBusDevice *pmdev)
+{
+ for (int i = 0; i < pmdev->num_pages; i++) {
+ if ((pmdev->pages[i].operation & PB_OP_ON) == 0) {
+ continue; /* don't check powered off devices */
+ }
+
+ if (pmdev->pages[i].read_vout > pmdev->pages[i].vout_ov_fault_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_VOUT;
+ pmdev->pages[i].status_vout |= PB_STATUS_VOUT_OV_FAULT;
+ }
+
+ if (pmdev->pages[i].read_vout > pmdev->pages[i].vout_ov_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_VOUT;
+ pmdev->pages[i].status_vout |= PB_STATUS_VOUT_OV_WARN;
+ }
+
+ if (pmdev->pages[i].read_vout < pmdev->pages[i].vout_uv_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_VOUT;
+ pmdev->pages[i].status_vout |= PB_STATUS_VOUT_UV_WARN;
+ }
+
+ if (pmdev->pages[i].read_vout < pmdev->pages[i].vout_uv_fault_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_VOUT;
+ pmdev->pages[i].status_vout |= PB_STATUS_VOUT_UV_FAULT;
+ }
+
+ if (pmdev->pages[i].read_vin > pmdev->pages[i].vin_ov_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_INPUT;
+ pmdev->pages[i].status_input |= PB_STATUS_INPUT_VIN_OV_WARN;
+ }
+
+ if (pmdev->pages[i].read_vin < pmdev->pages[i].vin_uv_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_INPUT;
+ pmdev->pages[i].status_input |= PB_STATUS_INPUT_VIN_UV_WARN;
+ }
+
+ if (pmdev->pages[i].read_iout > pmdev->pages[i].iout_oc_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_IOUT_POUT;
+ pmdev->pages[i].status_iout |= PB_STATUS_IOUT_OC_WARN;
+ }
+
+ if (pmdev->pages[i].read_iout > pmdev->pages[i].iout_oc_fault_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_IOUT_POUT;
+ pmdev->pages[i].status_iout |= PB_STATUS_IOUT_OC_FAULT;
+ }
+
+ if (pmdev->pages[i].read_pin > pmdev->pages[i].pin_op_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_INPUT;
+ pmdev->pages[i].status_input |= PB_STATUS_INPUT_PIN_OP_WARN;
+ }
+
+ if (pmdev->pages[i].read_temperature_1
+ > pmdev->pages[i].ot_fault_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_TEMPERATURE;
+ pmdev->pages[i].status_temperature |= PB_STATUS_OT_FAULT;
+ }
+
+ if (pmdev->pages[i].read_temperature_1
+ > pmdev->pages[i].ot_warn_limit) {
+ pmdev->pages[i].status_word |= PB_STATUS_TEMPERATURE;
+ pmdev->pages[i].status_temperature |= PB_STATUS_OT_WARN;
+ }
+ }
+}
+
+static uint8_t pmbus_receive_byte(SMBusDevice *smd)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(smd);
+ PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
+ uint8_t ret = 0xFF;
+ uint8_t index = pmdev->page;
+
+ if (pmdev->out_buf_len != 0) {
+ ret = pmbus_out_buf_pop(pmdev);
+ return ret;
+ }
+
+ switch (pmdev->code) {
+ case PMBUS_PAGE:
+ pmbus_send8(pmdev, pmdev->page);
+ break;
+
+ case PMBUS_OPERATION: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].operation);
+ break;
+
+ case PMBUS_ON_OFF_CONFIG: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].on_off_config);
+ break;
+
+ case PMBUS_PHASE: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].phase);
+ break;
+
+ case PMBUS_WRITE_PROTECT: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].write_protect);
+ break;
+
+ case PMBUS_CAPABILITY:
+ pmbus_send8(pmdev, pmdev->capability);
+ break;
+
+ case PMBUS_VOUT_MODE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MODE) {
+ pmbus_send8(pmdev, pmdev->pages[index].vout_mode);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_COMMAND: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_command);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_TRIM: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_trim);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_CAL_OFFSET: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_cal_offset);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_MAX: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_MARGIN_HIGH: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_margin_high);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_MARGIN_LOW: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_margin_low);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_TRANSITION_RATE: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_transition_rate);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_DROOP: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_droop);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_SCALE_LOOP: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_scale_loop);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_SCALE_MONITOR: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_scale_monitor);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ /* TODO: implement coefficients support */
+
+ case PMBUS_POUT_MAX: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].pout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_ON: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_on);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_OFF: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_off);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_CAL_GAIN: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT_GAIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_cal_gain);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_ov_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].vout_ov_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_ov_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_uv_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].vout_uv_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].vout_uv_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_oc_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].iout_oc_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_LV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_oc_lv_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_LV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].iout_oc_lv_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_oc_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_UC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].iout_uc_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IOUT_UC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].iout_uc_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_OT_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].ot_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_OT_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send8(pmdev, pmdev->pages[index].ot_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_OT_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].ot_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_UT_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].ut_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_UT_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].ut_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_UT_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send8(pmdev, pmdev->pages[index].ut_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_ov_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send8(pmdev, pmdev->pages[index].vin_ov_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_ov_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_uv_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].vin_uv_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send8(pmdev, pmdev->pages[index].vin_uv_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].iin_oc_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmbus_send8(pmdev, pmdev->pages[index].iin_oc_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].iin_oc_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].pout_op_fault_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].pout_op_fault_response);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].pout_op_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_PIN_OP_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].pin_op_warn_limit);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_BYTE: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].status_word & 0xFF);
+ break;
+
+ case PMBUS_STATUS_WORD: /* R/W word */
+ pmbus_send16(pmdev, pmdev->pages[index].status_word);
+ break;
+
+ case PMBUS_STATUS_VOUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].status_vout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_IOUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send8(pmdev, pmdev->pages[index].status_iout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_INPUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN ||
+ pmdev->pages[index].page_flags & PB_HAS_IIN ||
+ pmdev->pages[index].page_flags & PB_HAS_PIN) {
+ pmbus_send8(pmdev, pmdev->pages[index].status_input);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_TEMPERATURE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send8(pmdev, pmdev->pages[index].status_temperature);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_STATUS_CML: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].status_cml);
+ break;
+
+ case PMBUS_STATUS_OTHER: /* R/W byte */
+ pmbus_send8(pmdev, pmdev->pages[index].status_other);
+ break;
+
+ case PMBUS_READ_EIN: /* Read-Only block 5 bytes */
+ if (pmdev->pages[index].page_flags & PB_HAS_EIN) {
+ pmbus_send(pmdev, pmdev->pages[index].read_ein, 5);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_EOUT: /* Read-Only block 5 bytes */
+ if (pmdev->pages[index].page_flags & PB_HAS_EOUT) {
+ pmbus_send(pmdev, pmdev->pages[index].read_eout, 5);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_VIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_vin);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_IIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_iin);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_VOUT: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_vout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_IOUT: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_iout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_TEMPERATURE_1: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_temperature_1);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_TEMPERATURE_2: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP2) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_temperature_2);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_TEMPERATURE_3: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP3) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_temperature_3);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_POUT: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_pout);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_READ_PIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
+ pmbus_send16(pmdev, pmdev->pages[index].read_pin);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_REVISION: /* Read-Only byte */
+ pmbus_send8(pmdev, pmdev->pages[index].revision);
+ break;
+
+ case PMBUS_MFR_ID: /* R/W block */
+ if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
+ pmbus_send_string(pmdev, pmdev->pages[index].mfr_id);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_MODEL: /* R/W block */
+ if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
+ pmbus_send_string(pmdev, pmdev->pages[index].mfr_model);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_REVISION: /* R/W block */
+ if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
+ pmbus_send_string(pmdev, pmdev->pages[index].mfr_revision);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_LOCATION: /* R/W block */
+ if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
+ pmbus_send_string(pmdev, pmdev->pages[index].mfr_location);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_VIN_MIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_vin_min);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_VIN_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_vin_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_IIN_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_iin_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_PIN_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_PIN_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_pin_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_VOUT_MIN: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_vout_min);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_VOUT_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_vout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_IOUT_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_iout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_POUT_MAX: /* Read-Only word */
+ if (pmdev->pages[index].page_flags & PB_HAS_POUT_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_pout_max);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_MAX_TEMP_1: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_1);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_MAX_TEMP_2: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_2);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_MFR_MAX_TEMP_3: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
+ pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_3);
+ } else {
+ goto passthough;
+ }
+ break;
+
+ case PMBUS_CLEAR_FAULTS: /* Send Byte */
+ case PMBUS_PAGE_PLUS_WRITE: /* Block Write-only */
+ case PMBUS_STORE_DEFAULT_ALL: /* Send Byte */
+ case PMBUS_RESTORE_DEFAULT_ALL: /* Send Byte */
+ case PMBUS_STORE_DEFAULT_CODE: /* Write-only Byte */
+ case PMBUS_RESTORE_DEFAULT_CODE: /* Write-only Byte */
+ case PMBUS_STORE_USER_ALL: /* Send Byte */
+ case PMBUS_RESTORE_USER_ALL: /* Send Byte */
+ case PMBUS_STORE_USER_CODE: /* Write-only Byte */
+ case PMBUS_RESTORE_USER_CODE: /* Write-only Byte */
+ case PMBUS_QUERY: /* Write-Only */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from write only register 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+
+passthough:
+ default:
+ /* Pass through read request if not handled */
+ if (pmdc->receive_byte) {
+ ret = pmdc->receive_byte(pmdev);
+ }
+ break;
+ }
+
+ if (pmdev->out_buf_len != 0) {
+ ret = pmbus_out_buf_pop(pmdev);
+ return ret;
+ }
+
+ return ret;
+}
+
+/*
+ * PMBus clear faults command applies to all status registers, existing faults
+ * should separately get re-asserted.
+ */
+static void pmbus_clear_faults(PMBusDevice *pmdev)
+{
+ for (uint8_t i = 0; i < pmdev->num_pages; i++) {
+ pmdev->pages[i].status_word = 0;
+ pmdev->pages[i].status_vout = 0;
+ pmdev->pages[i].status_iout = 0;
+ pmdev->pages[i].status_input = 0;
+ pmdev->pages[i].status_temperature = 0;
+ pmdev->pages[i].status_cml = 0;
+ pmdev->pages[i].status_other = 0;
+ pmdev->pages[i].status_mfr_specific = 0;
+ pmdev->pages[i].status_fans_1_2 = 0;
+ pmdev->pages[i].status_fans_3_4 = 0;
+ }
+
+}
+
+/*
+ * PMBus operation is used to turn On and Off PSUs
+ * Therefore, default value for the Operation should be PB_OP_ON or 0x80
+ */
+static void pmbus_operation(PMBusDevice *pmdev)
+{
+ uint8_t index = pmdev->page;
+ if ((pmdev->pages[index].operation & PB_OP_ON) == 0) {
+ pmdev->pages[index].read_vout = 0;
+ pmdev->pages[index].read_iout = 0;
+ pmdev->pages[index].read_pout = 0;
+ return;
+ }
+
+ if (pmdev->pages[index].operation & (PB_OP_ON | PB_OP_MARGIN_HIGH)) {
+ pmdev->pages[index].read_vout = pmdev->pages[index].vout_margin_high;
+ }
+
+ if (pmdev->pages[index].operation & (PB_OP_ON | PB_OP_MARGIN_LOW)) {
+ pmdev->pages[index].read_vout = pmdev->pages[index].vout_margin_low;
+ }
+ pmbus_check_limits(pmdev);
+}
+
+static int pmbus_write_data(SMBusDevice *smd, uint8_t *buf, uint8_t len)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(smd);
+ PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
+ int ret = 0;
+ uint8_t index;
+
+ if (len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+ return -1;
+ }
+
+ if (!pmdev->pages) { /* allocate memory for pages on first use */
+ pmbus_pages_alloc(pmdev);
+ }
+
+ pmdev->in_buf_len = len;
+ pmdev->in_buf = buf;
+
+ pmdev->code = buf[0]; /* PMBus command code */
+ if (len == 1) { /* Single length writes are command codes only */
+ return 0;
+ }
+
+ if (pmdev->code == PMBUS_PAGE) {
+ pmdev->page = pmbus_receive8(pmdev);
+ return 0;
+ }
+ /* loop through all the pages when 0xFF is received */
+ if (pmdev->page == PB_ALL_PAGES) {
+ for (int i = 0; i < pmdev->num_pages; i++) {
+ pmdev->page = i;
+ pmbus_write_data(smd, buf, len);
+ }
+ pmdev->page = PB_ALL_PAGES;
+ return 0;
+ }
+
+ index = pmdev->page;
+
+ switch (pmdev->code) {
+ case PMBUS_OPERATION: /* R/W byte */
+ pmdev->pages[index].operation = pmbus_receive8(pmdev);
+ pmbus_operation(pmdev);
+ break;
+
+ case PMBUS_ON_OFF_CONFIG: /* R/W byte */
+ pmdev->pages[index].on_off_config = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_CLEAR_FAULTS: /* Send Byte */
+ pmbus_clear_faults(pmdev);
+ break;
+
+ case PMBUS_PHASE: /* R/W byte */
+ pmdev->pages[index].phase = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_PAGE_PLUS_WRITE: /* Block Write-only */
+ case PMBUS_WRITE_PROTECT: /* R/W byte */
+ pmdev->pages[index].write_protect = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_VOUT_MODE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MODE) {
+ pmdev->pages[index].vout_mode = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_COMMAND: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_command = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_TRIM: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_trim = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_CAL_OFFSET: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_cal_offset = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_MAX: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_max = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_MARGIN_HIGH: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
+ pmdev->pages[index].vout_margin_high = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_MARGIN_LOW: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
+ pmdev->pages[index].vout_margin_low = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_TRANSITION_RATE: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_transition_rate = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_DROOP: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_droop = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_SCALE_LOOP: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_scale_loop = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_SCALE_MONITOR: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_scale_monitor = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_POUT_MAX: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].pout_max = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_ON: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_on = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_OFF: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_off = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_CAL_GAIN: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT_GAIN) {
+ pmdev->pages[index].iout_cal_gain = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_ov_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_ov_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_OV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_ov_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_uv_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_uv_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VOUT_UV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].vout_uv_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_LV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_lv_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_LV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_lv_fault_response
+ = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_OC_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_oc_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_UC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_uc_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IOUT_UC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].iout_uc_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_OT_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ot_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_OT_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ot_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_OT_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ot_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_UT_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ut_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_UT_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ut_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_UT_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].ut_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_ov_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_ov_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_OV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_ov_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_uv_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_uv_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_VIN_UV_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
+ pmdev->pages[index].vin_uv_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmdev->pages[index].iin_oc_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmdev->pages[index].iin_oc_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_IIN_OC_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
+ pmdev->pages[index].iin_oc_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_FAULT_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].pout_op_fault_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_FAULT_RESPONSE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].pout_op_fault_response = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_POUT_OP_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].pout_op_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_PIN_OP_WARN_LIMIT: /* R/W word */
+ if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
+ pmdev->pages[index].pin_op_warn_limit = pmbus_receive16(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_STATUS_BYTE: /* R/W byte */
+ pmdev->pages[index].status_word = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_STATUS_WORD: /* R/W word */
+ pmdev->pages[index].status_word = pmbus_receive16(pmdev);
+ break;
+
+ case PMBUS_STATUS_VOUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
+ pmdev->pages[index].status_vout = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_STATUS_IOUT: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
+ pmdev->pages[index].status_iout = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_STATUS_INPUT: /* R/W byte */
+ pmdev->pages[index].status_input = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_STATUS_TEMPERATURE: /* R/W byte */
+ if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
+ pmdev->pages[index].status_temperature = pmbus_receive8(pmdev);
+ } else {
+ goto passthrough;
+ }
+ break;
+
+ case PMBUS_STATUS_CML: /* R/W byte */
+ pmdev->pages[index].status_cml = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_STATUS_OTHER: /* R/W byte */
+ pmdev->pages[index].status_other = pmbus_receive8(pmdev);
+ break;
+
+ case PMBUS_PAGE_PLUS_READ: /* Block Read-only */
+ case PMBUS_CAPABILITY: /* Read-Only byte */
+ case PMBUS_COEFFICIENTS: /* Read-only block 5 bytes */
+ case PMBUS_READ_EIN: /* Read-Only block 5 bytes */
+ case PMBUS_READ_EOUT: /* Read-Only block 5 bytes */
+ case PMBUS_READ_VIN: /* Read-Only word */
+ case PMBUS_READ_IIN: /* Read-Only word */
+ case PMBUS_READ_VCAP: /* Read-Only word */
+ case PMBUS_READ_VOUT: /* Read-Only word */
+ case PMBUS_READ_IOUT: /* Read-Only word */
+ case PMBUS_READ_TEMPERATURE_1: /* Read-Only word */
+ case PMBUS_READ_TEMPERATURE_2: /* Read-Only word */
+ case PMBUS_READ_TEMPERATURE_3: /* Read-Only word */
+ case PMBUS_READ_FAN_SPEED_1: /* Read-Only word */
+ case PMBUS_READ_FAN_SPEED_2: /* Read-Only word */
+ case PMBUS_READ_FAN_SPEED_3: /* Read-Only word */
+ case PMBUS_READ_FAN_SPEED_4: /* Read-Only word */
+ case PMBUS_READ_DUTY_CYCLE: /* Read-Only word */
+ case PMBUS_READ_FREQUENCY: /* Read-Only word */
+ case PMBUS_READ_POUT: /* Read-Only word */
+ case PMBUS_READ_PIN: /* Read-Only word */
+ case PMBUS_REVISION: /* Read-Only byte */
+ case PMBUS_APP_PROFILE_SUPPORT: /* Read-Only block-read */
+ case PMBUS_MFR_VIN_MIN: /* Read-Only word */
+ case PMBUS_MFR_VIN_MAX: /* Read-Only word */
+ case PMBUS_MFR_IIN_MAX: /* Read-Only word */
+ case PMBUS_MFR_PIN_MAX: /* Read-Only word */
+ case PMBUS_MFR_VOUT_MIN: /* Read-Only word */
+ case PMBUS_MFR_VOUT_MAX: /* Read-Only word */
+ case PMBUS_MFR_IOUT_MAX: /* Read-Only word */
+ case PMBUS_MFR_POUT_MAX: /* Read-Only word */
+ case PMBUS_MFR_TAMBIENT_MAX: /* Read-Only word */
+ case PMBUS_MFR_TAMBIENT_MIN: /* Read-Only word */
+ case PMBUS_MFR_EFFICIENCY_LL: /* Read-Only block 14 bytes */
+ case PMBUS_MFR_EFFICIENCY_HL: /* Read-Only block 14 bytes */
+ case PMBUS_MFR_PIN_ACCURACY: /* Read-Only byte */
+ case PMBUS_IC_DEVICE_ID: /* Read-Only block-read */
+ case PMBUS_IC_DEVICE_REV: /* Read-Only block-read */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to read-only register 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+
+passthrough:
+ /* Unimplimented registers get passed to the device */
+ default:
+ if (pmdc->write_data) {
+ ret = pmdc->write_data(pmdev, buf, len);
+ }
+ break;
+ }
+ pmbus_check_limits(pmdev);
+ pmdev->in_buf_len = 0;
+ return ret;
+}
+
+int pmbus_page_config(PMBusDevice *pmdev, uint8_t index, uint64_t flags)
+{
+ if (!pmdev->pages) { /* allocate memory for pages on first use */
+ pmbus_pages_alloc(pmdev);
+ }
+
+ /* The 0xFF page is special for commands applying to all pages */
+ if (index == PB_ALL_PAGES) {
+ for (int i = 0; i < pmdev->num_pages; i++) {
+ pmdev->pages[i].page_flags = flags;
+ }
+ return 0;
+ }
+
+ if (index > pmdev->num_pages - 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: index %u is out of range\n",
+ __func__, index);
+ return -1;
+ }
+
+ pmdev->pages[index].page_flags = flags;
+
+ return 0;
+}
+
+/* TODO: include pmbus page info in vmstate */
+const VMStateDescription vmstate_pmbus_device = {
+ .name = TYPE_PMBUS_DEVICE,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_SMBUS_DEVICE(smb, PMBusDevice),
+ VMSTATE_UINT8(num_pages, PMBusDevice),
+ VMSTATE_UINT8(code, PMBusDevice),
+ VMSTATE_UINT8(page, PMBusDevice),
+ VMSTATE_UINT8(capability, PMBusDevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pmbus_device_finalize(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ g_free(pmdev->pages);
+}
+
+static void pmbus_device_class_init(ObjectClass *klass, void *data)
+{
+ SMBusDeviceClass *k = SMBUS_DEVICE_CLASS(klass);
+
+ k->quick_cmd = pmbus_quick_cmd;
+ k->write_data = pmbus_write_data;
+ k->receive_byte = pmbus_receive_byte;
+}
+
+static const TypeInfo pmbus_device_type_info = {
+ .name = TYPE_PMBUS_DEVICE,
+ .parent = TYPE_SMBUS_DEVICE,
+ .instance_size = sizeof(PMBusDevice),
+ .instance_finalize = pmbus_device_finalize,
+ .abstract = true,
+ .class_size = sizeof(PMBusDeviceClass),
+ .class_init = pmbus_device_class_init,
+};
+
+static void pmbus_device_register_types(void)
+{
+ type_register_static(&pmbus_device_type_info);
+}
+
+type_init(pmbus_device_register_types)
diff --git a/hw/i2c/ppc4xx_i2c.c b/hw/i2c/ppc4xx_i2c.c
new file mode 100644
index 000000000..75d50f151
--- /dev/null
+++ b/hw/i2c/ppc4xx_i2c.c
@@ -0,0 +1,377 @@
+/*
+ * PPC4xx I2C controller emulation
+ *
+ * Documentation: PPC405GP User's Manual, Chapter 22. IIC Bus Interface
+ *
+ * Copyright (c) 2007 Jocelyn Mayer
+ * Copyright (c) 2012 François Revol
+ * Copyright (c) 2016-2018 BALATON Zoltan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/i2c/ppc4xx_i2c.h"
+#include "hw/irq.h"
+
+#define PPC4xx_I2C_MEM_SIZE 18
+
+enum {
+ IIC_MDBUF = 0,
+ /* IIC_SDBUF = 2, */
+ IIC_LMADR = 4,
+ IIC_HMADR,
+ IIC_CNTL,
+ IIC_MDCNTL,
+ IIC_STS,
+ IIC_EXTSTS,
+ IIC_LSADR,
+ IIC_HSADR,
+ IIC_CLKDIV,
+ IIC_INTRMSK,
+ IIC_XFRCNT,
+ IIC_XTCNTLSS,
+ IIC_DIRECTCNTL
+ /* IIC_INTR */
+};
+
+#define IIC_CNTL_PT (1 << 0)
+#define IIC_CNTL_READ (1 << 1)
+#define IIC_CNTL_CHT (1 << 2)
+#define IIC_CNTL_RPST (1 << 3)
+#define IIC_CNTL_AMD (1 << 6)
+#define IIC_CNTL_HMT (1 << 7)
+
+#define IIC_MDCNTL_EINT (1 << 2)
+#define IIC_MDCNTL_ESM (1 << 3)
+#define IIC_MDCNTL_FMDB (1 << 6)
+
+#define IIC_STS_PT (1 << 0)
+#define IIC_STS_IRQA (1 << 1)
+#define IIC_STS_ERR (1 << 2)
+#define IIC_STS_MDBF (1 << 4)
+#define IIC_STS_MDBS (1 << 5)
+
+#define IIC_EXTSTS_XFRA (1 << 0)
+#define IIC_EXTSTS_BCS_FREE (4 << 4)
+#define IIC_EXTSTS_BCS_BUSY (5 << 4)
+
+#define IIC_INTRMSK_EIMTC (1 << 0)
+#define IIC_INTRMSK_EITA (1 << 1)
+#define IIC_INTRMSK_EIIC (1 << 2)
+#define IIC_INTRMSK_EIHE (1 << 3)
+
+#define IIC_XTCNTLSS_SRST (1 << 0)
+
+#define IIC_DIRECTCNTL_SDAC (1 << 3)
+#define IIC_DIRECTCNTL_SCLC (1 << 2)
+#define IIC_DIRECTCNTL_MSDA (1 << 1)
+#define IIC_DIRECTCNTL_MSCL (1 << 0)
+
+static void ppc4xx_i2c_reset(DeviceState *s)
+{
+ PPC4xxI2CState *i2c = PPC4xx_I2C(s);
+
+ i2c->mdidx = -1;
+ memset(i2c->mdata, 0, ARRAY_SIZE(i2c->mdata));
+ /* [hl][ms]addr are not affected by reset */
+ i2c->cntl = 0;
+ i2c->mdcntl = 0;
+ i2c->sts = 0;
+ i2c->extsts = IIC_EXTSTS_BCS_FREE;
+ i2c->clkdiv = 0;
+ i2c->intrmsk = 0;
+ i2c->xfrcnt = 0;
+ i2c->xtcntlss = 0;
+ i2c->directcntl = 0xf; /* all non-reserved bits set */
+}
+
+static uint64_t ppc4xx_i2c_readb(void *opaque, hwaddr addr, unsigned int size)
+{
+ PPC4xxI2CState *i2c = PPC4xx_I2C(opaque);
+ uint64_t ret;
+ int i;
+
+ switch (addr) {
+ case IIC_MDBUF:
+ if (i2c->mdidx < 0) {
+ ret = 0xff;
+ break;
+ }
+ ret = i2c->mdata[0];
+ if (i2c->mdidx == 3) {
+ i2c->sts &= ~IIC_STS_MDBF;
+ } else if (i2c->mdidx == 0) {
+ i2c->sts &= ~IIC_STS_MDBS;
+ }
+ for (i = 0; i < i2c->mdidx; i++) {
+ i2c->mdata[i] = i2c->mdata[i + 1];
+ }
+ if (i2c->mdidx >= 0) {
+ i2c->mdidx--;
+ }
+ break;
+ case IIC_LMADR:
+ ret = i2c->lmadr;
+ break;
+ case IIC_HMADR:
+ ret = i2c->hmadr;
+ break;
+ case IIC_CNTL:
+ ret = i2c->cntl;
+ break;
+ case IIC_MDCNTL:
+ ret = i2c->mdcntl;
+ break;
+ case IIC_STS:
+ ret = i2c->sts;
+ break;
+ case IIC_EXTSTS:
+ ret = i2c_bus_busy(i2c->bus) ?
+ IIC_EXTSTS_BCS_BUSY : IIC_EXTSTS_BCS_FREE;
+ break;
+ case IIC_LSADR:
+ ret = i2c->lsadr;
+ break;
+ case IIC_HSADR:
+ ret = i2c->hsadr;
+ break;
+ case IIC_CLKDIV:
+ ret = i2c->clkdiv;
+ break;
+ case IIC_INTRMSK:
+ ret = i2c->intrmsk;
+ break;
+ case IIC_XFRCNT:
+ ret = i2c->xfrcnt;
+ break;
+ case IIC_XTCNTLSS:
+ ret = i2c->xtcntlss;
+ break;
+ case IIC_DIRECTCNTL:
+ ret = i2c->directcntl;
+ break;
+ default:
+ if (addr < PPC4xx_I2C_MEM_SIZE) {
+ qemu_log_mask(LOG_UNIMP, "%s: Unimplemented register 0x%"
+ HWADDR_PRIx "\n", __func__, addr);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad address 0x%"
+ HWADDR_PRIx "\n", __func__, addr);
+ }
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+static void ppc4xx_i2c_writeb(void *opaque, hwaddr addr, uint64_t value,
+ unsigned int size)
+{
+ PPC4xxI2CState *i2c = opaque;
+
+ switch (addr) {
+ case IIC_MDBUF:
+ if (i2c->mdidx >= 3) {
+ break;
+ }
+ i2c->mdata[++i2c->mdidx] = value;
+ if (i2c->mdidx == 3) {
+ i2c->sts |= IIC_STS_MDBF;
+ } else if (i2c->mdidx == 0) {
+ i2c->sts |= IIC_STS_MDBS;
+ }
+ break;
+ case IIC_LMADR:
+ i2c->lmadr = value;
+ break;
+ case IIC_HMADR:
+ i2c->hmadr = value;
+ break;
+ case IIC_CNTL:
+ i2c->cntl = value & ~IIC_CNTL_PT;
+ if (value & IIC_CNTL_AMD) {
+ qemu_log_mask(LOG_UNIMP, "%s: only 7 bit addresses supported\n",
+ __func__);
+ }
+ if (value & IIC_CNTL_HMT && i2c_bus_busy(i2c->bus)) {
+ i2c_end_transfer(i2c->bus);
+ if (i2c->mdcntl & IIC_MDCNTL_EINT &&
+ i2c->intrmsk & IIC_INTRMSK_EIHE) {
+ i2c->sts |= IIC_STS_IRQA;
+ qemu_irq_raise(i2c->irq);
+ }
+ } else if (value & IIC_CNTL_PT) {
+ int recv = (value & IIC_CNTL_READ) >> 1;
+ int tct = value >> 4 & 3;
+ int i;
+
+ if (recv && (i2c->lmadr >> 1) >= 0x50 && (i2c->lmadr >> 1) < 0x58) {
+ /* smbus emulation does not like multi byte reads w/o restart */
+ value |= IIC_CNTL_RPST;
+ }
+
+ for (i = 0; i <= tct; i++) {
+ if (!i2c_bus_busy(i2c->bus)) {
+ i2c->extsts = IIC_EXTSTS_BCS_FREE;
+ if (i2c_start_transfer(i2c->bus, i2c->lmadr >> 1, recv)) {
+ i2c->sts |= IIC_STS_ERR;
+ i2c->extsts |= IIC_EXTSTS_XFRA;
+ break;
+ } else {
+ i2c->sts &= ~IIC_STS_ERR;
+ }
+ }
+ if (!(i2c->sts & IIC_STS_ERR)) {
+ if (recv) {
+ i2c->mdata[i] = i2c_recv(i2c->bus);
+ } else if (i2c_send(i2c->bus, i2c->mdata[i]) < 0) {
+ i2c->sts |= IIC_STS_ERR;
+ i2c->extsts |= IIC_EXTSTS_XFRA;
+ break;
+ }
+ }
+ if (value & IIC_CNTL_RPST || !(value & IIC_CNTL_CHT)) {
+ i2c_end_transfer(i2c->bus);
+ }
+ }
+ i2c->xfrcnt = i;
+ i2c->mdidx = i - 1;
+ if (recv && i2c->mdidx >= 0) {
+ i2c->sts |= IIC_STS_MDBS;
+ }
+ if (recv && i2c->mdidx == 3) {
+ i2c->sts |= IIC_STS_MDBF;
+ }
+ if (i && i2c->mdcntl & IIC_MDCNTL_EINT &&
+ i2c->intrmsk & IIC_INTRMSK_EIMTC) {
+ i2c->sts |= IIC_STS_IRQA;
+ qemu_irq_raise(i2c->irq);
+ }
+ }
+ break;
+ case IIC_MDCNTL:
+ i2c->mdcntl = value & 0x3d;
+ if (value & IIC_MDCNTL_ESM) {
+ qemu_log_mask(LOG_UNIMP, "%s: slave mode not implemented\n",
+ __func__);
+ }
+ if (value & IIC_MDCNTL_FMDB) {
+ i2c->mdidx = -1;
+ memset(i2c->mdata, 0, ARRAY_SIZE(i2c->mdata));
+ i2c->sts &= ~(IIC_STS_MDBF | IIC_STS_MDBS);
+ }
+ break;
+ case IIC_STS:
+ i2c->sts &= ~(value & 0x0a);
+ if (value & IIC_STS_IRQA && i2c->mdcntl & IIC_MDCNTL_EINT) {
+ qemu_irq_lower(i2c->irq);
+ }
+ break;
+ case IIC_EXTSTS:
+ i2c->extsts &= ~(value & 0x8f);
+ break;
+ case IIC_LSADR:
+ i2c->lsadr = value;
+ break;
+ case IIC_HSADR:
+ i2c->hsadr = value;
+ break;
+ case IIC_CLKDIV:
+ i2c->clkdiv = value;
+ break;
+ case IIC_INTRMSK:
+ i2c->intrmsk = value;
+ break;
+ case IIC_XFRCNT:
+ i2c->xfrcnt = value & 0x77;
+ break;
+ case IIC_XTCNTLSS:
+ i2c->xtcntlss &= ~(value & 0xf0);
+ if (value & IIC_XTCNTLSS_SRST) {
+ /* Is it actually a full reset? U-Boot sets some regs before */
+ ppc4xx_i2c_reset(DEVICE(i2c));
+ break;
+ }
+ break;
+ case IIC_DIRECTCNTL:
+ i2c->directcntl = value & (IIC_DIRECTCNTL_SDAC & IIC_DIRECTCNTL_SCLC);
+ i2c->directcntl |= (value & IIC_DIRECTCNTL_SCLC ? 1 : 0);
+ bitbang_i2c_set(&i2c->bitbang, BITBANG_I2C_SCL,
+ i2c->directcntl & IIC_DIRECTCNTL_MSCL);
+ i2c->directcntl |= bitbang_i2c_set(&i2c->bitbang, BITBANG_I2C_SDA,
+ (value & IIC_DIRECTCNTL_SDAC) != 0) << 1;
+ break;
+ default:
+ if (addr < PPC4xx_I2C_MEM_SIZE) {
+ qemu_log_mask(LOG_UNIMP, "%s: Unimplemented register 0x%"
+ HWADDR_PRIx "\n", __func__, addr);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad address 0x%"
+ HWADDR_PRIx "\n", __func__, addr);
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps ppc4xx_i2c_ops = {
+ .read = ppc4xx_i2c_readb,
+ .write = ppc4xx_i2c_writeb,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ppc4xx_i2c_init(Object *o)
+{
+ PPC4xxI2CState *s = PPC4xx_I2C(o);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &ppc4xx_i2c_ops, s,
+ TYPE_PPC4xx_I2C, PPC4xx_I2C_MEM_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+ sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq);
+ s->bus = i2c_init_bus(DEVICE(s), "i2c");
+ bitbang_i2c_init(&s->bitbang, s->bus);
+}
+
+static void ppc4xx_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = ppc4xx_i2c_reset;
+}
+
+static const TypeInfo ppc4xx_i2c_type_info = {
+ .name = TYPE_PPC4xx_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PPC4xxI2CState),
+ .instance_init = ppc4xx_i2c_init,
+ .class_init = ppc4xx_i2c_class_init,
+};
+
+static void ppc4xx_i2c_register_types(void)
+{
+ type_register_static(&ppc4xx_i2c_type_info);
+}
+
+type_init(ppc4xx_i2c_register_types)
diff --git a/hw/i2c/smbus_eeprom.c b/hw/i2c/smbus_eeprom.c
new file mode 100644
index 000000000..12c5741f3
--- /dev/null
+++ b/hw/i2c/smbus_eeprom.c
@@ -0,0 +1,300 @@
+/*
+ * QEMU SMBus EEPROM device
+ *
+ * Copyright (c) 2007 Arastra, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "hw/boards.h"
+#include "hw/i2c/i2c.h"
+#include "hw/i2c/smbus_slave.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/i2c/smbus_eeprom.h"
+#include "qom/object.h"
+
+//#define DEBUG
+
+#define TYPE_SMBUS_EEPROM "smbus-eeprom"
+
+OBJECT_DECLARE_SIMPLE_TYPE(SMBusEEPROMDevice, SMBUS_EEPROM)
+
+#define SMBUS_EEPROM_SIZE 256
+
+struct SMBusEEPROMDevice {
+ SMBusDevice smbusdev;
+ uint8_t data[SMBUS_EEPROM_SIZE];
+ uint8_t *init_data;
+ uint8_t offset;
+ bool accessed;
+};
+
+static uint8_t eeprom_receive_byte(SMBusDevice *dev)
+{
+ SMBusEEPROMDevice *eeprom = SMBUS_EEPROM(dev);
+ uint8_t *data = eeprom->data;
+ uint8_t val = data[eeprom->offset++];
+
+ eeprom->accessed = true;
+#ifdef DEBUG
+ printf("eeprom_receive_byte: addr=0x%02x val=0x%02x\n",
+ dev->i2c.address, val);
+#endif
+ return val;
+}
+
+static int eeprom_write_data(SMBusDevice *dev, uint8_t *buf, uint8_t len)
+{
+ SMBusEEPROMDevice *eeprom = SMBUS_EEPROM(dev);
+ uint8_t *data = eeprom->data;
+
+ eeprom->accessed = true;
+#ifdef DEBUG
+ printf("eeprom_write_byte: addr=0x%02x cmd=0x%02x val=0x%02x\n",
+ dev->i2c.address, buf[0], buf[1]);
+#endif
+ /* len is guaranteed to be > 0 */
+ eeprom->offset = buf[0];
+ buf++;
+ len--;
+
+ for (; len > 0; len--) {
+ data[eeprom->offset] = *buf++;
+ eeprom->offset = (eeprom->offset + 1) % SMBUS_EEPROM_SIZE;
+ }
+
+ return 0;
+}
+
+static bool smbus_eeprom_vmstate_needed(void *opaque)
+{
+ MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine());
+ SMBusEEPROMDevice *eeprom = opaque;
+
+ return (eeprom->accessed || smbus_vmstate_needed(&eeprom->smbusdev)) &&
+ !mc->smbus_no_migration_support;
+}
+
+static const VMStateDescription vmstate_smbus_eeprom = {
+ .name = "smbus-eeprom",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = smbus_eeprom_vmstate_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_SMBUS_DEVICE(smbusdev, SMBusEEPROMDevice),
+ VMSTATE_UINT8_ARRAY(data, SMBusEEPROMDevice, SMBUS_EEPROM_SIZE),
+ VMSTATE_UINT8(offset, SMBusEEPROMDevice),
+ VMSTATE_BOOL(accessed, SMBusEEPROMDevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/*
+ * Reset the EEPROM contents to the initial state on a reset. This
+ * isn't really how an EEPROM works, of course, but the general
+ * principle of QEMU is to restore function on reset to what it would
+ * be if QEMU was stopped and started.
+ *
+ * The proper thing to do would be to have a backing blockdev to hold
+ * the contents and restore that on startup, and not do this on reset.
+ * But until that time, act as if we had been stopped and restarted.
+ */
+static void smbus_eeprom_reset(DeviceState *dev)
+{
+ SMBusEEPROMDevice *eeprom = SMBUS_EEPROM(dev);
+
+ memcpy(eeprom->data, eeprom->init_data, SMBUS_EEPROM_SIZE);
+ eeprom->offset = 0;
+}
+
+static void smbus_eeprom_realize(DeviceState *dev, Error **errp)
+{
+ SMBusEEPROMDevice *eeprom = SMBUS_EEPROM(dev);
+
+ smbus_eeprom_reset(dev);
+ if (eeprom->init_data == NULL) {
+ error_setg(errp, "init_data cannot be NULL");
+ }
+}
+
+static void smbus_eeprom_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SMBusDeviceClass *sc = SMBUS_DEVICE_CLASS(klass);
+
+ dc->realize = smbus_eeprom_realize;
+ dc->reset = smbus_eeprom_reset;
+ sc->receive_byte = eeprom_receive_byte;
+ sc->write_data = eeprom_write_data;
+ dc->vmsd = &vmstate_smbus_eeprom;
+ /* Reason: init_data */
+ dc->user_creatable = false;
+}
+
+static const TypeInfo smbus_eeprom_info = {
+ .name = TYPE_SMBUS_EEPROM,
+ .parent = TYPE_SMBUS_DEVICE,
+ .instance_size = sizeof(SMBusEEPROMDevice),
+ .class_init = smbus_eeprom_class_initfn,
+};
+
+static void smbus_eeprom_register_types(void)
+{
+ type_register_static(&smbus_eeprom_info);
+}
+
+type_init(smbus_eeprom_register_types)
+
+void smbus_eeprom_init_one(I2CBus *smbus, uint8_t address, uint8_t *eeprom_buf)
+{
+ DeviceState *dev;
+
+ dev = qdev_new(TYPE_SMBUS_EEPROM);
+ qdev_prop_set_uint8(dev, "address", address);
+ /* FIXME: use an array of byte or block backend property? */
+ SMBUS_EEPROM(dev)->init_data = eeprom_buf;
+ qdev_realize_and_unref(dev, (BusState *)smbus, &error_fatal);
+}
+
+void smbus_eeprom_init(I2CBus *smbus, int nb_eeprom,
+ const uint8_t *eeprom_spd, int eeprom_spd_size)
+{
+ int i;
+ /* XXX: make this persistent */
+
+ assert(nb_eeprom <= 8);
+ uint8_t *eeprom_buf = g_malloc0(8 * SMBUS_EEPROM_SIZE);
+ if (eeprom_spd_size > 0) {
+ memcpy(eeprom_buf, eeprom_spd, eeprom_spd_size);
+ }
+
+ for (i = 0; i < nb_eeprom; i++) {
+ smbus_eeprom_init_one(smbus, 0x50 + i,
+ eeprom_buf + (i * SMBUS_EEPROM_SIZE));
+ }
+}
+
+/* Generate SDRAM SPD EEPROM data describing a module of type and size */
+uint8_t *spd_data_generate(enum sdram_type type, ram_addr_t ram_size)
+{
+ uint8_t *spd;
+ uint8_t nbanks;
+ uint16_t density;
+ uint32_t size;
+ int min_log2, max_log2, sz_log2;
+ int i;
+
+ switch (type) {
+ case SDR:
+ min_log2 = 2;
+ max_log2 = 9;
+ break;
+ case DDR:
+ min_log2 = 5;
+ max_log2 = 12;
+ break;
+ case DDR2:
+ min_log2 = 7;
+ max_log2 = 14;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ size = ram_size >> 20; /* work in terms of megabytes */
+ sz_log2 = 31 - clz32(size);
+ size = 1U << sz_log2;
+ assert(ram_size == size * MiB);
+ assert(sz_log2 >= min_log2);
+
+ nbanks = 1;
+ while (sz_log2 > max_log2 && nbanks < 8) {
+ sz_log2--;
+ nbanks *= 2;
+ }
+
+ assert(size == (1ULL << sz_log2) * nbanks);
+
+ /* split to 2 banks if possible to avoid a bug in MIPS Malta firmware */
+ if (nbanks == 1 && sz_log2 > min_log2) {
+ sz_log2--;
+ nbanks++;
+ }
+
+ density = 1ULL << (sz_log2 - 2);
+ switch (type) {
+ case DDR2:
+ density = (density & 0xe0) | (density >> 8 & 0x1f);
+ break;
+ case DDR:
+ density = (density & 0xf8) | (density >> 8 & 0x07);
+ break;
+ case SDR:
+ default:
+ density &= 0xff;
+ break;
+ }
+
+ spd = g_malloc0(256);
+ spd[0] = 128; /* data bytes in EEPROM */
+ spd[1] = 8; /* log2 size of EEPROM */
+ spd[2] = type;
+ spd[3] = 13; /* row address bits */
+ spd[4] = 10; /* column address bits */
+ spd[5] = (type == DDR2 ? nbanks - 1 : nbanks);
+ spd[6] = 64; /* module data width */
+ /* reserved / data width high */
+ spd[8] = 4; /* interface voltage level */
+ spd[9] = 0x25; /* highest CAS latency */
+ spd[10] = 1; /* access time */
+ /* DIMM configuration 0 = non-ECC */
+ spd[12] = 0x82; /* refresh requirements */
+ spd[13] = 8; /* primary SDRAM width */
+ /* ECC SDRAM width */
+ spd[15] = (type == DDR2 ? 0 : 1); /* reserved / delay for random col rd */
+ spd[16] = 12; /* burst lengths supported */
+ spd[17] = 4; /* banks per SDRAM device */
+ spd[18] = 12; /* ~CAS latencies supported */
+ spd[19] = (type == DDR2 ? 0 : 1); /* reserved / ~CS latencies supported */
+ spd[20] = 2; /* DIMM type / ~WE latencies */
+ spd[21] = (type < DDR2 ? 0x20 : 0); /* module features */
+ /* memory chip features */
+ spd[23] = 0x12; /* clock cycle time @ medium CAS latency */
+ /* data access time */
+ /* clock cycle time @ short CAS latency */
+ /* data access time */
+ spd[27] = 20; /* min. row precharge time */
+ spd[28] = 15; /* min. row active row delay */
+ spd[29] = 20; /* min. ~RAS to ~CAS delay */
+ spd[30] = 45; /* min. active to precharge time */
+ spd[31] = density;
+ spd[32] = 20; /* addr/cmd setup time */
+ spd[33] = 8; /* addr/cmd hold time */
+ spd[34] = 20; /* data input setup time */
+ spd[35] = 8; /* data input hold time */
+
+ /* checksum */
+ for (i = 0; i < 63; i++) {
+ spd[63] += spd[i];
+ }
+ return spd;
+}
diff --git a/hw/i2c/smbus_ich9.c b/hw/i2c/smbus_ich9.c
new file mode 100644
index 000000000..44dd5653b
--- /dev/null
+++ b/hw/i2c/smbus_ich9.c
@@ -0,0 +1,155 @@
+/*
+ * ACPI implementation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2009 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ * Copyright (C) 2012 Jason Baron <jbaron@redhat.com>
+ *
+ * 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 "qemu/range.h"
+#include "hw/i2c/pm_smbus.h"
+#include "hw/pci/pci.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+
+#include "hw/i386/ich9.h"
+#include "qom/object.h"
+
+OBJECT_DECLARE_SIMPLE_TYPE(ICH9SMBState, ICH9_SMB_DEVICE)
+
+struct ICH9SMBState {
+ PCIDevice dev;
+
+ bool irq_enabled;
+
+ PMSMBus smb;
+};
+
+static bool ich9_vmstate_need_smbus(void *opaque, int version_id)
+{
+ return pm_smbus_vmstate_needed();
+}
+
+static const VMStateDescription vmstate_ich9_smbus = {
+ .name = "ich9_smb",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, ICH9SMBState),
+ VMSTATE_BOOL_TEST(irq_enabled, ICH9SMBState, ich9_vmstate_need_smbus),
+ VMSTATE_STRUCT_TEST(smb, ICH9SMBState, ich9_vmstate_need_smbus, 1,
+ pmsmb_vmstate, PMSMBus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ich9_smbus_write_config(PCIDevice *d, uint32_t address,
+ uint32_t val, int len)
+{
+ ICH9SMBState *s = ICH9_SMB_DEVICE(d);
+
+ pci_default_write_config(d, address, val, len);
+ if (range_covers_byte(address, len, ICH9_SMB_HOSTC)) {
+ uint8_t hostc = s->dev.config[ICH9_SMB_HOSTC];
+ if (hostc & ICH9_SMB_HOSTC_HST_EN) {
+ memory_region_set_enabled(&s->smb.io, true);
+ } else {
+ memory_region_set_enabled(&s->smb.io, false);
+ }
+ s->smb.i2c_enable = (hostc & ICH9_SMB_HOSTC_I2C_EN) != 0;
+ if (hostc & ICH9_SMB_HOSTC_SSRESET) {
+ s->smb.reset(&s->smb);
+ s->dev.config[ICH9_SMB_HOSTC] &= ~ICH9_SMB_HOSTC_SSRESET;
+ }
+ }
+}
+
+static void ich9_smbus_realize(PCIDevice *d, Error **errp)
+{
+ ICH9SMBState *s = ICH9_SMB_DEVICE(d);
+
+ /* TODO? D31IP.SMIP in chipset configuration space */
+ pci_config_set_interrupt_pin(d->config, 0x01); /* interrupt pin 1 */
+
+ pci_set_byte(d->config + ICH9_SMB_HOSTC, 0);
+ /* TODO bar0, bar1: 64bit BAR support*/
+
+ pm_smbus_init(&d->qdev, &s->smb, false);
+ pci_register_bar(d, ICH9_SMB_SMB_BASE_BAR, PCI_BASE_ADDRESS_SPACE_IO,
+ &s->smb.io);
+}
+
+static void ich9_smb_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_ICH9_6;
+ k->revision = ICH9_A2_SMB_REVISION;
+ k->class_id = PCI_CLASS_SERIAL_SMBUS;
+ dc->vmsd = &vmstate_ich9_smbus;
+ dc->desc = "ICH9 SMBUS Bridge";
+ k->realize = ich9_smbus_realize;
+ k->config_write = ich9_smbus_write_config;
+ /*
+ * Reason: part of ICH9 southbridge, needs to be wired up by
+ * pc_q35_init()
+ */
+ dc->user_creatable = false;
+}
+
+static void ich9_smb_set_irq(PMSMBus *pmsmb, bool enabled)
+{
+ ICH9SMBState *s = pmsmb->opaque;
+
+ if (enabled == s->irq_enabled) {
+ return;
+ }
+
+ s->irq_enabled = enabled;
+ pci_set_irq(&s->dev, enabled);
+}
+
+I2CBus *ich9_smb_init(PCIBus *bus, int devfn, uint32_t smb_io_base)
+{
+ PCIDevice *d =
+ pci_create_simple_multifunction(bus, devfn, true, TYPE_ICH9_SMB_DEVICE);
+ ICH9SMBState *s = ICH9_SMB_DEVICE(d);
+ s->smb.set_irq = ich9_smb_set_irq;
+ s->smb.opaque = s;
+ return s->smb.smbus;
+}
+
+static const TypeInfo ich9_smb_info = {
+ .name = TYPE_ICH9_SMB_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(ICH9SMBState),
+ .class_init = ich9_smb_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void ich9_smb_register(void)
+{
+ type_register_static(&ich9_smb_info);
+}
+
+type_init(ich9_smb_register);
diff --git a/hw/i2c/smbus_master.c b/hw/i2c/smbus_master.c
new file mode 100644
index 000000000..6a53c34e7
--- /dev/null
+++ b/hw/i2c/smbus_master.c
@@ -0,0 +1,164 @@
+/*
+ * QEMU SMBus host (master) emulation.
+ *
+ * This code emulates SMBus transactions from the master point of view,
+ * it runs the individual I2C transaction to do the SMBus protocol
+ * over I2C.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the LGPL.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "hw/i2c/smbus_master.h"
+
+/* Master device commands. */
+int smbus_quick_command(I2CBus *bus, uint8_t addr, int read)
+{
+ if (i2c_start_transfer(bus, addr, read)) {
+ return -1;
+ }
+ i2c_end_transfer(bus);
+ return 0;
+}
+
+int smbus_receive_byte(I2CBus *bus, uint8_t addr)
+{
+ uint8_t data;
+
+ if (i2c_start_recv(bus, addr)) {
+ return -1;
+ }
+ data = i2c_recv(bus);
+ i2c_nack(bus);
+ i2c_end_transfer(bus);
+ return data;
+}
+
+int smbus_send_byte(I2CBus *bus, uint8_t addr, uint8_t data)
+{
+ if (i2c_start_send(bus, addr)) {
+ return -1;
+ }
+ i2c_send(bus, data);
+ i2c_end_transfer(bus);
+ return 0;
+}
+
+int smbus_read_byte(I2CBus *bus, uint8_t addr, uint8_t command)
+{
+ uint8_t data;
+ if (i2c_start_send(bus, addr)) {
+ return -1;
+ }
+ i2c_send(bus, command);
+ if (i2c_start_recv(bus, addr)) {
+ i2c_end_transfer(bus);
+ return -1;
+ }
+ data = i2c_recv(bus);
+ i2c_nack(bus);
+ i2c_end_transfer(bus);
+ return data;
+}
+
+int smbus_write_byte(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t data)
+{
+ if (i2c_start_send(bus, addr)) {
+ return -1;
+ }
+ i2c_send(bus, command);
+ i2c_send(bus, data);
+ i2c_end_transfer(bus);
+ return 0;
+}
+
+int smbus_read_word(I2CBus *bus, uint8_t addr, uint8_t command)
+{
+ uint16_t data;
+ if (i2c_start_send(bus, addr)) {
+ return -1;
+ }
+ i2c_send(bus, command);
+ if (i2c_start_recv(bus, addr)) {
+ i2c_end_transfer(bus);
+ return -1;
+ }
+ data = i2c_recv(bus);
+ data |= i2c_recv(bus) << 8;
+ i2c_nack(bus);
+ i2c_end_transfer(bus);
+ return data;
+}
+
+int smbus_write_word(I2CBus *bus, uint8_t addr, uint8_t command, uint16_t data)
+{
+ if (i2c_start_send(bus, addr)) {
+ return -1;
+ }
+ i2c_send(bus, command);
+ i2c_send(bus, data & 0xff);
+ i2c_send(bus, data >> 8);
+ i2c_end_transfer(bus);
+ return 0;
+}
+
+int smbus_read_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data,
+ int len, bool recv_len, bool send_cmd)
+{
+ int rlen;
+ int i;
+
+ if (send_cmd) {
+ if (i2c_start_send(bus, addr)) {
+ return -1;
+ }
+ i2c_send(bus, command);
+ }
+ if (i2c_start_recv(bus, addr)) {
+ if (send_cmd) {
+ i2c_end_transfer(bus);
+ }
+ return -1;
+ }
+ if (recv_len) {
+ rlen = i2c_recv(bus);
+ } else {
+ rlen = len;
+ }
+ if (rlen > len) {
+ rlen = 0;
+ }
+ for (i = 0; i < rlen; i++) {
+ data[i] = i2c_recv(bus);
+ }
+ i2c_nack(bus);
+ i2c_end_transfer(bus);
+ return rlen;
+}
+
+int smbus_write_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data,
+ int len, bool send_len)
+{
+ int i;
+
+ if (len > 32) {
+ len = 32;
+ }
+
+ if (i2c_start_send(bus, addr)) {
+ return -1;
+ }
+ i2c_send(bus, command);
+ if (send_len) {
+ i2c_send(bus, len);
+ }
+ for (i = 0; i < len; i++) {
+ i2c_send(bus, data[i]);
+ }
+ i2c_end_transfer(bus);
+ return 0;
+}
diff --git a/hw/i2c/smbus_slave.c b/hw/i2c/smbus_slave.c
new file mode 100644
index 000000000..5d10e2766
--- /dev/null
+++ b/hw/i2c/smbus_slave.c
@@ -0,0 +1,237 @@
+/*
+ * QEMU SMBus device emulation.
+ *
+ * This code is a helper for SMBus device emulation. It implements an
+ * I2C device inteface and runs the SMBus protocol from the device
+ * point of view and maps those to simple calls to emulate.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the LGPL.
+ */
+
+/* TODO: Implement PEC. */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "hw/i2c/smbus_slave.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+
+//#define DEBUG_SMBUS 1
+
+#ifdef DEBUG_SMBUS
+#define DPRINTF(fmt, ...) \
+do { printf("smbus(%02x): " fmt , dev->i2c.address, ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "smbus: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "smbus: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+enum {
+ SMBUS_IDLE,
+ SMBUS_WRITE_DATA,
+ SMBUS_READ_DATA,
+ SMBUS_DONE,
+ SMBUS_CONFUSED = -1
+};
+
+static void smbus_do_quick_cmd(SMBusDevice *dev, int recv)
+{
+ SMBusDeviceClass *sc = SMBUS_DEVICE_GET_CLASS(dev);
+
+ DPRINTF("Quick Command %d\n", recv);
+ if (sc->quick_cmd) {
+ sc->quick_cmd(dev, recv);
+ }
+}
+
+static void smbus_do_write(SMBusDevice *dev)
+{
+ SMBusDeviceClass *sc = SMBUS_DEVICE_GET_CLASS(dev);
+
+ DPRINTF("Command %d len %d\n", dev->data_buf[0], dev->data_len);
+ if (sc->write_data) {
+ sc->write_data(dev, dev->data_buf, dev->data_len);
+ }
+}
+
+static int smbus_i2c_event(I2CSlave *s, enum i2c_event event)
+{
+ SMBusDevice *dev = SMBUS_DEVICE(s);
+
+ switch (event) {
+ case I2C_START_SEND:
+ switch (dev->mode) {
+ case SMBUS_IDLE:
+ DPRINTF("Incoming data\n");
+ dev->mode = SMBUS_WRITE_DATA;
+ break;
+
+ default:
+ BADF("Unexpected send start condition in state %d\n", dev->mode);
+ dev->mode = SMBUS_CONFUSED;
+ break;
+ }
+ break;
+
+ case I2C_START_RECV:
+ switch (dev->mode) {
+ case SMBUS_IDLE:
+ DPRINTF("Read mode\n");
+ dev->mode = SMBUS_READ_DATA;
+ break;
+
+ case SMBUS_WRITE_DATA:
+ if (dev->data_len == 0) {
+ BADF("Read after write with no data\n");
+ dev->mode = SMBUS_CONFUSED;
+ } else {
+ smbus_do_write(dev);
+ DPRINTF("Read mode\n");
+ dev->mode = SMBUS_READ_DATA;
+ }
+ break;
+
+ default:
+ BADF("Unexpected recv start condition in state %d\n", dev->mode);
+ dev->mode = SMBUS_CONFUSED;
+ break;
+ }
+ break;
+
+ case I2C_FINISH:
+ if (dev->data_len == 0) {
+ if (dev->mode == SMBUS_WRITE_DATA || dev->mode == SMBUS_READ_DATA) {
+ smbus_do_quick_cmd(dev, dev->mode == SMBUS_READ_DATA);
+ }
+ } else {
+ switch (dev->mode) {
+ case SMBUS_WRITE_DATA:
+ smbus_do_write(dev);
+ break;
+
+ case SMBUS_READ_DATA:
+ BADF("Unexpected stop during receive\n");
+ break;
+
+ default:
+ /* Nothing to do. */
+ break;
+ }
+ }
+ dev->mode = SMBUS_IDLE;
+ dev->data_len = 0;
+ break;
+
+ case I2C_NACK:
+ switch (dev->mode) {
+ case SMBUS_DONE:
+ /* Nothing to do. */
+ break;
+
+ case SMBUS_READ_DATA:
+ dev->mode = SMBUS_DONE;
+ break;
+
+ default:
+ BADF("Unexpected NACK in state %d\n", dev->mode);
+ dev->mode = SMBUS_CONFUSED;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static uint8_t smbus_i2c_recv(I2CSlave *s)
+{
+ SMBusDevice *dev = SMBUS_DEVICE(s);
+ SMBusDeviceClass *sc = SMBUS_DEVICE_GET_CLASS(dev);
+ uint8_t ret = 0xff;
+
+ switch (dev->mode) {
+ case SMBUS_READ_DATA:
+ if (sc->receive_byte) {
+ ret = sc->receive_byte(dev);
+ }
+ DPRINTF("Read data %02x\n", ret);
+ break;
+
+ default:
+ BADF("Unexpected read in state %d\n", dev->mode);
+ dev->mode = SMBUS_CONFUSED;
+ break;
+ }
+
+ return ret;
+}
+
+static int smbus_i2c_send(I2CSlave *s, uint8_t data)
+{
+ SMBusDevice *dev = SMBUS_DEVICE(s);
+
+ switch (dev->mode) {
+ case SMBUS_WRITE_DATA:
+ DPRINTF("Write data %02x\n", data);
+ if (dev->data_len >= sizeof(dev->data_buf)) {
+ BADF("Too many bytes sent\n");
+ } else {
+ dev->data_buf[dev->data_len++] = data;
+ }
+ break;
+
+ default:
+ BADF("Unexpected write in state %d\n", dev->mode);
+ break;
+ }
+
+ return 0;
+}
+
+static void smbus_device_class_init(ObjectClass *klass, void *data)
+{
+ I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass);
+
+ sc->event = smbus_i2c_event;
+ sc->recv = smbus_i2c_recv;
+ sc->send = smbus_i2c_send;
+}
+
+bool smbus_vmstate_needed(SMBusDevice *dev)
+{
+ return dev->mode != SMBUS_IDLE;
+}
+
+const VMStateDescription vmstate_smbus_device = {
+ .name = TYPE_SMBUS_DEVICE,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_I2C_SLAVE(i2c, SMBusDevice),
+ VMSTATE_INT32(mode, SMBusDevice),
+ VMSTATE_INT32(data_len, SMBusDevice),
+ VMSTATE_UINT8_ARRAY(data_buf, SMBusDevice, SMBUS_DATA_MAX_LEN),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const TypeInfo smbus_device_type_info = {
+ .name = TYPE_SMBUS_DEVICE,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(SMBusDevice),
+ .abstract = true,
+ .class_size = sizeof(SMBusDeviceClass),
+ .class_init = smbus_device_class_init,
+};
+
+static void smbus_device_register_types(void)
+{
+ type_register_static(&smbus_device_type_info);
+}
+
+type_init(smbus_device_register_types)
diff --git a/hw/i2c/trace-events b/hw/i2c/trace-events
new file mode 100644
index 000000000..7d8907c1e
--- /dev/null
+++ b/hw/i2c/trace-events
@@ -0,0 +1,33 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# core.c
+
+i2c_event(const char *event, uint8_t address) "%s(addr:0x%02x)"
+i2c_send(uint8_t address, uint8_t data) "send(addr:0x%02x) data:0x%02x"
+i2c_recv(uint8_t address, uint8_t data) "recv(addr:0x%02x) data:0x%02x"
+
+# aspeed_i2c.c
+
+aspeed_i2c_bus_cmd(uint32_t cmd, const char *cmd_flags, uint32_t count, uint32_t intr_status) "handling cmd=0x%x %s count=%d intr=0x%x"
+aspeed_i2c_bus_raise_interrupt(uint32_t intr_status, const char *str1, const char *str2, const char *str3, const char *str4, const char *str5) "handled intr=0x%x %s%s%s%s%s"
+aspeed_i2c_bus_read(uint32_t busid, uint64_t offset, unsigned size, uint64_t value) "bus[%d]: To 0x%" PRIx64 " of size %u: 0x%" PRIx64
+aspeed_i2c_bus_write(uint32_t busid, uint64_t offset, unsigned size, uint64_t value) "bus[%d]: To 0x%" PRIx64 " of size %u: 0x%" PRIx64
+aspeed_i2c_bus_send(const char *mode, int i, int count, uint8_t byte) "%s send %d/%d 0x%02x"
+aspeed_i2c_bus_recv(const char *mode, int i, int count, uint8_t byte) "%s recv %d/%d 0x%02x"
+
+# npcm7xx_smbus.c
+
+npcm7xx_smbus_read(const char *id, uint64_t offset, uint64_t value, unsigned size) "%s offset: 0x%04" PRIx64 " value: 0x%02" PRIx64 " size: %u"
+npcm7xx_smbus_write(const char *id, uint64_t offset, uint64_t value, unsigned size) "%s offset: 0x%04" PRIx64 " value: 0x%02" PRIx64 " size: %u"
+npcm7xx_smbus_start(const char *id, int success) "%s starting, success: %d"
+npcm7xx_smbus_send_address(const char *id, uint8_t addr, int recv, int success) "%s sending address: 0x%02x, recv: %d, success: %d"
+npcm7xx_smbus_send_byte(const char *id, uint8_t value, int success) "%s send byte: 0x%02x, success: %d"
+npcm7xx_smbus_recv_byte(const char *id, uint8_t value) "%s recv byte: 0x%02x"
+npcm7xx_smbus_stop(const char *id) "%s stopping"
+npcm7xx_smbus_nack(const char *id) "%s nacking"
+npcm7xx_smbus_recv_fifo(const char *id, uint8_t received, uint8_t expected) "%s recv fifo: received %u, expected %u"
+
+# i2c-mux-pca954x.c
+
+pca954x_write_bytes(uint8_t value) "PCA954X write data: 0x%02x"
+pca954x_read_data(uint8_t value) "PCA954X read data: 0x%02x"
diff --git a/hw/i2c/trace.h b/hw/i2c/trace.h
new file mode 100644
index 000000000..4843a8d54
--- /dev/null
+++ b/hw/i2c/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_i2c.h"
diff --git a/hw/i2c/versatile_i2c.c b/hw/i2c/versatile_i2c.c
new file mode 100644
index 000000000..3a04ba396
--- /dev/null
+++ b/hw/i2c/versatile_i2c.c
@@ -0,0 +1,112 @@
+/*
+ * ARM SBCon two-wire serial bus interface (I2C bitbang)
+ * a.k.a. ARM Versatile I2C controller
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Copyright (c) 2012 Oskar Andero <oskar.andero@gmail.com>
+ *
+ * This file is derived from hw/realview.c by Paul Brook
+ *
+ * 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/i2c/arm_sbcon_i2c.h"
+#include "hw/registerfields.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+typedef ArmSbconI2CState VersatileI2CState;
+DECLARE_INSTANCE_CHECKER(VersatileI2CState, VERSATILE_I2C,
+ TYPE_VERSATILE_I2C)
+
+
+
+REG32(CONTROL_GET, 0)
+REG32(CONTROL_SET, 0)
+REG32(CONTROL_CLR, 4)
+
+#define SCL BIT(0)
+#define SDA BIT(1)
+
+static uint64_t versatile_i2c_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ VersatileI2CState *s = (VersatileI2CState *)opaque;
+
+ switch (offset) {
+ case A_CONTROL_SET:
+ return (s->out & 1) | (s->in << 1);
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ return -1;
+ }
+}
+
+static void versatile_i2c_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ VersatileI2CState *s = (VersatileI2CState *)opaque;
+
+ switch (offset) {
+ case A_CONTROL_SET:
+ s->out |= value & 3;
+ break;
+ case A_CONTROL_CLR:
+ s->out &= ~value;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ }
+ bitbang_i2c_set(&s->bitbang, BITBANG_I2C_SCL, (s->out & SCL) != 0);
+ s->in = bitbang_i2c_set(&s->bitbang, BITBANG_I2C_SDA, (s->out & SDA) != 0);
+}
+
+static const MemoryRegionOps versatile_i2c_ops = {
+ .read = versatile_i2c_read,
+ .write = versatile_i2c_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void versatile_i2c_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ VersatileI2CState *s = VERSATILE_I2C(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ I2CBus *bus;
+
+ bus = i2c_init_bus(dev, "i2c");
+ bitbang_i2c_init(&s->bitbang, bus);
+ memory_region_init_io(&s->iomem, obj, &versatile_i2c_ops, s,
+ "arm_sbcon_i2c", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const TypeInfo versatile_i2c_info = {
+ .name = TYPE_VERSATILE_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(VersatileI2CState),
+ .instance_init = versatile_i2c_init,
+};
+
+static void versatile_i2c_register_types(void)
+{
+ type_register_static(&versatile_i2c_info);
+}
+
+type_init(versatile_i2c_register_types)