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 } 133 134 static void adc_write_input(QTestState *qts, const ADC *adc, 135 uint32_t index, uint32_t value) 136 { 137 char name[100]; 138 139 sprintf(name, "adci[%u]", index); 140 adc_qom_set(qts, adc, name, value); 141 } 142 143 static void adc_write_vref(QTestState *qts, const ADC *adc, uint32_t value) 144 { 145 adc_qom_set(qts, adc, "vref", value); 146 } 147 148 static uint32_t adc_calculate_output(uint32_t input, uint32_t ref) 149 { 150 uint32_t output; 151 152 g_assert_cmpuint(input, <=, ref); 153 output = (input * (MAX_RESULT + 1)) / ref; 154 if (output > MAX_RESULT) { 155 output = MAX_RESULT; 156 } 157 158 return output; 159 } 160 161 static uint32_t adc_prescaler(QTestState *qts, const ADC *adc) 162 { 163 uint32_t div = extract32(adc_read_con(qts, adc), 1, 8); 164 165 return 2 * (div + 1); 166 } 167 168 static int64_t adc_calculate_steps(uint32_t cycles, uint32_t prescale, 169 uint32_t clkdiv) 170 { 171 return (NANOSECONDS_PER_SECOND / (REF_HZ >> clkdiv)) * cycles * prescale; 172 } 173 174 static void adc_wait_conv_finished(QTestState *qts, const ADC *adc, 175 uint32_t clkdiv) 176 { 177 uint32_t prescaler = adc_prescaler(qts, adc); 178 179 /* 180 * ADC should takes roughly 20 cycles to convert one sample. So we assert it 181 * should take 10~30 cycles here. 182 */ 183 qtest_clock_step(qts, adc_calculate_steps(CONV_CYCLES / 2, prescaler, 184 clkdiv)); 185 /* ADC is still converting. */ 186 g_assert_true(adc_read_con(qts, adc) & CON_CONV); 187 qtest_clock_step(qts, adc_calculate_steps(CONV_CYCLES, prescaler, clkdiv)); 188 /* ADC has finished conversion. */ 189 g_assert_false(adc_read_con(qts, adc) & CON_CONV); 190 } 191 192 /* Check ADC can be reset to default value. */ 193 static void test_init(gconstpointer adc_p) 194 { 195 const ADC *adc = adc_p; 196 197 QTestState *qts = qtest_init("-machine quanta-gsj"); 198 adc_write_con(qts, adc, CON_REFSEL | CON_INT); 199 g_assert_cmphex(adc_read_con(qts, adc), ==, CON_REFSEL); 200 qtest_quit(qts); 201 } 202 203 /* Check ADC can convert from an internal reference. */ 204 static void test_convert_internal(gconstpointer adc_p) 205 { 206 const ADC *adc = adc_p; 207 uint32_t index, input, output, expected_output; 208 QTestState *qts = qtest_init("-machine quanta-gsj"); 209 qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); 210 211 for (index = 0; index < NUM_INPUTS; ++index) { 212 for (size_t i = 0; i < ARRAY_SIZE(input_list); ++i) { 213 input = input_list[i]; 214 expected_output = adc_calculate_output(input, DEFAULT_IREF); 215 216 adc_write_input(qts, adc, index, input); 217 adc_write_con(qts, adc, CON_MUX(index) | CON_REFSEL | CON_INT | 218 CON_EN | CON_CONV); 219 adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV); 220 g_assert_cmphex(adc_read_con(qts, adc), ==, CON_MUX(index) | 221 CON_REFSEL | CON_EN); 222 g_assert_false(qtest_get_irq(qts, adc->irq)); 223 output = adc_read_data(qts, adc); 224 g_assert_cmpuint(output, ==, expected_output); 225 } 226 } 227 228 qtest_quit(qts); 229 } 230 231 /* Check ADC can convert from an external reference. */ 232 static void test_convert_external(gconstpointer adc_p) 233 { 234 const ADC *adc = adc_p; 235 uint32_t index, input, vref, output, expected_output; 236 QTestState *qts = qtest_init("-machine quanta-gsj"); 237 qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); 238 239 for (index = 0; index < NUM_INPUTS; ++index) { 240 for (size_t i = 0; i < ARRAY_SIZE(input_list); ++i) { 241 for (size_t j = 0; j < ARRAY_SIZE(vref_list); ++j) { 242 input = input_list[i]; 243 vref = vref_list[j]; 244 expected_output = adc_calculate_output(input, vref); 245 246 adc_write_input(qts, adc, index, input); 247 adc_write_vref(qts, adc, vref); 248 adc_write_con(qts, adc, CON_MUX(index) | CON_INT | CON_EN | 249 CON_CONV); 250 adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV); 251 g_assert_cmphex(adc_read_con(qts, adc), ==, 252 CON_MUX(index) | CON_EN); 253 g_assert_false(qtest_get_irq(qts, adc->irq)); 254 output = adc_read_data(qts, adc); 255 g_assert_cmpuint(output, ==, expected_output); 256 } 257 } 258 } 259 260 qtest_quit(qts); 261 } 262 263 /* Check ADC interrupt files if and only if CON_INT_EN is set. */ 264 static void test_interrupt(gconstpointer adc_p) 265 { 266 const ADC *adc = adc_p; 267 uint32_t index, input, output, expected_output; 268 QTestState *qts = qtest_init("-machine quanta-gsj"); 269 270 index = 1; 271 input = input_list[1]; 272 expected_output = adc_calculate_output(input, DEFAULT_IREF); 273 274 qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); 275 adc_write_input(qts, adc, index, input); 276 g_assert_false(qtest_get_irq(qts, adc->irq)); 277 adc_write_con(qts, adc, CON_MUX(index) | CON_INT_EN | CON_REFSEL | CON_INT 278 | CON_EN | CON_CONV); 279 adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV); 280 g_assert_cmphex(adc_read_con(qts, adc), ==, CON_MUX(index) | CON_INT_EN 281 | CON_REFSEL | CON_INT | CON_EN); 282 g_assert_true(qtest_get_irq(qts, adc->irq)); 283 output = adc_read_data(qts, adc); 284 g_assert_cmpuint(output, ==, expected_output); 285 286 qtest_quit(qts); 287 } 288 289 /* Check ADC is reset after setting ADC_RST for 10 ADC cycles. */ 290 static void test_reset(gconstpointer adc_p) 291 { 292 const ADC *adc = adc_p; 293 QTestState *qts = qtest_init("-machine quanta-gsj"); 294 295 for (size_t i = 0; i < ARRAY_SIZE(div_list); ++i) { 296 uint32_t div = div_list[i]; 297 298 adc_write_con(qts, adc, CON_INT | CON_EN | CON_RST | CON_DIV(div)); 299 qtest_clock_step(qts, adc_calculate_steps(RESET_CYCLES, 300 adc_prescaler(qts, adc), DEFAULT_CLKDIV)); 301 g_assert_false(adc_read_con(qts, adc) & CON_EN); 302 } 303 qtest_quit(qts); 304 } 305 306 /* Check ADC Calibration works as desired. */ 307 static void test_calibrate(gconstpointer adc_p) 308 { 309 int i, j; 310 const ADC *adc = adc_p; 311 312 for (j = 0; j < ARRAY_SIZE(iref_list); ++j) { 313 uint32_t iref = iref_list[j]; 314 uint32_t expected_rv[] = { 315 adc_calculate_output(R0_INPUT, iref), 316 adc_calculate_output(R1_INPUT, iref), 317 }; 318 char buf[100]; 319 QTestState *qts; 320 321 sprintf(buf, "-machine quanta-gsj -global npcm7xx-adc.iref=%u", iref); 322 qts = qtest_init(buf); 323 324 /* Check the converted value is correct using the calibration value. */ 325 for (i = 0; i < ARRAY_SIZE(input_list); ++i) { 326 uint32_t input; 327 uint32_t output; 328 uint32_t expected_output; 329 uint32_t calibrated_voltage; 330 uint32_t index = 0; 331 332 input = input_list[i]; 333 /* Calibration only works for input range 0.1V ~ 1.8V. */ 334 if (input < MIN_CALIB_INPUT || input > MAX_CALIB_INPUT) { 335 continue; 336 } 337 expected_output = adc_calculate_output(input, iref); 338 339 adc_write_input(qts, adc, index, input); 340 adc_write_con(qts, adc, CON_MUX(index) | CON_REFSEL | CON_INT | 341 CON_EN | CON_CONV); 342 adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV); 343 g_assert_cmphex(adc_read_con(qts, adc), ==, 344 CON_REFSEL | CON_MUX(index) | CON_EN); 345 output = adc_read_data(qts, adc); 346 g_assert_cmpuint(output, ==, expected_output); 347 348 calibrated_voltage = adc_calibrate(output, expected_rv); 349 g_assert_cmpuint(calibrated_voltage, >, input - MAX_ERROR); 350 g_assert_cmpuint(calibrated_voltage, <, input + MAX_ERROR); 351 } 352 353 qtest_quit(qts); 354 } 355 } 356 357 static void adc_add_test(const char *name, const ADC* wd, 358 GTestDataFunc fn) 359 { 360 g_autofree char *full_name = g_strdup_printf("npcm7xx_adc/%s", name); 361 qtest_add_data_func(full_name, wd, fn); 362 } 363 #define add_test(name, td) adc_add_test(#name, td, test_##name) 364 365 int main(int argc, char **argv) 366 { 367 g_test_init(&argc, &argv, NULL); 368 369 add_test(init, &adc); 370 add_test(convert_internal, &adc); 371 add_test(convert_external, &adc); 372 add_test(interrupt, &adc); 373 add_test(reset, &adc); 374 add_test(calibrate, &adc); 375 376 return g_test_run(); 377 } 378