npcm7xx_adc-test.c (11466B)
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}