On Mon, Dec 14, 2020 at 4:13 PM Hao Wu <wuhao...@google.com> wrote:
> 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. > > Users can alter input value by using qom-set QMP command. > > Reviewed-by: Havard Skinnemoen <hskinnem...@google.com> > Reviewed-by: Tyrone Ting <kft...@nuvoton.com> > Signed-off-by: Hao Wu <wuhao...@google.com> > --- > docs/system/arm/nuvoton.rst | 2 +- > hw/adc/meson.build | 1 + > hw/adc/npcm7xx_adc.c | 321 ++++++++++++++++++++++++++ > hw/adc/trace-events | 5 + > hw/arm/npcm7xx.c | 24 +- > include/hw/adc/npcm7xx_adc.h | 72 ++++++ > include/hw/arm/npcm7xx.h | 2 + > meson.build | 1 + > tests/qtest/meson.build | 3 +- > tests/qtest/npcm7xx_adc-test.c | 400 +++++++++++++++++++++++++++++++++ > 10 files changed, 828 insertions(+), 3 deletions(-) > create mode 100644 hw/adc/npcm7xx_adc.c > create mode 100644 hw/adc/trace-events > 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..c2c4819d3f > --- /dev/null > +++ b/hw/adc/npcm7xx_adc.c > @@ -0,0 +1,321 @@ > +/* > + * 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" > +#include "trace.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; > + } > + > + trace_npcm7xx_adc_read(DEVICE(s)->canonical_path, offset, value); > + 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); > + > + trace_npcm7xx_adc_write(DEVICE(s)->canonical_path, offset, v); > + 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/adc/trace-events b/hw/adc/trace-events > new file mode 100644 > index 0000000000..4c3279ece2 > --- /dev/null > +++ b/hw/adc/trace-events > @@ -0,0 +1,5 @@ > +# See docs/devel/tracing.txt for syntax documentation. > + > +# npcm7xx_adc.c > +npcm7xx_adc_read(const char *id, uint64_t offset, uint32_t value) " %s > offset: 0x%04" PRIx64 " value 0x%04" PRIx32 > +npcm7xx_adc_write(const char *id, uint64_t offset, uint32_t value) "%s > offset: 0x%04" PRIx64 " value 0x%04" PRIx32 > 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/meson.build b/meson.build > index f344b25955..fb03cdbdcc 100644 > --- a/meson.build > +++ b/meson.build > @@ -1435,6 +1435,7 @@ if have_system > 'chardev', > 'hw/9pfs', > 'hw/acpi', > + 'hw/adc', > 'hw/alpha', > 'hw/arm', > 'hw/audio', > 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 > >