qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support
@ 2020-12-11  1:51 Hao Wu via
  2020-12-11  1:51 ` [PATCH 1/7] hw/misc: Add clock converter in NPCM7XX CLK module Hao Wu via
                   ` (7 more replies)
  0 siblings, 8 replies; 14+ messages in thread
From: Hao Wu via @ 2020-12-11  1:51 UTC (permalink / raw)
  To: peter.maydell
  Cc: minyard, venture, qemu-devel, hskinnemoen, wuhaotsh, kfting,
	qemu-arm, Avi.Fishman

This patch series include a few more NPCM7XX devices including

- Analog Digital Converter (ADC)
- Pulse Width Modulation (PWM)
- Keyboard Style Controller (KSC)

To utilize these modules we also add two extra functionalities:

1. We modified the CLK module to generate clock values using qdev_clock.
   These clocks are used to determine various clocks in NPCM7XX devices.
2. We added support for emulating IPMI responder devices in BMC machines,
   similar to the existing IPMI device support for CPU emulation. This allows
   a qemu instance running BMC firmware to serve as an external BMC for a qemu
   instance running server software. It utilizes the KCS module we implemented.

Hao Wu (7):
  hw/misc: Add clock converter in NPCM7XX CLK module
  hw/timer: Refactor NPCM7XX Timer to use CLK clock
  hw/adc: Add an ADC module for NPCM7XX
  hw/misc: Add a PWM module for NPCM7XX
  hw/ipmi: Add an IPMI host interface
  hw/ipmi: Add a KCS Module for NPCM7XX
  hw/ipmi: Add an IPMI external host device

 default-configs/devices/arm-softmmu.mak |   2 +
 docs/system/arm/nuvoton.rst             |   6 +-
 hw/adc/meson.build                      |   1 +
 hw/adc/npcm7xx_adc.c                    | 318 ++++++++++
 hw/arm/npcm7xx.c                        |  65 +-
 hw/ipmi/Kconfig                         |   5 +
 hw/ipmi/ipmi_host.c                     |  40 ++
 hw/ipmi/ipmi_host_extern.c              | 435 +++++++++++++
 hw/ipmi/meson.build                     |   3 +
 hw/ipmi/npcm7xx_kcs.c                   | 570 +++++++++++++++++
 hw/misc/meson.build                     |   1 +
 hw/misc/npcm7xx_clk.c                   | 795 +++++++++++++++++++++++-
 hw/misc/npcm7xx_pwm.c                   | 535 ++++++++++++++++
 hw/timer/npcm7xx_timer.c                |  25 +-
 include/hw/adc/npcm7xx_adc.h            |  72 +++
 include/hw/arm/npcm7xx.h                |   6 +
 include/hw/ipmi/ipmi_host.h             |  56 ++
 include/hw/ipmi/ipmi_responder.h        |  66 ++
 include/hw/ipmi/npcm7xx_kcs.h           | 105 ++++
 include/hw/misc/npcm7xx_clk.h           | 146 ++++-
 include/hw/misc/npcm7xx_pwm.h           | 106 ++++
 include/hw/timer/npcm7xx_timer.h        |   1 +
 tests/qtest/meson.build                 |   4 +-
 tests/qtest/npcm7xx_adc-test.c          | 400 ++++++++++++
 tests/qtest/npcm7xx_pwm-test.c          | 490 +++++++++++++++
 25 files changed, 4221 insertions(+), 32 deletions(-)
 create mode 100644 hw/adc/npcm7xx_adc.c
 create mode 100644 hw/ipmi/ipmi_host.c
 create mode 100644 hw/ipmi/ipmi_host_extern.c
 create mode 100644 hw/ipmi/npcm7xx_kcs.c
 create mode 100644 hw/misc/npcm7xx_pwm.c
 create mode 100644 include/hw/adc/npcm7xx_adc.h
 create mode 100644 include/hw/ipmi/ipmi_host.h
 create mode 100644 include/hw/ipmi/ipmi_responder.h
 create mode 100644 include/hw/ipmi/npcm7xx_kcs.h
 create mode 100644 include/hw/misc/npcm7xx_pwm.h
 create mode 100644 tests/qtest/npcm7xx_adc-test.c
 create mode 100644 tests/qtest/npcm7xx_pwm-test.c

-- 
2.29.2.684.gfbc64c5ab5-goog



^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH 1/7] hw/misc: Add clock converter in NPCM7XX CLK module
  2020-12-11  1:51 [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Hao Wu via
@ 2020-12-11  1:51 ` Hao Wu via
  2020-12-11  1:51 ` [PATCH 2/7] hw/timer: Refactor NPCM7XX Timer to use CLK clock Hao Wu via
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Hao Wu via @ 2020-12-11  1:51 UTC (permalink / raw)
  To: peter.maydell
  Cc: minyard, venture, qemu-devel, hskinnemoen, wuhaotsh, kfting,
	qemu-arm, Avi.Fishman

This patch allows NPCM7XX CLK module to compute clocks that are used by
other NPCM7XX modules.

Add a new struct NPCM7xxClockConverterState which represents a
single converter.  Each clock converter in CLK module represents one
converter in NPCM7XX CLK Module(PLL, SEL or Divider). Each converter
takes one or more input clocks and converts them into one output clock.
They form a clock hierarchy in the CLK module and are responsible for
outputing clocks for various other modules in an NPCM7XX SoC.

Each converter has a function pointer called "convert" which represents
the unique logic for that converter.

The clock contains two initialization information: ConverterInitInfo and
ConverterConnectionInfo. They represent the vertices and edges in the
clock diagram respectively.

Reviewed-by: Havard Skinnemoen <hskinnemoen@google.com>
Reviewed-by: Tyrone Ting <kfting@nuvoton.com>
Signed-off-by: Hao Wu <wuhaotsh@google.com>
---
 hw/misc/npcm7xx_clk.c         | 795 +++++++++++++++++++++++++++++++++-
 include/hw/misc/npcm7xx_clk.h | 140 +++++-
 2 files changed, 927 insertions(+), 8 deletions(-)

diff --git a/hw/misc/npcm7xx_clk.c b/hw/misc/npcm7xx_clk.c
index 6732437fe2..48bc9bdda5 100644
--- a/hw/misc/npcm7xx_clk.c
+++ b/hw/misc/npcm7xx_clk.c
@@ -18,6 +18,7 @@
 
 #include "hw/misc/npcm7xx_clk.h"
 #include "hw/timer/npcm7xx_timer.h"
+#include "hw/qdev-clock.h"
 #include "migration/vmstate.h"
 #include "qemu/error-report.h"
 #include "qemu/log.h"
@@ -27,9 +28,22 @@
 #include "trace.h"
 #include "sysemu/watchdog.h"
 
+/*
+ * The reference clock hz, and the SECCNT and CNTR25M registers in this module,
+ * is always 25 MHz.
+ */
+#define NPCM7XX_CLOCK_REF_HZ            (25000000)
+
+/* Register Field Definitions */
+#define NPCM7XX_CLK_WDRCR_CA9C  BIT(0) /* Cortex A9 Cores */
+
 #define PLLCON_LOKI     BIT(31)
 #define PLLCON_LOKS     BIT(30)
 #define PLLCON_PWDEN    BIT(12)
+#define PLLCON_FBDV(con) extract32((con), 16, 12)
+#define PLLCON_OTDV2(con) extract32((con), 13, 3)
+#define PLLCON_OTDV1(con) extract32((con), 8, 3)
+#define PLLCON_INDV(con) extract32((con), 0, 6)
 
 enum NPCM7xxCLKRegisters {
     NPCM7XX_CLK_CLKEN1,
@@ -89,12 +103,609 @@ static const uint32_t cold_reset_values[NPCM7XX_CLK_NR_REGS] = {
     [NPCM7XX_CLK_AHBCKFI]       = 0x000000c8,
 };
 
-/* Register Field Definitions */
-#define NPCM7XX_CLK_WDRCR_CA9C  BIT(0) /* Cortex A9 Cores */
-
 /* The number of watchdogs that can trigger a reset. */
 #define NPCM7XX_NR_WATCHDOGS    (3)
 
+/* Clock converter functions */
+
+#define TYPE_NPCM7XX_CLOCK_PLL "npcm7xx-clock-pll"
+#define NPCM7XX_CLOCK_PLL(obj) OBJECT_CHECK(NPCM7xxClockPLLState, \
+        (obj), TYPE_NPCM7XX_CLOCK_PLL)
+#define TYPE_NPCM7XX_CLOCK_SEL "npcm7xx-clock-sel"
+#define NPCM7XX_CLOCK_SEL(obj) OBJECT_CHECK(NPCM7xxClockSELState, \
+        (obj), TYPE_NPCM7XX_CLOCK_SEL)
+#define TYPE_NPCM7XX_CLOCK_DIVIDER "npcm7xx-clock-divider"
+#define NPCM7XX_CLOCK_DIVIDER(obj) OBJECT_CHECK(NPCM7xxClockDividerState, \
+        (obj), TYPE_NPCM7XX_CLOCK_DIVIDER)
+
+static void npcm7xx_clk_update_pll(void *opaque)
+{
+    NPCM7xxClockPLLState *s = opaque;
+    uint32_t con = s->clk->regs[s->reg];
+    uint64_t freq;
+
+    /* The PLL is grounded if it is not locked yet. */
+    if (con & PLLCON_LOKI) {
+        freq = clock_get_hz(s->clock_in);
+        freq *= PLLCON_FBDV(con);
+        freq /= PLLCON_INDV(con) * PLLCON_OTDV1(con) * PLLCON_OTDV2(con);
+    } else {
+        freq = 0;
+    }
+
+    clock_update_hz(s->clock_out, freq);
+}
+
+static void npcm7xx_clk_update_sel(void *opaque)
+{
+    NPCM7xxClockSELState *s = opaque;
+    uint32_t index = extract32(s->clk->regs[NPCM7XX_CLK_CLKSEL], s->offset,
+            s->len);
+
+    if (index >= s->input_size) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: SEL index: %u out of range\n",
+                      __func__, index);
+        index = 0;
+    }
+    clock_update_hz(s->clock_out, clock_get_hz(s->clock_in[index]));
+}
+
+static void npcm7xx_clk_update_divider(void *opaque)
+{
+    NPCM7xxClockDividerState *s = opaque;
+    uint32_t freq;
+
+    freq = s->divide(s);
+    clock_update_hz(s->clock_out, freq);
+}
+
+static uint32_t divide_by_constant(NPCM7xxClockDividerState *s)
+{
+    return clock_get_hz(s->clock_in) / s->divisor;
+}
+
+static uint32_t divide_by_reg_divisor(NPCM7xxClockDividerState *s)
+{
+    return clock_get_hz(s->clock_in) /
+            (extract32(s->clk->regs[s->reg], s->offset, s->len) + 1);
+}
+
+static uint32_t divide_by_reg_divisor_times_2(NPCM7xxClockDividerState *s)
+{
+    return divide_by_reg_divisor(s) / 2;
+}
+
+static uint32_t shift_by_reg_divisor(NPCM7xxClockDividerState *s)
+{
+    return clock_get_hz(s->clock_in) >>
+        extract32(s->clk->regs[s->reg], s->offset, s->len);
+}
+
+static NPCM7xxClockPLL find_pll_by_reg(enum NPCM7xxCLKRegisters reg)
+{
+    switch (reg) {
+    case NPCM7XX_CLK_PLLCON0:
+        return NPCM7XX_CLOCK_PLL0;
+    case NPCM7XX_CLK_PLLCON1:
+        return NPCM7XX_CLOCK_PLL1;
+    case NPCM7XX_CLK_PLLCON2:
+        return NPCM7XX_CLOCK_PLL2;
+    case NPCM7XX_CLK_PLLCONG:
+        return NPCM7XX_CLOCK_PLLG;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static void npcm7xx_clk_update_all_plls(NPCM7xxCLKState *clk)
+{
+    int i;
+
+    for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) {
+        npcm7xx_clk_update_pll(&clk->plls[i]);
+    }
+}
+
+static void npcm7xx_clk_update_all_sels(NPCM7xxCLKState *clk)
+{
+    int i;
+
+    for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) {
+        npcm7xx_clk_update_sel(&clk->sels[i]);
+    }
+}
+
+static void npcm7xx_clk_update_all_dividers(NPCM7xxCLKState *clk)
+{
+    int i;
+
+    for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) {
+        npcm7xx_clk_update_divider(&clk->dividers[i]);
+    }
+}
+
+static void npcm7xx_clk_update_all_clocks(NPCM7xxCLKState *clk)
+{
+    clock_update_hz(clk->clkref, NPCM7XX_CLOCK_REF_HZ);
+    npcm7xx_clk_update_all_plls(clk);
+    npcm7xx_clk_update_all_sels(clk);
+    npcm7xx_clk_update_all_dividers(clk);
+}
+
+/* Types of clock sources. */
+typedef enum ClockSrcType {
+    CLKSRC_REF,
+    CLKSRC_PLL,
+    CLKSRC_SEL,
+    CLKSRC_DIV,
+} ClockSrcType;
+
+typedef struct PLLInitInfo {
+    const char *name;
+    ClockSrcType src_type;
+    int src_index;
+    int reg;
+    const char *public_name;
+} PLLInitInfo;
+
+typedef struct SELInitInfo {
+    const char *name;
+    uint8_t input_size;
+    ClockSrcType src_type[NPCM7XX_CLK_SEL_MAX_INPUT];
+    int src_index[NPCM7XX_CLK_SEL_MAX_INPUT];
+    int offset;
+    int len;
+    const char *public_name;
+} SELInitInfo;
+
+typedef struct DividerInitInfo {
+    const char *name;
+    ClockSrcType src_type;
+    int src_index;
+    uint32_t (*divide)(NPCM7xxClockDividerState *s);
+    int reg; /* not used when type == CONSTANT */
+    int offset; /* not used when type == CONSTANT */
+    int len; /* not used when type == CONSTANT */
+    int divisor; /* used only when type == CONSTANT */
+    const char *public_name;
+} DividerInitInfo;
+
+static const PLLInitInfo pll_init_info_list[] = {
+    [NPCM7XX_CLOCK_PLL0] = {
+        .name = "pll0",
+        .src_type = CLKSRC_REF,
+        .reg = NPCM7XX_CLK_PLLCON0,
+    },
+    [NPCM7XX_CLOCK_PLL1] = {
+        .name = "pll1",
+        .src_type = CLKSRC_REF,
+        .reg = NPCM7XX_CLK_PLLCON1,
+    },
+    [NPCM7XX_CLOCK_PLL2] = {
+        .name = "pll2",
+        .src_type = CLKSRC_REF,
+        .reg = NPCM7XX_CLK_PLLCON2,
+    },
+    [NPCM7XX_CLOCK_PLLG] = {
+        .name = "pllg",
+        .src_type = CLKSRC_REF,
+        .reg = NPCM7XX_CLK_PLLCONG,
+    },
+};
+
+static const SELInitInfo sel_init_info_list[] = {
+    [NPCM7XX_CLOCK_PIXCKSEL] = {
+        .name = "pixcksel",
+        .input_size = 2,
+        .src_type = {CLKSRC_PLL, CLKSRC_REF},
+        .src_index = {NPCM7XX_CLOCK_PLLG, 0},
+        .offset = 5,
+        .len = 1,
+        .public_name = "pixel-clock",
+    },
+    [NPCM7XX_CLOCK_MCCKSEL] = {
+        .name = "mccksel",
+        .input_size = 4,
+        .src_type = {CLKSRC_DIV, CLKSRC_REF, CLKSRC_REF,
+            /*MCBPCK, shouldn't be used in normal operation*/
+            CLKSRC_REF},
+        .src_index = {NPCM7XX_CLOCK_PLL1D2, 0, 0, 0},
+        .offset = 12,
+        .len = 2,
+        .public_name = "mc-phy-clock",
+    },
+    [NPCM7XX_CLOCK_CPUCKSEL] = {
+        .name = "cpucksel",
+        .input_size = 4,
+        .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF,
+            /*SYSBPCK, shouldn't be used in normal operation*/
+            CLKSRC_REF},
+        .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, 0},
+        .offset = 0,
+        .len = 2,
+        .public_name = "system-clock",
+    },
+    [NPCM7XX_CLOCK_CLKOUTSEL] = {
+        .name = "clkoutsel",
+        .input_size = 5,
+        .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF,
+            CLKSRC_PLL, CLKSRC_DIV},
+        .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0,
+            NPCM7XX_CLOCK_PLLG, NPCM7XX_CLOCK_PLL2D2},
+        .offset = 18,
+        .len = 3,
+        .public_name = "tock",
+    },
+    [NPCM7XX_CLOCK_UARTCKSEL] = {
+        .name = "uartcksel",
+        .input_size = 4,
+        .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV},
+        .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0,
+            NPCM7XX_CLOCK_PLL2D2},
+        .offset = 8,
+        .len = 2,
+    },
+    [NPCM7XX_CLOCK_TIMCKSEL] = {
+        .name = "timcksel",
+        .input_size = 4,
+        .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV},
+        .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0,
+            NPCM7XX_CLOCK_PLL2D2},
+        .offset = 14,
+        .len = 2,
+    },
+    [NPCM7XX_CLOCK_SDCKSEL] = {
+        .name = "sdcksel",
+        .input_size = 4,
+        .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV},
+        .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0,
+            NPCM7XX_CLOCK_PLL2D2},
+        .offset = 6,
+        .len = 2,
+    },
+    [NPCM7XX_CLOCK_GFXMSEL] = {
+        .name = "gfxmksel",
+        .input_size = 2,
+        .src_type = {CLKSRC_REF, CLKSRC_PLL},
+        .src_index = {0, NPCM7XX_CLOCK_PLL2},
+        .offset = 21,
+        .len = 1,
+    },
+    [NPCM7XX_CLOCK_SUCKSEL] = {
+        .name = "sucksel",
+        .input_size = 4,
+        .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV},
+        .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0,
+            NPCM7XX_CLOCK_PLL2D2},
+        .offset = 10,
+        .len = 2,
+    },
+};
+
+static const DividerInitInfo divider_init_info_list[] = {
+    [NPCM7XX_CLOCK_PLL1D2] = {
+        .name = "pll1d2",
+        .src_type = CLKSRC_PLL,
+        .src_index = NPCM7XX_CLOCK_PLL1,
+        .divide = divide_by_constant,
+        .divisor = 2,
+    },
+    [NPCM7XX_CLOCK_PLL2D2] = {
+        .name = "pll2d2",
+        .src_type = CLKSRC_PLL,
+        .src_index = NPCM7XX_CLOCK_PLL2,
+        .divide = divide_by_constant,
+        .divisor = 2,
+    },
+    [NPCM7XX_CLOCK_MC_DIVIDER] = {
+        .name = "mc-divider",
+        .src_type = CLKSRC_SEL,
+        .src_index = NPCM7XX_CLOCK_MCCKSEL,
+        .divide = divide_by_constant,
+        .divisor = 2,
+        .public_name = "mc-clock"
+    },
+    [NPCM7XX_CLOCK_AXI_DIVIDER] = {
+        .name = "axi-divider",
+        .src_type = CLKSRC_SEL,
+        .src_index = NPCM7XX_CLOCK_CPUCKSEL,
+        .divide = shift_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV1,
+        .offset = 0,
+        .len = 1,
+        .public_name = "clk2"
+    },
+    [NPCM7XX_CLOCK_AHB_DIVIDER] = {
+        .name = "ahb-divider",
+        .src_type = CLKSRC_DIV,
+        .src_index = NPCM7XX_CLOCK_AXI_DIVIDER,
+        .divide = divide_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV1,
+        .offset = 26,
+        .len = 2,
+        .public_name = "clk4"
+    },
+    [NPCM7XX_CLOCK_AHB3_DIVIDER] = {
+        .name = "ahb3-divider",
+        .src_type = CLKSRC_DIV,
+        .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+        .divide = divide_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV1,
+        .offset = 6,
+        .len = 5,
+        .public_name = "ahb3-spi3-clock"
+    },
+    [NPCM7XX_CLOCK_SPI0_DIVIDER] = {
+        .name = "spi0-divider",
+        .src_type = CLKSRC_DIV,
+        .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+        .divide = divide_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV3,
+        .offset = 6,
+        .len = 5,
+        .public_name = "spi0-clock",
+    },
+    [NPCM7XX_CLOCK_SPIX_DIVIDER] = {
+        .name = "spix-divider",
+        .src_type = CLKSRC_DIV,
+        .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+        .divide = divide_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV3,
+        .offset = 1,
+        .len = 5,
+        .public_name = "spix-clock",
+    },
+    [NPCM7XX_CLOCK_APB1_DIVIDER] = {
+        .name = "apb1-divider",
+        .src_type = CLKSRC_DIV,
+        .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+        .divide = shift_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV2,
+        .offset = 24,
+        .len = 2,
+        .public_name = "apb1-clock",
+    },
+    [NPCM7XX_CLOCK_APB2_DIVIDER] = {
+        .name = "apb2-divider",
+        .src_type = CLKSRC_DIV,
+        .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+        .divide = shift_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV2,
+        .offset = 26,
+        .len = 2,
+        .public_name = "apb2-clock",
+    },
+    [NPCM7XX_CLOCK_APB3_DIVIDER] = {
+        .name = "apb3-divider",
+        .src_type = CLKSRC_DIV,
+        .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+        .divide = shift_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV2,
+        .offset = 28,
+        .len = 2,
+        .public_name = "apb3-clock",
+    },
+    [NPCM7XX_CLOCK_APB4_DIVIDER] = {
+        .name = "apb4-divider",
+        .src_type = CLKSRC_DIV,
+        .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+        .divide = shift_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV2,
+        .offset = 30,
+        .len = 2,
+        .public_name = "apb4-clock",
+    },
+    [NPCM7XX_CLOCK_APB5_DIVIDER] = {
+        .name = "apb5-divider",
+        .src_type = CLKSRC_DIV,
+        .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+        .divide = shift_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV2,
+        .offset = 22,
+        .len = 2,
+        .public_name = "apb5-clock",
+    },
+    [NPCM7XX_CLOCK_CLKOUT_DIVIDER] = {
+        .name = "clkout-divider",
+        .src_type = CLKSRC_SEL,
+        .src_index = NPCM7XX_CLOCK_CLKOUTSEL,
+        .divide = divide_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV2,
+        .offset = 16,
+        .len = 5,
+        .public_name = "clkout",
+    },
+    [NPCM7XX_CLOCK_UART_DIVIDER] = {
+        .name = "uart-divider",
+        .src_type = CLKSRC_SEL,
+        .src_index = NPCM7XX_CLOCK_UARTCKSEL,
+        .divide = divide_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV1,
+        .offset = 16,
+        .len = 5,
+        .public_name = "uart-clock",
+    },
+    [NPCM7XX_CLOCK_TIMER_DIVIDER] = {
+        .name = "timer-divider",
+        .src_type = CLKSRC_SEL,
+        .src_index = NPCM7XX_CLOCK_TIMCKSEL,
+        .divide = divide_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV1,
+        .offset = 21,
+        .len = 5,
+        .public_name = "timer-clock",
+    },
+    [NPCM7XX_CLOCK_ADC_DIVIDER] = {
+        .name = "adc-divider",
+        .src_type = CLKSRC_DIV,
+        .src_index = NPCM7XX_CLOCK_TIMER_DIVIDER,
+        .divide = shift_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV1,
+        .offset = 28,
+        .len = 3,
+        .public_name = "adc-clock",
+    },
+    [NPCM7XX_CLOCK_MMC_DIVIDER] = {
+        .name = "mmc-divider",
+        .src_type = CLKSRC_SEL,
+        .src_index = NPCM7XX_CLOCK_SDCKSEL,
+        .divide = divide_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV1,
+        .offset = 11,
+        .len = 5,
+        .public_name = "mmc-clock",
+    },
+    [NPCM7XX_CLOCK_SDHC_DIVIDER] = {
+        .name = "sdhc-divider",
+        .src_type = CLKSRC_SEL,
+        .src_index = NPCM7XX_CLOCK_SDCKSEL,
+        .divide = divide_by_reg_divisor_times_2,
+        .reg = NPCM7XX_CLK_CLKDIV2,
+        .offset = 0,
+        .len = 4,
+        .public_name = "sdhc-clock",
+    },
+    [NPCM7XX_CLOCK_GFXM_DIVIDER] = {
+        .name = "gfxm-divider",
+        .src_type = CLKSRC_SEL,
+        .src_index = NPCM7XX_CLOCK_GFXMSEL,
+        .divide = divide_by_constant,
+        .divisor = 3,
+        .public_name = "gfxm-clock",
+    },
+    [NPCM7XX_CLOCK_UTMI_DIVIDER] = {
+        .name = "utmi-divider",
+        .src_type = CLKSRC_SEL,
+        .src_index = NPCM7XX_CLOCK_SUCKSEL,
+        .divide = divide_by_reg_divisor,
+        .reg = NPCM7XX_CLK_CLKDIV2,
+        .offset = 8,
+        .len = 5,
+        .public_name = "utmi-clock",
+    },
+};
+
+static void npcm7xx_clk_pll_init(Object *obj)
+{
+    NPCM7xxClockPLLState *pll = NPCM7XX_CLOCK_PLL(obj);
+
+    pll->clock_in = qdev_init_clock_in(DEVICE(pll), "clock-in",
+            npcm7xx_clk_update_pll, pll);
+    pll->clock_out = qdev_init_clock_out(DEVICE(pll), "clock-out");
+}
+
+static void npcm7xx_clk_sel_init(Object *obj)
+{
+    int i;
+    NPCM7xxClockSELState *sel = NPCM7XX_CLOCK_SEL(obj);
+
+    for (i = 0; i < NPCM7XX_CLK_SEL_MAX_INPUT; ++i) {
+        sel->clock_in[i] = qdev_init_clock_in(DEVICE(sel),
+                g_strdup_printf("clock-in[%d]", i),
+                npcm7xx_clk_update_sel, sel);
+    }
+    sel->clock_out = qdev_init_clock_out(DEVICE(sel), "clock-out");
+}
+static void npcm7xx_clk_divider_init(Object *obj)
+{
+    NPCM7xxClockDividerState *div = NPCM7XX_CLOCK_DIVIDER(obj);
+
+    div->clock_in = qdev_init_clock_in(DEVICE(div), "clock-in",
+            npcm7xx_clk_update_divider, div);
+    div->clock_out = qdev_init_clock_out(DEVICE(div), "clock-out");
+}
+
+static void npcm7xx_init_clock_pll(NPCM7xxClockPLLState *pll,
+        NPCM7xxCLKState *clk, const PLLInitInfo *init_info)
+{
+    pll->name = init_info->name;
+    pll->clk = clk;
+    pll->reg = init_info->reg;
+    if (init_info->public_name != NULL) {
+        qdev_alias_clock(DEVICE(pll), "clock-out", DEVICE(clk),
+                init_info->public_name);
+    }
+}
+
+static void npcm7xx_init_clock_sel(NPCM7xxClockSELState *sel,
+        NPCM7xxCLKState *clk, const SELInitInfo *init_info)
+{
+    int input_size = init_info->input_size;
+
+    sel->name = init_info->name;
+    sel->clk = clk;
+    sel->input_size = init_info->input_size;
+    g_assert(input_size <= NPCM7XX_CLK_SEL_MAX_INPUT);
+    sel->offset = init_info->offset;
+    sel->len = init_info->len;
+    if (init_info->public_name != NULL) {
+        qdev_alias_clock(DEVICE(sel), "clock-out", DEVICE(clk),
+                init_info->public_name);
+    }
+}
+
+static void npcm7xx_init_clock_divider(NPCM7xxClockDividerState *div,
+        NPCM7xxCLKState *clk, const DividerInitInfo *init_info)
+{
+    div->name = init_info->name;
+    div->clk = clk;
+
+    div->divide = init_info->divide;
+    if (div->divide == divide_by_constant) {
+        div->divisor = init_info->divisor;
+    } else {
+        div->reg = init_info->reg;
+        div->offset = init_info->offset;
+        div->len = init_info->len;
+    }
+    if (init_info->public_name != NULL) {
+        qdev_alias_clock(DEVICE(div), "clock-out", DEVICE(clk),
+                init_info->public_name);
+    }
+}
+
+static Clock *npcm7xx_get_clock(NPCM7xxCLKState *clk, ClockSrcType type,
+        int index)
+{
+    switch (type) {
+    case CLKSRC_REF:
+        return clk->clkref;
+    case CLKSRC_PLL:
+        return clk->plls[index].clock_out;
+    case CLKSRC_SEL:
+        return clk->sels[index].clock_out;
+    case CLKSRC_DIV:
+        return clk->dividers[index].clock_out;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static void npcm7xx_connect_clocks(NPCM7xxCLKState *clk)
+{
+    int i, j;
+    Clock *src;
+
+    for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) {
+        src = npcm7xx_get_clock(clk, pll_init_info_list[i].src_type,
+                pll_init_info_list[i].src_index);
+        clock_set_source(clk->plls[i].clock_in, src);
+    }
+    for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) {
+        for (j = 0; j < sel_init_info_list[i].input_size; ++j) {
+            src = npcm7xx_get_clock(clk, sel_init_info_list[i].src_type[j],
+                    sel_init_info_list[i].src_index[j]);
+            clock_set_source(clk->sels[i].clock_in[j], src);
+        }
+    }
+    for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) {
+        src = npcm7xx_get_clock(clk, divider_init_info_list[i].src_type,
+                divider_init_info_list[i].src_index);
+        clock_set_source(clk->dividers[i].clock_in, src);
+    }
+}
+
 static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size)
 {
     uint32_t reg = offset / sizeof(uint32_t);
@@ -129,7 +740,7 @@ static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size)
          *
          * The 4 LSBs are always zero: (1e9 / 640) << 4 = 25000000.
          */
-        value = (((now_ns - s->ref_ns) / 640) << 4) % NPCM7XX_TIMER_REF_HZ;
+        value = (((now_ns - s->ref_ns) / 640) << 4) % NPCM7XX_CLOCK_REF_HZ;
         break;
 
     default:
@@ -183,6 +794,20 @@ static void npcm7xx_clk_write(void *opaque, hwaddr offset,
                 value |= (value & PLLCON_LOKS);
             }
         }
+        /* Only update PLL when it is locked. */
+        if (value & PLLCON_LOKI) {
+            npcm7xx_clk_update_pll(&s->plls[find_pll_by_reg(reg)]);
+        }
+        break;
+
+    case NPCM7XX_CLK_CLKSEL:
+        npcm7xx_clk_update_all_sels(s);
+        break;
+
+    case NPCM7XX_CLK_CLKDIV1:
+    case NPCM7XX_CLK_CLKDIV2:
+    case NPCM7XX_CLK_CLKDIV3:
+        npcm7xx_clk_update_all_dividers(s);
         break;
 
     case NPCM7XX_CLK_CNTR25M:
@@ -234,6 +859,7 @@ static void npcm7xx_clk_enter_reset(Object *obj, ResetType type)
     case RESET_TYPE_COLD:
         memcpy(s->regs, cold_reset_values, sizeof(cold_reset_values));
         s->ref_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        npcm7xx_clk_update_all_clocks(s);
         return;
     }
 
@@ -245,6 +871,42 @@ static void npcm7xx_clk_enter_reset(Object *obj, ResetType type)
                   __func__, type);
 }
 
+static void npcm7xx_clk_init_clock_hierarchy(NPCM7xxCLKState *s)
+{
+    int i;
+
+    s->clkref = qdev_init_clock_in(DEVICE(s), "clkref", NULL, NULL);
+
+    /* First pass: init all converter modules */
+    QEMU_BUILD_BUG_ON(ARRAY_SIZE(pll_init_info_list) != NPCM7XX_CLOCK_NR_PLLS);
+    QEMU_BUILD_BUG_ON(ARRAY_SIZE(sel_init_info_list) != NPCM7XX_CLOCK_NR_SELS);
+    QEMU_BUILD_BUG_ON(ARRAY_SIZE(divider_init_info_list)
+            != NPCM7XX_CLOCK_NR_DIVIDERS);
+    for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) {
+        object_initialize_child(OBJECT(s), pll_init_info_list[i].name,
+                &s->plls[i], TYPE_NPCM7XX_CLOCK_PLL);
+        npcm7xx_init_clock_pll(&s->plls[i], s,
+                &pll_init_info_list[i]);
+    }
+    for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) {
+        object_initialize_child(OBJECT(s), sel_init_info_list[i].name,
+                &s->sels[i], TYPE_NPCM7XX_CLOCK_SEL);
+        npcm7xx_init_clock_sel(&s->sels[i], s,
+                &sel_init_info_list[i]);
+    }
+    for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) {
+        object_initialize_child(OBJECT(s), divider_init_info_list[i].name,
+                &s->dividers[i], TYPE_NPCM7XX_CLOCK_DIVIDER);
+        npcm7xx_init_clock_divider(&s->dividers[i], s,
+                &divider_init_info_list[i]);
+    }
+
+    /* Second pass: connect converter modules */
+    npcm7xx_connect_clocks(s);
+
+    clock_update_hz(s->clkref, NPCM7XX_CLOCK_REF_HZ);
+}
+
 static void npcm7xx_clk_init(Object *obj)
 {
     NPCM7xxCLKState *s = NPCM7XX_CLK(obj);
@@ -252,21 +914,114 @@ static void npcm7xx_clk_init(Object *obj)
     memory_region_init_io(&s->iomem, obj, &npcm7xx_clk_ops, s,
                           TYPE_NPCM7XX_CLK, 4 * KiB);
     sysbus_init_mmio(&s->parent, &s->iomem);
+}
+
+static int npcm7xx_clk_post_load(void *opaque, int version_id)
+{
+    if (version_id >= 1) {
+        NPCM7xxCLKState *clk = opaque;
+
+        npcm7xx_clk_update_all_clocks(clk);
+    }
+
+    return 0;
+}
+
+static void npcm7xx_clk_realize(DeviceState *dev, Error **errp)
+{
+    int i;
+    NPCM7xxCLKState *s = NPCM7XX_CLK(dev);
+
     qdev_init_gpio_in_named(DEVICE(s), npcm7xx_clk_perform_watchdog_reset,
             NPCM7XX_WATCHDOG_RESET_GPIO_IN, NPCM7XX_NR_WATCHDOGS);
+    npcm7xx_clk_init_clock_hierarchy(s);
+
+    /* Realize child devices */
+    for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) {
+        if (!qdev_realize(DEVICE(&s->plls[i]), NULL, errp)) {
+            return;
+        }
+    }
+    for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) {
+        if (!qdev_realize(DEVICE(&s->sels[i]), NULL, errp)) {
+            return;
+        }
+    }
+    for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) {
+        if (!qdev_realize(DEVICE(&s->dividers[i]), NULL, errp)) {
+            return;
+        }
+    }
 }
 
-static const VMStateDescription vmstate_npcm7xx_clk = {
-    .name = "npcm7xx-clk",
+static const VMStateDescription vmstate_npcm7xx_clk_pll = {
+    .name = "npcm7xx-clock-pll",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields =  (VMStateField[]) {
+        VMSTATE_CLOCK(clock_in, NPCM7xxClockPLLState),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static const VMStateDescription vmstate_npcm7xx_clk_sel = {
+    .name = "npcm7xx-clock-sel",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields =  (VMStateField[]) {
+        VMSTATE_ARRAY_OF_POINTER_TO_STRUCT(clock_in, NPCM7xxClockSELState,
+                NPCM7XX_CLK_SEL_MAX_INPUT, 0, vmstate_clock, Clock),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static const VMStateDescription vmstate_npcm7xx_clk_divider = {
+    .name = "npcm7xx-clock-divider",
     .version_id = 0,
     .minimum_version_id = 0,
+    .fields =  (VMStateField[]) {
+        VMSTATE_CLOCK(clock_in, NPCM7xxClockDividerState),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static const VMStateDescription vmstate_npcm7xx_clk = {
+    .name = "npcm7xx-clk",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = npcm7xx_clk_post_load,
     .fields = (VMStateField[]) {
         VMSTATE_UINT32_ARRAY(regs, NPCM7xxCLKState, NPCM7XX_CLK_NR_REGS),
         VMSTATE_INT64(ref_ns, NPCM7xxCLKState),
+        VMSTATE_CLOCK(clkref, NPCM7xxCLKState),
         VMSTATE_END_OF_LIST(),
     },
 };
 
+static void npcm7xx_clk_pll_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->desc = "NPCM7xx Clock PLL Module";
+    dc->vmsd = &vmstate_npcm7xx_clk_pll;
+}
+
+static void npcm7xx_clk_sel_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->desc = "NPCM7xx Clock SEL Module";
+    dc->vmsd = &vmstate_npcm7xx_clk_sel;
+}
+
+static void npcm7xx_clk_divider_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->desc = "NPCM7xx Clock Divider Module";
+    dc->vmsd = &vmstate_npcm7xx_clk_divider;
+}
+
 static void npcm7xx_clk_class_init(ObjectClass *klass, void *data)
 {
     ResettableClass *rc = RESETTABLE_CLASS(klass);
@@ -276,9 +1031,34 @@ static void npcm7xx_clk_class_init(ObjectClass *klass, void *data)
 
     dc->desc = "NPCM7xx Clock Control Registers";
     dc->vmsd = &vmstate_npcm7xx_clk;
+    dc->realize = npcm7xx_clk_realize;
     rc->phases.enter = npcm7xx_clk_enter_reset;
 }
 
+static const TypeInfo npcm7xx_clk_pll_info = {
+    .name               = TYPE_NPCM7XX_CLOCK_PLL,
+    .parent             = TYPE_DEVICE,
+    .instance_size      = sizeof(NPCM7xxClockPLLState),
+    .instance_init      = npcm7xx_clk_pll_init,
+    .class_init         = npcm7xx_clk_pll_class_init,
+};
+
+static const TypeInfo npcm7xx_clk_sel_info = {
+    .name               = TYPE_NPCM7XX_CLOCK_SEL,
+    .parent             = TYPE_DEVICE,
+    .instance_size      = sizeof(NPCM7xxClockSELState),
+    .instance_init      = npcm7xx_clk_sel_init,
+    .class_init         = npcm7xx_clk_sel_class_init,
+};
+
+static const TypeInfo npcm7xx_clk_divider_info = {
+    .name               = TYPE_NPCM7XX_CLOCK_DIVIDER,
+    .parent             = TYPE_DEVICE,
+    .instance_size      = sizeof(NPCM7xxClockDividerState),
+    .instance_init      = npcm7xx_clk_divider_init,
+    .class_init         = npcm7xx_clk_divider_class_init,
+};
+
 static const TypeInfo npcm7xx_clk_info = {
     .name               = TYPE_NPCM7XX_CLK,
     .parent             = TYPE_SYS_BUS_DEVICE,
@@ -289,6 +1069,9 @@ static const TypeInfo npcm7xx_clk_info = {
 
 static void npcm7xx_clk_register_type(void)
 {
+    type_register_static(&npcm7xx_clk_pll_info);
+    type_register_static(&npcm7xx_clk_sel_info);
+    type_register_static(&npcm7xx_clk_divider_info);
     type_register_static(&npcm7xx_clk_info);
 }
 type_init(npcm7xx_clk_register_type);
diff --git a/include/hw/misc/npcm7xx_clk.h b/include/hw/misc/npcm7xx_clk.h
index 2338fbbdb5..f641f95f3e 100644
--- a/include/hw/misc/npcm7xx_clk.h
+++ b/include/hw/misc/npcm7xx_clk.h
@@ -17,6 +17,7 @@
 #define NPCM7XX_CLK_H
 
 #include "exec/memory.h"
+#include "hw/clock.h"
 #include "hw/sysbus.h"
 
 /*
@@ -33,16 +34,151 @@
 
 #define NPCM7XX_WATCHDOG_RESET_GPIO_IN "npcm7xx-clk-watchdog-reset-gpio-in"
 
-typedef struct NPCM7xxCLKState {
+/* Maximum amount of clock inputs in a SEL module. */
+#define NPCM7XX_CLK_SEL_MAX_INPUT 5
+
+/* PLLs in CLK module. */
+typedef enum NPCM7xxClockPLL {
+    NPCM7XX_CLOCK_PLL0,
+    NPCM7XX_CLOCK_PLL1,
+    NPCM7XX_CLOCK_PLL2,
+    NPCM7XX_CLOCK_PLLG,
+    NPCM7XX_CLOCK_NR_PLLS,
+} NPCM7xxClockPLL;
+
+/* SEL/MUX in CLK module. */
+typedef enum NPCM7xxClockSEL {
+    NPCM7XX_CLOCK_PIXCKSEL,
+    NPCM7XX_CLOCK_MCCKSEL,
+    NPCM7XX_CLOCK_CPUCKSEL,
+    NPCM7XX_CLOCK_CLKOUTSEL,
+    NPCM7XX_CLOCK_UARTCKSEL,
+    NPCM7XX_CLOCK_TIMCKSEL,
+    NPCM7XX_CLOCK_SDCKSEL,
+    NPCM7XX_CLOCK_GFXMSEL,
+    NPCM7XX_CLOCK_SUCKSEL,
+    NPCM7XX_CLOCK_NR_SELS,
+} NPCM7xxClockSEL;
+
+/* Dividers in CLK module. */
+typedef enum NPCM7xxClockDivider {
+    NPCM7XX_CLOCK_PLL1D2, /* PLL1/2 */
+    NPCM7XX_CLOCK_PLL2D2, /* PLL2/2 */
+    NPCM7XX_CLOCK_MC_DIVIDER,
+    NPCM7XX_CLOCK_AXI_DIVIDER,
+    NPCM7XX_CLOCK_AHB_DIVIDER,
+    NPCM7XX_CLOCK_AHB3_DIVIDER,
+    NPCM7XX_CLOCK_SPI0_DIVIDER,
+    NPCM7XX_CLOCK_SPIX_DIVIDER,
+    NPCM7XX_CLOCK_APB1_DIVIDER,
+    NPCM7XX_CLOCK_APB2_DIVIDER,
+    NPCM7XX_CLOCK_APB3_DIVIDER,
+    NPCM7XX_CLOCK_APB4_DIVIDER,
+    NPCM7XX_CLOCK_APB5_DIVIDER,
+    NPCM7XX_CLOCK_CLKOUT_DIVIDER,
+    NPCM7XX_CLOCK_UART_DIVIDER,
+    NPCM7XX_CLOCK_TIMER_DIVIDER,
+    NPCM7XX_CLOCK_ADC_DIVIDER,
+    NPCM7XX_CLOCK_MMC_DIVIDER,
+    NPCM7XX_CLOCK_SDHC_DIVIDER,
+    NPCM7XX_CLOCK_GFXM_DIVIDER, /* divide by 3 */
+    NPCM7XX_CLOCK_UTMI_DIVIDER,
+    NPCM7XX_CLOCK_NR_DIVIDERS,
+} NPCM7xxClockConverter;
+
+typedef struct NPCM7xxCLKState NPCM7xxCLKState;
+
+/**
+ * struct NPCM7xxClockPLLState - A PLL module in CLK module.
+ * @name: The name of the module.
+ * @clk: The CLK module that owns this module.
+ * @clock_in: The input clock of this module.
+ * @clock_out: The output clock of this module.
+ * @reg: The control registers for this PLL module.
+ */
+typedef struct NPCM7xxClockPLLState {
+    DeviceState parent;
+
+    const char *name;
+    NPCM7xxCLKState *clk;
+    Clock *clock_in;
+    Clock *clock_out;
+
+    int reg;
+} NPCM7xxClockPLLState;
+
+/**
+ * struct NPCM7xxClockSELState - A SEL module in CLK module.
+ * @name: The name of the module.
+ * @clk: The CLK module that owns this module.
+ * @input_size: The size of inputs of this module.
+ * @clock_in: The input clocks of this module.
+ * @clock_out: The output clocks of this module.
+ * @offset: The offset of this module in the control register.
+ * @len: The length of this module in the control register.
+ */
+typedef struct NPCM7xxClockSELState {
+    DeviceState parent;
+
+    const char *name;
+    NPCM7xxCLKState *clk;
+    uint8_t input_size;
+    Clock *clock_in[NPCM7XX_CLK_SEL_MAX_INPUT];
+    Clock *clock_out;
+
+    int offset;
+    int len;
+} NPCM7xxClockSELState;
+
+/**
+ * struct NPCM7xxClockDividerState - A Divider module in CLK module.
+ * @name: The name of the module.
+ * @clk: The CLK module that owns this module.
+ * @clock_in: The input clock of this module.
+ * @clock_out: The output clock of this module.
+ * @divide: The function the divider uses to divide the input.
+ * @reg: The index of the control register that contains the divisor.
+ * @offset: The offset of the divisor in the control register.
+ * @len: The length of the divisor in the control register.
+ * @divisor: The divisor for a constant divisor
+ */
+typedef struct NPCM7xxClockDividerState {
+    DeviceState parent;
+
+    const char *name;
+    NPCM7xxCLKState *clk;
+    Clock *clock_in;
+    Clock *clock_out;
+
+    uint32_t (*divide)(struct NPCM7xxClockDividerState *s);
+    union {
+        struct {
+            int reg;
+            int offset;
+            int len;
+        };
+        int divisor;
+    };
+} NPCM7xxClockDividerState;
+
+struct NPCM7xxCLKState {
     SysBusDevice parent;
 
     MemoryRegion iomem;
 
+    /* Clock converters */
+    NPCM7xxClockPLLState plls[NPCM7XX_CLOCK_NR_PLLS];
+    NPCM7xxClockSELState sels[NPCM7XX_CLOCK_NR_SELS];
+    NPCM7xxClockDividerState dividers[NPCM7XX_CLOCK_NR_DIVIDERS];
+
     uint32_t regs[NPCM7XX_CLK_NR_REGS];
 
     /* Time reference for SECCNT and CNTR25M, initialized by power on reset */
     int64_t ref_ns;
-} NPCM7xxCLKState;
+
+    /* The incoming reference clock. */
+    Clock *clkref;
+};
 
 #define TYPE_NPCM7XX_CLK "npcm7xx-clk"
 #define NPCM7XX_CLK(obj) OBJECT_CHECK(NPCM7xxCLKState, (obj), TYPE_NPCM7XX_CLK)
-- 
2.29.2.684.gfbc64c5ab5-goog



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 2/7] hw/timer: Refactor NPCM7XX Timer to use CLK clock
  2020-12-11  1:51 [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Hao Wu via
  2020-12-11  1:51 ` [PATCH 1/7] hw/misc: Add clock converter in NPCM7XX CLK module Hao Wu via
@ 2020-12-11  1:51 ` Hao Wu via
  2020-12-11  1:51 ` [PATCH 3/7] hw/adc: Add an ADC module for NPCM7XX Hao Wu via
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Hao Wu via @ 2020-12-11  1:51 UTC (permalink / raw)
  To: peter.maydell
  Cc: minyard, venture, qemu-devel, hskinnemoen, wuhaotsh, kfting,
	qemu-arm, Avi.Fishman

This patch makes NPCM7XX Timer to use a the timer clock generated by the
CLK module instead of the magic nubmer TIMER_REF_HZ.

Reviewed-by: Havard Skinnemoen <hskinnemoen@google.com>
Reviewed-by: Tyrone Ting <kfting@nuvoton.com>
Signed-off-by: Hao Wu <wuhaotsh@google.com>
---
 hw/arm/npcm7xx.c                 |  5 +++++
 hw/timer/npcm7xx_timer.c         | 25 +++++++++++++++----------
 include/hw/misc/npcm7xx_clk.h    |  6 ------
 include/hw/timer/npcm7xx_timer.h |  1 +
 4 files changed, 21 insertions(+), 16 deletions(-)

diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c
index 47e2b6fc40..fabfb1697b 100644
--- a/hw/arm/npcm7xx.c
+++ b/hw/arm/npcm7xx.c
@@ -22,6 +22,7 @@
 #include "hw/char/serial.h"
 #include "hw/loader.h"
 #include "hw/misc/unimp.h"
+#include "hw/qdev-clock.h"
 #include "hw/qdev-properties.h"
 #include "qapi/error.h"
 #include "qemu/units.h"
@@ -420,6 +421,10 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
         int first_irq;
         int j;
 
+        /* Connect the timer clock. */
+        qdev_connect_clock_in(DEVICE(&s->tim[i]), "clock", qdev_get_clock_out(
+                    DEVICE(&s->clk), "timer-clock"));
+
         sysbus_realize(sbd, &error_abort);
         sysbus_mmio_map(sbd, 0, npcm7xx_tim_addr[i]);
 
diff --git a/hw/timer/npcm7xx_timer.c b/hw/timer/npcm7xx_timer.c
index d24445bd6e..9469c959e2 100644
--- a/hw/timer/npcm7xx_timer.c
+++ b/hw/timer/npcm7xx_timer.c
@@ -17,8 +17,8 @@
 #include "qemu/osdep.h"
 
 #include "hw/irq.h"
+#include "hw/qdev-clock.h"
 #include "hw/qdev-properties.h"
-#include "hw/misc/npcm7xx_clk.h"
 #include "hw/timer/npcm7xx_timer.h"
 #include "migration/vmstate.h"
 #include "qemu/bitops.h"
@@ -130,7 +130,7 @@ static int64_t npcm7xx_timer_count_to_ns(NPCM7xxTimer *t, uint32_t count)
 {
     int64_t ns = count;
 
-    ns *= NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ;
+    ns *= NANOSECONDS_PER_SECOND / clock_get_hz(t->ctrl->clock);
     ns *= npcm7xx_tcsr_prescaler(t->tcsr);
 
     return ns;
@@ -141,7 +141,7 @@ static uint32_t npcm7xx_timer_ns_to_count(NPCM7xxTimer *t, int64_t ns)
 {
     int64_t count;
 
-    count = ns / (NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ);
+    count = ns / (NANOSECONDS_PER_SECOND / clock_get_hz(t->ctrl->clock));
     count /= npcm7xx_tcsr_prescaler(t->tcsr);
 
     return count;
@@ -166,8 +166,10 @@ static uint32_t npcm7xx_watchdog_timer_prescaler(const NPCM7xxWatchdogTimer *t)
 static void npcm7xx_watchdog_timer_reset_cycles(NPCM7xxWatchdogTimer *t,
         int64_t cycles)
 {
+    g_assert(clock_get_hz(t->ctrl->clock) == 25000000);
     uint32_t prescaler = npcm7xx_watchdog_timer_prescaler(t);
-    int64_t ns = (NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ) * cycles;
+    int64_t ns = (NANOSECONDS_PER_SECOND / clock_get_hz(t->ctrl->clock))
+        * cycles;
 
     /*
      * The reset function always clears the current timer. The caller of the
@@ -606,10 +608,11 @@ static void npcm7xx_timer_hold_reset(Object *obj)
     qemu_irq_lower(s->watchdog_timer.irq);
 }
 
-static void npcm7xx_timer_realize(DeviceState *dev, Error **errp)
+static void npcm7xx_timer_init(Object *obj)
 {
-    NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(dev);
+    NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(obj);
     SysBusDevice *sbd = &s->parent;
+    DeviceState *dev = DEVICE(obj);
     int i;
     NPCM7xxWatchdogTimer *w;
 
@@ -627,11 +630,12 @@ static void npcm7xx_timer_realize(DeviceState *dev, Error **errp)
             npcm7xx_watchdog_timer_expired, w);
     sysbus_init_irq(sbd, &w->irq);
 
-    memory_region_init_io(&s->iomem, OBJECT(s), &npcm7xx_timer_ops, s,
+    memory_region_init_io(&s->iomem, obj, &npcm7xx_timer_ops, s,
                           TYPE_NPCM7XX_TIMER, 4 * KiB);
     sysbus_init_mmio(sbd, &s->iomem);
     qdev_init_gpio_out_named(dev, &w->reset_signal,
             NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 1);
+    s->clock = qdev_init_clock_in(dev, "clock", NULL, NULL);
 }
 
 static const VMStateDescription vmstate_npcm7xx_base_timer = {
@@ -675,10 +679,11 @@ static const VMStateDescription vmstate_npcm7xx_watchdog_timer = {
 
 static const VMStateDescription vmstate_npcm7xx_timer_ctrl = {
     .name = "npcm7xx-timer-ctrl",
-    .version_id = 1,
-    .minimum_version_id = 1,
+    .version_id = 2,
+    .minimum_version_id = 2,
     .fields = (VMStateField[]) {
         VMSTATE_UINT32(tisr, NPCM7xxTimerCtrlState),
+        VMSTATE_CLOCK(clock, NPCM7xxTimerCtrlState),
         VMSTATE_STRUCT_ARRAY(timer, NPCM7xxTimerCtrlState,
                              NPCM7XX_TIMERS_PER_CTRL, 0, vmstate_npcm7xx_timer,
                              NPCM7xxTimer),
@@ -697,7 +702,6 @@ static void npcm7xx_timer_class_init(ObjectClass *klass, void *data)
     QEMU_BUILD_BUG_ON(NPCM7XX_TIMER_REGS_END > NPCM7XX_TIMER_NR_REGS);
 
     dc->desc = "NPCM7xx Timer Controller";
-    dc->realize = npcm7xx_timer_realize;
     dc->vmsd = &vmstate_npcm7xx_timer_ctrl;
     rc->phases.enter = npcm7xx_timer_enter_reset;
     rc->phases.hold = npcm7xx_timer_hold_reset;
@@ -708,6 +712,7 @@ static const TypeInfo npcm7xx_timer_info = {
     .parent             = TYPE_SYS_BUS_DEVICE,
     .instance_size      = sizeof(NPCM7xxTimerCtrlState),
     .class_init         = npcm7xx_timer_class_init,
+    .instance_init      = npcm7xx_timer_init,
 };
 
 static void npcm7xx_timer_register_type(void)
diff --git a/include/hw/misc/npcm7xx_clk.h b/include/hw/misc/npcm7xx_clk.h
index f641f95f3e..d5c8d16ca4 100644
--- a/include/hw/misc/npcm7xx_clk.h
+++ b/include/hw/misc/npcm7xx_clk.h
@@ -20,12 +20,6 @@
 #include "hw/clock.h"
 #include "hw/sysbus.h"
 
-/*
- * The reference clock frequency for the timer modules, and the SECCNT and
- * CNTR25M registers in this module, is always 25 MHz.
- */
-#define NPCM7XX_TIMER_REF_HZ            (25000000)
-
 /*
  * Number of registers in our device state structure. Don't change this without
  * incrementing the version_id in the vmstate.
diff --git a/include/hw/timer/npcm7xx_timer.h b/include/hw/timer/npcm7xx_timer.h
index 6993fd723a..d45c051b56 100644
--- a/include/hw/timer/npcm7xx_timer.h
+++ b/include/hw/timer/npcm7xx_timer.h
@@ -101,6 +101,7 @@ struct NPCM7xxTimerCtrlState {
 
     uint32_t    tisr;
 
+    Clock       *clock;
     NPCM7xxTimer timer[NPCM7XX_TIMERS_PER_CTRL];
     NPCM7xxWatchdogTimer watchdog_timer;
 };
-- 
2.29.2.684.gfbc64c5ab5-goog



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 3/7] hw/adc: Add an ADC module for NPCM7XX
  2020-12-11  1:51 [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Hao Wu via
  2020-12-11  1:51 ` [PATCH 1/7] hw/misc: Add clock converter in NPCM7XX CLK module Hao Wu via
  2020-12-11  1:51 ` [PATCH 2/7] hw/timer: Refactor NPCM7XX Timer to use CLK clock Hao Wu via
@ 2020-12-11  1:51 ` Hao Wu via
  2020-12-11  1:51 ` [PATCH 4/7] hw/misc: Add a PWM " Hao Wu via
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Hao Wu via @ 2020-12-11  1:51 UTC (permalink / raw)
  To: peter.maydell
  Cc: minyard, venture, qemu-devel, hskinnemoen, wuhaotsh, kfting,
	qemu-arm, Avi.Fishman

The ADC is part of NPCM7XX Module. Its behavior is controled by the
ADC_CON register. It converts one of the eight analog inputs into a
digital input and stores it in the ADC_DATA register when enabled.

Reviewed-by: Havard Skinnemoen <hskinnemoen@google.com>
Reviewed-by: Tyrone Ting <kfting@nuvoton.com>
Signed-off-by: Hao Wu <wuhaotsh@google.com>
---
 docs/system/arm/nuvoton.rst    |   2 +-
 hw/adc/meson.build             |   1 +
 hw/adc/npcm7xx_adc.c           | 318 ++++++++++++++++++++++++++
 hw/arm/npcm7xx.c               |  24 +-
 include/hw/adc/npcm7xx_adc.h   |  72 ++++++
 include/hw/arm/npcm7xx.h       |   2 +
 tests/qtest/meson.build        |   3 +-
 tests/qtest/npcm7xx_adc-test.c | 400 +++++++++++++++++++++++++++++++++
 8 files changed, 819 insertions(+), 3 deletions(-)
 create mode 100644 hw/adc/npcm7xx_adc.c
 create mode 100644 include/hw/adc/npcm7xx_adc.h
 create mode 100644 tests/qtest/npcm7xx_adc-test.c

diff --git a/docs/system/arm/nuvoton.rst b/docs/system/arm/nuvoton.rst
index b00d405d52..35829f8d0b 100644
--- a/docs/system/arm/nuvoton.rst
+++ b/docs/system/arm/nuvoton.rst
@@ -41,6 +41,7 @@ Supported devices
  * Random Number Generator (RNG)
  * USB host (USBH)
  * GPIO controller
+ * Analog to Digital Converter (ADC)
 
 Missing devices
 ---------------
@@ -58,7 +59,6 @@ Missing devices
  * USB device (USBD)
  * SMBus controller (SMBF)
  * Peripheral SPI controller (PSPI)
- * Analog to Digital Converter (ADC)
  * SD/MMC host
  * PECI interface
  * Pulse Width Modulation (PWM)
diff --git a/hw/adc/meson.build b/hw/adc/meson.build
index 0d62ae96ae..6ddee23813 100644
--- a/hw/adc/meson.build
+++ b/hw/adc/meson.build
@@ -1 +1,2 @@
 softmmu_ss.add(when: 'CONFIG_STM32F2XX_ADC', if_true: files('stm32f2xx_adc.c'))
+softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_adc.c'))
diff --git a/hw/adc/npcm7xx_adc.c b/hw/adc/npcm7xx_adc.c
new file mode 100644
index 0000000000..4492303977
--- /dev/null
+++ b/hw/adc/npcm7xx_adc.c
@@ -0,0 +1,318 @@
+/*
+ * Nuvoton NPCM7xx ADC 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 "hw/adc/npcm7xx_adc.h"
+#include "hw/qdev-clock.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qemu/units.h"
+
+/* 32-bit register indices. */
+enum NPCM7xxADCRegisters {
+    NPCM7XX_ADC_CON,
+    NPCM7XX_ADC_DATA,
+    NPCM7XX_ADC_REGS_END,
+};
+
+/* Register field definitions. */
+#define NPCM7XX_ADC_CON_MUX(rv) extract32(rv, 24, 4)
+#define NPCM7XX_ADC_CON_INT_EN  BIT(21)
+#define NPCM7XX_ADC_CON_REFSEL  BIT(19)
+#define NPCM7XX_ADC_CON_INT     BIT(18)
+#define NPCM7XX_ADC_CON_EN      BIT(17)
+#define NPCM7XX_ADC_CON_RST     BIT(16)
+#define NPCM7XX_ADC_CON_CONV    BIT(14)
+#define NPCM7XX_ADC_CON_DIV(rv) extract32(rv, 1, 8)
+
+#define NPCM7XX_ADC_MAX_RESULT      1023
+#define NPCM7XX_ADC_DEFAULT_IREF    2000000
+#define NPCM7XX_ADC_CONV_CYCLES     20
+#define NPCM7XX_ADC_RESET_CYCLES    10
+#define NPCM7XX_ADC_R0_INPUT        500000
+#define NPCM7XX_ADC_R1_INPUT        1500000
+
+static void npcm7xx_adc_reset(NPCM7xxADCState *s)
+{
+    timer_del(&s->conv_timer);
+    timer_del(&s->reset_timer);
+    s->con = 0x000c0001;
+    s->data = 0x00000000;
+}
+
+static uint32_t npcm7xx_adc_convert(uint32_t input, uint32_t ref)
+{
+    uint32_t result;
+
+    result = input * (NPCM7XX_ADC_MAX_RESULT + 1) / ref;
+    if (result > NPCM7XX_ADC_MAX_RESULT) {
+        result = NPCM7XX_ADC_MAX_RESULT;
+    }
+
+    return result;
+}
+
+static uint32_t npcm7xx_adc_prescaler(NPCM7xxADCState *s)
+{
+    return 2 * (NPCM7XX_ADC_CON_DIV(s->con) + 1);
+}
+
+static void npcm7xx_adc_start_timer(Clock *clk, QEMUTimer *timer,
+        uint32_t cycles, uint32_t prescaler)
+{
+    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    int64_t freq = clock_get_hz(clk);
+    int64_t ns;
+
+    ns = (NANOSECONDS_PER_SECOND * cycles * prescaler / freq);
+    ns += now;
+    timer_mod(timer, ns);
+}
+
+static void npcm7xx_adc_start_reset(NPCM7xxADCState *s)
+{
+    uint32_t prescaler = npcm7xx_adc_prescaler(s);
+
+    npcm7xx_adc_start_timer(s->clock, &s->reset_timer, NPCM7XX_ADC_RESET_CYCLES,
+            prescaler);
+}
+
+static void npcm7xx_adc_start_convert(NPCM7xxADCState *s)
+{
+    uint32_t prescaler = npcm7xx_adc_prescaler(s);
+
+    npcm7xx_adc_start_timer(s->clock, &s->conv_timer, NPCM7XX_ADC_CONV_CYCLES,
+            prescaler);
+}
+
+static void npcm7xx_adc_reset_done(void *opaque)
+{
+    NPCM7xxADCState *s = opaque;
+
+    npcm7xx_adc_reset(s);
+}
+
+static void npcm7xx_adc_convert_done(void *opaque)
+{
+    NPCM7xxADCState *s = opaque;
+    uint32_t input = NPCM7XX_ADC_CON_MUX(s->con);
+    uint32_t ref = (s->con & NPCM7XX_ADC_CON_REFSEL)
+        ? s->iref : s->vref;
+
+    g_assert(input < NPCM7XX_ADC_NUM_INPUTS);
+    s->data = npcm7xx_adc_convert(s->adci[input], ref);
+    if (s->con & NPCM7XX_ADC_CON_INT_EN) {
+        s->con |= NPCM7XX_ADC_CON_INT;
+        qemu_irq_raise(s->irq);
+    }
+    s->con &= ~NPCM7XX_ADC_CON_CONV;
+}
+
+static void npcm7xx_adc_calibrate(NPCM7xxADCState *adc)
+{
+    adc->calibration_r_values[0] = npcm7xx_adc_convert(NPCM7XX_ADC_R0_INPUT,
+            adc->iref);
+    adc->calibration_r_values[1] = npcm7xx_adc_convert(NPCM7XX_ADC_R1_INPUT,
+            adc->iref);
+}
+
+static void npcm7xx_adc_write_con(NPCM7xxADCState *s, uint32_t new_con)
+{
+    uint32_t old_con = s->con;
+
+    /* Write ADC_INT to 1 to clear it */
+    if (new_con & NPCM7XX_ADC_CON_INT) {
+        new_con &= ~NPCM7XX_ADC_CON_INT;
+    } else if (old_con & NPCM7XX_ADC_CON_INT) {
+        new_con |= NPCM7XX_ADC_CON_INT;
+    }
+
+    s->con = new_con;
+
+    if (s->con & NPCM7XX_ADC_CON_RST) {
+        if (!(old_con & NPCM7XX_ADC_CON_RST)) {
+            npcm7xx_adc_start_reset(s);
+        }
+    } else {
+        timer_del(&s->reset_timer);
+    }
+
+    if ((s->con & NPCM7XX_ADC_CON_EN)) {
+        if (s->con & NPCM7XX_ADC_CON_CONV) {
+            if (!(old_con & NPCM7XX_ADC_CON_CONV)) {
+                npcm7xx_adc_start_convert(s);
+            }
+        } else {
+            timer_del(&s->conv_timer);
+        }
+    }
+}
+
+static uint64_t npcm7xx_adc_read(void *opaque, hwaddr offset, unsigned size)
+{
+    uint64_t value = 0;
+    NPCM7xxADCState *s = opaque;
+    hwaddr reg = offset / sizeof(uint32_t);
+
+    switch (reg) {
+    case NPCM7XX_ADC_CON:
+        value = s->con;
+        break;
+
+    case NPCM7XX_ADC_DATA:
+        value = s->data;
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid offset 0x%04" HWADDR_PRIx "\n",
+                      __func__, offset);
+        break;
+    }
+
+    return value;
+}
+
+static void npcm7xx_adc_write(void *opaque, hwaddr offset, uint64_t v,
+        unsigned size)
+{
+    NPCM7xxADCState *s = opaque;
+    hwaddr reg = offset / sizeof(uint32_t);
+
+    switch (reg) {
+    case NPCM7XX_ADC_CON:
+        npcm7xx_adc_write_con(s, v);
+        break;
+
+    case NPCM7XX_ADC_DATA:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
+                      __func__, offset);
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid offset 0x%04" HWADDR_PRIx "\n",
+                      __func__, offset);
+        break;
+    }
+
+}
+
+static const struct MemoryRegionOps npcm7xx_adc_ops = {
+    .read       = npcm7xx_adc_read,
+    .write      = npcm7xx_adc_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid      = {
+        .min_access_size        = 4,
+        .max_access_size        = 4,
+        .unaligned              = false,
+    },
+};
+
+static void npcm7xx_adc_enter_reset(Object *obj, ResetType type)
+{
+    NPCM7xxADCState *s = NPCM7XX_ADC(obj);
+
+    npcm7xx_adc_reset(s);
+}
+
+static void npcm7xx_adc_hold_reset(Object *obj)
+{
+    NPCM7xxADCState *s = NPCM7XX_ADC(obj);
+
+    qemu_irq_lower(s->irq);
+}
+
+static void npcm7xx_adc_init(Object *obj)
+{
+    NPCM7xxADCState *s = NPCM7XX_ADC(obj);
+    SysBusDevice *sbd = &s->parent;
+    int i;
+
+    sysbus_init_irq(sbd, &s->irq);
+
+    timer_init_ns(&s->conv_timer, QEMU_CLOCK_VIRTUAL,
+            npcm7xx_adc_convert_done, s);
+    timer_init_ns(&s->reset_timer, QEMU_CLOCK_VIRTUAL,
+            npcm7xx_adc_reset_done, s);
+    memory_region_init_io(&s->iomem, obj, &npcm7xx_adc_ops, s,
+                          TYPE_NPCM7XX_ADC, 4 * KiB);
+    sysbus_init_mmio(sbd, &s->iomem);
+    s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL);
+
+    for (i = 0; i < NPCM7XX_ADC_NUM_INPUTS; ++i) {
+        object_property_add_uint32_ptr(obj, "adci[*]",
+                &s->adci[i], OBJ_PROP_FLAG_WRITE);
+    }
+    object_property_add_uint32_ptr(obj, "vref",
+            &s->vref, OBJ_PROP_FLAG_WRITE);
+    npcm7xx_adc_calibrate(s);
+}
+
+static const VMStateDescription vmstate_npcm7xx_adc = {
+    .name = "npcm7xx-adc",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (VMStateField[]) {
+        VMSTATE_TIMER(conv_timer, NPCM7xxADCState),
+        VMSTATE_TIMER(reset_timer, NPCM7xxADCState),
+        VMSTATE_UINT32(con, NPCM7xxADCState),
+        VMSTATE_UINT32(data, NPCM7xxADCState),
+        VMSTATE_CLOCK(clock, NPCM7xxADCState),
+        VMSTATE_UINT32_ARRAY(adci, NPCM7xxADCState, NPCM7XX_ADC_NUM_INPUTS),
+        VMSTATE_UINT32(vref, NPCM7xxADCState),
+        VMSTATE_UINT32(iref, NPCM7xxADCState),
+        VMSTATE_UINT16_ARRAY(calibration_r_values, NPCM7xxADCState,
+                NPCM7XX_ADC_NUM_CALIB),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static Property npcm7xx_timer_properties[] = {
+    DEFINE_PROP_UINT32("iref", NPCM7xxADCState, iref, NPCM7XX_ADC_DEFAULT_IREF),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void npcm7xx_adc_class_init(ObjectClass *klass, void *data)
+{
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->desc = "NPCM7xx ADC Module";
+    dc->vmsd = &vmstate_npcm7xx_adc;
+    rc->phases.enter = npcm7xx_adc_enter_reset;
+    rc->phases.hold = npcm7xx_adc_hold_reset;
+
+    device_class_set_props(dc, npcm7xx_timer_properties);
+}
+
+static const TypeInfo npcm7xx_adc_info = {
+    .name               = TYPE_NPCM7XX_ADC,
+    .parent             = TYPE_SYS_BUS_DEVICE,
+    .instance_size      = sizeof(NPCM7xxADCState),
+    .class_init         = npcm7xx_adc_class_init,
+    .instance_init      = npcm7xx_adc_init,
+};
+
+static void npcm7xx_adc_register_types(void)
+{
+    type_register_static(&npcm7xx_adc_info);
+}
+
+type_init(npcm7xx_adc_register_types);
diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c
index fabfb1697b..b22a8c966d 100644
--- a/hw/arm/npcm7xx.c
+++ b/hw/arm/npcm7xx.c
@@ -51,6 +51,9 @@
 #define NPCM7XX_EHCI_BA         (0xf0806000)
 #define NPCM7XX_OHCI_BA         (0xf0807000)
 
+/* ADC Module */
+#define NPCM7XX_ADC_BA          (0xf000c000)
+
 /* Internal AHB SRAM */
 #define NPCM7XX_RAM3_BA         (0xc0008000)
 #define NPCM7XX_RAM3_SZ         (4 * KiB)
@@ -61,6 +64,7 @@
 #define NPCM7XX_ROM_BA          (0xffff0000)
 #define NPCM7XX_ROM_SZ          (64 * KiB)
 
+
 /* Clock configuration values to be fixed up when bypassing bootloader */
 
 /* Run PLL1 at 1600 MHz */
@@ -73,6 +77,7 @@
  * interrupts.
  */
 enum NPCM7xxInterrupt {
+    NPCM7XX_ADC_IRQ             = 0,
     NPCM7XX_UART0_IRQ           = 2,
     NPCM7XX_UART1_IRQ,
     NPCM7XX_UART2_IRQ,
@@ -296,6 +301,14 @@ static void npcm7xx_init_fuses(NPCM7xxState *s)
                             sizeof(value));
 }
 
+static void npcm7xx_write_adc_calibration(NPCM7xxState *s)
+{
+    /* Both ADC and the fuse array must have realized. */
+    QEMU_BUILD_BUG_ON(sizeof(s->adc.calibration_r_values) != 4);
+    npcm7xx_otp_array_write(&s->fuse_array, s->adc.calibration_r_values,
+            NPCM7XX_FUSE_ADC_CALIB, sizeof(s->adc.calibration_r_values));
+}
+
 static qemu_irq npcm7xx_irq(NPCM7xxState *s, int n)
 {
     return qdev_get_gpio_in(DEVICE(&s->a9mpcore), n);
@@ -322,6 +335,7 @@ static void npcm7xx_init(Object *obj)
                             TYPE_NPCM7XX_FUSE_ARRAY);
     object_initialize_child(obj, "mc", &s->mc, TYPE_NPCM7XX_MC);
     object_initialize_child(obj, "rng", &s->rng, TYPE_NPCM7XX_RNG);
+    object_initialize_child(obj, "adc", &s->adc, TYPE_NPCM7XX_ADC);
 
     for (i = 0; i < ARRAY_SIZE(s->tim); i++) {
         object_initialize_child(obj, "tim[*]", &s->tim[i], TYPE_NPCM7XX_TIMER);
@@ -414,6 +428,15 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
     sysbus_realize(SYS_BUS_DEVICE(&s->mc), &error_abort);
     sysbus_mmio_map(SYS_BUS_DEVICE(&s->mc), 0, NPCM7XX_MC_BA);
 
+    /* ADC Modules. Cannot fail. */
+    qdev_connect_clock_in(DEVICE(&s->adc), "clock", qdev_get_clock_out(
+                          DEVICE(&s->clk), "adc-clock"));
+    sysbus_realize(SYS_BUS_DEVICE(&s->adc), &error_abort);
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->adc), 0, NPCM7XX_ADC_BA);
+    sysbus_connect_irq(SYS_BUS_DEVICE(&s->adc), 0,
+            npcm7xx_irq(s, NPCM7XX_ADC_IRQ));
+    npcm7xx_write_adc_calibration(s);
+
     /* Timer Modules (TIM). Cannot fail. */
     QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_tim_addr) != ARRAY_SIZE(s->tim));
     for (i = 0; i < ARRAY_SIZE(s->tim); i++) {
@@ -528,7 +551,6 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
     create_unimplemented_device("npcm7xx.vdmx",         0xe0800000,   4 * KiB);
     create_unimplemented_device("npcm7xx.pcierc",       0xe1000000,  64 * KiB);
     create_unimplemented_device("npcm7xx.kcs",          0xf0007000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.adc",          0xf000c000,   4 * KiB);
     create_unimplemented_device("npcm7xx.gfxi",         0xf000e000,   4 * KiB);
     create_unimplemented_device("npcm7xx.gpio[0]",      0xf0010000,   4 * KiB);
     create_unimplemented_device("npcm7xx.gpio[1]",      0xf0011000,   4 * KiB);
diff --git a/include/hw/adc/npcm7xx_adc.h b/include/hw/adc/npcm7xx_adc.h
new file mode 100644
index 0000000000..7f9acbeaa1
--- /dev/null
+++ b/include/hw/adc/npcm7xx_adc.h
@@ -0,0 +1,72 @@
+/*
+ * Nuvoton NPCM7xx ADC 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.
+ */
+#ifndef NPCM7XX_ADC_H
+#define NPCM7XX_ADC_H
+
+#include "qemu/osdep.h"
+#include "hw/clock.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+
+#define NPCM7XX_ADC_NUM_INPUTS      8
+/**
+ * This value should not be changed unless write_adc_calibration function in
+ * hw/arm/npcm7xx.c is also changed.
+ */
+#define NPCM7XX_ADC_NUM_CALIB       2
+
+/**
+ * struct NPCM7xxADCState - Analog to Digital Converter Module device state.
+ * @parent: System bus device.
+ * @iomem: Memory region through which registers are accessed.
+ * @conv_timer: The timer counts down remaining cycles for the conversion.
+ * @reset_timer: The timer counts down remaining cycles for reset.
+ * @irq: GIC interrupt line to fire on expiration (if enabled).
+ * @con: The Control Register.
+ * @data: The Data Buffer.
+ * @clock: The ADC Clock.
+ * @adci: The input voltage in units of uV. 1uv = 1e-6V.
+ * @vref: The external reference voltage.
+ * @iref: The internal reference voltage, initialized at launch time.
+ * @rv: The calibrated output values of 0.5V and 1.5V for the ADC.
+ */
+typedef struct {
+    SysBusDevice parent;
+
+    MemoryRegion iomem;
+
+    QEMUTimer    conv_timer;
+    QEMUTimer    reset_timer;
+
+    qemu_irq     irq;
+    uint32_t     con;
+    uint32_t     data;
+    Clock       *clock;
+
+    /* Voltages are in unit of uV. 1V = 1000000uV. */
+    uint32_t     adci[NPCM7XX_ADC_NUM_INPUTS];
+    uint32_t     vref;
+    uint32_t     iref;
+
+    uint16_t     calibration_r_values[NPCM7XX_ADC_NUM_CALIB];
+} NPCM7xxADCState;
+
+#define TYPE_NPCM7XX_ADC "npcm7xx-adc"
+#define NPCM7XX_ADC(obj) \
+    OBJECT_CHECK(NPCM7xxADCState, (obj), TYPE_NPCM7XX_ADC)
+
+#endif /* NPCM7XX_ADC_H */
diff --git a/include/hw/arm/npcm7xx.h b/include/hw/arm/npcm7xx.h
index 5469247e38..51e1c7620d 100644
--- a/include/hw/arm/npcm7xx.h
+++ b/include/hw/arm/npcm7xx.h
@@ -17,6 +17,7 @@
 #define NPCM7XX_H
 
 #include "hw/boards.h"
+#include "hw/adc/npcm7xx_adc.h"
 #include "hw/cpu/a9mpcore.h"
 #include "hw/gpio/npcm7xx_gpio.h"
 #include "hw/mem/npcm7xx_mc.h"
@@ -76,6 +77,7 @@ typedef struct NPCM7xxState {
     NPCM7xxGCRState     gcr;
     NPCM7xxCLKState     clk;
     NPCM7xxTimerCtrlState tim[3];
+    NPCM7xxADCState     adc;
     NPCM7xxOTPState     key_storage;
     NPCM7xxOTPState     fuse_array;
     NPCM7xxMCState      mc;
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 6a67c538be..955710d1c5 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -134,7 +134,8 @@ qtests_sparc64 = \
   ['prom-env-test', 'boot-serial-test']
 
 qtests_npcm7xx = \
-  ['npcm7xx_gpio-test',
+  ['npcm7xx_adc-test',
+   'npcm7xx_gpio-test',
    'npcm7xx_rng-test',
    'npcm7xx_timer-test',
    'npcm7xx_watchdog_timer-test']
diff --git a/tests/qtest/npcm7xx_adc-test.c b/tests/qtest/npcm7xx_adc-test.c
new file mode 100644
index 0000000000..e63c544e51
--- /dev/null
+++ b/tests/qtest/npcm7xx_adc-test.c
@@ -0,0 +1,400 @@
+/*
+ * QTests for Nuvoton NPCM7xx ADCModules.
+ *
+ * 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 "qemu/bitops.h"
+#include "qemu/timer.h"
+#include "libqos/libqtest.h"
+#include "qapi/qmp/qdict.h"
+
+#define REF_HZ          (25000000)
+
+#define CON_OFFSET      0x0
+#define DATA_OFFSET     0x4
+
+#define NUM_INPUTS      8
+#define DEFAULT_IREF    2000000
+#define CONV_CYCLES     20
+#define RESET_CYCLES    10
+#define R0_INPUT        500000
+#define R1_INPUT        1500000
+#define MAX_RESULT      1023
+
+#define DEFAULT_CLKDIV  5
+
+#define FUSE_ARRAY_BA   0xf018a000
+#define FCTL_OFFSET     0x14
+#define FST_OFFSET      0x0
+#define FADDR_OFFSET    0x4
+#define FDATA_OFFSET    0x8
+#define ADC_CALIB_ADDR  24
+#define FUSE_READ       0x2
+
+/* Register field definitions. */
+#define CON_MUX(rv) ((rv) << 24)
+#define CON_INT_EN  BIT(21)
+#define CON_REFSEL  BIT(19)
+#define CON_INT     BIT(18)
+#define CON_EN      BIT(17)
+#define CON_RST     BIT(16)
+#define CON_CONV    BIT(14)
+#define CON_DIV(rv) extract32(rv, 1, 8)
+
+#define FST_RDST    BIT(1)
+#define FDATA_MASK  0xff
+
+#define MAX_ERROR   10000
+#define MIN_CALIB_INPUT 100000
+#define MAX_CALIB_INPUT 1800000
+
+static const uint32_t input_list[] = {
+    100000,
+    500000,
+    1000000,
+    1500000,
+    1800000,
+    2000000,
+};
+
+static const uint32_t vref_list[] = {
+    2000000,
+    2200000,
+    2500000,
+};
+
+static const uint32_t iref_list[] = {
+    1800000,
+    1900000,
+    2000000,
+    2100000,
+    2200000,
+};
+
+static const uint32_t div_list[] = {0, 1, 3, 7, 15};
+
+typedef struct ADC {
+    int irq;
+    uint64_t base_addr;
+} ADC;
+
+ADC adc = {
+    .irq        = 0,
+    .base_addr  = 0xf000c000
+};
+
+static uint32_t adc_read_con(QTestState *qts, const ADC *adc)
+{
+    return qtest_readl(qts, adc->base_addr + CON_OFFSET);
+}
+
+static void adc_write_con(QTestState *qts, const ADC *adc, uint32_t value)
+{
+    qtest_writel(qts, adc->base_addr + CON_OFFSET, value);
+}
+
+static uint32_t adc_read_data(QTestState *qts, const ADC *adc)
+{
+    return qtest_readl(qts, adc->base_addr + DATA_OFFSET);
+}
+
+static uint32_t adc_calibrate(uint32_t measured, uint32_t *rv)
+{
+    return R0_INPUT + (R1_INPUT - R0_INPUT) * (int32_t)(measured - rv[0])
+        / (int32_t)(rv[1] - rv[0]);
+}
+
+static void adc_qom_set(QTestState *qts, const ADC *adc,
+        const char *name, uint32_t value)
+{
+    QDict *response;
+    const char *path = "/machine/soc/adc";
+
+    g_test_message("Setting properties %s of %s with value %u",
+            name, path, value);
+    response = qtest_qmp(qts, "{ 'execute': 'qom-set',"
+            " 'arguments': { 'path': %s, 'property': %s, 'value': %u}}",
+            path, name, value);
+    /* The qom set message returns successfully. */
+    g_assert_true(qdict_haskey(response, "return"));
+}
+
+static void adc_write_input(QTestState *qts, const ADC *adc,
+        uint32_t index, uint32_t value)
+{
+    char name[100];
+
+    sprintf(name, "adci[%u]", index);
+    adc_qom_set(qts, adc, name, value);
+}
+
+static void adc_write_vref(QTestState *qts, const ADC *adc, uint32_t value)
+{
+    adc_qom_set(qts, adc, "vref", value);
+}
+
+static uint32_t adc_calculate_output(uint32_t input, uint32_t ref)
+{
+    uint32_t output;
+
+    g_assert_cmpuint(input, <=, ref);
+    output = (input * (MAX_RESULT + 1)) / ref;
+    if (output > MAX_RESULT) {
+        output = MAX_RESULT;
+    }
+
+    return output;
+}
+
+static uint32_t adc_prescaler(QTestState *qts, const ADC *adc)
+{
+    uint32_t div = extract32(adc_read_con(qts, adc), 1, 8);
+
+    return 2 * (div + 1);
+}
+
+static int64_t adc_calculate_steps(uint32_t cycles, uint32_t prescale,
+        uint32_t clkdiv)
+{
+    return (NANOSECONDS_PER_SECOND / (REF_HZ >> clkdiv)) * cycles * prescale;
+}
+
+static void adc_wait_conv_finished(QTestState *qts, const ADC *adc,
+        uint32_t clkdiv)
+{
+    uint32_t prescaler = adc_prescaler(qts, adc);
+
+    /*
+     * ADC should takes roughly 20 cycles to convert one sample. So we assert it
+     * should take 10~30 cycles here.
+     */
+    qtest_clock_step(qts, adc_calculate_steps(CONV_CYCLES / 2, prescaler,
+                clkdiv));
+    /* ADC is still converting. */
+    g_assert_true(adc_read_con(qts, adc) & CON_CONV);
+    qtest_clock_step(qts, adc_calculate_steps(CONV_CYCLES, prescaler, clkdiv));
+    /* ADC has finished conversion. */
+    g_assert_false(adc_read_con(qts, adc) & CON_CONV);
+}
+
+/* Check ADC can be reset to default value. */
+static void test_init(gconstpointer adc_p)
+{
+    const ADC *adc = adc_p;
+
+    QTestState *qts = qtest_init("-machine quanta-gsj");
+    adc_write_con(qts, adc, CON_REFSEL | CON_INT);
+    g_assert_cmphex(adc_read_con(qts, adc), ==, CON_REFSEL);
+    qtest_quit(qts);
+}
+
+/* Check ADC can convert from an internal reference. */
+static void test_convert_internal(gconstpointer adc_p)
+{
+    const ADC *adc = adc_p;
+    uint32_t index, input, output, expected_output;
+    QTestState *qts = qtest_init("-machine quanta-gsj");
+    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+
+    for (index = 0; index < NUM_INPUTS; ++index) {
+        for (size_t i = 0; i < ARRAY_SIZE(input_list); ++i) {
+            input = input_list[i];
+            expected_output = adc_calculate_output(input, DEFAULT_IREF);
+
+            adc_write_input(qts, adc, index, input);
+            adc_write_con(qts, adc, CON_MUX(index) | CON_REFSEL | CON_INT |
+                    CON_EN | CON_CONV);
+            adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV);
+            g_assert_cmphex(adc_read_con(qts, adc), ==, CON_MUX(index) |
+                    CON_REFSEL | CON_EN);
+            g_assert_false(qtest_get_irq(qts, adc->irq));
+            output = adc_read_data(qts, adc);
+            g_assert_cmpuint(output, ==, expected_output);
+        }
+    }
+
+    qtest_quit(qts);
+}
+
+/* Check ADC can convert from an external reference. */
+static void test_convert_external(gconstpointer adc_p)
+{
+    const ADC *adc = adc_p;
+    uint32_t index, input, vref, output, expected_output;
+    QTestState *qts = qtest_init("-machine quanta-gsj");
+    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+
+    for (index = 0; index < NUM_INPUTS; ++index) {
+        for (size_t i = 0; i < ARRAY_SIZE(input_list); ++i) {
+            for (size_t j = 0; j < ARRAY_SIZE(vref_list); ++j) {
+                input = input_list[i];
+                vref = vref_list[j];
+                expected_output = adc_calculate_output(input, vref);
+
+                adc_write_input(qts, adc, index, input);
+                adc_write_vref(qts, adc, vref);
+                adc_write_con(qts, adc, CON_MUX(index) | CON_INT | CON_EN |
+                        CON_CONV);
+                adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV);
+                g_assert_cmphex(adc_read_con(qts, adc), ==,
+                        CON_MUX(index) | CON_EN);
+                g_assert_false(qtest_get_irq(qts, adc->irq));
+                output = adc_read_data(qts, adc);
+                g_assert_cmpuint(output, ==, expected_output);
+            }
+        }
+    }
+
+    qtest_quit(qts);
+}
+
+/* Check ADC interrupt files if and only if CON_INT_EN is set. */
+static void test_interrupt(gconstpointer adc_p)
+{
+    const ADC *adc = adc_p;
+    uint32_t index, input, output, expected_output;
+    QTestState *qts = qtest_init("-machine quanta-gsj");
+
+    index = 1;
+    input = input_list[1];
+    expected_output = adc_calculate_output(input, DEFAULT_IREF);
+
+    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+    adc_write_input(qts, adc, index, input);
+    g_assert_false(qtest_get_irq(qts, adc->irq));
+    adc_write_con(qts, adc, CON_MUX(index) | CON_INT_EN | CON_REFSEL | CON_INT
+            | CON_EN | CON_CONV);
+    adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV);
+    g_assert_cmphex(adc_read_con(qts, adc), ==, CON_MUX(index) | CON_INT_EN
+            | CON_REFSEL | CON_INT | CON_EN);
+    g_assert_true(qtest_get_irq(qts, adc->irq));
+    output = adc_read_data(qts, adc);
+    g_assert_cmpuint(output, ==, expected_output);
+
+    qtest_quit(qts);
+}
+
+/* Check ADC is reset after setting ADC_RST for 10 ADC cycles. */
+static void test_reset(gconstpointer adc_p)
+{
+    const ADC *adc = adc_p;
+    QTestState *qts = qtest_init("-machine quanta-gsj");
+
+    for (size_t i = 0; i < ARRAY_SIZE(div_list); ++i) {
+        uint32_t div = div_list[i];
+
+        adc_write_con(qts, adc, CON_INT | CON_EN | CON_RST | CON_DIV(div));
+        qtest_clock_step(qts, adc_calculate_steps(RESET_CYCLES,
+                    adc_prescaler(qts, adc), DEFAULT_CLKDIV) - 1);
+        g_assert_true(adc_read_con(qts, adc) & CON_EN);
+        qtest_clock_step(qts, 1);
+        g_assert_false(adc_read_con(qts, adc) & CON_EN);
+    }
+    qtest_quit(qts);
+}
+
+/* Check ADC is not reset if we set ADC_RST for <10 ADC cycles. */
+static void test_premature_reset(gconstpointer adc_p)
+{
+    const ADC *adc = adc_p;
+    QTestState *qts = qtest_init("-machine quanta-gsj");
+
+    for (size_t i = 0; i < ARRAY_SIZE(div_list); ++i) {
+        uint32_t div = div_list[i];
+
+        adc_write_con(qts, adc, CON_INT | CON_EN | CON_RST | CON_DIV(div));
+        qtest_clock_step(qts, adc_calculate_steps(RESET_CYCLES,
+                    adc_prescaler(qts, adc), DEFAULT_CLKDIV) - 1);
+        g_assert_true(adc_read_con(qts, adc) & CON_EN);
+        adc_write_con(qts, adc, CON_INT | CON_EN | CON_DIV(div));
+        qtest_clock_step(qts, 1000);
+        g_assert_true(adc_read_con(qts, adc) & CON_EN);
+    }
+    qtest_quit(qts);
+}
+
+/* Check ADC Calibration works as desired. */
+static void test_calibrate(gconstpointer adc_p)
+{
+    int i, j;
+    const ADC *adc = adc_p;
+
+    for (j = 0; j < ARRAY_SIZE(iref_list); ++j) {
+        uint32_t iref = iref_list[j];
+        uint32_t expected_rv[] = {
+            adc_calculate_output(R0_INPUT, iref),
+            adc_calculate_output(R1_INPUT, iref),
+        };
+        char buf[100];
+        QTestState *qts;
+
+        sprintf(buf, "-machine quanta-gsj -global npcm7xx-adc.iref=%u", iref);
+        qts = qtest_init(buf);
+
+        /* Check the converted value is correct using the calibration value. */
+        for (i = 0; i < ARRAY_SIZE(input_list); ++i) {
+            uint32_t input;
+            uint32_t output;
+            uint32_t expected_output;
+            uint32_t calibrated_voltage;
+            uint32_t index = 0;
+
+            input = input_list[i];
+            /* Calibration only works for input range 0.1V ~ 1.8V. */
+            if (input < MIN_CALIB_INPUT || input > MAX_CALIB_INPUT) {
+                continue;
+            }
+            expected_output = adc_calculate_output(input, iref);
+
+            adc_write_input(qts, adc, index, input);
+            adc_write_con(qts, adc, CON_MUX(index) | CON_REFSEL | CON_INT |
+                    CON_EN | CON_CONV);
+            adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV);
+            g_assert_cmphex(adc_read_con(qts, adc), ==,
+                    CON_REFSEL | CON_MUX(index) | CON_EN);
+            output = adc_read_data(qts, adc);
+            g_assert_cmpuint(output, ==, expected_output);
+
+            calibrated_voltage = adc_calibrate(output, expected_rv);
+            g_assert_cmpuint(calibrated_voltage, >, input - MAX_ERROR);
+            g_assert_cmpuint(calibrated_voltage, <, input + MAX_ERROR);
+        }
+
+        qtest_quit(qts);
+    }
+}
+
+static void adc_add_test(const char *name, const ADC* wd,
+        GTestDataFunc fn)
+{
+    g_autofree char *full_name = g_strdup_printf("npcm7xx_adc/%s",  name);
+    qtest_add_data_func(full_name, wd, fn);
+}
+#define add_test(name, td) adc_add_test(#name, td, test_##name)
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    add_test(init, &adc);
+    add_test(convert_internal, &adc);
+    add_test(convert_external, &adc);
+    add_test(interrupt, &adc);
+    add_test(reset, &adc);
+    add_test(premature_reset, &adc);
+    add_test(calibrate, &adc);
+
+    return g_test_run();
+}
-- 
2.29.2.684.gfbc64c5ab5-goog



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 4/7] hw/misc: Add a PWM module for NPCM7XX
  2020-12-11  1:51 [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Hao Wu via
                   ` (2 preceding siblings ...)
  2020-12-11  1:51 ` [PATCH 3/7] hw/adc: Add an ADC module for NPCM7XX Hao Wu via
@ 2020-12-11  1:51 ` Hao Wu via
  2020-12-11  1:51 ` [PATCH 5/7] hw/ipmi: Add an IPMI host interface Hao Wu via
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Hao Wu via @ 2020-12-11  1:51 UTC (permalink / raw)
  To: peter.maydell
  Cc: minyard, venture, qemu-devel, hskinnemoen, wuhaotsh, kfting,
	qemu-arm, Avi.Fishman

The PWM module is part of NPCM7XX module. Each NPCM7XX module has two
identical PWM modules. Each module contains 4 PWM entries. Each PWM has
two outputs: frequency and duty_cycle. Both are computed using inputs
from software side.

This module does not model detail pulse signals since it is expensive.
It also does not model interrupts and watchdogs that are dependant on
the detail models. The interfaces for these are left in the module so
that anyone in need for these functionalities can implement on their
own.

Reviewed-by: Havard Skinnemoen <hskinnemoen@google.com>
Reviewed-by: Tyrone Ting <kfting@nuvoton.com>
Signed-off-by: Hao Wu <wuhaotsh@google.com>
---
 docs/system/arm/nuvoton.rst    |   2 +-
 hw/arm/npcm7xx.c               |  26 +-
 hw/misc/meson.build            |   1 +
 hw/misc/npcm7xx_pwm.c          | 535 +++++++++++++++++++++++++++++++++
 include/hw/arm/npcm7xx.h       |   2 +
 include/hw/misc/npcm7xx_pwm.h  | 106 +++++++
 tests/qtest/meson.build        |   1 +
 tests/qtest/npcm7xx_pwm-test.c | 490 ++++++++++++++++++++++++++++++
 8 files changed, 1160 insertions(+), 3 deletions(-)
 create mode 100644 hw/misc/npcm7xx_pwm.c
 create mode 100644 include/hw/misc/npcm7xx_pwm.h
 create mode 100644 tests/qtest/npcm7xx_pwm-test.c

diff --git a/docs/system/arm/nuvoton.rst b/docs/system/arm/nuvoton.rst
index 35829f8d0b..a1786342e2 100644
--- a/docs/system/arm/nuvoton.rst
+++ b/docs/system/arm/nuvoton.rst
@@ -42,6 +42,7 @@ Supported devices
  * USB host (USBH)
  * GPIO controller
  * Analog to Digital Converter (ADC)
+ * Pulse Width Modulation (PWM)
 
 Missing devices
 ---------------
@@ -61,7 +62,6 @@ Missing devices
  * Peripheral SPI controller (PSPI)
  * SD/MMC host
  * PECI interface
- * Pulse Width Modulation (PWM)
  * Tachometer
  * PCI and PCIe root complex and bridges
  * VDM and MCTP support
diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c
index b22a8c966d..72040d4079 100644
--- a/hw/arm/npcm7xx.c
+++ b/hw/arm/npcm7xx.c
@@ -102,6 +102,8 @@ enum NPCM7xxInterrupt {
     NPCM7XX_WDG2_IRQ,                   /* Timer Module 2 Watchdog */
     NPCM7XX_EHCI_IRQ            = 61,
     NPCM7XX_OHCI_IRQ            = 62,
+    NPCM7XX_PWM0_IRQ            = 93,   /* PWM module 0 */
+    NPCM7XX_PWM1_IRQ,                   /* PWM module 1 */
     NPCM7XX_GPIO0_IRQ           = 116,
     NPCM7XX_GPIO1_IRQ,
     NPCM7XX_GPIO2_IRQ,
@@ -144,6 +146,12 @@ static const hwaddr npcm7xx_fiu3_flash_addr[] = {
     0xb8000000, /* CS3 */
 };
 
+/* Register base address for each PWM Module */
+static const hwaddr npcm7xx_pwm_addr[] = {
+    0xf0103000,
+    0xf0104000,
+};
+
 static const struct {
     hwaddr regs_addr;
     uint32_t unconnected_pins;
@@ -353,6 +361,10 @@ static void npcm7xx_init(Object *obj)
         object_initialize_child(obj, npcm7xx_fiu[i].name, &s->fiu[i],
                                 TYPE_NPCM7XX_FIU);
     }
+
+    for (i = 0; i < ARRAY_SIZE(s->pwm); i++) {
+        object_initialize_child(obj, "pwm[*]", &s->pwm[i], TYPE_NPCM7XX_PWM);
+    }
 }
 
 static void npcm7xx_realize(DeviceState *dev, Error **errp)
@@ -513,6 +525,18 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
     sysbus_connect_irq(SYS_BUS_DEVICE(&s->ohci), 0,
                        npcm7xx_irq(s, NPCM7XX_OHCI_IRQ));
 
+    /* PWM Modules. Cannot fail. */
+    QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_pwm_addr) != ARRAY_SIZE(s->pwm));
+    for (i = 0; i < ARRAY_SIZE(s->pwm); i++) {
+        SysBusDevice *sbd = SYS_BUS_DEVICE(&s->pwm[i]);
+
+        qdev_connect_clock_in(DEVICE(&s->pwm[i]), "clock", qdev_get_clock_out(
+                    DEVICE(&s->clk), "apb3-clock"));
+        sysbus_realize(sbd, &error_abort);
+        sysbus_mmio_map(sbd, 0, npcm7xx_pwm_addr[i]);
+        sysbus_connect_irq(sbd, i, npcm7xx_irq(s, NPCM7XX_PWM0_IRQ + i));
+    }
+
     /*
      * Flash Interface Unit (FIU). Can fail if incorrect number of chip selects
      * specified, but this is a programming error.
@@ -580,8 +604,6 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
     create_unimplemented_device("npcm7xx.peci",         0xf0100000,   4 * KiB);
     create_unimplemented_device("npcm7xx.siox[1]",      0xf0101000,   4 * KiB);
     create_unimplemented_device("npcm7xx.siox[2]",      0xf0102000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.pwm[0]",       0xf0103000,   4 * KiB);
-    create_unimplemented_device("npcm7xx.pwm[1]",       0xf0104000,   4 * KiB);
     create_unimplemented_device("npcm7xx.mft[0]",       0xf0180000,   4 * KiB);
     create_unimplemented_device("npcm7xx.mft[1]",       0xf0181000,   4 * KiB);
     create_unimplemented_device("npcm7xx.mft[2]",       0xf0182000,   4 * KiB);
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index ce15ffceb9..607cd38a21 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -64,6 +64,7 @@ softmmu_ss.add(when: 'CONFIG_MAINSTONE', if_true: files('mst_fpga.c'))
 softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files(
   'npcm7xx_clk.c',
   'npcm7xx_gcr.c',
+  'npcm7xx_pwm.c',
   'npcm7xx_rng.c',
 ))
 softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files(
diff --git a/hw/misc/npcm7xx_pwm.c b/hw/misc/npcm7xx_pwm.c
new file mode 100644
index 0000000000..c1753b2e3d
--- /dev/null
+++ b/hw/misc/npcm7xx_pwm.c
@@ -0,0 +1,535 @@
+/*
+ * Nuvoton NPCM7xx PWM 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/irq.h"
+#include "hw/qdev-clock.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/npcm7xx_pwm.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "trace.h"
+
+/* 32-bit register indices. */
+enum NPCM7xxPWMRegisters {
+    NPCM7XX_PWM_PPR,
+    NPCM7XX_PWM_CSR,
+    NPCM7XX_PWM_PCR,
+    NPCM7XX_PWM_CNR0,
+    NPCM7XX_PWM_CMR0,
+    NPCM7XX_PWM_PDR0,
+    NPCM7XX_PWM_CNR1,
+    NPCM7XX_PWM_CMR1,
+    NPCM7XX_PWM_PDR1,
+    NPCM7XX_PWM_CNR2,
+    NPCM7XX_PWM_CMR2,
+    NPCM7XX_PWM_PDR2,
+    NPCM7XX_PWM_CNR3,
+    NPCM7XX_PWM_CMR3,
+    NPCM7XX_PWM_PDR3,
+    NPCM7XX_PWM_PIER,
+    NPCM7XX_PWM_PIIR,
+    NPCM7XX_PWM_PWDR0,
+    NPCM7XX_PWM_PWDR1,
+    NPCM7XX_PWM_PWDR2,
+    NPCM7XX_PWM_PWDR3,
+    NPCM7XX_PWM_REGS_END,
+};
+
+/* Register field definitions. */
+#define NPCM7XX_PPR(rv, index)      extract32((rv), npcm7xx_ppr_base[index], 8)
+#define NPCM7XX_CSR(rv, index)      extract32((rv), npcm7xx_csr_base[index], 3)
+#define NPCM7XX_CH(rv, index)       extract32((rv), npcm7xx_ch_base[index], 4)
+#define NPCM7XX_CH_EN               BIT(0)
+#define NPCM7XX_CH_INV              BIT(2)
+#define NPCM7XX_CH_MOD              BIT(3)
+
+/* Offset of each PWM channel's prescaler in the PPR register. */
+static const int npcm7xx_ppr_base[] = { 0, 0, 8, 8 };
+/* Offset of each PWM channel's clock selector in the CSR register. */
+static const int npcm7xx_csr_base[] = { 0, 4, 8, 12 };
+/* Offset of each PWM channel's control variable in the PCR register. */
+static const int npcm7xx_ch_base[] = { 0, 8, 12, 16 };
+
+static uint32_t npcm7xx_pwm_calculate_freq(NPCM7xxPWM *p)
+{
+    uint32_t ppr;
+    uint32_t csr;
+    uint32_t freq;
+
+    if (!p->running) {
+        return 0;
+    }
+
+    csr = NPCM7XX_CSR(p->module->csr, p->index);
+    ppr = NPCM7XX_PPR(p->module->ppr, p->index);
+    freq = clock_get_hz(p->module->clock);
+    freq /= ppr + 1;
+    /* csr can only be 0~4 */
+    if (csr > 4) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid csr value %u\n",
+                      __func__, csr);
+        csr = 4;
+    }
+    /* freq won't be changed if csr == 4. */
+    if (csr < 4) {
+        freq >>= csr + 1;
+    }
+
+    return freq / (p->cnr + 1);
+}
+
+static uint32_t npcm7xx_pwm_calculate_duty(NPCM7xxPWM *p)
+{
+    uint64_t duty;
+
+    if (p->running) {
+        if (p->cnr == 0) {
+            duty = 0;
+        } else if (p->cmr >= p->cnr) {
+            duty = NPCM7XX_PWM_MAX_DUTY;
+        } else {
+            duty = NPCM7XX_PWM_MAX_DUTY * (p->cmr + 1) / (p->cnr + 1);
+        }
+    } else {
+        duty = 0;
+    }
+
+    if (p->inverted) {
+        duty = NPCM7XX_PWM_MAX_DUTY - duty;
+    }
+
+    return duty;
+}
+
+static void npcm7xx_pwm_calculate_output(NPCM7xxPWM *p)
+{
+    p->freq = npcm7xx_pwm_calculate_freq(p);
+    p->duty = npcm7xx_pwm_calculate_duty(p);
+}
+
+static void npcm7xx_pwm_write_ppr(NPCM7xxPWMState *s, uint32_t new_ppr)
+{
+    int i;
+    uint32_t old_ppr = s->ppr;
+
+    QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_ppr_base) != NPCM7XX_PWM_PER_MODULE);
+    s->ppr = new_ppr;
+    for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
+        if (NPCM7XX_PPR(old_ppr, i) != NPCM7XX_PPR(new_ppr, i)) {
+            s->pwm[i].freq = npcm7xx_pwm_calculate_freq(&s->pwm[i]);
+        }
+    }
+}
+
+static void npcm7xx_pwm_write_csr(NPCM7xxPWMState *s, uint32_t new_csr)
+{
+    int i;
+    uint32_t old_csr = s->csr;
+
+    QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_csr_base) != NPCM7XX_PWM_PER_MODULE);
+    s->csr = new_csr;
+    for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
+        if (NPCM7XX_CSR(old_csr, i) != NPCM7XX_CSR(new_csr, i)) {
+            s->pwm[i].freq = npcm7xx_pwm_calculate_freq(&s->pwm[i]);
+        }
+    }
+}
+
+static void npcm7xx_pwm_write_pcr(NPCM7xxPWMState *s, uint32_t new_pcr)
+{
+    int i;
+    bool inverted;
+    uint32_t pcr;
+    NPCM7xxPWM *p;
+
+    s->pcr = new_pcr;
+    QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_ch_base) != NPCM7XX_PWM_PER_MODULE);
+    for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
+        p = &s->pwm[i];
+        pcr = NPCM7XX_CH(new_pcr, i);
+        inverted = pcr & NPCM7XX_CH_INV;
+
+        /*
+         * We only run a PWM channel with toggle mode. Single-shot mode does not
+         * generate frequency and duty-cycle values.
+         */
+        if ((pcr & NPCM7XX_CH_EN) && (pcr & NPCM7XX_CH_MOD)) {
+            if (p->running) {
+                /* Re-run this PWM channel if inverted changed. */
+                if (p->inverted ^ inverted) {
+                    p->inverted = inverted;
+                    p->duty = npcm7xx_pwm_calculate_duty(p);
+                }
+            } else {
+                /* Run this PWM channel. */
+                p->running = true;
+                p->inverted = inverted;
+                npcm7xx_pwm_calculate_output(p);
+            }
+        } else {
+            /* Clear this PWM channel. */
+            p->running = false;
+            p->inverted = inverted;
+            npcm7xx_pwm_calculate_output(p);
+        }
+    }
+
+}
+
+static hwaddr npcm7xx_cnr_index(hwaddr reg)
+{
+    switch (reg) {
+    case NPCM7XX_PWM_CNR0:
+        return 0;
+    case NPCM7XX_PWM_CNR1:
+        return 1;
+    case NPCM7XX_PWM_CNR2:
+        return 2;
+    case NPCM7XX_PWM_CNR3:
+        return 3;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static hwaddr npcm7xx_cmr_index(hwaddr reg)
+{
+    switch (reg) {
+    case NPCM7XX_PWM_CMR0:
+        return 0;
+    case NPCM7XX_PWM_CMR1:
+        return 1;
+    case NPCM7XX_PWM_CMR2:
+        return 2;
+    case NPCM7XX_PWM_CMR3:
+        return 3;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static hwaddr npcm7xx_pdr_index(hwaddr reg)
+{
+    switch (reg) {
+    case NPCM7XX_PWM_PDR0:
+        return 0;
+    case NPCM7XX_PWM_PDR1:
+        return 1;
+    case NPCM7XX_PWM_PDR2:
+        return 2;
+    case NPCM7XX_PWM_PDR3:
+        return 3;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static hwaddr npcm7xx_pwdr_index(hwaddr reg)
+{
+    switch (reg) {
+    case NPCM7XX_PWM_PWDR0:
+        return 0;
+    case NPCM7XX_PWM_PWDR1:
+        return 1;
+    case NPCM7XX_PWM_PWDR2:
+        return 2;
+    case NPCM7XX_PWM_PWDR3:
+        return 3;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static uint64_t npcm7xx_pwm_read(void *opaque, hwaddr offset, unsigned size)
+{
+    NPCM7xxPWMState *s = opaque;
+    hwaddr reg = offset / sizeof(uint32_t);
+    uint64_t value = 0;
+
+    switch (reg) {
+    case NPCM7XX_PWM_CNR0:
+    case NPCM7XX_PWM_CNR1:
+    case NPCM7XX_PWM_CNR2:
+    case NPCM7XX_PWM_CNR3:
+        value = s->pwm[npcm7xx_cnr_index(reg)].cnr;
+        break;
+
+    case NPCM7XX_PWM_CMR0:
+    case NPCM7XX_PWM_CMR1:
+    case NPCM7XX_PWM_CMR2:
+    case NPCM7XX_PWM_CMR3:
+        value = s->pwm[npcm7xx_cmr_index(reg)].cmr;
+        break;
+
+    case NPCM7XX_PWM_PDR0:
+    case NPCM7XX_PWM_PDR1:
+    case NPCM7XX_PWM_PDR2:
+    case NPCM7XX_PWM_PDR3:
+        value = s->pwm[npcm7xx_pdr_index(reg)].pdr;
+        break;
+
+    case NPCM7XX_PWM_PWDR0:
+    case NPCM7XX_PWM_PWDR1:
+    case NPCM7XX_PWM_PWDR2:
+    case NPCM7XX_PWM_PWDR3:
+        value = s->pwm[npcm7xx_pwdr_index(reg)].pwdr;
+        break;
+
+    case NPCM7XX_PWM_PPR:
+        value = s->ppr;
+        break;
+
+    case NPCM7XX_PWM_CSR:
+        value = s->csr;
+        break;
+
+    case NPCM7XX_PWM_PCR:
+        value = s->pcr;
+        break;
+
+    case NPCM7XX_PWM_PIER:
+        value = s->pier;
+        break;
+
+    case NPCM7XX_PWM_PIIR:
+        value = s->piir;
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid offset 0x%04" HWADDR_PRIx "\n",
+                      __func__, offset);
+        break;
+    }
+
+    return value;
+}
+
+static void npcm7xx_pwm_write(void *opaque, hwaddr offset,
+                                uint64_t v, unsigned size)
+{
+    NPCM7xxPWMState *s = opaque;
+    NPCM7xxPWM *p;
+    hwaddr reg = offset / sizeof(uint32_t);
+    uint32_t value = v;
+
+    switch (reg) {
+    case NPCM7XX_PWM_CNR0:
+    case NPCM7XX_PWM_CNR1:
+    case NPCM7XX_PWM_CNR2:
+    case NPCM7XX_PWM_CNR3:
+        p = &s->pwm[npcm7xx_cnr_index(reg)];
+        p->cnr = value;
+        npcm7xx_pwm_calculate_output(p);
+        break;
+
+    case NPCM7XX_PWM_CMR0:
+    case NPCM7XX_PWM_CMR1:
+    case NPCM7XX_PWM_CMR2:
+    case NPCM7XX_PWM_CMR3:
+        p = &s->pwm[npcm7xx_cmr_index(reg)];
+        p->cmr = value;
+        npcm7xx_pwm_calculate_output(p);
+        break;
+
+    case NPCM7XX_PWM_PDR0:
+    case NPCM7XX_PWM_PDR1:
+    case NPCM7XX_PWM_PDR2:
+    case NPCM7XX_PWM_PDR3:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
+                      __func__, offset);
+        break;
+
+    case NPCM7XX_PWM_PWDR0:
+    case NPCM7XX_PWM_PWDR1:
+    case NPCM7XX_PWM_PWDR2:
+    case NPCM7XX_PWM_PWDR3:
+        qemu_log_mask(LOG_UNIMP,
+                     "%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n",
+                     __func__, offset);
+        break;
+
+    case NPCM7XX_PWM_PPR:
+        npcm7xx_pwm_write_ppr(s, value);
+        break;
+
+    case NPCM7XX_PWM_CSR:
+        npcm7xx_pwm_write_csr(s, value);
+        break;
+
+    case NPCM7XX_PWM_PCR:
+        npcm7xx_pwm_write_pcr(s, value);
+        break;
+
+    case NPCM7XX_PWM_PIER:
+        qemu_log_mask(LOG_UNIMP,
+                     "%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n",
+                     __func__, offset);
+        break;
+
+    case NPCM7XX_PWM_PIIR:
+        qemu_log_mask(LOG_UNIMP,
+                     "%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n",
+                     __func__, offset);
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: invalid offset 0x%04" HWADDR_PRIx "\n",
+                      __func__, offset);
+        break;
+    }
+}
+
+
+static const struct MemoryRegionOps npcm7xx_pwm_ops = {
+    .read       = npcm7xx_pwm_read,
+    .write      = npcm7xx_pwm_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid      = {
+        .min_access_size        = 4,
+        .max_access_size        = 4,
+        .unaligned              = false,
+    },
+};
+
+static void npcm7xx_pwm_enter_reset(Object *obj, ResetType type)
+{
+    NPCM7xxPWMState *s = NPCM7XX_PWM(obj);
+    int i;
+
+    for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) {
+        NPCM7xxPWM *p = &s->pwm[i];
+
+        p->cnr = 0x00000000;
+        p->cmr = 0x00000000;
+        p->pdr = 0x00000000;
+        p->pwdr = 0x00000000;
+    }
+
+    s->ppr = 0x00000000;
+    s->csr = 0x00000000;
+    s->pcr = 0x00000000;
+    s->pier = 0x00000000;
+    s->piir = 0x00000000;
+}
+
+static void npcm7xx_pwm_hold_reset(Object *obj)
+{
+    NPCM7xxPWMState *s = NPCM7XX_PWM(obj);
+    int i;
+
+    for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) {
+        qemu_irq_lower(s->pwm[i].irq);
+    }
+}
+
+static void npcm7xx_pwm_init(Object *obj)
+{
+    NPCM7xxPWMState *s = NPCM7XX_PWM(obj);
+    SysBusDevice *sbd = &s->parent;
+    int i;
+
+    for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) {
+        NPCM7xxPWM *p = &s->pwm[i];
+        p->module = s;
+        p->index = i;
+        sysbus_init_irq(sbd, &p->irq);
+    }
+
+    memory_region_init_io(&s->iomem, obj, &npcm7xx_pwm_ops, s,
+                          TYPE_NPCM7XX_PWM, 4 * KiB);
+    sysbus_init_mmio(sbd, &s->iomem);
+    s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL);
+
+    for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
+        object_property_add_uint32_ptr(obj, "freq[*]",
+                &s->pwm[i].freq, OBJ_PROP_FLAG_READ);
+        object_property_add_uint32_ptr(obj, "duty[*]",
+                &s->pwm[i].duty, OBJ_PROP_FLAG_READ);
+    }
+}
+
+static const VMStateDescription vmstate_npcm7xx_pwm = {
+    .name = "npcm7xx-pwm",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (VMStateField[]) {
+        VMSTATE_BOOL(running, NPCM7xxPWM),
+        VMSTATE_BOOL(inverted, NPCM7xxPWM),
+        VMSTATE_UINT8(index, NPCM7xxPWM),
+        VMSTATE_UINT32(cnr, NPCM7xxPWM),
+        VMSTATE_UINT32(cmr, NPCM7xxPWM),
+        VMSTATE_UINT32(pdr, NPCM7xxPWM),
+        VMSTATE_UINT32(pwdr, NPCM7xxPWM),
+        VMSTATE_UINT32(freq, NPCM7xxPWM),
+        VMSTATE_UINT32(duty, NPCM7xxPWM),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static const VMStateDescription vmstate_npcm7xx_pwm_module = {
+    .name = "npcm7xx-pwm-module",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (VMStateField[]) {
+        VMSTATE_CLOCK(clock, NPCM7xxPWMState),
+        VMSTATE_STRUCT_ARRAY(pwm, NPCM7xxPWMState,
+                             NPCM7XX_PWM_PER_MODULE, 0, vmstate_npcm7xx_pwm,
+                             NPCM7xxPWM),
+        VMSTATE_UINT32(ppr, NPCM7xxPWMState),
+        VMSTATE_UINT32(csr, NPCM7xxPWMState),
+        VMSTATE_UINT32(pcr, NPCM7xxPWMState),
+        VMSTATE_UINT32(pier, NPCM7xxPWMState),
+        VMSTATE_UINT32(piir, NPCM7xxPWMState),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static void npcm7xx_pwm_class_init(ObjectClass *klass, void *data)
+{
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    QEMU_BUILD_BUG_ON(NPCM7XX_PWM_REGS_END > NPCM7XX_PWM_NR_REGS);
+
+    dc->desc = "NPCM7xx PWM Controller";
+    dc->vmsd = &vmstate_npcm7xx_pwm_module;
+    rc->phases.enter = npcm7xx_pwm_enter_reset;
+    rc->phases.hold = npcm7xx_pwm_hold_reset;
+}
+
+static const TypeInfo npcm7xx_pwm_info = {
+    .name               = TYPE_NPCM7XX_PWM,
+    .parent             = TYPE_SYS_BUS_DEVICE,
+    .instance_size      = sizeof(NPCM7xxPWMState),
+    .class_init         = npcm7xx_pwm_class_init,
+    .instance_init      = npcm7xx_pwm_init,
+};
+
+static void npcm7xx_pwm_register_type(void)
+{
+    type_register_static(&npcm7xx_pwm_info);
+}
+type_init(npcm7xx_pwm_register_type);
diff --git a/include/hw/arm/npcm7xx.h b/include/hw/arm/npcm7xx.h
index 51e1c7620d..f6227aa8aa 100644
--- a/include/hw/arm/npcm7xx.h
+++ b/include/hw/arm/npcm7xx.h
@@ -23,6 +23,7 @@
 #include "hw/mem/npcm7xx_mc.h"
 #include "hw/misc/npcm7xx_clk.h"
 #include "hw/misc/npcm7xx_gcr.h"
+#include "hw/misc/npcm7xx_pwm.h"
 #include "hw/misc/npcm7xx_rng.h"
 #include "hw/nvram/npcm7xx_otp.h"
 #include "hw/timer/npcm7xx_timer.h"
@@ -78,6 +79,7 @@ typedef struct NPCM7xxState {
     NPCM7xxCLKState     clk;
     NPCM7xxTimerCtrlState tim[3];
     NPCM7xxADCState     adc;
+    NPCM7xxPWMState     pwm[2];
     NPCM7xxOTPState     key_storage;
     NPCM7xxOTPState     fuse_array;
     NPCM7xxMCState      mc;
diff --git a/include/hw/misc/npcm7xx_pwm.h b/include/hw/misc/npcm7xx_pwm.h
new file mode 100644
index 0000000000..b83f965f1a
--- /dev/null
+++ b/include/hw/misc/npcm7xx_pwm.h
@@ -0,0 +1,106 @@
+/*
+ * Nuvoton NPCM7xx PWM 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.
+ */
+#ifndef NPCM7XX_PWM_H
+#define NPCM7XX_PWM_H
+
+#include "qemu/osdep.h"
+#include "hw/clock.h"
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+
+/* Each PWM module holds 4 PWM channels. */
+#define NPCM7XX_PWM_PER_MODULE 4
+
+/*
+ * Number of registers in one pwm module. Don't change this without increasing
+ * the version_id in vmstate.
+ */
+#define NPCM7XX_PWM_NR_REGS (0x54 / sizeof(uint32_t))
+
+/*
+ * The maximum duty values. Each duty unit represents 1/NPCM7XX_PWM_MAX_DUTY
+ * cycles. For example, if NPCM7XX_PWM_MAX_DUTY=1,000,000 and a PWM has a duty
+ * value of 100,000 the duty cycle for that PWM is 10%.
+ */
+#define NPCM7XX_PWM_MAX_DUTY 1000000
+
+typedef struct NPCM7xxPWMState NPCM7xxPWMState;
+
+/**
+ * struct NPCM7xxPWM - The state of a single PWM channel.
+ * @module: The PWM module that contains this channel.
+ * @irq: GIC interrupt line to fire on expiration if enabled.
+ * @running: Whether this PWM channel is generating output.
+ * @inverted: Whether this PWM channel is inverted.
+ * @index: The index of this PWM channel.
+ * @cnr: The counter register.
+ * @cmr: The comparator register.
+ * @pdr: The data register.
+ * @pwdr: The watchdog register.
+ * @freq: The frequency of this PWM channel.
+ * @duty: The duty cycle of this PWM channel. One unit represents
+ *   1/NPCM7XX_MAX_DUTY cycles.
+ */
+typedef struct NPCM7xxPWM {
+    NPCM7xxPWMState         *module;
+
+    qemu_irq                irq;
+
+    bool                    running;
+    bool                    inverted;
+
+    uint8_t                 index;
+    uint32_t                cnr;
+    uint32_t                cmr;
+    uint32_t                pdr;
+    uint32_t                pwdr;
+
+    uint32_t                freq;
+    uint32_t                duty;
+} NPCM7xxPWM;
+
+/**
+ * struct NPCM7xxPWMState - Pulse Width Modulation device state.
+ * @parent: System bus device.
+ * @iomem: Memory region through which registers are accessed.
+ * @clock: The PWM clock.
+ * @pwm: The PWM channels owned by this module.
+ * @ppr: The prescaler register.
+ * @csr: The clock selector register.
+ * @pcr: The control register.
+ * @pier: The interrupt enable register.
+ * @piir: The interrupt indication register.
+ */
+struct NPCM7xxPWMState {
+    SysBusDevice parent;
+
+    MemoryRegion iomem;
+
+    Clock       *clock;
+    NPCM7xxPWM pwm[NPCM7XX_PWM_PER_MODULE];
+
+    uint32_t    ppr;
+    uint32_t    csr;
+    uint32_t    pcr;
+    uint32_t    pier;
+    uint32_t    piir;
+};
+
+#define TYPE_NPCM7XX_PWM "npcm7xx-pwm"
+#define NPCM7XX_PWM(obj) \
+    OBJECT_CHECK(NPCM7xxPWMState, (obj), TYPE_NPCM7XX_PWM)
+
+#endif /* NPCM7XX_PWM_H */
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 955710d1c5..0b5467f084 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -136,6 +136,7 @@ qtests_sparc64 = \
 qtests_npcm7xx = \
   ['npcm7xx_adc-test',
    'npcm7xx_gpio-test',
+   'npcm7xx_pwm-test',
    'npcm7xx_rng-test',
    'npcm7xx_timer-test',
    'npcm7xx_watchdog_timer-test']
diff --git a/tests/qtest/npcm7xx_pwm-test.c b/tests/qtest/npcm7xx_pwm-test.c
new file mode 100644
index 0000000000..33fbdf5f54
--- /dev/null
+++ b/tests/qtest/npcm7xx_pwm-test.c
@@ -0,0 +1,490 @@
+/*
+ * QTests for Nuvoton NPCM7xx PWM Modules.
+ *
+ * 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 "qemu/bitops.h"
+#include "libqos/libqtest.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qnum.h"
+
+#define REF_HZ          25000000
+
+/* Register field definitions. */
+#define CH_EN           BIT(0)
+#define CH_INV          BIT(2)
+#define CH_MOD          BIT(3)
+
+/* Registers shared between all PWMs in a module */
+#define PPR             0x00
+#define CSR             0x04
+#define PCR             0x08
+#define PIER            0x3c
+#define PIIR            0x40
+
+/* CLK module related */
+#define CLK_BA          0xf0801000
+#define CLKSEL          0x04
+#define CLKDIV1         0x08
+#define CLKDIV2         0x2c
+#define PLLCON0         0x0c
+#define PLLCON1         0x10
+#define PLL_INDV(rv)    extract32((rv), 0, 6)
+#define PLL_FBDV(rv)    extract32((rv), 16, 12)
+#define PLL_OTDV1(rv)   extract32((rv), 8, 3)
+#define PLL_OTDV2(rv)   extract32((rv), 13, 3)
+#define APB3CKDIV(rv)   extract32((rv), 28, 2)
+#define CLK2CKDIV(rv)   extract32((rv), 0, 1)
+#define CLK4CKDIV(rv)   extract32((rv), 26, 2)
+#define CPUCKSEL(rv)    extract32((rv), 0, 2)
+
+#define MAX_DUTY        1000000
+
+typedef struct PWMModule {
+    int irq;
+    uint64_t base_addr;
+} PWMModule;
+
+typedef struct PWM {
+    uint32_t cnr_offset;
+    uint32_t cmr_offset;
+    uint32_t pdr_offset;
+    uint32_t pwdr_offset;
+} PWM;
+
+typedef struct TestData {
+    const PWMModule *module;
+    const PWM *pwm;
+} TestData;
+
+static const PWMModule pwm_module_list[] = {
+    {
+        .irq        = 93,
+        .base_addr  = 0xf0103000
+    },
+    {
+        .irq        = 94,
+        .base_addr  = 0xf0104000
+    }
+};
+
+static const PWM pwm_list[] = {
+    {
+        .cnr_offset     = 0x0c,
+        .cmr_offset     = 0x10,
+        .pdr_offset     = 0x14,
+        .pwdr_offset    = 0x44,
+    },
+    {
+        .cnr_offset     = 0x18,
+        .cmr_offset     = 0x1c,
+        .pdr_offset     = 0x20,
+        .pwdr_offset    = 0x48,
+    },
+    {
+        .cnr_offset     = 0x24,
+        .cmr_offset     = 0x28,
+        .pdr_offset     = 0x2c,
+        .pwdr_offset    = 0x4c,
+    },
+    {
+        .cnr_offset     = 0x30,
+        .cmr_offset     = 0x34,
+        .pdr_offset     = 0x38,
+        .pwdr_offset    = 0x50,
+    },
+};
+
+static const int ppr_base[] = { 0, 0, 8, 8 };
+static const int csr_base[] = { 0, 4, 8, 12 };
+static const int pcr_base[] = { 0, 8, 12, 16 };
+
+static const uint32_t ppr_list[] = {
+    0,
+    1,
+    10,
+    100,
+    255, /* Max possible value. */
+};
+
+static const uint32_t csr_list[] = {
+    0,
+    1,
+    2,
+    3,
+    4, /* Max possible value. */
+};
+
+static const uint32_t cnr_list[] = {
+    0,
+    1,
+    50,
+    100,
+    150,
+    200,
+    1000,
+    10000,
+    65535, /* Max possible value. */
+};
+
+static const uint32_t cmr_list[] = {
+    0,
+    1,
+    10,
+    50,
+    100,
+    150,
+    200,
+    1000,
+    10000,
+    65535, /* Max possible value. */
+};
+
+/* Returns the index of the PWM module. */
+static int pwm_module_index(const PWMModule *module)
+{
+    ptrdiff_t diff = module - pwm_module_list;
+
+    g_assert_true(diff >= 0 && diff < ARRAY_SIZE(pwm_module_list));
+
+    return diff;
+}
+
+/* Returns the index of the PWM entry. */
+static int pwm_index(const PWM *pwm)
+{
+    ptrdiff_t diff = pwm - pwm_list;
+
+    g_assert_true(diff >= 0 && diff < ARRAY_SIZE(pwm_list));
+
+    return diff;
+}
+
+static uint64_t pwm_qom_get(QTestState *qts, const char *path, const char *name)
+{
+    QDict *response;
+
+    g_test_message("Getting properties %s from %s", name, path);
+    response = qtest_qmp(qts, "{ 'execute': 'qom-get',"
+            " 'arguments': { 'path': %s, 'property': %s}}",
+            path, name);
+    /* The qom set message returns successfully. */
+    g_assert_true(qdict_haskey(response, "return"));
+    return qnum_get_uint(qobject_to(QNum, qdict_get(response, "return")));
+}
+
+static uint64_t pwm_get_freq(QTestState *qts, int module_index, int pwm_index)
+{
+    char path[100];
+    char name[100];
+
+    sprintf(path, "/machine/soc/pwm[%d]", module_index);
+    sprintf(name, "freq[%d]", pwm_index);
+
+    return pwm_qom_get(qts, path, name);
+}
+
+static uint64_t pwm_get_duty(QTestState *qts, int module_index, int pwm_index)
+{
+    char path[100];
+    char name[100];
+
+    sprintf(path, "/machine/soc/pwm[%d]", module_index);
+    sprintf(name, "duty[%d]", pwm_index);
+
+    return pwm_qom_get(qts, path, name);
+}
+
+static uint32_t get_pll(uint32_t con)
+{
+    return REF_HZ * PLL_FBDV(con) / (PLL_INDV(con) * PLL_OTDV1(con)
+            * PLL_OTDV2(con));
+}
+
+static uint64_t read_pclk(QTestState *qts)
+{
+    uint64_t freq = REF_HZ;
+    uint32_t clksel = qtest_readl(qts, CLK_BA + CLKSEL);
+    uint32_t pllcon;
+    uint32_t clkdiv1 = qtest_readl(qts, CLK_BA + CLKDIV1);
+    uint32_t clkdiv2 = qtest_readl(qts, CLK_BA + CLKDIV2);
+
+    switch (CPUCKSEL(clksel)) {
+    case 0:
+        pllcon = qtest_readl(qts, CLK_BA + PLLCON0);
+        freq = get_pll(pllcon);
+        break;
+    case 1:
+        pllcon = qtest_readl(qts, CLK_BA + PLLCON1);
+        freq = get_pll(pllcon);
+        break;
+    case 2:
+        break;
+    case 3:
+        break;
+    default:
+        g_assert_not_reached();
+    }
+
+    freq >>= (CLK2CKDIV(clkdiv1) + CLK4CKDIV(clkdiv1) + APB3CKDIV(clkdiv2));
+
+    return freq;
+}
+
+static uint32_t pwm_selector(uint32_t csr)
+{
+    switch (csr) {
+    case 0:
+        return 2;
+    case 1:
+        return 4;
+    case 2:
+        return 8;
+    case 3:
+        return 16;
+    case 4:
+        return 1;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static uint64_t pwm_compute_freq(QTestState *qts, uint32_t ppr, uint32_t csr,
+        uint32_t cnr)
+{
+    return read_pclk(qts) / ((ppr + 1) * pwm_selector(csr) * (cnr + 1));
+}
+
+static uint64_t pwm_compute_duty(uint32_t cnr, uint32_t cmr, bool inverted)
+{
+    uint64_t duty;
+
+    if (cnr == 0) {
+        /* PWM is stopped. */
+        duty = 0;
+    } else if (cmr >= cnr) {
+        duty = MAX_DUTY;
+    } else {
+        duty = MAX_DUTY * (cmr + 1) / (cnr + 1);
+    }
+
+    if (inverted) {
+        duty = MAX_DUTY - duty;
+    }
+
+    return duty;
+}
+
+static uint32_t pwm_read(QTestState *qts, const TestData *td, unsigned offset)
+{
+    return qtest_readl(qts, td->module->base_addr + offset);
+}
+
+static void pwm_write(QTestState *qts, const TestData *td, unsigned offset,
+        uint32_t value)
+{
+    qtest_writel(qts, td->module->base_addr + offset, value);
+}
+
+static uint32_t pwm_read_ppr(QTestState *qts, const TestData *td)
+{
+    return extract32(pwm_read(qts, td, PPR), ppr_base[pwm_index(td->pwm)], 8);
+}
+
+static void pwm_write_ppr(QTestState *qts, const TestData *td, uint32_t value)
+{
+    pwm_write(qts, td, PPR, value << ppr_base[pwm_index(td->pwm)]);
+}
+
+static uint32_t pwm_read_csr(QTestState *qts, const TestData *td)
+{
+    return extract32(pwm_read(qts, td, CSR), csr_base[pwm_index(td->pwm)], 3);
+}
+
+static void pwm_write_csr(QTestState *qts, const TestData *td, uint32_t value)
+{
+    pwm_write(qts, td, CSR, value << csr_base[pwm_index(td->pwm)]);
+}
+
+static uint32_t pwm_read_pcr(QTestState *qts, const TestData *td)
+{
+    return extract32(pwm_read(qts, td, PCR), pcr_base[pwm_index(td->pwm)], 4);
+}
+
+static void pwm_write_pcr(QTestState *qts, const TestData *td, uint32_t value)
+{
+    pwm_write(qts, td, PCR, value << pcr_base[pwm_index(td->pwm)]);
+}
+
+static uint32_t pwm_read_cnr(QTestState *qts, const TestData *td)
+{
+    return pwm_read(qts, td, td->pwm->cnr_offset);
+}
+
+static void pwm_write_cnr(QTestState *qts, const TestData *td, uint32_t value)
+{
+    pwm_write(qts, td, td->pwm->cnr_offset, value);
+}
+
+static uint32_t pwm_read_cmr(QTestState *qts, const TestData *td)
+{
+    return pwm_read(qts, td, td->pwm->cmr_offset);
+}
+
+static void pwm_write_cmr(QTestState *qts, const TestData *td, uint32_t value)
+{
+    pwm_write(qts, td, td->pwm->cmr_offset, value);
+}
+
+/* Check pwm registers can be reset to default value */
+static void test_init(gconstpointer test_data)
+{
+    const TestData *td = test_data;
+    QTestState *qts = qtest_init("-machine quanta-gsj");
+    int module = pwm_module_index(td->module);
+    int pwm = pwm_index(td->pwm);
+
+    g_assert_cmpuint(pwm_get_freq(qts, module, pwm), ==, 0);
+    g_assert_cmpuint(pwm_get_duty(qts, module, pwm), ==, 0);
+
+    qtest_quit(qts);
+}
+
+/* One-shot mode should not change frequency and duty cycle. */
+static void test_oneshot(gconstpointer test_data)
+{
+    const TestData *td = test_data;
+    QTestState *qts = qtest_init("-machine quanta-gsj");
+    int module = pwm_module_index(td->module);
+    int pwm = pwm_index(td->pwm);
+    uint32_t ppr, csr, pcr;
+    int i, j;
+
+    pcr = CH_EN;
+    for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) {
+        ppr = ppr_list[i];
+        pwm_write_ppr(qts, td, ppr);
+
+        for (j = 0; j < ARRAY_SIZE(csr_list); ++j) {
+            csr = csr_list[j];
+            pwm_write_csr(qts, td, csr);
+            pwm_write_pcr(qts, td, pcr);
+
+            g_assert_cmpuint(pwm_read_ppr(qts, td), ==, ppr);
+            g_assert_cmpuint(pwm_read_csr(qts, td), ==, csr);
+            g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr);
+            g_assert_cmpuint(pwm_get_freq(qts, module, pwm), ==, 0);
+            g_assert_cmpuint(pwm_get_duty(qts, module, pwm), ==, 0);
+        }
+    }
+
+    qtest_quit(qts);
+}
+
+/* In toggle mode, the PWM generates correct outputs. */
+static void test_toggle(gconstpointer test_data)
+{
+    const TestData *td = test_data;
+    QTestState *qts = qtest_init("-machine quanta-gsj");
+    int module = pwm_module_index(td->module);
+    int pwm = pwm_index(td->pwm);
+    uint32_t ppr, csr, pcr, cnr, cmr;
+    int i, j, k, l;
+    uint64_t expected_freq, expected_duty;
+
+    pcr = CH_EN | CH_MOD;
+    for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) {
+        ppr = ppr_list[i];
+        pwm_write_ppr(qts, td, ppr);
+
+        for (j = 0; j < ARRAY_SIZE(csr_list); ++j) {
+            csr = csr_list[j];
+            pwm_write_csr(qts, td, csr);
+
+            for (k = 0; k < ARRAY_SIZE(cnr_list); ++k) {
+                cnr = cnr_list[k];
+                pwm_write_cnr(qts, td, cnr);
+
+                for (l = 0; l < ARRAY_SIZE(cmr_list); ++l) {
+                    cmr = cmr_list[l];
+                    pwm_write_cmr(qts, td, cmr);
+                    expected_freq = pwm_compute_freq(qts, ppr, csr, cnr);
+                    expected_duty = pwm_compute_duty(cnr, cmr, false);
+
+                    pwm_write_pcr(qts, td, pcr);
+                    g_assert_cmpuint(pwm_read_ppr(qts, td), ==, ppr);
+                    g_assert_cmpuint(pwm_read_csr(qts, td), ==, csr);
+                    g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr);
+                    g_assert_cmpuint(pwm_read_cnr(qts, td), ==, cnr);
+                    g_assert_cmpuint(pwm_read_cmr(qts, td), ==, cmr);
+                    g_assert_cmpuint(pwm_get_duty(qts, module, pwm),
+                            ==, expected_duty);
+                    if (expected_duty != 0 && expected_duty != 100) {
+                        /* Duty cycle with 0 or 100 doesn't need frequency. */
+                        g_assert_cmpuint(pwm_get_freq(qts, module, pwm),
+                                ==, expected_freq);
+                    }
+
+                    /* Test inverted mode */
+                    expected_duty = pwm_compute_duty(cnr, cmr, true);
+                    pwm_write_pcr(qts, td, pcr | CH_INV);
+                    g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr | CH_INV);
+                    g_assert_cmpuint(pwm_get_duty(qts, module, pwm),
+                            ==, expected_duty);
+                    if (expected_duty != 0 && expected_duty != 100) {
+                        /* Duty cycle with 0 or 100 doesn't need frequency. */
+                        g_assert_cmpuint(pwm_get_freq(qts, module, pwm),
+                                ==, expected_freq);
+                    }
+
+                }
+            }
+        }
+    }
+
+    qtest_quit(qts);
+}
+
+static void pwm_add_test(const char *name, const TestData* td,
+        GTestDataFunc fn)
+{
+    g_autofree char *full_name = g_strdup_printf(
+            "npcm7xx_pwm/module[%d]/pwm[%d]/%s", pwm_module_index(td->module),
+            pwm_index(td->pwm), name);
+    qtest_add_data_func(full_name, td, fn);
+}
+#define add_test(name, td) pwm_add_test(#name, td, test_##name)
+
+int main(int argc, char **argv)
+{
+    TestData test_data_list[ARRAY_SIZE(pwm_module_list) * ARRAY_SIZE(pwm_list)];
+
+    g_test_init(&argc, &argv, NULL);
+
+    for (int i = 0; i < ARRAY_SIZE(pwm_module_list); ++i) {
+        for (int j = 0; j < ARRAY_SIZE(pwm_list); ++j) {
+            TestData *td = &test_data_list[i * ARRAY_SIZE(pwm_list) + j];
+
+            td->module = &pwm_module_list[i];
+            td->pwm = &pwm_list[j];
+
+            add_test(init, td);
+            add_test(oneshot, td);
+            add_test(toggle, td);
+        }
+    }
+
+    return g_test_run();
+}
-- 
2.29.2.684.gfbc64c5ab5-goog



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 5/7] hw/ipmi: Add an IPMI host interface
  2020-12-11  1:51 [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Hao Wu via
                   ` (3 preceding siblings ...)
  2020-12-11  1:51 ` [PATCH 4/7] hw/misc: Add a PWM " Hao Wu via
@ 2020-12-11  1:51 ` Hao Wu via
  2020-12-11  1:51 ` [PATCH 6/7] hw/ipmi: Add a KCS Module for NPCM7XX Hao Wu via
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Hao Wu via @ 2020-12-11  1:51 UTC (permalink / raw)
  To: peter.maydell
  Cc: minyard, venture, qemu-devel, hskinnemoen, wuhaotsh, kfting,
	qemu-arm, Avi.Fishman

The IPMI host interface is used to emulate IPMI related devices on a
System-on-Chip (SoC) used for Baseband Management Controller (BMC).
This interface consists of two components: IPMI host and IPMI responder.
An IPMI responder is a device to intercept reads and writes to the
system interface registers in the host. An IPMI host emulates the host
behavior on a BMC emulation.

For more information see docs/spec/ipmi.rst.

Reviewed-by: Havard Skinnemoen <hskinnemoen@google.com>
Reviewed-by: Tyrone Ting <kfting@nuvoton.com>
Signed-off-by: Hao Wu <wuhaotsh@google.com>
---
 default-configs/devices/arm-softmmu.mak |  2 +
 hw/ipmi/Kconfig                         |  5 ++
 hw/ipmi/ipmi_host.c                     | 40 +++++++++++++++
 hw/ipmi/meson.build                     |  1 +
 include/hw/ipmi/ipmi_host.h             | 56 +++++++++++++++++++++
 include/hw/ipmi/ipmi_responder.h        | 66 +++++++++++++++++++++++++
 6 files changed, 170 insertions(+)
 create mode 100644 hw/ipmi/ipmi_host.c
 create mode 100644 include/hw/ipmi/ipmi_host.h
 create mode 100644 include/hw/ipmi/ipmi_responder.h

diff --git a/default-configs/devices/arm-softmmu.mak b/default-configs/devices/arm-softmmu.mak
index 08a32123b4..864bac4501 100644
--- a/default-configs/devices/arm-softmmu.mak
+++ b/default-configs/devices/arm-softmmu.mak
@@ -27,6 +27,8 @@ CONFIG_GUMSTIX=y
 CONFIG_SPITZ=y
 CONFIG_TOSA=y
 CONFIG_Z2=y
+CONFIG_IPMI=y
+CONFIG_IPMI_HOST=y
 CONFIG_NPCM7XX=y
 CONFIG_COLLIE=y
 CONFIG_ASPEED_SOC=y
diff --git a/hw/ipmi/Kconfig b/hw/ipmi/Kconfig
index 9befd4f422..9e487eb42f 100644
--- a/hw/ipmi/Kconfig
+++ b/hw/ipmi/Kconfig
@@ -6,6 +6,11 @@ config IPMI_LOCAL
     default y
     depends on IPMI
 
+config IPMI_HOST
+    bool
+    default y
+    depends on IPMI
+
 config IPMI_EXTERN
     bool
     default y
diff --git a/hw/ipmi/ipmi_host.c b/hw/ipmi/ipmi_host.c
new file mode 100644
index 0000000000..7a6d4eb323
--- /dev/null
+++ b/hw/ipmi/ipmi_host.c
@@ -0,0 +1,40 @@
+/*
+ * IPMI Host emulation
+ *
+ * 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 "hw/ipmi/ipmi_host.h"
+#include "hw/ipmi/ipmi_responder.h"
+
+static TypeInfo ipmi_responder_type_info = {
+    .name = TYPE_IPMI_RESPONDER,
+    .parent = TYPE_INTERFACE,
+    .class_size = sizeof(IPMIResponderClass),
+};
+
+static TypeInfo ipmi_host_type_info = {
+    .name = TYPE_IPMI_HOST,
+    .parent = TYPE_DEVICE,
+    .instance_size = sizeof(IPMIHost),
+    .abstract = true,
+    .class_size = sizeof(IPMIHostClass),
+};
+
+static void ipmi_register_types(void)
+{
+    type_register_static(&ipmi_responder_type_info);
+    type_register_static(&ipmi_host_type_info);
+}
+
+type_init(ipmi_register_types)
diff --git a/hw/ipmi/meson.build b/hw/ipmi/meson.build
index 9622ea2a2c..9ec4dcb957 100644
--- a/hw/ipmi/meson.build
+++ b/hw/ipmi/meson.build
@@ -7,5 +7,6 @@ ipmi_ss.add(when: 'CONFIG_PCI_IPMI_KCS', if_true: files('pci_ipmi_kcs.c'))
 ipmi_ss.add(when: 'CONFIG_ISA_IPMI_BT', if_true: files('isa_ipmi_bt.c'))
 ipmi_ss.add(when: 'CONFIG_PCI_IPMI_BT', if_true: files('pci_ipmi_bt.c'))
 ipmi_ss.add(when: 'CONFIG_IPMI_SSIF', if_true: files('smbus_ipmi.c'))
+ipmi_ss.add(when: 'CONFIG_IPMI_HOST', if_true: files('ipmi_host.c'))
 
 softmmu_ss.add_all(when: 'CONFIG_IPMI', if_true: ipmi_ss)
diff --git a/include/hw/ipmi/ipmi_host.h b/include/hw/ipmi/ipmi_host.h
new file mode 100644
index 0000000000..a703cc3854
--- /dev/null
+++ b/include/hw/ipmi/ipmi_host.h
@@ -0,0 +1,56 @@
+/*
+ * IPMI host interface
+ *
+ * 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.
+ */
+
+#ifndef HW_IPMI_HOST_H
+#define HW_IPMI_HOST_H
+
+#include "hw/ipmi/ipmi_responder.h"
+
+#define TYPE_IPMI_HOST "ipmi-host"
+#define IPMI_HOST(obj) \
+     OBJECT_CHECK(IPMIHost, (obj), TYPE_IPMI_HOST)
+#define IPMI_HOST_CLASS(obj_class) \
+     OBJECT_CLASS_CHECK(IPMIHostClass, (obj_class), TYPE_IPMI_HOST)
+#define IPMI_HOST_GET_CLASS(obj) \
+     OBJECT_GET_CLASS(IPMIHostClass, (obj), TYPE_IPMI_HOST)
+
+/**
+ * struct IPMIHost defines an IPMI host interface. It can be a simulator or a
+ * connection to an emulated or real host.
+ * @responder: The IPMI responder that handles an IPMI message.
+ */
+typedef struct IPMIHost {
+    DeviceState parent;
+
+    IPMIResponder *responder;
+} IPMIHost;
+
+/**
+ * struct IPMIHostClass defines an IPMI host class.
+ * @handle_command: Handle a command to the host.
+ */
+typedef struct IPMIHostClass {
+    DeviceClass parent;
+
+    /*
+     * Handle a command to the bmc.
+     */
+    void (*handle_command)(struct IPMIHost *s,
+                           uint8_t *cmd, unsigned int cmd_len,
+                           unsigned int max_cmd_len, uint8_t msg_id);
+} IPMIHostClass;
+
+#endif /* HW_IPMI_HOST_H */
diff --git a/include/hw/ipmi/ipmi_responder.h b/include/hw/ipmi/ipmi_responder.h
new file mode 100644
index 0000000000..e3e4ef39d4
--- /dev/null
+++ b/include/hw/ipmi/ipmi_responder.h
@@ -0,0 +1,66 @@
+/*
+ * IPMI responder interface
+ *
+ * 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.
+ */
+
+#ifndef HW_IPMI_RESPONDER_H
+#define HW_IPMI_RESPONDER_H
+
+#include "qemu/osdep.h"
+#include "hw/qdev-core.h"
+#include "qom/object.h"
+
+#define TYPE_IPMI_RESPONDER_PREFIX "ipmi-responder-"
+#define TYPE_IPMI_RESPONDER "ipmi-responder"
+#define IPMI_RESPONDER(obj) \
+     INTERFACE_CHECK(IPMIResponder, (obj), TYPE_IPMI_RESPONDER)
+#define IPMI_RESPONDER_CLASS(class) \
+     OBJECT_CLASS_CHECK(IPMIResponderClass, (class), TYPE_IPMI_RESPONDER)
+#define IPMI_RESPONDER_GET_CLASS(class) \
+     OBJECT_GET_CLASS(IPMIResponderClass, (class), TYPE_IPMI_RESPONDER)
+
+struct IPMIHost;
+
+/**
+ * This interface is implemented by each IPMI responder device (KCS, BT, PCI,
+ * etc.) An IPMI host device uses it to transfer data to the emulated BMC.
+ */
+typedef struct IPMIResponder IPMIResponder;
+
+/**
+ * struct IPMIResponderClass implemented by an IPMI responder device like KCS to
+ * handle commands from connected IPMI host device.
+ * @get_host: Return the IPMI host (e.g. ipmi-host-extern) that uses this
+ *  responder.
+ * @set_host: Set the IPMI host (e.g. ipmi-host-extern) that uses this
+ *  responder.
+ * @get_backend_data: Return the backend device (e.g. KCS, BT) of the
+ *  corresponding responder.
+ * @handle_req: The IPMI Host device calls this function when it receives a sane
+ *  IPMI message. A responder should handle this message.
+ */
+typedef struct IPMIResponderClass {
+    InterfaceClass parent;
+
+    struct IPMIHost *(*get_host)(struct IPMIResponder *s);
+
+    void (*set_host)(struct IPMIResponder *s, struct IPMIHost *h);
+
+    void *(*get_backend_data)(struct IPMIResponder *s);
+
+    void (*handle_req)(struct IPMIResponder *s, uint8_t msg_id,
+            unsigned char *req, unsigned req_len);
+} IPMIResponderClass;
+
+#endif /* HW_IPMI_RESPONDER_H */
-- 
2.29.2.684.gfbc64c5ab5-goog



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 6/7] hw/ipmi: Add a KCS Module for NPCM7XX
  2020-12-11  1:51 [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Hao Wu via
                   ` (4 preceding siblings ...)
  2020-12-11  1:51 ` [PATCH 5/7] hw/ipmi: Add an IPMI host interface Hao Wu via
@ 2020-12-11  1:51 ` Hao Wu via
  2020-12-11  1:51 ` [PATCH 7/7] hw/ipmi: Add an IPMI external host device Hao Wu via
  2020-12-11  3:04 ` [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Corey Minyard
  7 siblings, 0 replies; 14+ messages in thread
From: Hao Wu via @ 2020-12-11  1:51 UTC (permalink / raw)
  To: peter.maydell
  Cc: minyard, venture, qemu-devel, hskinnemoen, wuhaotsh, kfting,
	qemu-arm, Avi.Fishman

Add a KCS module for NPCM7xx SoC. This module implements the IPMI
responder interface and is responsible to communicate with an external
host via the KCS channels in an NPCM7xx SoC.

Reviewed-by: Tyrone Ting <kfting@nuvoton.com>
Signed-off-by: Hao Wu <wuhaotsh@google.com>
---
 docs/system/arm/nuvoton.rst   |   2 +-
 hw/arm/npcm7xx.c              |  10 +-
 hw/ipmi/meson.build           |   1 +
 hw/ipmi/npcm7xx_kcs.c         | 570 ++++++++++++++++++++++++++++++++++
 include/hw/arm/npcm7xx.h      |   2 +
 include/hw/ipmi/npcm7xx_kcs.h | 105 +++++++
 6 files changed, 688 insertions(+), 2 deletions(-)
 create mode 100644 hw/ipmi/npcm7xx_kcs.c
 create mode 100644 include/hw/ipmi/npcm7xx_kcs.h

diff --git a/docs/system/arm/nuvoton.rst b/docs/system/arm/nuvoton.rst
index a1786342e2..21a02deba5 100644
--- a/docs/system/arm/nuvoton.rst
+++ b/docs/system/arm/nuvoton.rst
@@ -43,6 +43,7 @@ Supported devices
  * GPIO controller
  * Analog to Digital Converter (ADC)
  * Pulse Width Modulation (PWM)
+ * Keyboard Controller Style (KCS) channels
 
 Missing devices
 ---------------
@@ -50,7 +51,6 @@ Missing devices
  * LPC/eSPI host-to-BMC interface, including
 
    * Keyboard and mouse controller interface (KBCI)
-   * Keyboard Controller Style (KCS) channels
    * BIOS POST code FIFO
    * System Wake-up Control (SWC)
    * Shared memory (SHM)
diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c
index 72040d4079..4b7116d496 100644
--- a/hw/arm/npcm7xx.c
+++ b/hw/arm/npcm7xx.c
@@ -46,6 +46,7 @@
 #define NPCM7XX_CLK_BA          (0xf0801000)
 #define NPCM7XX_MC_BA           (0xf0824000)
 #define NPCM7XX_RNG_BA          (0xf000b000)
+#define NPCM7XX_KCS_BA          (0xf0007000)
 
 /* USB Host modules */
 #define NPCM7XX_EHCI_BA         (0xf0806000)
@@ -82,6 +83,7 @@ enum NPCM7xxInterrupt {
     NPCM7XX_UART1_IRQ,
     NPCM7XX_UART2_IRQ,
     NPCM7XX_UART3_IRQ,
+    NPCM7XX_KCS_HIB_IRQ         = 9,
     NPCM7XX_TIMER0_IRQ          = 32,   /* Timer Module 0 */
     NPCM7XX_TIMER1_IRQ,
     NPCM7XX_TIMER2_IRQ,
@@ -353,6 +355,7 @@ static void npcm7xx_init(Object *obj)
         object_initialize_child(obj, "gpio[*]", &s->gpio[i], TYPE_NPCM7XX_GPIO);
     }
 
+    object_initialize_child(obj, "kcs", &s->kcs, TYPE_NPCM7XX_KCS);
     object_initialize_child(obj, "ehci", &s->ehci, TYPE_NPCM7XX_EHCI);
     object_initialize_child(obj, "ohci", &s->ohci, TYPE_SYSBUS_OHCI);
 
@@ -509,6 +512,12 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
                            npcm7xx_irq(s, NPCM7XX_GPIO0_IRQ + i));
     }
 
+    /* KCS modules. Cannot fail. */
+    sysbus_realize(SYS_BUS_DEVICE(&s->kcs), &error_abort);
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->kcs), 0, NPCM7XX_KCS_BA);
+    sysbus_connect_irq(SYS_BUS_DEVICE(&s->kcs), 0,
+                       npcm7xx_irq(s, NPCM7XX_KCS_HIB_IRQ));
+
     /* USB Host */
     object_property_set_bool(OBJECT(&s->ehci), "companion-enable", true,
                              &error_abort);
@@ -574,7 +583,6 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
     create_unimplemented_device("npcm7xx.shm",          0xc0001000,   4 * KiB);
     create_unimplemented_device("npcm7xx.vdmx",         0xe0800000,   4 * KiB);
     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.gpio[0]",      0xf0010000,   4 * KiB);
     create_unimplemented_device("npcm7xx.gpio[1]",      0xf0011000,   4 * KiB);
diff --git a/hw/ipmi/meson.build b/hw/ipmi/meson.build
index 9ec4dcb957..1261489fbd 100644
--- a/hw/ipmi/meson.build
+++ b/hw/ipmi/meson.build
@@ -8,5 +8,6 @@ ipmi_ss.add(when: 'CONFIG_ISA_IPMI_BT', if_true: files('isa_ipmi_bt.c'))
 ipmi_ss.add(when: 'CONFIG_PCI_IPMI_BT', if_true: files('pci_ipmi_bt.c'))
 ipmi_ss.add(when: 'CONFIG_IPMI_SSIF', if_true: files('smbus_ipmi.c'))
 ipmi_ss.add(when: 'CONFIG_IPMI_HOST', if_true: files('ipmi_host.c'))
+ipmi_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_kcs.c'))
 
 softmmu_ss.add_all(when: 'CONFIG_IPMI', if_true: ipmi_ss)
diff --git a/hw/ipmi/npcm7xx_kcs.c b/hw/ipmi/npcm7xx_kcs.c
new file mode 100644
index 0000000000..f568f0cf20
--- /dev/null
+++ b/hw/ipmi/npcm7xx_kcs.c
@@ -0,0 +1,570 @@
+/*
+ * Nuvoton NPCM7xx KCS 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/ipmi/ipmi_host.h"
+#include "hw/ipmi/npcm7xx_kcs.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/units.h"
+
+/* Registers in each KCS channel. */
+typedef enum NPCM7xxKCSRegister {
+    NPCM7XX_KCSST,
+    NPCM7XX_KCSDO,
+    NPCM7XX_KCSDI,
+    NPCM7XX_KCSDOC,
+    NPCM7XX_KCSDOM,
+    NPCM7XX_KCSDIC,
+    NPCM7XX_KCSCTL,
+    NPCM7XX_KCSIC,
+    NPCM7XX_KCSIE,
+    NPCM7XX_KCS_REGS_END,
+} NPCM7xxKCSRegister;
+
+static const hwaddr npcm7xx_kcs_channel_base[] = {
+    0x0c, 0x1e, 0x30, 0x42
+};
+
+#define NPCM7XX_KCS_REG_DIFF    2
+
+/* Register field definitions. */
+#define NPCM7XX_CTL_OBEIE       BIT(1)
+#define NPCM7XX_CTL_IBFIE       BIT(0)
+
+/* IPMI 2.0 Table 9.1 status register bits */
+#define KCS_ST_STATE(rv)    extract8(rv, 6, 2)
+#define KCS_ST_CMD          BIT(3)
+#define KCS_ST_SMS_ATN      BIT(2)
+#define KCS_ST_IBF          BIT(1)
+#define KCS_ST_OBF          BIT(0)
+
+/* IPMI 2.0 Table 9.2 state bits */
+enum KCSState {
+    IDLE_STATE,
+    READ_STATE,
+    WRITE_STATE,
+    ERROR_STATE,
+};
+
+/* IPMI 2.0 Table 9.3 control codes */
+#define KCS_CMD_GET_STATUS_ABORT    0x60
+#define KCS_CMD_WRITE_START         0x61
+#define KCS_CMD_WRITE_END           0x62
+#define KCS_CMD_READ                0x68
+
+/* Host Side Operations. */
+
+static uint8_t npcm7xx_kcs_host_read_byte(NPCM7xxKCSChannel *c)
+{
+    uint8_t v;
+
+    v = c->dbbout;
+    c->status &= ~KCS_ST_OBF;
+    if (c->ctl & NPCM7XX_CTL_OBEIE) {
+        qemu_irq_raise(c->owner->irq);
+    }
+
+    return v;
+}
+
+static void npcm7xx_kcs_host_write_byte(NPCM7xxKCSChannel *c, uint8_t value,
+        bool is_cmd)
+{
+    c->dbbin = value;
+    c->status |= KCS_ST_IBF;
+    if (is_cmd) {
+        c->status |= KCS_ST_CMD;
+    } else {
+        c->status &= ~KCS_ST_CMD;
+    }
+    if (c->ctl & NPCM7XX_CTL_IBFIE) {
+        qemu_irq_raise(c->owner->irq);
+    }
+}
+
+static void npcm7xx_kcs_handle_event(NPCM7xxKCSChannel *c)
+{
+    uint8_t v;
+    IPMIHostClass *hk;
+
+    switch (KCS_ST_STATE(c->status)) {
+    case IDLE_STATE:
+        if (c->status & KCS_ST_OBF) {
+            /* Read a dummy byte. */
+            npcm7xx_kcs_host_read_byte(c);
+            if (c->outlen > 0) {
+                /* Send to ipmi host when msg ends. */
+                hk = IPMI_HOST_GET_CLASS(c->host);
+                hk->handle_command(c->host, c->outmsg, c->outlen,
+                        MAX_IPMI_MSG_SIZE, c->last_msg_id);
+                /* The last byte has been read. return to empty state. */
+                c->outlen = 0;
+            }
+        }
+        if (c->inlen > 0) {
+            /* Send to bmc the next request */
+            npcm7xx_kcs_host_write_byte(c, KCS_CMD_WRITE_START, true);
+            c->last_byte_not_ready = true;
+        }
+        break;
+    case READ_STATE:
+        if (c->status & KCS_ST_OBF) {
+            /* Read in a byte from BMC */
+            v = npcm7xx_kcs_host_read_byte(c);
+            if (c->outlen < MAX_IPMI_MSG_SIZE) {
+                c->outmsg[c->outlen++] = v;
+            }
+            npcm7xx_kcs_host_write_byte(c, KCS_CMD_READ, false);
+        }
+        break;
+    case WRITE_STATE:
+        if (c->status & KCS_ST_IBF) {
+            /* The guest hasn't read the byte yet. We'll wait. */
+            break;
+        }
+        /* Clear OBF. */
+        c->status &= ~KCS_ST_OBF;
+        /* If it's the last byte written, we need to first send a command. */
+        if (c->last_byte_not_ready && c->inpos == c->inlen - 1) {
+            npcm7xx_kcs_host_write_byte(c, KCS_CMD_WRITE_END, true);
+            c->last_byte_not_ready = false;
+        } else {
+            npcm7xx_kcs_host_write_byte(c, c->inmsg[c->inpos++], false);
+            if (!c->last_byte_not_ready) {
+                /* The last byte has been sent. */
+                c->inlen = 0;
+                c->inpos = 0;
+            }
+        }
+        break;
+    case ERROR_STATE:
+        if (c->status & KCS_ST_OBF) {
+            /* Read in error byte from BMC */
+            npcm7xx_kcs_host_read_byte(c);
+        }
+        /* Force abort */
+        c->outlen = 0;
+        c->inlen = 0;
+        c->inpos = 0;
+        if (!(c->status & KCS_ST_IBF)) {
+            npcm7xx_kcs_host_write_byte(c, KCS_CMD_GET_STATUS_ABORT, true);
+        }
+        break;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+/* Received a request from the host and send it to BMC core. */
+static void npcm7xx_kcs_handle_req(IPMIResponder *ii, uint8_t msg_id,
+                                unsigned char *req, unsigned req_len)
+{
+    IPMIResponderClass *iic = IPMI_RESPONDER_GET_CLASS(ii);
+    NPCM7xxKCSChannel *c = iic->get_backend_data(ii);
+
+    /* Drop the request if the last request is still not handled. */
+    if (c->inlen > 0) {
+        return;
+    }
+    if (req_len == 0) {
+        return;
+    }
+    if (req_len > MAX_IPMI_MSG_SIZE) {
+        /* Truncate the extra bytes that don't fit. */
+        req_len = MAX_IPMI_MSG_SIZE;
+    }
+    memcpy(c->inmsg, req, req_len);
+    c->inpos = 0;
+    c->inlen = req_len;
+
+    c->last_msg_id = msg_id;
+
+    npcm7xx_kcs_handle_event(c);
+}
+
+/* Core Side Operations. */
+/* Return the channel index for addr. If the addr is out of range, return -1. */
+static int npcm7xx_kcs_channel_index(hwaddr addr)
+{
+    int index;
+
+    if (unlikely(addr < npcm7xx_kcs_channel_base[0])) {
+        return -1;
+    }
+    if (unlikely(addr >= npcm7xx_kcs_channel_base[NPCM7XX_KCS_NR_CHANNELS])) {
+        return -1;
+    }
+    if (unlikely(addr % NPCM7XX_KCS_REG_DIFF)) {
+        return -1;
+    }
+
+    for (index = 0; index < NPCM7XX_KCS_NR_CHANNELS; ++index) {
+        if (addr < npcm7xx_kcs_channel_base[index + 1]) {
+            return index;
+        }
+    }
+
+    g_assert_not_reached();
+}
+
+static NPCM7xxKCSRegister npcm7xx_kcs_reg_index(hwaddr addr, int channel)
+{
+    NPCM7xxKCSRegister reg;
+
+    reg = (addr - npcm7xx_kcs_channel_base[channel]) / NPCM7XX_KCS_REG_DIFF;
+    return reg;
+}
+
+static uint8_t npcm7xx_kcs_read_byte(NPCM7xxKCSChannel *c,
+        NPCM7xxKCSRegister reg)
+{
+    uint8_t v = 0;
+
+    v = c->dbbin;
+
+    c->status &= ~KCS_ST_IBF;
+    if (c->ctl & NPCM7XX_CTL_IBFIE) {
+        qemu_irq_lower(c->owner->irq);
+    }
+
+    if (reg == NPCM7XX_KCSDIC) {
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Host nSCIPME interrupt is not implemented.\n",
+                __func__);
+    }
+
+    npcm7xx_kcs_handle_event(c);
+    return v;
+}
+
+static void npcm7xx_kcs_write_byte(NPCM7xxKCSChannel *c,
+        NPCM7xxKCSRegister reg, uint8_t value)
+{
+    c->dbbout = value;
+
+    c->status |= KCS_ST_OBF;
+    if (c->ctl & NPCM7XX_CTL_OBEIE) {
+        qemu_irq_lower(c->owner->irq);
+    }
+
+    if (reg == NPCM7XX_KCSDOC) {
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Host nSCIPME interrupt is not implemented.\n",
+                __func__);
+    } else if (reg == NPCM7XX_KCSDOM) {
+        qemu_log_mask(LOG_UNIMP,
+                "%s: Host nSMI interrupt is not implemented.\n",
+                __func__);
+    }
+
+    npcm7xx_kcs_handle_event(c);
+}
+
+static uint8_t npcm7xx_kcs_read_status(NPCM7xxKCSChannel *c)
+{
+    uint8_t status = c->status;
+
+    return status;
+}
+
+static void npcm7xx_kcs_write_status(NPCM7xxKCSChannel *c, uint8_t value)
+{
+    static const uint8_t mask =
+        KCS_ST_CMD | KCS_ST_IBF | KCS_ST_OBF;
+
+    if (value & mask) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: read-only bits in 0x%02x ignored\n",
+                      __func__, value);
+        value &= ~mask;
+    }
+
+    c->status = (c->status & mask) | value;
+}
+
+static void npcm7xx_kcs_write_ctl(NPCM7xxKCSChannel *c, uint8_t value)
+{
+    if (value & ~(NPCM7XX_CTL_OBEIE | NPCM7XX_CTL_IBFIE)) {
+        qemu_log_mask(LOG_UNIMP, "%s: Host interrupts are not implemented.\n",
+                __func__);
+    }
+
+    c->ctl = value;
+}
+
+static uint64_t npcm7xx_kcs_read(void *opaque, hwaddr addr, unsigned int size)
+{
+    NPCM7xxKCSState *s = opaque;
+    uint64_t value = 0;
+    int channel;
+    NPCM7xxKCSRegister reg;
+
+    channel = npcm7xx_kcs_channel_index(addr);
+    if (channel < 0 || channel >= NPCM7XX_KCS_NR_CHANNELS) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: read from addr 0x%04" HWADDR_PRIx
+                      " is invalid or unimplemented.\n",
+                      __func__, addr);
+        return value;
+    }
+
+    reg = npcm7xx_kcs_reg_index(addr, channel);
+    switch (reg) {
+    case NPCM7XX_KCSDI:
+    case NPCM7XX_KCSDIC:
+        value = npcm7xx_kcs_read_byte(&s->channels[channel], reg);
+        break;
+    case NPCM7XX_KCSDO:
+    case NPCM7XX_KCSDOC:
+    case NPCM7XX_KCSDOM:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: register @ 0x%04" HWADDR_PRIx " is write-only\n",
+                      __func__, addr);
+        break;
+    case NPCM7XX_KCSST:
+        value = npcm7xx_kcs_read_status(&s->channels[channel]);
+        break;
+    case NPCM7XX_KCSCTL:
+        value = s->channels[channel].ctl;
+        break;
+    case NPCM7XX_KCSIC:
+        value = s->channels[channel].ic;
+        break;
+    case NPCM7XX_KCSIE:
+        value = s->channels[channel].ie;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: read from invalid addr 0x%04" HWADDR_PRIx "\n",
+                      __func__, addr);
+    }
+
+    return value;
+}
+
+static void npcm7xx_kcs_write(void *opaque, hwaddr addr, uint64_t v,
+                                    unsigned int size)
+{
+    NPCM7xxKCSState *s = opaque;
+    int channel;
+    NPCM7xxKCSRegister reg;
+
+    channel = npcm7xx_kcs_channel_index(addr);
+    if (channel < 0 || channel >= NPCM7XX_KCS_NR_CHANNELS) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: write to addr 0x%04" HWADDR_PRIx
+                      " is invalid or unimplemented.\n",
+                      __func__, addr);
+        return;
+    }
+
+    reg = npcm7xx_kcs_reg_index(addr, channel);
+    switch (reg) {
+    case NPCM7XX_KCSDI:
+    case NPCM7XX_KCSDIC:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
+                      __func__, addr);
+        break;
+    case NPCM7XX_KCSDO:
+    case NPCM7XX_KCSDOC:
+    case NPCM7XX_KCSDOM:
+        npcm7xx_kcs_write_byte(&s->channels[channel], reg, v);
+        break;
+    case NPCM7XX_KCSST:
+        npcm7xx_kcs_write_status(&s->channels[channel], v);
+        break;
+    case NPCM7XX_KCSCTL:
+        npcm7xx_kcs_write_ctl(&s->channels[channel], v);
+        break;
+    case NPCM7XX_KCSIC:
+        qemu_log_mask(LOG_UNIMP, "%s: Host interrupts are not implemented.\n",
+                __func__);
+        break;
+    case NPCM7XX_KCSIE:
+        qemu_log_mask(LOG_UNIMP, "%s: Host interrupts are not implemented.\n",
+                __func__);
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: read from invalid addr 0x%04" HWADDR_PRIx "\n",
+                      __func__, addr);
+    }
+}
+
+static const MemoryRegionOps npcm7xx_kcs_ops = {
+    .read = npcm7xx_kcs_read,
+    .write = npcm7xx_kcs_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 1, /* KCS registers are 8-bits. */
+        .max_access_size = 1, /* KCS registers are 8-bits. */
+        .unaligned = false,
+    },
+};
+
+static const VMStateDescription vmstate_npcm7xx_kcs_channel = {
+    .name = "npcm7xx-kcs-channel",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8(status, NPCM7xxKCSChannel),
+        VMSTATE_UINT8(dbbout, NPCM7xxKCSChannel),
+        VMSTATE_UINT8(dbbin, NPCM7xxKCSChannel),
+        VMSTATE_UINT8(ctl, NPCM7xxKCSChannel),
+        VMSTATE_UINT8(ic, NPCM7xxKCSChannel),
+        VMSTATE_UINT8(ie, NPCM7xxKCSChannel),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static IPMIHost *npcm7xx_kcs_get_host(IPMIResponder *ii)
+{
+    NPCM7xxKCSChannel *c = NPCM7XX_KCS_CHANNEL(ii);
+
+    return c->host;
+}
+
+static void npcm7xx_kcs_set_host(IPMIResponder *ii, IPMIHost *h)
+{
+    NPCM7xxKCSChannel *c = NPCM7XX_KCS_CHANNEL(ii);
+
+    c->host = h;
+}
+
+static void *npcm7xx_kcs_get_backend_data(IPMIResponder *ii)
+{
+    NPCM7xxKCSChannel *c = NPCM7XX_KCS_CHANNEL(ii);
+
+    return c;
+}
+
+static void npcm7xx_kcs_channel_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    IPMIResponderClass *iic = IPMI_RESPONDER_CLASS(klass);
+
+    QEMU_BUILD_BUG_ON(NPCM7XX_KCS_REGS_END != NPCM7XX_KCS_CHANNEL_NR_REGS);
+
+    dc->desc = "NPCM7xx KCS Channel";
+    dc->vmsd = &vmstate_npcm7xx_kcs_channel;
+
+    iic->get_host = npcm7xx_kcs_get_host;
+    iic->set_host = npcm7xx_kcs_set_host;
+    iic->get_backend_data = npcm7xx_kcs_get_backend_data;
+    iic->handle_req = npcm7xx_kcs_handle_req;
+}
+
+static const TypeInfo npcm7xx_kcs_channel_info = {
+    .name               = TYPE_NPCM7XX_KCS_CHANNEL,
+    .parent             = TYPE_DEVICE,
+    .instance_size      = sizeof(NPCM7xxKCSChannel),
+    .class_init         = npcm7xx_kcs_channel_class_init,
+    .interfaces         = (InterfaceInfo[]) {
+        { TYPE_IPMI_RESPONDER },
+        { },
+    },
+};
+
+static void npcm7xx_kcs_enter_reset(Object *obj, ResetType type)
+{
+    NPCM7xxKCSState *s = NPCM7XX_KCS(obj);
+    int i;
+
+    for (i = 0; i < NPCM7XX_KCS_NR_CHANNELS; i++) {
+        NPCM7xxKCSChannel *c = &s->channels[i];
+
+        c->status = 0x00;
+        c->ctl = 0xc0;
+        c->ic = 0x41;
+        c->ie = 0x00;
+    }
+}
+
+static void npcm7xx_kcs_hold_reset(Object *obj)
+{
+    NPCM7xxKCSState *s = NPCM7XX_KCS(obj);
+
+    qemu_irq_lower(s->irq);
+}
+
+static const VMStateDescription vmstate_npcm7xx_kcs = {
+    .name = "npcm7xx-kcs",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (VMStateField[]) {
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static void npcm7xx_kcs_realize(DeviceState *dev, Error **errp)
+{
+    NPCM7xxKCSState *s = NPCM7XX_KCS(dev);
+    SysBusDevice *sbd = &s->parent;
+    int i;
+
+    memory_region_init_io(&s->iomem, OBJECT(s), &npcm7xx_kcs_ops, s,
+                          TYPE_NPCM7XX_KCS, 4 * KiB);
+    sysbus_init_mmio(sbd, &s->iomem);
+    sysbus_init_irq(sbd, &s->irq);
+
+    for (i = 0; i < NPCM7XX_KCS_NR_CHANNELS; i++) {
+        s->channels[i].owner = s;
+        if (!qdev_realize(DEVICE(&s->channels[i]), NULL, errp)) {
+            return;
+        }
+    }
+}
+
+static void npcm7xx_kcs_init(Object *obj)
+{
+    NPCM7xxKCSState *s = NPCM7XX_KCS(obj);
+    int i;
+
+    for (i = 0; i < NPCM7XX_KCS_NR_CHANNELS; i++) {
+        object_initialize_child(obj, g_strdup_printf("channels[%d]", i),
+                &s->channels[i], TYPE_NPCM7XX_KCS_CHANNEL);
+    }
+}
+
+static void npcm7xx_kcs_class_init(ObjectClass *klass, void *data)
+{
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->desc = "NPCM7xx Timer Controller";
+    dc->vmsd = &vmstate_npcm7xx_kcs;
+    dc->realize = npcm7xx_kcs_realize;
+    rc->phases.enter = npcm7xx_kcs_enter_reset;
+    rc->phases.hold = npcm7xx_kcs_hold_reset;
+}
+
+static const TypeInfo npcm7xx_kcs_info = {
+    .name               = TYPE_NPCM7XX_KCS,
+    .parent             = TYPE_SYS_BUS_DEVICE,
+    .instance_size      = sizeof(NPCM7xxKCSState),
+    .class_init         = npcm7xx_kcs_class_init,
+    .instance_init      = npcm7xx_kcs_init,
+};
+
+static void npcm7xx_kcs_register_type(void)
+{
+    type_register_static(&npcm7xx_kcs_channel_info);
+    type_register_static(&npcm7xx_kcs_info);
+}
+type_init(npcm7xx_kcs_register_type);
diff --git a/include/hw/arm/npcm7xx.h b/include/hw/arm/npcm7xx.h
index f6227aa8aa..7d5ea24355 100644
--- a/include/hw/arm/npcm7xx.h
+++ b/include/hw/arm/npcm7xx.h
@@ -20,6 +20,7 @@
 #include "hw/adc/npcm7xx_adc.h"
 #include "hw/cpu/a9mpcore.h"
 #include "hw/gpio/npcm7xx_gpio.h"
+#include "hw/ipmi/npcm7xx_kcs.h"
 #include "hw/mem/npcm7xx_mc.h"
 #include "hw/misc/npcm7xx_clk.h"
 #include "hw/misc/npcm7xx_gcr.h"
@@ -85,6 +86,7 @@ typedef struct NPCM7xxState {
     NPCM7xxMCState      mc;
     NPCM7xxRNGState     rng;
     NPCM7xxGPIOState    gpio[8];
+    NPCM7xxKCSState     kcs;
     EHCISysBusState     ehci;
     OHCISysBusState     ohci;
     NPCM7xxFIUState     fiu[2];
diff --git a/include/hw/ipmi/npcm7xx_kcs.h b/include/hw/ipmi/npcm7xx_kcs.h
new file mode 100644
index 0000000000..bae4d21f5d
--- /dev/null
+++ b/include/hw/ipmi/npcm7xx_kcs.h
@@ -0,0 +1,105 @@
+/*
+ * Nuvoton NPCM7xx KCS 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.
+ */
+#ifndef NPCM7XX_KCS_H
+#define NPCM7XX_KCS_H
+
+#include "exec/memory.h"
+#include "hw/ipmi/ipmi.h"
+#include "hw/ipmi/ipmi_host.h"
+#include "hw/ipmi/ipmi_responder.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+
+#define NPCM7XX_KCS_NR_CHANNELS             3
+/*
+ * Number of registers in each KCS channel. Don't change this without
+ * incrementing the version_id in the vmstate.
+ */
+#define NPCM7XX_KCS_CHANNEL_NR_REGS         9
+
+typedef struct NPCM7xxKCSState NPCM7xxKCSState;
+
+/**
+ * struct NPCM7xxKCSChannel - KCS Channel that can be read or written by the
+ * host.
+ * @parent: Parent device.
+ * @owner: The KCS module that manages this KCS channel.
+ * @status: The status of this KCS module.
+ * @dbbout: The output buffer to the host.
+ * @dbbin: The input buffer from the host.
+ * @ctl: The control register.
+ * @ic: The host interrupt control register. Not implemented.
+ * @ie: The host interrupt enable register. Not implemented.
+ * @inmsg: The input message from the host. To be put in dbbin.
+ * @inpos: The current position of input message.
+ * @inlen: The length of input message.
+ * @outmsg: The input message from the host. To be put in dbbout.
+ * @outpos: The current position of output message.
+ * @outlen: The length of output message.
+ * @last_byte_not_ready: The last byte in inmsg is not ready to be sent.
+ * @last_msg_id: The message id of last incoming request from host.
+ */
+typedef struct NPCM7xxKCSChannel {
+    DeviceState             parent;
+
+    NPCM7xxKCSState         *owner;
+    IPMIHost                *host;
+    /* Core side registers. */
+    uint8_t                 status;
+    uint8_t                 dbbout;
+    uint8_t                 dbbin;
+    uint8_t                 ctl;
+    uint8_t                 ic;
+    uint8_t                 ie;
+
+    /* Host side buffers. */
+    uint8_t                 inmsg[MAX_IPMI_MSG_SIZE];
+    uint32_t                inpos;
+    uint32_t                inlen;
+    uint8_t                 outmsg[MAX_IPMI_MSG_SIZE];
+    uint32_t                outpos;
+    uint32_t                outlen;
+
+    /* Flags. */
+    bool                    last_byte_not_ready;
+    uint8_t                 last_msg_id;
+} NPCM7xxKCSChannel;
+
+/**
+ * struct NPCM7xxKCSState - Keyboard Control Style (KCS) Module device state.
+ * @parent: System bus device.
+ * @iomem: Memory region through which registers are accessed.
+ * @irq: GIC interrupt line to fire on reading or writing the buffer.
+ * @channels: The KCS channels this module manages.
+ */
+struct NPCM7xxKCSState {
+    SysBusDevice            parent;
+
+    MemoryRegion            iomem;
+
+    qemu_irq                irq;
+    NPCM7xxKCSChannel       channels[NPCM7XX_KCS_NR_CHANNELS];
+};
+
+#define TYPE_NPCM7XX_KCS_CHANNEL "npcm7xx-kcs-channel"
+#define NPCM7XX_KCS_CHANNEL(obj)                                      \
+    OBJECT_CHECK(NPCM7xxKCSChannel, (obj), TYPE_NPCM7XX_KCS_CHANNEL)
+
+#define TYPE_NPCM7XX_KCS "npcm7xx-kcs"
+#define NPCM7XX_KCS(obj)                                              \
+    OBJECT_CHECK(NPCM7xxKCSState, (obj), TYPE_NPCM7XX_KCS)
+
+#endif /* NPCM7XX_KCS_H */
-- 
2.29.2.684.gfbc64c5ab5-goog



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH 7/7] hw/ipmi: Add an IPMI external host device
  2020-12-11  1:51 [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Hao Wu via
                   ` (5 preceding siblings ...)
  2020-12-11  1:51 ` [PATCH 6/7] hw/ipmi: Add a KCS Module for NPCM7XX Hao Wu via
@ 2020-12-11  1:51 ` Hao Wu via
  2020-12-11  3:04 ` [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Corey Minyard
  7 siblings, 0 replies; 14+ messages in thread
From: Hao Wu via @ 2020-12-11  1:51 UTC (permalink / raw)
  To: peter.maydell
  Cc: minyard, venture, qemu-devel, hskinnemoen, wuhaotsh, kfting,
	qemu-arm, Avi.Fishman

The IPMI external host device works for Baseband Management Controller
(BMC) emulations. It works as a representation of a host class that
connects to a given BMC.  It can connect to a real host hardware or a
emulated or simulated host device. In particular it can connect to a
host QEMU instance with device ipmi-bmc-extern.

For more details of IPMI host device in BMC emulation, see
docs/specs/ipmi.rst.

Reviewed-by: Tyrone Ting <kfting@nuvoton.com>
Signed-off-by: Hao Wu <wuhaotsh@google.com>
---
 hw/ipmi/ipmi_host_extern.c | 435 +++++++++++++++++++++++++++++++++++++
 hw/ipmi/meson.build        |   1 +
 2 files changed, 436 insertions(+)
 create mode 100644 hw/ipmi/ipmi_host_extern.c

diff --git a/hw/ipmi/ipmi_host_extern.c b/hw/ipmi/ipmi_host_extern.c
new file mode 100644
index 0000000000..6b5d45d403
--- /dev/null
+++ b/hw/ipmi/ipmi_host_extern.c
@@ -0,0 +1,435 @@
+/*
+ * IPMI Host external connection
+ *
+ * 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.
+ */
+
+/*
+ * This is designed to connect to a host QEMU VM that runs ipmi_bmc_extern.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "qemu/timer.h"
+#include "chardev/char-fe.h"
+#include "hw/ipmi/ipmi.h"
+#include "hw/ipmi/ipmi_host.h"
+#include "hw/ipmi/ipmi_responder.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+
+#define VM_MSG_CHAR        0xA0 /* Marks end of message */
+#define VM_CMD_CHAR        0xA1 /* Marks end of a command */
+#define VM_ESCAPE_CHAR     0xAA /* Set bit 4 from the next byte to 0 */
+
+#define VM_PROTOCOL_VERSION        1
+#define VM_CMD_VERSION             0xff /* A version number byte follows */
+#define VM_CMD_RESET               0x04
+#define VM_CMD_CAPABILITIES        0x08
+
+#define TYPE_IPMI_HOST_EXTERN "ipmi-host-extern"
+#define IPMI_HOST_EXTERN(obj) OBJECT_CHECK(IPMIHostExtern, (obj), \
+                                        TYPE_IPMI_HOST_EXTERN)
+
+typedef struct IPMIHostExtern {
+    IPMIHost parent;
+    CharBackend chr;
+    struct QEMUTimer *extern_timer;
+
+    bool connected;
+    uint8_t capability;
+
+    unsigned char inbuf[MAX_IPMI_MSG_SIZE + 2];
+    unsigned int inpos;
+    bool in_escape;
+    bool in_too_many;
+    bool sending_cmd;
+
+    unsigned char outbuf[(MAX_IPMI_MSG_SIZE + 2) * 2 + 1];
+    unsigned int outpos;
+    unsigned int outlen;
+} IPMIHostExtern;
+
+static unsigned char
+ipmb_checksum(const unsigned char *data, int size, unsigned char start)
+{
+    unsigned char csum = start;
+
+    for (; size > 0; size--, data++) {
+            csum += *data;
+    }
+    return csum;
+}
+
+static void continue_send(IPMIHostExtern *ihe)
+{
+    int ret;
+
+    if (ihe->outlen == 0) {
+        return;
+    }
+    ret = qemu_chr_fe_write(&ihe->chr, ihe->outbuf + ihe->outpos,
+                            ihe->outlen - ihe->outpos);
+    if (ret > 0) {
+        ihe->outpos += ret;
+    }
+    if (ihe->outpos < ihe->outlen) {
+        /* Not fully transmitted, try again in a 10ms */
+        timer_mod_ns(ihe->extern_timer,
+                     qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 10000000);
+    } else {
+        /* Sent */
+        ihe->outlen = 0;
+        ihe->outpos = 0;
+    }
+}
+
+static void extern_timeout(void *opaque)
+{
+    IPMIHostExtern *ihe = opaque;
+
+    if (ihe->connected) {
+        continue_send(ihe);
+    }
+}
+
+static void addchar(IPMIHostExtern *ihe, unsigned char ch)
+{
+    switch (ch) {
+    case VM_MSG_CHAR:
+    case VM_CMD_CHAR:
+    case VM_ESCAPE_CHAR:
+        /* Escape the special characters. */
+        ihe->outbuf[ihe->outlen++] = VM_ESCAPE_CHAR;
+        ch |= 0x10;
+        /* fall through */
+    default:
+        ihe->outbuf[ihe->outlen++] = ch;
+    }
+}
+
+static void send_version(IPMIHostExtern *ihe)
+{
+    addchar(ihe, VM_CMD_VERSION);
+    addchar(ihe, VM_PROTOCOL_VERSION);
+    ihe->outbuf[ihe->outlen++] = VM_CMD_CHAR;
+    continue_send(ihe);
+}
+
+/*
+ * Handle a command (typically IPMI response) from IPMI responder
+ * and send it out to the external host.
+ */
+static void ipmi_host_extern_handle_command(IPMIHost *h, uint8_t *cmd,
+        unsigned cmd_len, unsigned max_cmd_len, uint8_t msg_id)
+{
+    IPMIHostExtern *ihe = IPMI_HOST_EXTERN(h);
+    uint8_t err = 0, csum;
+    int i;
+
+    if (!ihe->connected) {
+        /* We are not connected to external host. Just do nothing. */
+        return;
+    }
+    addchar(ihe, msg_id);
+    /* If it's too short or it was truncated, return an error. */
+    if (cmd_len < 2) {
+        err = IPMI_CC_REQUEST_DATA_LENGTH_INVALID;
+    } else if ((cmd_len > max_cmd_len) || (cmd_len > MAX_IPMI_MSG_SIZE)) {
+        err = IPMI_CC_REQUEST_DATA_TRUNCATED;
+    }
+    if (err) {
+        /* Send out the error message */
+        unsigned char rsp[3];
+
+        rsp[0] = cmd[0] | 0x04;
+        rsp[1] = cmd[1];
+        rsp[2] = err;
+        for (i = 0; i < 3; ++i) {
+            addchar(ihe, rsp[i]);
+        }
+        csum = ipmb_checksum(&msg_id, 1, 0);
+        addchar(ihe, -ipmb_checksum(rsp, 3, csum));
+    } else {
+        for (i = 0; i < cmd_len; i++) {
+            addchar(ihe, cmd[i]);
+        }
+        csum = ipmb_checksum(&msg_id, 1, 0);
+        addchar(ihe, -ipmb_checksum(cmd, cmd_len, csum));
+    }
+
+    ihe->outbuf[ihe->outlen++] = VM_MSG_CHAR;
+
+    /* Start the transmit */
+    continue_send(ihe);
+}
+
+/*
+ * This function handles an IPMI message received from an external host by
+ * sending it to the IPMI responder class.
+ */
+static void handle_msg(IPMIHostExtern *ihe)
+{
+    IPMIResponderClass *k = IPMI_RESPONDER_GET_CLASS(ihe->parent.responder);
+    if (unlikely(ihe->in_escape)) {
+        ipmi_debug("msg escape not ended\n");
+        return;
+    }
+    if (unlikely(ihe->inpos < 4)) {
+        ipmi_debug("msg too short\n");
+        return;
+    }
+    if (unlikely(ihe->in_too_many)) {
+        ihe->inbuf[3] = IPMI_CC_REQUEST_DATA_TRUNCATED;
+        ihe->inpos = 4;
+    } else if (unlikely(ipmb_checksum(ihe->inbuf, ihe->inpos, 0) != 0)) {
+        ipmi_debug("msg checksum failure\n");
+        return;
+    } else {
+        ihe->inpos--; /* Remove checkum */
+    }
+
+    k->handle_req(ihe->parent.responder, ihe->inbuf[0], ihe->inbuf + 1,
+            ihe->inpos - 1);
+}
+
+/* This function handles a control command from the host. */
+static void handle_command(IPMIHostExtern *ihe)
+{
+    uint8_t cmd;
+
+    if (unlikely(ihe->in_too_many)) {
+        ipmi_debug("cmd in too many\n");
+        return;
+    }
+
+    if (unlikely(ihe->in_escape)) {
+        ipmi_debug("cmd ends with escape character\n");
+        return;
+    }
+
+    if (unlikely(ihe->inpos < 1)) {
+        ipmi_debug("empty command.\n");
+        return;
+    }
+
+    cmd = ihe->inbuf[0];
+    switch (cmd) {
+    case VM_CMD_VERSION:
+        /* The host informs us the protocol version. */
+        if (unlikely(ihe->inpos < 2)) {
+            ipmi_debug("Host cmd version truncated.\n");
+            break;
+        }
+        if (unlikely(ihe->inbuf[1] != VM_PROTOCOL_VERSION)) {
+            ipmi_debug("Host protocol version %u is different from our version"
+                    " %u\n", ihe->inbuf[1], VM_PROTOCOL_VERSION);
+        }
+        break;
+    case VM_CMD_RESET:
+        /* The host tells us a reset has happened. */
+        break;
+    case VM_CMD_CAPABILITIES:
+        /* The host tells us its capability. */
+        if (unlikely(ihe->inpos < 2)) {
+            ipmi_debug("Host cmd capability truncated.\n");
+            break;
+        }
+        ihe->capability = ihe->inbuf[1];
+        break;
+    default:
+        /* The host shouldn't send us this command. Just ignore if they do. */
+        ipmi_debug("Host cmd type %02x is invalid.\n", cmd);
+    }
+}
+
+/* Clear the state of ipmi-host-extern. Happens at the end of a message. */
+static void clear_state(IPMIHostExtern *ihe)
+{
+    ihe->in_escape = false;
+    ihe->in_too_many = false;
+    ihe->inpos = 0;
+}
+
+/* We always welcome an incoming request. */
+static int can_receive(void *opaque)
+{
+    return 1;
+}
+
+/*
+ * This function mirrors ipmi-bmc-extern. It handles an incoming character
+ * sequence and translates it into IPMI message.
+ */
+static void receive(void *opaque, const uint8_t *buf, int size)
+{
+    IPMIHostExtern *ihe = opaque;
+    int i;
+
+    for (i = 0; i < size; ++i) {
+        uint8_t ch = buf[i];
+
+        switch (ch) {
+        case VM_MSG_CHAR:
+            /* The preceding characters are an IPMI message. */
+            handle_msg(ihe);
+            clear_state(ihe);
+            break;
+
+        case VM_CMD_CHAR:
+            /* The preceding characters are a control command. */
+            handle_command(ihe);
+            clear_state(ihe);
+            break;
+
+        case VM_ESCAPE_CHAR:
+            ihe->in_escape = true;
+            break;
+
+        default:
+            if (ihe->in_escape) {
+                ch &= ~0x10;
+                ihe->in_escape = false;
+            }
+            if (ihe->in_too_many) {
+                break;
+            }
+            if (ihe->inpos >= ARRAY_SIZE(ihe->inbuf)) {
+                ihe->in_too_many = true;
+                break;
+            }
+            ihe->inbuf[ihe->inpos++] = ch;
+            break;
+        }
+    }
+    return;
+}
+
+static void chr_event(void *opaque, QEMUChrEvent event)
+{
+    IPMIHostExtern *ihe = opaque;
+
+    switch (event) {
+    case CHR_EVENT_OPENED:
+        ihe->connected = true;
+        clear_state(ihe);
+        send_version(ihe);
+        break;
+
+    case CHR_EVENT_CLOSED:
+        ihe->connected = false;
+        break;
+
+    case CHR_EVENT_BREAK:
+    case CHR_EVENT_MUX_IN:
+    case CHR_EVENT_MUX_OUT:
+        /* Ignore */
+        break;
+
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static void ipmi_host_extern_responder_check(const Object *obj,
+        const char *name, Object *val, Error **errp)
+{
+    IPMIHostExtern *ihe = IPMI_HOST_EXTERN(obj);
+
+    if (ihe->parent.responder) {
+        error_setg(errp, "IPMI host already has a responder");
+    }
+}
+
+static void ipmi_host_extern_realize(DeviceState *dev, Error **errp)
+{
+    IPMIHostExtern *ihe = IPMI_HOST_EXTERN(dev);
+    IPMIResponderClass *rk;
+
+    if (!qemu_chr_fe_backend_connected(&ihe->chr)) {
+        error_setg(errp, "IPMI external host requires chardev attribute");
+        return;
+    }
+
+    qemu_chr_fe_set_handlers(&ihe->chr, can_receive, receive,
+                             chr_event, NULL, ihe, NULL, true);
+
+    if (ihe->parent.responder == NULL) {
+        error_setg(errp, "IPMI host requires responder attribute");
+        return;
+    }
+    rk = IPMI_RESPONDER_GET_CLASS(ihe->parent.responder);
+    rk->set_host(ihe->parent.responder, &ihe->parent);
+}
+
+static const VMStateDescription vmstate_ipmi_host_extern = {
+    .name = TYPE_IPMI_HOST_EXTERN,
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields      = (VMStateField[]) {
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void ipmi_host_extern_init(Object *obj)
+{
+    IPMIHostExtern *ihe = IPMI_HOST_EXTERN(obj);
+
+    object_property_add_link(OBJECT(ihe), "responder", TYPE_IPMI_RESPONDER,
+            (Object **)(&ihe->parent.responder),
+            ipmi_host_extern_responder_check, OBJ_PROP_LINK_STRONG);
+    ihe->extern_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, extern_timeout, ihe);
+    vmstate_register(NULL, 0, &vmstate_ipmi_host_extern, ihe);
+}
+
+static void ipmi_host_extern_finalize(Object *obj)
+{
+    IPMIHostExtern *ihe = IPMI_HOST_EXTERN(obj);
+
+    timer_del(ihe->extern_timer);
+    timer_free(ihe->extern_timer);
+}
+
+static Property ipmi_host_extern_properties[] = {
+    DEFINE_PROP_CHR("chardev", IPMIHostExtern, chr),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ipmi_host_extern_class_init(ObjectClass *oc, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(oc);
+    IPMIHostClass *bk = IPMI_HOST_CLASS(oc);
+
+    bk->handle_command = ipmi_host_extern_handle_command;
+    dc->hotpluggable = false;
+    dc->realize = ipmi_host_extern_realize;
+    device_class_set_props(dc, ipmi_host_extern_properties);
+}
+
+static const TypeInfo ipmi_host_extern_type = {
+    .name          = TYPE_IPMI_HOST_EXTERN,
+    .parent        = TYPE_IPMI_HOST,
+    .instance_size = sizeof(IPMIHostExtern),
+    .instance_init = ipmi_host_extern_init,
+    .instance_finalize = ipmi_host_extern_finalize,
+    .class_init    = ipmi_host_extern_class_init,
+ };
+
+static void ipmi_host_extern_register_types(void)
+{
+    type_register_static(&ipmi_host_extern_type);
+}
+
+type_init(ipmi_host_extern_register_types)
diff --git a/hw/ipmi/meson.build b/hw/ipmi/meson.build
index 1261489fbd..8352913288 100644
--- a/hw/ipmi/meson.build
+++ b/hw/ipmi/meson.build
@@ -8,6 +8,7 @@ ipmi_ss.add(when: 'CONFIG_ISA_IPMI_BT', if_true: files('isa_ipmi_bt.c'))
 ipmi_ss.add(when: 'CONFIG_PCI_IPMI_BT', if_true: files('pci_ipmi_bt.c'))
 ipmi_ss.add(when: 'CONFIG_IPMI_SSIF', if_true: files('smbus_ipmi.c'))
 ipmi_ss.add(when: 'CONFIG_IPMI_HOST', if_true: files('ipmi_host.c'))
+ipmi_ss.add(when: 'CONFIG_IPMI_HOST', if_true: files('ipmi_host_extern.c'))
 ipmi_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_kcs.c'))
 
 softmmu_ss.add_all(when: 'CONFIG_IPMI', if_true: ipmi_ss)
-- 
2.29.2.684.gfbc64c5ab5-goog



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support
  2020-12-11  1:51 [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Hao Wu via
                   ` (6 preceding siblings ...)
  2020-12-11  1:51 ` [PATCH 7/7] hw/ipmi: Add an IPMI external host device Hao Wu via
@ 2020-12-11  3:04 ` Corey Minyard
  2020-12-11 20:56   ` Hao Wu via
  7 siblings, 1 reply; 14+ messages in thread
From: Corey Minyard @ 2020-12-11  3:04 UTC (permalink / raw)
  To: Hao Wu
  Cc: peter.maydell, venture, hskinnemoen, qemu-devel, kfting,
	qemu-arm, Avi.Fishman

On Thu, Dec 10, 2020 at 05:51:49PM -0800, Hao Wu wrote:
> This patch series include a few more NPCM7XX devices including
> 
> - Analog Digital Converter (ADC)
> - Pulse Width Modulation (PWM)
> - Keyboard Style Controller (KSC)
> 
> To utilize these modules we also add two extra functionalities:
> 
> 1. We modified the CLK module to generate clock values using qdev_clock.
>    These clocks are used to determine various clocks in NPCM7XX devices.
> 2. We added support for emulating IPMI responder devices in BMC machines,
>    similar to the existing IPMI device support for CPU emulation. This allows
>    a qemu instance running BMC firmware to serve as an external BMC for a qemu
>    instance running server software. It utilizes the KCS module we implemented.

Looking at the IPMI changes, why didn't you just re-use the existing
IPMI infrastructure?  ipmi_host.[ch] is basically a subset of ipmi.[ch],
and the ipmi_host_extern looks like a copy of of ipmi_bmc_extern with
some names changed.  That kind of code duplication is not acceptable.
Plus you copied my code and removed my copyrights, which is really
not acceptable and illegal.

I'm not exactly sure why you needed you own KCS interface, either.  It
looks like the interface is somewhat different in some ways, but
integrating it into the current KCS code is probably a better choice if
that can be done.

-corey

> 
> Hao Wu (7):
>   hw/misc: Add clock converter in NPCM7XX CLK module
>   hw/timer: Refactor NPCM7XX Timer to use CLK clock
>   hw/adc: Add an ADC module for NPCM7XX
>   hw/misc: Add a PWM module for NPCM7XX
>   hw/ipmi: Add an IPMI host interface
>   hw/ipmi: Add a KCS Module for NPCM7XX
>   hw/ipmi: Add an IPMI external host device
> 
>  default-configs/devices/arm-softmmu.mak |   2 +
>  docs/system/arm/nuvoton.rst             |   6 +-
>  hw/adc/meson.build                      |   1 +
>  hw/adc/npcm7xx_adc.c                    | 318 ++++++++++
>  hw/arm/npcm7xx.c                        |  65 +-
>  hw/ipmi/Kconfig                         |   5 +
>  hw/ipmi/ipmi_host.c                     |  40 ++
>  hw/ipmi/ipmi_host_extern.c              | 435 +++++++++++++
>  hw/ipmi/meson.build                     |   3 +
>  hw/ipmi/npcm7xx_kcs.c                   | 570 +++++++++++++++++
>  hw/misc/meson.build                     |   1 +
>  hw/misc/npcm7xx_clk.c                   | 795 +++++++++++++++++++++++-
>  hw/misc/npcm7xx_pwm.c                   | 535 ++++++++++++++++
>  hw/timer/npcm7xx_timer.c                |  25 +-
>  include/hw/adc/npcm7xx_adc.h            |  72 +++
>  include/hw/arm/npcm7xx.h                |   6 +
>  include/hw/ipmi/ipmi_host.h             |  56 ++
>  include/hw/ipmi/ipmi_responder.h        |  66 ++
>  include/hw/ipmi/npcm7xx_kcs.h           | 105 ++++
>  include/hw/misc/npcm7xx_clk.h           | 146 ++++-
>  include/hw/misc/npcm7xx_pwm.h           | 106 ++++
>  include/hw/timer/npcm7xx_timer.h        |   1 +
>  tests/qtest/meson.build                 |   4 +-
>  tests/qtest/npcm7xx_adc-test.c          | 400 ++++++++++++
>  tests/qtest/npcm7xx_pwm-test.c          | 490 +++++++++++++++
>  25 files changed, 4221 insertions(+), 32 deletions(-)
>  create mode 100644 hw/adc/npcm7xx_adc.c
>  create mode 100644 hw/ipmi/ipmi_host.c
>  create mode 100644 hw/ipmi/ipmi_host_extern.c
>  create mode 100644 hw/ipmi/npcm7xx_kcs.c
>  create mode 100644 hw/misc/npcm7xx_pwm.c
>  create mode 100644 include/hw/adc/npcm7xx_adc.h
>  create mode 100644 include/hw/ipmi/ipmi_host.h
>  create mode 100644 include/hw/ipmi/ipmi_responder.h
>  create mode 100644 include/hw/ipmi/npcm7xx_kcs.h
>  create mode 100644 include/hw/misc/npcm7xx_pwm.h
>  create mode 100644 tests/qtest/npcm7xx_adc-test.c
>  create mode 100644 tests/qtest/npcm7xx_pwm-test.c
> 
> -- 
> 2.29.2.684.gfbc64c5ab5-goog
> 


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support
  2020-12-11  3:04 ` [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Corey Minyard
@ 2020-12-11 20:56   ` Hao Wu via
  2020-12-12  0:16     ` Corey Minyard
  0 siblings, 1 reply; 14+ messages in thread
From: Hao Wu via @ 2020-12-11 20:56 UTC (permalink / raw)
  To: minyard
  Cc: Peter Maydell, Patrick Venture, Havard Skinnemoen,
	QEMU Developers, CS20 KFTing, qemu-arm, IS20 Avi Fishman

[-- Attachment #1: Type: text/plain, Size: 5058 bytes --]

Tl,dr: We'll remove the IPMI changes from the current patch set and
refactor
          them in a separate patch set.

Thank you for your review! On high level, we are trying to emulate the BMC
side of the IPMI protocol. So we cannot directly use the existing IPMI code.
However, they do have a lot in duplication as you pointed out. So we'll
refactor
the existing IPMI code and update in a way that we only add the required
functionality.

As for the KCS module, the BMC side of the protocol is the opposite
direction
of the existing ipmi_kcs.c code which is on the host/CPU side. For example,
in READ_STATE the CPU would read data while the BMC would write data.
So we can't directly use the same implementation. (They're different files
in
Linux either.) However, we can refactor it to re-use some of the common
definitions.

We would like to remove the IPMI and KCS stuff from the current patch set.
We'll send the refactored code in a separate patch set after addressing
your concerns.

Thanks again for the review!

On Thu, Dec 10, 2020 at 7:04 PM Corey Minyard <minyard@acm.org> wrote:

> On Thu, Dec 10, 2020 at 05:51:49PM -0800, Hao Wu wrote:
> > This patch series include a few more NPCM7XX devices including
> >
> > - Analog Digital Converter (ADC)
> > - Pulse Width Modulation (PWM)
> > - Keyboard Style Controller (KSC)
> >
> > To utilize these modules we also add two extra functionalities:
> >
> > 1. We modified the CLK module to generate clock values using qdev_clock.
> >    These clocks are used to determine various clocks in NPCM7XX devices.
> > 2. We added support for emulating IPMI responder devices in BMC machines,
> >    similar to the existing IPMI device support for CPU emulation. This
> allows
> >    a qemu instance running BMC firmware to serve as an external BMC for
> a qemu
> >    instance running server software. It utilizes the KCS module we
> implemented.
>
> Looking at the IPMI changes, why didn't you just re-use the existing
> IPMI infrastructure?  ipmi_host.[ch] is basically a subset of ipmi.[ch],
> and the ipmi_host_extern looks like a copy of of ipmi_bmc_extern with
> some names changed.  That kind of code duplication is not acceptable.
> Plus you copied my code and removed my copyrights, which is really
> not acceptable and illegal.
>
> I'm not exactly sure why you needed you own KCS interface, either.  It
> looks like the interface is somewhat different in some ways, but
> integrating it into the current KCS code is probably a better choice if
> that can be done.
>
> -corey
>
> >
> > Hao Wu (7):
> >   hw/misc: Add clock converter in NPCM7XX CLK module
> >   hw/timer: Refactor NPCM7XX Timer to use CLK clock
> >   hw/adc: Add an ADC module for NPCM7XX
> >   hw/misc: Add a PWM module for NPCM7XX
> >   hw/ipmi: Add an IPMI host interface
> >   hw/ipmi: Add a KCS Module for NPCM7XX
> >   hw/ipmi: Add an IPMI external host device
> >
> >  default-configs/devices/arm-softmmu.mak |   2 +
> >  docs/system/arm/nuvoton.rst             |   6 +-
> >  hw/adc/meson.build                      |   1 +
> >  hw/adc/npcm7xx_adc.c                    | 318 ++++++++++
> >  hw/arm/npcm7xx.c                        |  65 +-
> >  hw/ipmi/Kconfig                         |   5 +
> >  hw/ipmi/ipmi_host.c                     |  40 ++
> >  hw/ipmi/ipmi_host_extern.c              | 435 +++++++++++++
> >  hw/ipmi/meson.build                     |   3 +
> >  hw/ipmi/npcm7xx_kcs.c                   | 570 +++++++++++++++++
> >  hw/misc/meson.build                     |   1 +
> >  hw/misc/npcm7xx_clk.c                   | 795 +++++++++++++++++++++++-
> >  hw/misc/npcm7xx_pwm.c                   | 535 ++++++++++++++++
> >  hw/timer/npcm7xx_timer.c                |  25 +-
> >  include/hw/adc/npcm7xx_adc.h            |  72 +++
> >  include/hw/arm/npcm7xx.h                |   6 +
> >  include/hw/ipmi/ipmi_host.h             |  56 ++
> >  include/hw/ipmi/ipmi_responder.h        |  66 ++
> >  include/hw/ipmi/npcm7xx_kcs.h           | 105 ++++
> >  include/hw/misc/npcm7xx_clk.h           | 146 ++++-
> >  include/hw/misc/npcm7xx_pwm.h           | 106 ++++
> >  include/hw/timer/npcm7xx_timer.h        |   1 +
> >  tests/qtest/meson.build                 |   4 +-
> >  tests/qtest/npcm7xx_adc-test.c          | 400 ++++++++++++
> >  tests/qtest/npcm7xx_pwm-test.c          | 490 +++++++++++++++
> >  25 files changed, 4221 insertions(+), 32 deletions(-)
> >  create mode 100644 hw/adc/npcm7xx_adc.c
> >  create mode 100644 hw/ipmi/ipmi_host.c
> >  create mode 100644 hw/ipmi/ipmi_host_extern.c
> >  create mode 100644 hw/ipmi/npcm7xx_kcs.c
> >  create mode 100644 hw/misc/npcm7xx_pwm.c
> >  create mode 100644 include/hw/adc/npcm7xx_adc.h
> >  create mode 100644 include/hw/ipmi/ipmi_host.h
> >  create mode 100644 include/hw/ipmi/ipmi_responder.h
> >  create mode 100644 include/hw/ipmi/npcm7xx_kcs.h
> >  create mode 100644 include/hw/misc/npcm7xx_pwm.h
> >  create mode 100644 tests/qtest/npcm7xx_adc-test.c
> >  create mode 100644 tests/qtest/npcm7xx_pwm-test.c
> >
> > --
> > 2.29.2.684.gfbc64c5ab5-goog
> >
>

[-- Attachment #2: Type: text/html, Size: 6364 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support
  2020-12-11 20:56   ` Hao Wu via
@ 2020-12-12  0:16     ` Corey Minyard
  2020-12-12  0:26       ` Havard Skinnemoen via
  0 siblings, 1 reply; 14+ messages in thread
From: Corey Minyard @ 2020-12-12  0:16 UTC (permalink / raw)
  To: Hao Wu
  Cc: Peter Maydell, Patrick Venture, Havard Skinnemoen,
	QEMU Developers, CS20 KFTing, qemu-arm, IS20 Avi Fishman

On Fri, Dec 11, 2020 at 12:56:07PM -0800, Hao Wu wrote:
> Tl,dr: We'll remove the IPMI changes from the current patch set and
> refactor
>           them in a separate patch set.
> 
> Thank you for your review! On high level, we are trying to emulate the BMC
> side of the IPMI protocol. So we cannot directly use the existing IPMI code.
> However, they do have a lot in duplication as you pointed out. So we'll
> refactor
> the existing IPMI code and update in a way that we only add the required
> functionality.

Ah, I didn't figure that out from what you posted.  So the idea is you
can create the BMC side of the system in one qemu session with your
changes and then you connect it to a host system running qemu with the
host side of the interface.

The wire protocol is basically symmetric, but the command handling will
need to be separate.  So you probably want to split out the base
protocol from ipmi_bmc_extern into its own file and use that from your
own file, to avoid the duplication.

You need to do proper ATTN handling on the BMC side.  And you will also
need ties into GPIOs and whatnot for doing the reset, NMI, etc.

"ipmi_host" is probably not the best name.  At least to me that implied
the host side of the interface.  I'm not coming up with something I
really like, though.  Maybe "bmc_host"?  That's more descriptive, though
I'm sure a better name exists.  Then you could have "bmc_host_extern"
for the protocol.  If you come up with a better naming scheme, the
existing files can be renamed, too.

Thanks,

-corey

> 
> As for the KCS module, the BMC side of the protocol is the opposite
> direction
> of the existing ipmi_kcs.c code which is on the host/CPU side. For example,
> in READ_STATE the CPU would read data while the BMC would write data.
> So we can't directly use the same implementation. (They're different files
> in
> Linux either.) However, we can refactor it to re-use some of the common
> definitions.
> 
> We would like to remove the IPMI and KCS stuff from the current patch set.
> We'll send the refactored code in a separate patch set after addressing
> your concerns.
> 
> Thanks again for the review!
> 
> On Thu, Dec 10, 2020 at 7:04 PM Corey Minyard <minyard@acm.org> wrote:
> 
> > On Thu, Dec 10, 2020 at 05:51:49PM -0800, Hao Wu wrote:
> > > This patch series include a few more NPCM7XX devices including
> > >
> > > - Analog Digital Converter (ADC)
> > > - Pulse Width Modulation (PWM)
> > > - Keyboard Style Controller (KSC)
> > >
> > > To utilize these modules we also add two extra functionalities:
> > >
> > > 1. We modified the CLK module to generate clock values using qdev_clock.
> > >    These clocks are used to determine various clocks in NPCM7XX devices.
> > > 2. We added support for emulating IPMI responder devices in BMC machines,
> > >    similar to the existing IPMI device support for CPU emulation. This
> > allows
> > >    a qemu instance running BMC firmware to serve as an external BMC for
> > a qemu
> > >    instance running server software. It utilizes the KCS module we
> > implemented.
> >
> > Looking at the IPMI changes, why didn't you just re-use the existing
> > IPMI infrastructure?  ipmi_host.[ch] is basically a subset of ipmi.[ch],
> > and the ipmi_host_extern looks like a copy of of ipmi_bmc_extern with
> > some names changed.  That kind of code duplication is not acceptable.
> > Plus you copied my code and removed my copyrights, which is really
> > not acceptable and illegal.
> >
> > I'm not exactly sure why you needed you own KCS interface, either.  It
> > looks like the interface is somewhat different in some ways, but
> > integrating it into the current KCS code is probably a better choice if
> > that can be done.
> >
> > -corey
> >
> > >
> > > Hao Wu (7):
> > >   hw/misc: Add clock converter in NPCM7XX CLK module
> > >   hw/timer: Refactor NPCM7XX Timer to use CLK clock
> > >   hw/adc: Add an ADC module for NPCM7XX
> > >   hw/misc: Add a PWM module for NPCM7XX
> > >   hw/ipmi: Add an IPMI host interface
> > >   hw/ipmi: Add a KCS Module for NPCM7XX
> > >   hw/ipmi: Add an IPMI external host device
> > >
> > >  default-configs/devices/arm-softmmu.mak |   2 +
> > >  docs/system/arm/nuvoton.rst             |   6 +-
> > >  hw/adc/meson.build                      |   1 +
> > >  hw/adc/npcm7xx_adc.c                    | 318 ++++++++++
> > >  hw/arm/npcm7xx.c                        |  65 +-
> > >  hw/ipmi/Kconfig                         |   5 +
> > >  hw/ipmi/ipmi_host.c                     |  40 ++
> > >  hw/ipmi/ipmi_host_extern.c              | 435 +++++++++++++
> > >  hw/ipmi/meson.build                     |   3 +
> > >  hw/ipmi/npcm7xx_kcs.c                   | 570 +++++++++++++++++
> > >  hw/misc/meson.build                     |   1 +
> > >  hw/misc/npcm7xx_clk.c                   | 795 +++++++++++++++++++++++-
> > >  hw/misc/npcm7xx_pwm.c                   | 535 ++++++++++++++++
> > >  hw/timer/npcm7xx_timer.c                |  25 +-
> > >  include/hw/adc/npcm7xx_adc.h            |  72 +++
> > >  include/hw/arm/npcm7xx.h                |   6 +
> > >  include/hw/ipmi/ipmi_host.h             |  56 ++
> > >  include/hw/ipmi/ipmi_responder.h        |  66 ++
> > >  include/hw/ipmi/npcm7xx_kcs.h           | 105 ++++
> > >  include/hw/misc/npcm7xx_clk.h           | 146 ++++-
> > >  include/hw/misc/npcm7xx_pwm.h           | 106 ++++
> > >  include/hw/timer/npcm7xx_timer.h        |   1 +
> > >  tests/qtest/meson.build                 |   4 +-
> > >  tests/qtest/npcm7xx_adc-test.c          | 400 ++++++++++++
> > >  tests/qtest/npcm7xx_pwm-test.c          | 490 +++++++++++++++
> > >  25 files changed, 4221 insertions(+), 32 deletions(-)
> > >  create mode 100644 hw/adc/npcm7xx_adc.c
> > >  create mode 100644 hw/ipmi/ipmi_host.c
> > >  create mode 100644 hw/ipmi/ipmi_host_extern.c
> > >  create mode 100644 hw/ipmi/npcm7xx_kcs.c
> > >  create mode 100644 hw/misc/npcm7xx_pwm.c
> > >  create mode 100644 include/hw/adc/npcm7xx_adc.h
> > >  create mode 100644 include/hw/ipmi/ipmi_host.h
> > >  create mode 100644 include/hw/ipmi/ipmi_responder.h
> > >  create mode 100644 include/hw/ipmi/npcm7xx_kcs.h
> > >  create mode 100644 include/hw/misc/npcm7xx_pwm.h
> > >  create mode 100644 tests/qtest/npcm7xx_adc-test.c
> > >  create mode 100644 tests/qtest/npcm7xx_pwm-test.c
> > >
> > > --
> > > 2.29.2.684.gfbc64c5ab5-goog
> > >
> >


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support
  2020-12-12  0:16     ` Corey Minyard
@ 2020-12-12  0:26       ` Havard Skinnemoen via
  2020-12-12  0:34         ` Hao Wu via
  2020-12-12  0:48         ` Corey Minyard
  0 siblings, 2 replies; 14+ messages in thread
From: Havard Skinnemoen via @ 2020-12-12  0:26 UTC (permalink / raw)
  To: minyard
  Cc: Peter Maydell, Patrick Venture, QEMU Developers, Hao Wu,
	CS20 KFTing, qemu-arm, IS20 Avi Fishman

[-- Attachment #1: Type: text/plain, Size: 7332 bytes --]

On Fri, Dec 11, 2020 at 4:16 PM Corey Minyard <minyard@acm.org> wrote:

> On Fri, Dec 11, 2020 at 12:56:07PM -0800, Hao Wu wrote:
> > Tl,dr: We'll remove the IPMI changes from the current patch set and
> > refactor
> >           them in a separate patch set.
> >
> > Thank you for your review! On high level, we are trying to emulate the
> BMC
> > side of the IPMI protocol. So we cannot directly use the existing IPMI
> code.
> > However, they do have a lot in duplication as you pointed out. So we'll
> > refactor
> > the existing IPMI code and update in a way that we only add the required
> > functionality.
>
> Ah, I didn't figure that out from what you posted.  So the idea is you
> can create the BMC side of the system in one qemu session with your
> changes and then you connect it to a host system running qemu with the
> host side of the interface.
>
> The wire protocol is basically symmetric, but the command handling will
> need to be separate.  So you probably want to split out the base
> protocol from ipmi_bmc_extern into its own file and use that from your
> own file, to avoid the duplication.
>
> You need to do proper ATTN handling on the BMC side.  And you will also
> need ties into GPIOs and whatnot for doing the reset, NMI, etc.
>
> "ipmi_host" is probably not the best name.  At least to me that implied
> the host side of the interface.  I'm not coming up with something I
> really like, though.  Maybe "bmc_host"?  That's more descriptive, though
> I'm sure a better name exists.  Then you could have "bmc_host_extern"
> for the protocol.  If you come up with a better naming scheme, the
> existing files can be renamed, too.
>

The naming is my fault.

My thinking was that ipmi-host-extern is to the BMC what ipmi-bmc-extern is
to the host. That is, the former represents the host as seen by the BMC,
and the latter represents the BMC as seen by the host.

I sent some docs to the list earlier this year, but sadly, I never got
around to follow up. You can see the generated docs here:

https://hskinnemoen.github.io/qemu/specs/ipmi.html

Hao, perhaps you should include my documentation patches in your next IPMI
series? If we come up with a better naming scheme for both sides, I can
update the docs accordingly.

Havard


> Thanks,
>
> -corey
>
> >
> > As for the KCS module, the BMC side of the protocol is the opposite
> > direction
> > of the existing ipmi_kcs.c code which is on the host/CPU side. For
> example,
> > in READ_STATE the CPU would read data while the BMC would write data.
> > So we can't directly use the same implementation. (They're different
> files
> > in
> > Linux either.) However, we can refactor it to re-use some of the common
> > definitions.
> >
> > We would like to remove the IPMI and KCS stuff from the current patch
> set.
> > We'll send the refactored code in a separate patch set after addressing
> > your concerns.
> >
> > Thanks again for the review!
> >
> > On Thu, Dec 10, 2020 at 7:04 PM Corey Minyard <minyard@acm.org> wrote:
> >
> > > On Thu, Dec 10, 2020 at 05:51:49PM -0800, Hao Wu wrote:
> > > > This patch series include a few more NPCM7XX devices including
> > > >
> > > > - Analog Digital Converter (ADC)
> > > > - Pulse Width Modulation (PWM)
> > > > - Keyboard Style Controller (KSC)
> > > >
> > > > To utilize these modules we also add two extra functionalities:
> > > >
> > > > 1. We modified the CLK module to generate clock values using
> qdev_clock.
> > > >    These clocks are used to determine various clocks in NPCM7XX
> devices.
> > > > 2. We added support for emulating IPMI responder devices in BMC
> machines,
> > > >    similar to the existing IPMI device support for CPU emulation.
> This
> > > allows
> > > >    a qemu instance running BMC firmware to serve as an external BMC
> for
> > > a qemu
> > > >    instance running server software. It utilizes the KCS module we
> > > implemented.
> > >
> > > Looking at the IPMI changes, why didn't you just re-use the existing
> > > IPMI infrastructure?  ipmi_host.[ch] is basically a subset of
> ipmi.[ch],
> > > and the ipmi_host_extern looks like a copy of of ipmi_bmc_extern with
> > > some names changed.  That kind of code duplication is not acceptable.
> > > Plus you copied my code and removed my copyrights, which is really
> > > not acceptable and illegal.
> > >
> > > I'm not exactly sure why you needed you own KCS interface, either.  It
> > > looks like the interface is somewhat different in some ways, but
> > > integrating it into the current KCS code is probably a better choice if
> > > that can be done.
> > >
> > > -corey
> > >
> > > >
> > > > Hao Wu (7):
> > > >   hw/misc: Add clock converter in NPCM7XX CLK module
> > > >   hw/timer: Refactor NPCM7XX Timer to use CLK clock
> > > >   hw/adc: Add an ADC module for NPCM7XX
> > > >   hw/misc: Add a PWM module for NPCM7XX
> > > >   hw/ipmi: Add an IPMI host interface
> > > >   hw/ipmi: Add a KCS Module for NPCM7XX
> > > >   hw/ipmi: Add an IPMI external host device
> > > >
> > > >  default-configs/devices/arm-softmmu.mak |   2 +
> > > >  docs/system/arm/nuvoton.rst             |   6 +-
> > > >  hw/adc/meson.build                      |   1 +
> > > >  hw/adc/npcm7xx_adc.c                    | 318 ++++++++++
> > > >  hw/arm/npcm7xx.c                        |  65 +-
> > > >  hw/ipmi/Kconfig                         |   5 +
> > > >  hw/ipmi/ipmi_host.c                     |  40 ++
> > > >  hw/ipmi/ipmi_host_extern.c              | 435 +++++++++++++
> > > >  hw/ipmi/meson.build                     |   3 +
> > > >  hw/ipmi/npcm7xx_kcs.c                   | 570 +++++++++++++++++
> > > >  hw/misc/meson.build                     |   1 +
> > > >  hw/misc/npcm7xx_clk.c                   | 795
> +++++++++++++++++++++++-
> > > >  hw/misc/npcm7xx_pwm.c                   | 535 ++++++++++++++++
> > > >  hw/timer/npcm7xx_timer.c                |  25 +-
> > > >  include/hw/adc/npcm7xx_adc.h            |  72 +++
> > > >  include/hw/arm/npcm7xx.h                |   6 +
> > > >  include/hw/ipmi/ipmi_host.h             |  56 ++
> > > >  include/hw/ipmi/ipmi_responder.h        |  66 ++
> > > >  include/hw/ipmi/npcm7xx_kcs.h           | 105 ++++
> > > >  include/hw/misc/npcm7xx_clk.h           | 146 ++++-
> > > >  include/hw/misc/npcm7xx_pwm.h           | 106 ++++
> > > >  include/hw/timer/npcm7xx_timer.h        |   1 +
> > > >  tests/qtest/meson.build                 |   4 +-
> > > >  tests/qtest/npcm7xx_adc-test.c          | 400 ++++++++++++
> > > >  tests/qtest/npcm7xx_pwm-test.c          | 490 +++++++++++++++
> > > >  25 files changed, 4221 insertions(+), 32 deletions(-)
> > > >  create mode 100644 hw/adc/npcm7xx_adc.c
> > > >  create mode 100644 hw/ipmi/ipmi_host.c
> > > >  create mode 100644 hw/ipmi/ipmi_host_extern.c
> > > >  create mode 100644 hw/ipmi/npcm7xx_kcs.c
> > > >  create mode 100644 hw/misc/npcm7xx_pwm.c
> > > >  create mode 100644 include/hw/adc/npcm7xx_adc.h
> > > >  create mode 100644 include/hw/ipmi/ipmi_host.h
> > > >  create mode 100644 include/hw/ipmi/ipmi_responder.h
> > > >  create mode 100644 include/hw/ipmi/npcm7xx_kcs.h
> > > >  create mode 100644 include/hw/misc/npcm7xx_pwm.h
> > > >  create mode 100644 tests/qtest/npcm7xx_adc-test.c
> > > >  create mode 100644 tests/qtest/npcm7xx_pwm-test.c
> > > >
> > > > --
> > > > 2.29.2.684.gfbc64c5ab5-goog
> > > >
> > >
>

[-- Attachment #2: Type: text/html, Size: 9590 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support
  2020-12-12  0:26       ` Havard Skinnemoen via
@ 2020-12-12  0:34         ` Hao Wu via
  2020-12-12  0:48         ` Corey Minyard
  1 sibling, 0 replies; 14+ messages in thread
From: Hao Wu via @ 2020-12-12  0:34 UTC (permalink / raw)
  To: Havard Skinnemoen
  Cc: Peter Maydell, minyard, Patrick Venture, QEMU Developers,
	CS20 KFTing, qemu-arm, IS20 Avi Fishman

[-- Attachment #1: Type: text/plain, Size: 7857 bytes --]

Thanks for the comments!

I've removed IPMI part from the patch sets. I'll send a separate patch sets
once the refactor is done. I'll also include Havard's documentation in it.

I haven't thought of a better name. We can update the name accordingly.

On Fri, Dec 11, 2020 at 4:26 PM Havard Skinnemoen <hskinnemoen@google.com>
wrote:

> On Fri, Dec 11, 2020 at 4:16 PM Corey Minyard <minyard@acm.org> wrote:
>
>> On Fri, Dec 11, 2020 at 12:56:07PM -0800, Hao Wu wrote:
>> > Tl,dr: We'll remove the IPMI changes from the current patch set and
>> > refactor
>> >           them in a separate patch set.
>> >
>> > Thank you for your review! On high level, we are trying to emulate the
>> BMC
>> > side of the IPMI protocol. So we cannot directly use the existing IPMI
>> code.
>> > However, they do have a lot in duplication as you pointed out. So we'll
>> > refactor
>> > the existing IPMI code and update in a way that we only add the required
>> > functionality.
>>
>> Ah, I didn't figure that out from what you posted.  So the idea is you
>> can create the BMC side of the system in one qemu session with your
>> changes and then you connect it to a host system running qemu with the
>> host side of the interface.
>>
>> The wire protocol is basically symmetric, but the command handling will
>> need to be separate.  So you probably want to split out the base
>> protocol from ipmi_bmc_extern into its own file and use that from your
>> own file, to avoid the duplication.
>>
>> You need to do proper ATTN handling on the BMC side.  And you will also
>> need ties into GPIOs and whatnot for doing the reset, NMI, etc.
>>
>> "ipmi_host" is probably not the best name.  At least to me that implied
>> the host side of the interface.  I'm not coming up with something I
>> really like, though.  Maybe "bmc_host"?  That's more descriptive, though
>> I'm sure a better name exists.  Then you could have "bmc_host_extern"
>> for the protocol.  If you come up with a better naming scheme, the
>> existing files can be renamed, too.
>>
>
> The naming is my fault.
>
> My thinking was that ipmi-host-extern is to the BMC what ipmi-bmc-extern
> is to the host. That is, the former represents the host as seen by the BMC,
> and the latter represents the BMC as seen by the host.
>
> I sent some docs to the list earlier this year, but sadly, I never got
> around to follow up. You can see the generated docs here:
>
> https://hskinnemoen.github.io/qemu/specs/ipmi.html
>
> Hao, perhaps you should include my documentation patches in your next IPMI
> series? If we come up with a better naming scheme for both sides, I can
> update the docs accordingly.
>
> Havard
>
>
>> Thanks,
>>
>> -corey
>>
>> >
>> > As for the KCS module, the BMC side of the protocol is the opposite
>> > direction
>> > of the existing ipmi_kcs.c code which is on the host/CPU side. For
>> example,
>> > in READ_STATE the CPU would read data while the BMC would write data.
>> > So we can't directly use the same implementation. (They're different
>> files
>> > in
>> > Linux either.) However, we can refactor it to re-use some of the common
>> > definitions.
>> >
>> > We would like to remove the IPMI and KCS stuff from the current patch
>> set.
>> > We'll send the refactored code in a separate patch set after addressing
>> > your concerns.
>> >
>> > Thanks again for the review!
>> >
>> > On Thu, Dec 10, 2020 at 7:04 PM Corey Minyard <minyard@acm.org> wrote:
>> >
>> > > On Thu, Dec 10, 2020 at 05:51:49PM -0800, Hao Wu wrote:
>> > > > This patch series include a few more NPCM7XX devices including
>> > > >
>> > > > - Analog Digital Converter (ADC)
>> > > > - Pulse Width Modulation (PWM)
>> > > > - Keyboard Style Controller (KSC)
>> > > >
>> > > > To utilize these modules we also add two extra functionalities:
>> > > >
>> > > > 1. We modified the CLK module to generate clock values using
>> qdev_clock.
>> > > >    These clocks are used to determine various clocks in NPCM7XX
>> devices.
>> > > > 2. We added support for emulating IPMI responder devices in BMC
>> machines,
>> > > >    similar to the existing IPMI device support for CPU emulation.
>> This
>> > > allows
>> > > >    a qemu instance running BMC firmware to serve as an external BMC
>> for
>> > > a qemu
>> > > >    instance running server software. It utilizes the KCS module we
>> > > implemented.
>> > >
>> > > Looking at the IPMI changes, why didn't you just re-use the existing
>> > > IPMI infrastructure?  ipmi_host.[ch] is basically a subset of
>> ipmi.[ch],
>> > > and the ipmi_host_extern looks like a copy of of ipmi_bmc_extern with
>> > > some names changed.  That kind of code duplication is not acceptable.
>> > > Plus you copied my code and removed my copyrights, which is really
>> > > not acceptable and illegal.
>> > >
>> > > I'm not exactly sure why you needed you own KCS interface, either.  It
>> > > looks like the interface is somewhat different in some ways, but
>> > > integrating it into the current KCS code is probably a better choice
>> if
>> > > that can be done.
>> > >
>> > > -corey
>> > >
>> > > >
>> > > > Hao Wu (7):
>> > > >   hw/misc: Add clock converter in NPCM7XX CLK module
>> > > >   hw/timer: Refactor NPCM7XX Timer to use CLK clock
>> > > >   hw/adc: Add an ADC module for NPCM7XX
>> > > >   hw/misc: Add a PWM module for NPCM7XX
>> > > >   hw/ipmi: Add an IPMI host interface
>> > > >   hw/ipmi: Add a KCS Module for NPCM7XX
>> > > >   hw/ipmi: Add an IPMI external host device
>> > > >
>> > > >  default-configs/devices/arm-softmmu.mak |   2 +
>> > > >  docs/system/arm/nuvoton.rst             |   6 +-
>> > > >  hw/adc/meson.build                      |   1 +
>> > > >  hw/adc/npcm7xx_adc.c                    | 318 ++++++++++
>> > > >  hw/arm/npcm7xx.c                        |  65 +-
>> > > >  hw/ipmi/Kconfig                         |   5 +
>> > > >  hw/ipmi/ipmi_host.c                     |  40 ++
>> > > >  hw/ipmi/ipmi_host_extern.c              | 435 +++++++++++++
>> > > >  hw/ipmi/meson.build                     |   3 +
>> > > >  hw/ipmi/npcm7xx_kcs.c                   | 570 +++++++++++++++++
>> > > >  hw/misc/meson.build                     |   1 +
>> > > >  hw/misc/npcm7xx_clk.c                   | 795
>> +++++++++++++++++++++++-
>> > > >  hw/misc/npcm7xx_pwm.c                   | 535 ++++++++++++++++
>> > > >  hw/timer/npcm7xx_timer.c                |  25 +-
>> > > >  include/hw/adc/npcm7xx_adc.h            |  72 +++
>> > > >  include/hw/arm/npcm7xx.h                |   6 +
>> > > >  include/hw/ipmi/ipmi_host.h             |  56 ++
>> > > >  include/hw/ipmi/ipmi_responder.h        |  66 ++
>> > > >  include/hw/ipmi/npcm7xx_kcs.h           | 105 ++++
>> > > >  include/hw/misc/npcm7xx_clk.h           | 146 ++++-
>> > > >  include/hw/misc/npcm7xx_pwm.h           | 106 ++++
>> > > >  include/hw/timer/npcm7xx_timer.h        |   1 +
>> > > >  tests/qtest/meson.build                 |   4 +-
>> > > >  tests/qtest/npcm7xx_adc-test.c          | 400 ++++++++++++
>> > > >  tests/qtest/npcm7xx_pwm-test.c          | 490 +++++++++++++++
>> > > >  25 files changed, 4221 insertions(+), 32 deletions(-)
>> > > >  create mode 100644 hw/adc/npcm7xx_adc.c
>> > > >  create mode 100644 hw/ipmi/ipmi_host.c
>> > > >  create mode 100644 hw/ipmi/ipmi_host_extern.c
>> > > >  create mode 100644 hw/ipmi/npcm7xx_kcs.c
>> > > >  create mode 100644 hw/misc/npcm7xx_pwm.c
>> > > >  create mode 100644 include/hw/adc/npcm7xx_adc.h
>> > > >  create mode 100644 include/hw/ipmi/ipmi_host.h
>> > > >  create mode 100644 include/hw/ipmi/ipmi_responder.h
>> > > >  create mode 100644 include/hw/ipmi/npcm7xx_kcs.h
>> > > >  create mode 100644 include/hw/misc/npcm7xx_pwm.h
>> > > >  create mode 100644 tests/qtest/npcm7xx_adc-test.c
>> > > >  create mode 100644 tests/qtest/npcm7xx_pwm-test.c
>> > > >
>> > > > --
>> > > > 2.29.2.684.gfbc64c5ab5-goog
>> > > >
>> > >
>>
>

[-- Attachment #2: Type: text/html, Size: 10291 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support
  2020-12-12  0:26       ` Havard Skinnemoen via
  2020-12-12  0:34         ` Hao Wu via
@ 2020-12-12  0:48         ` Corey Minyard
  1 sibling, 0 replies; 14+ messages in thread
From: Corey Minyard @ 2020-12-12  0:48 UTC (permalink / raw)
  To: Havard Skinnemoen
  Cc: Peter Maydell, Patrick Venture, QEMU Developers, Hao Wu,
	CS20 KFTing, qemu-arm, IS20 Avi Fishman

On Fri, Dec 11, 2020 at 04:26:03PM -0800, Havard Skinnemoen wrote:
> On Fri, Dec 11, 2020 at 4:16 PM Corey Minyard <minyard@acm.org> wrote:
> 
> > On Fri, Dec 11, 2020 at 12:56:07PM -0800, Hao Wu wrote:
> > > Tl,dr: We'll remove the IPMI changes from the current patch set and
> > > refactor
> > >           them in a separate patch set.
> > >
> > > Thank you for your review! On high level, we are trying to emulate the
> > BMC
> > > side of the IPMI protocol. So we cannot directly use the existing IPMI
> > code.
> > > However, they do have a lot in duplication as you pointed out. So we'll
> > > refactor
> > > the existing IPMI code and update in a way that we only add the required
> > > functionality.
> >
> > Ah, I didn't figure that out from what you posted.  So the idea is you
> > can create the BMC side of the system in one qemu session with your
> > changes and then you connect it to a host system running qemu with the
> > host side of the interface.
> >
> > The wire protocol is basically symmetric, but the command handling will
> > need to be separate.  So you probably want to split out the base
> > protocol from ipmi_bmc_extern into its own file and use that from your
> > own file, to avoid the duplication.
> >
> > You need to do proper ATTN handling on the BMC side.  And you will also
> > need ties into GPIOs and whatnot for doing the reset, NMI, etc.
> >
> > "ipmi_host" is probably not the best name.  At least to me that implied
> > the host side of the interface.  I'm not coming up with something I
> > really like, though.  Maybe "bmc_host"?  That's more descriptive, though
> > I'm sure a better name exists.  Then you could have "bmc_host_extern"
> > for the protocol.  If you come up with a better naming scheme, the
> > existing files can be renamed, too.
> >
> 
> The naming is my fault.
> 
> My thinking was that ipmi-host-extern is to the BMC what ipmi-bmc-extern is
> to the host. That is, the former represents the host as seen by the BMC,
> and the latter represents the BMC as seen by the host.

Yeah, I kind of figured that, which is why I suggested that the existing
files can be renamed.  I really like it when I can look at a directory
and tell what everything does.  Unfortunately, that's generally harder
to achieve than it sounds.

We could also create a separate bmc directory under hw.  Then the names
you have make more sense.  It would also make things a bit cleaner,
since I wouldn't really be maintaining the BMC side of things.  I'm
already dealing with that on the Linux side.

> 
> I sent some docs to the list earlier this year, but sadly, I never got
> around to follow up. You can see the generated docs here:
> 
> https://hskinnemoen.github.io/qemu/specs/ipmi.html
> 
> Hao, perhaps you should include my documentation patches in your next IPMI
> series? If we come up with a better naming scheme for both sides, I can
> update the docs accordingly.

I remember that now.  Yes, documentation would be good.

Thanks,

-corey

> 
> Havard
> 
> 
> > Thanks,
> >
> > -corey
> >
> > >
> > > As for the KCS module, the BMC side of the protocol is the opposite
> > > direction
> > > of the existing ipmi_kcs.c code which is on the host/CPU side. For
> > example,
> > > in READ_STATE the CPU would read data while the BMC would write data.
> > > So we can't directly use the same implementation. (They're different
> > files
> > > in
> > > Linux either.) However, we can refactor it to re-use some of the common
> > > definitions.
> > >
> > > We would like to remove the IPMI and KCS stuff from the current patch
> > set.
> > > We'll send the refactored code in a separate patch set after addressing
> > > your concerns.
> > >
> > > Thanks again for the review!
> > >
> > > On Thu, Dec 10, 2020 at 7:04 PM Corey Minyard <minyard@acm.org> wrote:
> > >
> > > > On Thu, Dec 10, 2020 at 05:51:49PM -0800, Hao Wu wrote:
> > > > > This patch series include a few more NPCM7XX devices including
> > > > >
> > > > > - Analog Digital Converter (ADC)
> > > > > - Pulse Width Modulation (PWM)
> > > > > - Keyboard Style Controller (KSC)
> > > > >
> > > > > To utilize these modules we also add two extra functionalities:
> > > > >
> > > > > 1. We modified the CLK module to generate clock values using
> > qdev_clock.
> > > > >    These clocks are used to determine various clocks in NPCM7XX
> > devices.
> > > > > 2. We added support for emulating IPMI responder devices in BMC
> > machines,
> > > > >    similar to the existing IPMI device support for CPU emulation.
> > This
> > > > allows
> > > > >    a qemu instance running BMC firmware to serve as an external BMC
> > for
> > > > a qemu
> > > > >    instance running server software. It utilizes the KCS module we
> > > > implemented.
> > > >
> > > > Looking at the IPMI changes, why didn't you just re-use the existing
> > > > IPMI infrastructure?  ipmi_host.[ch] is basically a subset of
> > ipmi.[ch],
> > > > and the ipmi_host_extern looks like a copy of of ipmi_bmc_extern with
> > > > some names changed.  That kind of code duplication is not acceptable.
> > > > Plus you copied my code and removed my copyrights, which is really
> > > > not acceptable and illegal.
> > > >
> > > > I'm not exactly sure why you needed you own KCS interface, either.  It
> > > > looks like the interface is somewhat different in some ways, but
> > > > integrating it into the current KCS code is probably a better choice if
> > > > that can be done.
> > > >
> > > > -corey
> > > >
> > > > >
> > > > > Hao Wu (7):
> > > > >   hw/misc: Add clock converter in NPCM7XX CLK module
> > > > >   hw/timer: Refactor NPCM7XX Timer to use CLK clock
> > > > >   hw/adc: Add an ADC module for NPCM7XX
> > > > >   hw/misc: Add a PWM module for NPCM7XX
> > > > >   hw/ipmi: Add an IPMI host interface
> > > > >   hw/ipmi: Add a KCS Module for NPCM7XX
> > > > >   hw/ipmi: Add an IPMI external host device
> > > > >
> > > > >  default-configs/devices/arm-softmmu.mak |   2 +
> > > > >  docs/system/arm/nuvoton.rst             |   6 +-
> > > > >  hw/adc/meson.build                      |   1 +
> > > > >  hw/adc/npcm7xx_adc.c                    | 318 ++++++++++
> > > > >  hw/arm/npcm7xx.c                        |  65 +-
> > > > >  hw/ipmi/Kconfig                         |   5 +
> > > > >  hw/ipmi/ipmi_host.c                     |  40 ++
> > > > >  hw/ipmi/ipmi_host_extern.c              | 435 +++++++++++++
> > > > >  hw/ipmi/meson.build                     |   3 +
> > > > >  hw/ipmi/npcm7xx_kcs.c                   | 570 +++++++++++++++++
> > > > >  hw/misc/meson.build                     |   1 +
> > > > >  hw/misc/npcm7xx_clk.c                   | 795
> > +++++++++++++++++++++++-
> > > > >  hw/misc/npcm7xx_pwm.c                   | 535 ++++++++++++++++
> > > > >  hw/timer/npcm7xx_timer.c                |  25 +-
> > > > >  include/hw/adc/npcm7xx_adc.h            |  72 +++
> > > > >  include/hw/arm/npcm7xx.h                |   6 +
> > > > >  include/hw/ipmi/ipmi_host.h             |  56 ++
> > > > >  include/hw/ipmi/ipmi_responder.h        |  66 ++
> > > > >  include/hw/ipmi/npcm7xx_kcs.h           | 105 ++++
> > > > >  include/hw/misc/npcm7xx_clk.h           | 146 ++++-
> > > > >  include/hw/misc/npcm7xx_pwm.h           | 106 ++++
> > > > >  include/hw/timer/npcm7xx_timer.h        |   1 +
> > > > >  tests/qtest/meson.build                 |   4 +-
> > > > >  tests/qtest/npcm7xx_adc-test.c          | 400 ++++++++++++
> > > > >  tests/qtest/npcm7xx_pwm-test.c          | 490 +++++++++++++++
> > > > >  25 files changed, 4221 insertions(+), 32 deletions(-)
> > > > >  create mode 100644 hw/adc/npcm7xx_adc.c
> > > > >  create mode 100644 hw/ipmi/ipmi_host.c
> > > > >  create mode 100644 hw/ipmi/ipmi_host_extern.c
> > > > >  create mode 100644 hw/ipmi/npcm7xx_kcs.c
> > > > >  create mode 100644 hw/misc/npcm7xx_pwm.c
> > > > >  create mode 100644 include/hw/adc/npcm7xx_adc.h
> > > > >  create mode 100644 include/hw/ipmi/ipmi_host.h
> > > > >  create mode 100644 include/hw/ipmi/ipmi_responder.h
> > > > >  create mode 100644 include/hw/ipmi/npcm7xx_kcs.h
> > > > >  create mode 100644 include/hw/misc/npcm7xx_pwm.h
> > > > >  create mode 100644 tests/qtest/npcm7xx_adc-test.c
> > > > >  create mode 100644 tests/qtest/npcm7xx_pwm-test.c
> > > > >
> > > > > --
> > > > > 2.29.2.684.gfbc64c5ab5-goog
> > > > >
> > > >
> >


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2020-12-12  1:12 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-12-11  1:51 [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Hao Wu via
2020-12-11  1:51 ` [PATCH 1/7] hw/misc: Add clock converter in NPCM7XX CLK module Hao Wu via
2020-12-11  1:51 ` [PATCH 2/7] hw/timer: Refactor NPCM7XX Timer to use CLK clock Hao Wu via
2020-12-11  1:51 ` [PATCH 3/7] hw/adc: Add an ADC module for NPCM7XX Hao Wu via
2020-12-11  1:51 ` [PATCH 4/7] hw/misc: Add a PWM " Hao Wu via
2020-12-11  1:51 ` [PATCH 5/7] hw/ipmi: Add an IPMI host interface Hao Wu via
2020-12-11  1:51 ` [PATCH 6/7] hw/ipmi: Add a KCS Module for NPCM7XX Hao Wu via
2020-12-11  1:51 ` [PATCH 7/7] hw/ipmi: Add an IPMI external host device Hao Wu via
2020-12-11  3:04 ` [PATCH 0/7] Additional NPCM7xx devices and IPMI BMC emulation support Corey Minyard
2020-12-11 20:56   ` Hao Wu via
2020-12-12  0:16     ` Corey Minyard
2020-12-12  0:26       ` Havard Skinnemoen via
2020-12-12  0:34         ` Hao Wu via
2020-12-12  0:48         ` Corey Minyard

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).