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 63 static const uint32_t input_list[] = { 64 100000, 65 500000, 66 1000000, 67 1500000, 68 1800000, 69 2000000, 70 }; 71 72 static const uint32_t vref_list[] = { 73 2000000, 74 2200000, 75 2500000, 76 }; 77 78 static const uint32_t iref_list[] = { 79 1800000, 80 1900000, 81 2000000, 82 2100000, 83 2200000, 84 }; 85 86 static const uint32_t div_list[] = {0, 1, 3, 7, 15}; 87 88 typedef struct ADC { 89 int irq; 90 uint64_t base_addr; 91 } ADC; 92 93 ADC adc = { 94 .irq = 0, 95 .base_addr = 0xf000c000 96 }; 97 98 static uint32_t adc_read_con(QTestState *qts, const ADC *adc) 99 { 100 return qtest_readl(qts, adc->base_addr + CON_OFFSET); 101 } 102 103 static 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 108 static uint32_t adc_read_data(QTestState *qts, const ADC *adc) 109 { 110 return qtest_readl(qts, adc->base_addr + DATA_OFFSET); 111 } 112 113 static 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 119 static 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 135 static 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 144 static void adc_write_vref(QTestState *qts, const ADC *adc, uint32_t value) 145 { 146 adc_qom_set(qts, adc, "vref", value); 147 } 148 149 static 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 162 static 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 169 static 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 175 static 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. */ 194 static 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. */ 205 static 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. */ 233 static 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. */ 265 static 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. */ 291 static 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. */ 308 static 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 358 static 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 366 int 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