summary refs log tree commit diff stats
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to 'hw')
-rw-r--r--hw/arm/Kconfig1
-rw-r--r--hw/arm/npcm7xx.c68
-rw-r--r--hw/arm/npcm7xx_boards.c46
-rw-r--r--hw/i2c/meson.build1
-rw-r--r--hw/i2c/npcm7xx_smbus.c1099
-rw-r--r--hw/i2c/trace-events12
6 files changed, 1211 insertions, 16 deletions
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index be017b997a..4e6f4ffe90 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -370,6 +370,7 @@ config NPCM7XX
     bool
     select A9MPCORE
     select ARM_GIC
+    select AT24C  # EEPROM
     select PL310  # cache controller
     select SERIAL
     select SSI
diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c
index d1fe9bd1df..f8950f9470 100644
--- a/hw/arm/npcm7xx.c
+++ b/hw/arm/npcm7xx.c
@@ -102,6 +102,22 @@ enum NPCM7xxInterrupt {
     NPCM7XX_WDG2_IRQ,                   /* Timer Module 2 Watchdog */
     NPCM7XX_EHCI_IRQ            = 61,
     NPCM7XX_OHCI_IRQ            = 62,
+    NPCM7XX_SMBUS0_IRQ          = 64,
+    NPCM7XX_SMBUS1_IRQ,
+    NPCM7XX_SMBUS2_IRQ,
+    NPCM7XX_SMBUS3_IRQ,
+    NPCM7XX_SMBUS4_IRQ,
+    NPCM7XX_SMBUS5_IRQ,
+    NPCM7XX_SMBUS6_IRQ,
+    NPCM7XX_SMBUS7_IRQ,
+    NPCM7XX_SMBUS8_IRQ,
+    NPCM7XX_SMBUS9_IRQ,
+    NPCM7XX_SMBUS10_IRQ,
+    NPCM7XX_SMBUS11_IRQ,
+    NPCM7XX_SMBUS12_IRQ,
+    NPCM7XX_SMBUS13_IRQ,
+    NPCM7XX_SMBUS14_IRQ,
+    NPCM7XX_SMBUS15_IRQ,
     NPCM7XX_PWM0_IRQ            = 93,   /* PWM module 0 */
     NPCM7XX_PWM1_IRQ,                   /* PWM module 1 */
     NPCM7XX_GPIO0_IRQ           = 116,
@@ -152,6 +168,26 @@ static const hwaddr npcm7xx_pwm_addr[] = {
     0xf0104000,
 };
 
+/* Direct memory-mapped access to each SMBus Module. */
+static const hwaddr npcm7xx_smbus_addr[] = {
+    0xf0080000,
+    0xf0081000,
+    0xf0082000,
+    0xf0083000,
+    0xf0084000,
+    0xf0085000,
+    0xf0086000,
+    0xf0087000,
+    0xf0088000,
+    0xf0089000,
+    0xf008a000,
+    0xf008b000,
+    0xf008c000,
+    0xf008d000,
+    0xf008e000,
+    0xf008f000,
+};
+
 static const struct {
     hwaddr regs_addr;
     uint32_t unconnected_pins;
@@ -353,6 +389,11 @@ static void npcm7xx_init(Object *obj)
         object_initialize_child(obj, "gpio[*]", &s->gpio[i], TYPE_NPCM7XX_GPIO);
     }
 
+    for (i = 0; i < ARRAY_SIZE(s->smbus); i++) {
+        object_initialize_child(obj, "smbus[*]", &s->smbus[i],
+                                TYPE_NPCM7XX_SMBUS);
+    }
+
     object_initialize_child(obj, "ehci", &s->ehci, TYPE_NPCM7XX_EHCI);
     object_initialize_child(obj, "ohci", &s->ohci, TYPE_SYSBUS_OHCI);
 
@@ -509,6 +550,17 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
                            npcm7xx_irq(s, NPCM7XX_GPIO0_IRQ + i));
     }
 
+    /* SMBus modules. Cannot fail. */
+    QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_smbus_addr) != ARRAY_SIZE(s->smbus));
+    for (i = 0; i < ARRAY_SIZE(s->smbus); i++) {
+        Object *obj = OBJECT(&s->smbus[i]);
+
+        sysbus_realize(SYS_BUS_DEVICE(obj), &error_abort);
+        sysbus_mmio_map(SYS_BUS_DEVICE(obj), 0, npcm7xx_smbus_addr[i]);
+        sysbus_connect_irq(SYS_BUS_DEVICE(obj), 0,
+                           npcm7xx_irq(s, NPCM7XX_SMBUS0_IRQ + i));
+    }
+
     /* USB Host */
     object_property_set_bool(OBJECT(&s->ehci), "companion-enable", true,
                              &error_abort);
@@ -576,22 +628,6 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
     create_unimplemented_device("npcm7xx.pcierc",       0xe1000000,  64 * KiB);
     create_unimplemented_device("npcm7xx.kcs",          0xf0007000,   4 * KiB);
     create_unimplemented_device("npcm7xx.gfxi",         0xf000e000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[0]",     0xf0080000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[1]",     0xf0081000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[2]",     0xf0082000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[3]",     0xf0083000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[4]",     0xf0084000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[5]",     0xf0085000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[6]",     0xf0086000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[7]",     0xf0087000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[8]",     0xf0088000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[9]",     0xf0089000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[10]",    0xf008a000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[11]",    0xf008b000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[12]",    0xf008c000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[13]",    0xf008d000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[14]",    0xf008e000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.smbus[15]",    0xf008f000,   4 * KiB);
     create_unimplemented_device("npcm7xx.espi",         0xf009f000,   4 * KiB);
     create_unimplemented_device("npcm7xx.peci",         0xf0100000,   4 * KiB);
     create_unimplemented_device("npcm7xx.siox[1]",      0xf0101000,   4 * KiB);
diff --git a/hw/arm/npcm7xx_boards.c b/hw/arm/npcm7xx_boards.c
index 3fdd5cab01..fbf6ce8e02 100644
--- a/hw/arm/npcm7xx_boards.c
+++ b/hw/arm/npcm7xx_boards.c
@@ -19,6 +19,7 @@
 #include "exec/address-spaces.h"
 #include "hw/arm/npcm7xx.h"
 #include "hw/core/cpu.h"
+#include "hw/i2c/smbus_eeprom.h"
 #include "hw/loader.h"
 #include "hw/qdev-properties.h"
 #include "qapi/error.h"
@@ -98,6 +99,49 @@ static NPCM7xxState *npcm7xx_create_soc(MachineState *machine,
     return NPCM7XX(obj);
 }
 
+static I2CBus *npcm7xx_i2c_get_bus(NPCM7xxState *soc, uint32_t num)
+{
+    g_assert(num < ARRAY_SIZE(soc->smbus));
+    return I2C_BUS(qdev_get_child_bus(DEVICE(&soc->smbus[num]), "i2c-bus"));
+}
+
+static void at24c_eeprom_init(NPCM7xxState *soc, int bus, uint8_t addr,
+                              uint32_t rsize)
+{
+    I2CBus *i2c_bus = npcm7xx_i2c_get_bus(soc, bus);
+    I2CSlave *i2c_dev = i2c_slave_new("at24c-eeprom", addr);
+    DeviceState *dev = DEVICE(i2c_dev);
+
+    qdev_prop_set_uint32(dev, "rom-size", rsize);
+    i2c_slave_realize_and_unref(i2c_dev, i2c_bus, &error_abort);
+}
+
+static void npcm750_evb_i2c_init(NPCM7xxState *soc)
+{
+    /* lm75 temperature sensor on SVB, tmp105 is compatible */
+    i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 0), "tmp105", 0x48);
+    /* lm75 temperature sensor on EB, tmp105 is compatible */
+    i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 1), "tmp105", 0x48);
+    /* tmp100 temperature sensor on EB, tmp105 is compatible */
+    i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 2), "tmp105", 0x48);
+    /* tmp100 temperature sensor on SVB, tmp105 is compatible */
+    i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 6), "tmp105", 0x48);
+}
+
+static void quanta_gsj_i2c_init(NPCM7xxState *soc)
+{
+    /* GSJ machine have 4 max31725 temperature sensors, tmp105 is compatible. */
+    i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 1), "tmp105", 0x5c);
+    i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 2), "tmp105", 0x5c);
+    i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 3), "tmp105", 0x5c);
+    i2c_slave_create_simple(npcm7xx_i2c_get_bus(soc, 4), "tmp105", 0x5c);
+
+    at24c_eeprom_init(soc, 9, 0x55, 8192);
+    at24c_eeprom_init(soc, 10, 0x55, 8192);
+
+    /* TODO: Add additional i2c devices. */
+}
+
 static void npcm750_evb_init(MachineState *machine)
 {
     NPCM7xxState *soc;
@@ -108,6 +152,7 @@ static void npcm750_evb_init(MachineState *machine)
 
     npcm7xx_load_bootrom(machine, soc);
     npcm7xx_connect_flash(&soc->fiu[0], 0, "w25q256", drive_get(IF_MTD, 0, 0));
+    npcm750_evb_i2c_init(soc);
     npcm7xx_load_kernel(machine, soc);
 }
 
@@ -122,6 +167,7 @@ static void quanta_gsj_init(MachineState *machine)
     npcm7xx_load_bootrom(machine, soc);
     npcm7xx_connect_flash(&soc->fiu[0], 0, "mx25l25635e",
                           drive_get(IF_MTD, 0, 0));
+    quanta_gsj_i2c_init(soc);
     npcm7xx_load_kernel(machine, soc);
 }
 
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
index 3a511539ad..cdcd694a7f 100644
--- a/hw/i2c/meson.build
+++ b/hw/i2c/meson.build
@@ -9,6 +9,7 @@ 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'))
diff --git a/hw/i2c/npcm7xx_smbus.c b/hw/i2c/npcm7xx_smbus.c
new file mode 100644
index 0000000000..6b2f9e1aaa
--- /dev/null
+++ b/hw/i2c/npcm7xx_smbus.c
@@ -0,0 +1,1099 @@
+/*
+ * 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");
+    s->status = NPCM7XX_SMBUS_STATUS_IDLE;
+}
+
+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/trace-events b/hw/i2c/trace-events
index 08db8fa689..82fe6f965f 100644
--- a/hw/i2c/trace-events
+++ b/hw/i2c/trace-events
@@ -14,3 +14,15 @@ aspeed_i2c_bus_read(uint32_t busid, uint64_t offset, unsigned size, uint64_t val
 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"