qemu/tests/qtest/npcm7xx_adc-test.c
<<
>>
Prefs
   1/*
   2 * QTests for Nuvoton NPCM7xx ADCModules.
   3 *
   4 * Copyright 2020 Google LLC
   5 *
   6 * This program is free software; you can redistribute it and/or modify it
   7 * under the terms of the GNU General Public License as published by the
   8 * Free Software Foundation; either version 2 of the License, or
   9 * (at your option) any later version.
  10 *
  11 * This program is distributed in the hope that it will be useful, but WITHOUT
  12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  14 * for more details.
  15 */
  16
  17#include "qemu/osdep.h"
  18#include "qemu/bitops.h"
  19#include "qemu/timer.h"
  20#include "libqos/libqtest.h"
  21#include "qapi/qmp/qdict.h"
  22
  23#define REF_HZ          (25000000)
  24
  25#define CON_OFFSET      0x0
  26#define DATA_OFFSET     0x4
  27
  28#define NUM_INPUTS      8
  29#define DEFAULT_IREF    2000000
  30#define CONV_CYCLES     20
  31#define RESET_CYCLES    10
  32#define R0_INPUT        500000
  33#define R1_INPUT        1500000
  34#define MAX_RESULT      1023
  35
  36#define DEFAULT_CLKDIV  5
  37
  38#define FUSE_ARRAY_BA   0xf018a000
  39#define FCTL_OFFSET     0x14
  40#define FST_OFFSET      0x0
  41#define FADDR_OFFSET    0x4
  42#define FDATA_OFFSET    0x8
  43#define ADC_CALIB_ADDR  24
  44#define FUSE_READ       0x2
  45
  46/* Register field definitions. */
  47#define CON_MUX(rv) ((rv) << 24)
  48#define CON_INT_EN  BIT(21)
  49#define CON_REFSEL  BIT(19)
  50#define CON_INT     BIT(18)
  51#define CON_EN      BIT(17)
  52#define CON_RST     BIT(16)
  53#define CON_CONV    BIT(14)
  54#define CON_DIV(rv) extract32(rv, 1, 8)
  55
  56#define FST_RDST    BIT(1)
  57#define FDATA_MASK  0xff
  58
  59#define MAX_ERROR   10000
  60#define MIN_CALIB_INPUT 100000
  61#define MAX_CALIB_INPUT 1800000
  62
  63static const uint32_t input_list[] = {
  64    100000,
  65    500000,
  66    1000000,
  67    1500000,
  68    1800000,
  69    2000000,
  70};
  71
  72static const uint32_t vref_list[] = {
  73    2000000,
  74    2200000,
  75    2500000,
  76};
  77
  78static const uint32_t iref_list[] = {
  79    1800000,
  80    1900000,
  81    2000000,
  82    2100000,
  83    2200000,
  84};
  85
  86static const uint32_t div_list[] = {0, 1, 3, 7, 15};
  87
  88typedef struct ADC {
  89    int irq;
  90    uint64_t base_addr;
  91} ADC;
  92
  93ADC adc = {
  94    .irq        = 0,
  95    .base_addr  = 0xf000c000
  96};
  97
  98static uint32_t adc_read_con(QTestState *qts, const ADC *adc)
  99{
 100    return qtest_readl(qts, adc->base_addr + CON_OFFSET);
 101}
 102
 103static void adc_write_con(QTestState *qts, const ADC *adc, uint32_t value)
 104{
 105    qtest_writel(qts, adc->base_addr + CON_OFFSET, value);
 106}
 107
 108static uint32_t adc_read_data(QTestState *qts, const ADC *adc)
 109{
 110    return qtest_readl(qts, adc->base_addr + DATA_OFFSET);
 111}
 112
 113static uint32_t adc_calibrate(uint32_t measured, uint32_t *rv)
 114{
 115    return R0_INPUT + (R1_INPUT - R0_INPUT) * (int32_t)(measured - rv[0])
 116        / (int32_t)(rv[1] - rv[0]);
 117}
 118
 119static void adc_qom_set(QTestState *qts, const ADC *adc,
 120        const char *name, uint32_t value)
 121{
 122    QDict *response;
 123    const char *path = "/machine/soc/adc";
 124
 125    g_test_message("Setting properties %s of %s with value %u",
 126            name, path, value);
 127    response = qtest_qmp(qts, "{ 'execute': 'qom-set',"
 128            " 'arguments': { 'path': %s, 'property': %s, 'value': %u}}",
 129            path, name, value);
 130    /* The qom set message returns successfully. */
 131    g_assert_true(qdict_haskey(response, "return"));
 132    qobject_unref(response);
 133}
 134
 135static void adc_write_input(QTestState *qts, const ADC *adc,
 136        uint32_t index, uint32_t value)
 137{
 138    char name[100];
 139
 140    sprintf(name, "adci[%u]", index);
 141    adc_qom_set(qts, adc, name, value);
 142}
 143
 144static void adc_write_vref(QTestState *qts, const ADC *adc, uint32_t value)
 145{
 146    adc_qom_set(qts, adc, "vref", value);
 147}
 148
 149static uint32_t adc_calculate_output(uint32_t input, uint32_t ref)
 150{
 151    uint32_t output;
 152
 153    g_assert_cmpuint(input, <=, ref);
 154    output = (input * (MAX_RESULT + 1)) / ref;
 155    if (output > MAX_RESULT) {
 156        output = MAX_RESULT;
 157    }
 158
 159    return output;
 160}
 161
 162static uint32_t adc_prescaler(QTestState *qts, const ADC *adc)
 163{
 164    uint32_t div = extract32(adc_read_con(qts, adc), 1, 8);
 165
 166    return 2 * (div + 1);
 167}
 168
 169static int64_t adc_calculate_steps(uint32_t cycles, uint32_t prescale,
 170        uint32_t clkdiv)
 171{
 172    return (NANOSECONDS_PER_SECOND / (REF_HZ >> clkdiv)) * cycles * prescale;
 173}
 174
 175static void adc_wait_conv_finished(QTestState *qts, const ADC *adc,
 176        uint32_t clkdiv)
 177{
 178    uint32_t prescaler = adc_prescaler(qts, adc);
 179
 180    /*
 181     * ADC should takes roughly 20 cycles to convert one sample. So we assert it
 182     * should take 10~30 cycles here.
 183     */
 184    qtest_clock_step(qts, adc_calculate_steps(CONV_CYCLES / 2, prescaler,
 185                clkdiv));
 186    /* ADC is still converting. */
 187    g_assert_true(adc_read_con(qts, adc) & CON_CONV);
 188    qtest_clock_step(qts, adc_calculate_steps(CONV_CYCLES, prescaler, clkdiv));
 189    /* ADC has finished conversion. */
 190    g_assert_false(adc_read_con(qts, adc) & CON_CONV);
 191}
 192
 193/* Check ADC can be reset to default value. */
 194static void test_init(gconstpointer adc_p)
 195{
 196    const ADC *adc = adc_p;
 197
 198    QTestState *qts = qtest_init("-machine quanta-gsj");
 199    adc_write_con(qts, adc, CON_REFSEL | CON_INT);
 200    g_assert_cmphex(adc_read_con(qts, adc), ==, CON_REFSEL);
 201    qtest_quit(qts);
 202}
 203
 204/* Check ADC can convert from an internal reference. */
 205static void test_convert_internal(gconstpointer adc_p)
 206{
 207    const ADC *adc = adc_p;
 208    uint32_t index, input, output, expected_output;
 209    QTestState *qts = qtest_init("-machine quanta-gsj");
 210    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
 211
 212    for (index = 0; index < NUM_INPUTS; ++index) {
 213        for (size_t i = 0; i < ARRAY_SIZE(input_list); ++i) {
 214            input = input_list[i];
 215            expected_output = adc_calculate_output(input, DEFAULT_IREF);
 216
 217            adc_write_input(qts, adc, index, input);
 218            adc_write_con(qts, adc, CON_MUX(index) | CON_REFSEL | CON_INT |
 219                    CON_EN | CON_CONV);
 220            adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV);
 221            g_assert_cmphex(adc_read_con(qts, adc), ==, CON_MUX(index) |
 222                    CON_REFSEL | CON_EN);
 223            g_assert_false(qtest_get_irq(qts, adc->irq));
 224            output = adc_read_data(qts, adc);
 225            g_assert_cmpuint(output, ==, expected_output);
 226        }
 227    }
 228
 229    qtest_quit(qts);
 230}
 231
 232/* Check ADC can convert from an external reference. */
 233static void test_convert_external(gconstpointer adc_p)
 234{
 235    const ADC *adc = adc_p;
 236    uint32_t index, input, vref, output, expected_output;
 237    QTestState *qts = qtest_init("-machine quanta-gsj");
 238    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
 239
 240    for (index = 0; index < NUM_INPUTS; ++index) {
 241        for (size_t i = 0; i < ARRAY_SIZE(input_list); ++i) {
 242            for (size_t j = 0; j < ARRAY_SIZE(vref_list); ++j) {
 243                input = input_list[i];
 244                vref = vref_list[j];
 245                expected_output = adc_calculate_output(input, vref);
 246
 247                adc_write_input(qts, adc, index, input);
 248                adc_write_vref(qts, adc, vref);
 249                adc_write_con(qts, adc, CON_MUX(index) | CON_INT | CON_EN |
 250                        CON_CONV);
 251                adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV);
 252                g_assert_cmphex(adc_read_con(qts, adc), ==,
 253                        CON_MUX(index) | CON_EN);
 254                g_assert_false(qtest_get_irq(qts, adc->irq));
 255                output = adc_read_data(qts, adc);
 256                g_assert_cmpuint(output, ==, expected_output);
 257            }
 258        }
 259    }
 260
 261    qtest_quit(qts);
 262}
 263
 264/* Check ADC interrupt files if and only if CON_INT_EN is set. */
 265static void test_interrupt(gconstpointer adc_p)
 266{
 267    const ADC *adc = adc_p;
 268    uint32_t index, input, output, expected_output;
 269    QTestState *qts = qtest_init("-machine quanta-gsj");
 270
 271    index = 1;
 272    input = input_list[1];
 273    expected_output = adc_calculate_output(input, DEFAULT_IREF);
 274
 275    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
 276    adc_write_input(qts, adc, index, input);
 277    g_assert_false(qtest_get_irq(qts, adc->irq));
 278    adc_write_con(qts, adc, CON_MUX(index) | CON_INT_EN | CON_REFSEL | CON_INT
 279            | CON_EN | CON_CONV);
 280    adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV);
 281    g_assert_cmphex(adc_read_con(qts, adc), ==, CON_MUX(index) | CON_INT_EN
 282            | CON_REFSEL | CON_INT | CON_EN);
 283    g_assert_true(qtest_get_irq(qts, adc->irq));
 284    output = adc_read_data(qts, adc);
 285    g_assert_cmpuint(output, ==, expected_output);
 286
 287    qtest_quit(qts);
 288}
 289
 290/* Check ADC is reset after setting ADC_RST for 10 ADC cycles. */
 291static void test_reset(gconstpointer adc_p)
 292{
 293    const ADC *adc = adc_p;
 294    QTestState *qts = qtest_init("-machine quanta-gsj");
 295
 296    for (size_t i = 0; i < ARRAY_SIZE(div_list); ++i) {
 297        uint32_t div = div_list[i];
 298
 299        adc_write_con(qts, adc, CON_INT | CON_EN | CON_RST | CON_DIV(div));
 300        qtest_clock_step(qts, adc_calculate_steps(RESET_CYCLES,
 301                    adc_prescaler(qts, adc), DEFAULT_CLKDIV));
 302        g_assert_false(adc_read_con(qts, adc) & CON_EN);
 303    }
 304    qtest_quit(qts);
 305}
 306
 307/* Check ADC Calibration works as desired. */
 308static void test_calibrate(gconstpointer adc_p)
 309{
 310    int i, j;
 311    const ADC *adc = adc_p;
 312
 313    for (j = 0; j < ARRAY_SIZE(iref_list); ++j) {
 314        uint32_t iref = iref_list[j];
 315        uint32_t expected_rv[] = {
 316            adc_calculate_output(R0_INPUT, iref),
 317            adc_calculate_output(R1_INPUT, iref),
 318        };
 319        char buf[100];
 320        QTestState *qts;
 321
 322        sprintf(buf, "-machine quanta-gsj -global npcm7xx-adc.iref=%u", iref);
 323        qts = qtest_init(buf);
 324
 325        /* Check the converted value is correct using the calibration value. */
 326        for (i = 0; i < ARRAY_SIZE(input_list); ++i) {
 327            uint32_t input;
 328            uint32_t output;
 329            uint32_t expected_output;
 330            uint32_t calibrated_voltage;
 331            uint32_t index = 0;
 332
 333            input = input_list[i];
 334            /* Calibration only works for input range 0.1V ~ 1.8V. */
 335            if (input < MIN_CALIB_INPUT || input > MAX_CALIB_INPUT) {
 336                continue;
 337            }
 338            expected_output = adc_calculate_output(input, iref);
 339
 340            adc_write_input(qts, adc, index, input);
 341            adc_write_con(qts, adc, CON_MUX(index) | CON_REFSEL | CON_INT |
 342                    CON_EN | CON_CONV);
 343            adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV);
 344            g_assert_cmphex(adc_read_con(qts, adc), ==,
 345                    CON_REFSEL | CON_MUX(index) | CON_EN);
 346            output = adc_read_data(qts, adc);
 347            g_assert_cmpuint(output, ==, expected_output);
 348
 349            calibrated_voltage = adc_calibrate(output, expected_rv);
 350            g_assert_cmpuint(calibrated_voltage, >, input - MAX_ERROR);
 351            g_assert_cmpuint(calibrated_voltage, <, input + MAX_ERROR);
 352        }
 353
 354        qtest_quit(qts);
 355    }
 356}
 357
 358static void adc_add_test(const char *name, const ADC* wd,
 359        GTestDataFunc fn)
 360{
 361    g_autofree char *full_name = g_strdup_printf("npcm7xx_adc/%s",  name);
 362    qtest_add_data_func(full_name, wd, fn);
 363}
 364#define add_test(name, td) adc_add_test(#name, td, test_##name)
 365
 366int main(int argc, char **argv)
 367{
 368    g_test_init(&argc, &argv, NULL);
 369
 370    add_test(init, &adc);
 371    add_test(convert_internal, &adc);
 372    add_test(convert_external, &adc);
 373    add_test(interrupt, &adc);
 374    add_test(reset, &adc);
 375    add_test(calibrate, &adc);
 376
 377    return g_test_run();
 378}
 379