summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorHao Wu2021-01-08 20:09:42 +0100
committerPeter Maydell2021-01-12 22:19:02 +0100
commit77c05b0b746119a78bffb595b0313d39ac6b20fc (patch)
tree72bd9b4e4f3773f94809fafec303c7cff44074f3 /tests
parenthw/timer: Refactor NPCM7XX Timer to use CLK clock (diff)
downloadqemu-77c05b0b746119a78bffb595b0313d39ac6b20fc.tar.gz
qemu-77c05b0b746119a78bffb595b0313d39ac6b20fc.tar.xz
qemu-77c05b0b746119a78bffb595b0313d39ac6b20fc.zip
hw/adc: Add an ADC module for NPCM7XX
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 <hskinnemoen@google.com> Reviewed-by: Tyrone Ting <kfting@nuvoton.com> Signed-off-by: Hao Wu <wuhaotsh@google.com> Message-id: 20210108190945.949196-4-wuhaotsh@google.com [PMM: Added missing hw/adc/trace.h file] Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'tests')
-rw-r--r--tests/qtest/meson.build3
-rw-r--r--tests/qtest/npcm7xx_adc-test.c377
2 files changed, 379 insertions, 1 deletions
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..f029706945
--- /dev/null
+++ b/tests/qtest/npcm7xx_adc-test.c
@@ -0,0 +1,377 @@
+/*
+ * 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));
+ g_assert_false(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(calibrate, &adc);
+
+ return g_test_run();
+}