 ec3ad0face
			
		
	
	
		ec3ad0face
		
	
	
	
	
		
			
			Rename the global 'adc' variable in order to avoid:
  tests/qtest/npcm7xx_adc-test.c:98:58: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
  static uint32_t adc_read_con(QTestState *qts, const ADC *adc)
                                                           ^
  tests/qtest/npcm7xx_adc-test.c:103:55: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
  static void adc_write_con(QTestState *qts, const ADC *adc, uint32_t value)
                                                        ^
  tests/qtest/npcm7xx_adc-test.c:108:59: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
  static uint32_t adc_read_data(QTestState *qts, const ADC *adc)
                                                            ^
  tests/qtest/npcm7xx_adc-test.c:119:53: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
  static void adc_qom_set(QTestState *qts, const ADC *adc,
                                                      ^
  tests/qtest/npcm7xx_adc-test.c:135:57: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
  static void adc_write_input(QTestState *qts, const ADC *adc,
                                                          ^
  tests/qtest/npcm7xx_adc-test.c:144:56: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
  static void adc_write_vref(QTestState *qts, const ADC *adc, uint32_t value)
                                                         ^
  tests/qtest/npcm7xx_adc-test.c:162:59: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
  static uint32_t adc_prescaler(QTestState *qts, const ADC *adc)
                                                            ^
  tests/qtest/npcm7xx_adc-test.c:175:64: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
  static void adc_wait_conv_finished(QTestState *qts, const ADC *adc,
                                                                 ^
  tests/qtest/npcm7xx_adc-test.c:196:16: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
    const ADC *adc = adc_p;
               ^
  tests/qtest/npcm7xx_adc-test.c:207:16: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
    const ADC *adc = adc_p;
               ^
  tests/qtest/npcm7xx_adc-test.c:235:16: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
    const ADC *adc = adc_p;
               ^
  tests/qtest/npcm7xx_adc-test.c:267:16: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
    const ADC *adc = adc_p;
               ^
  tests/qtest/npcm7xx_adc-test.c:293:16: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
    const ADC *adc = adc_p;
               ^
  tests/qtest/npcm7xx_adc-test.c:311:16: error: declaration shadows a variable in the global scope [-Werror,-Wshadow]
    const ADC *adc = adc_p;
               ^
  tests/qtest/npcm7xx_adc-test.c:93:5: note: previous declaration is here
  ADC adc = {
      ^
Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Message-ID: <20231009100251.56019-8-philmd@linaro.org>
Signed-off-by: Thomas Huth <thuth@redhat.com>
		
	
			
		
			
				
	
	
		
			379 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			379 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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 "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(13)
 | |
| #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_defs = {
 | |
|     .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"));
 | |
|     qobject_unref(response);
 | |
| }
 | |
| 
 | |
| 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_defs);
 | |
|     add_test(convert_internal, &adc_defs);
 | |
|     add_test(convert_external, &adc_defs);
 | |
|     add_test(interrupt, &adc_defs);
 | |
|     add_test(reset, &adc_defs);
 | |
|     add_test(calibrate, &adc_defs);
 | |
| 
 | |
|     return g_test_run();
 | |
| }
 |