Hao Wu | 77c05b0 | 2021-01-08 11:09:42 -0800 | [diff] [blame] | 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" |
Marc-André Lureau | 907b510 | 2022-03-30 13:39:05 +0400 | [diff] [blame] | 20 | #include "libqtest.h" |
Hao Wu | 77c05b0 | 2021-01-08 11:09:42 -0800 | [diff] [blame] | 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) |
Hao Wu | 4a84e85 | 2022-07-14 11:28:31 -0700 | [diff] [blame] | 53 | #define CON_CONV BIT(13) |
Hao Wu | 77c05b0 | 2021-01-08 11:09:42 -0800 | [diff] [blame] | 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 | |
Philippe Mathieu-Daudé | ec3ad0f | 2023-10-09 12:02:48 +0200 | [diff] [blame] | 93 | ADC adc_defs = { |
Hao Wu | 77c05b0 | 2021-01-08 11:09:42 -0800 | [diff] [blame] | 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")); |
Gan Qixin | 73624e0 | 2021-01-18 14:56:27 +0800 | [diff] [blame] | 132 | qobject_unref(response); |
Hao Wu | 77c05b0 | 2021-01-08 11:09:42 -0800 | [diff] [blame] | 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 | |
Philippe Mathieu-Daudé | ec3ad0f | 2023-10-09 12:02:48 +0200 | [diff] [blame] | 370 | add_test(init, &adc_defs); |
| 371 | add_test(convert_internal, &adc_defs); |
| 372 | add_test(convert_external, &adc_defs); |
| 373 | add_test(interrupt, &adc_defs); |
| 374 | add_test(reset, &adc_defs); |
| 375 | add_test(calibrate, &adc_defs); |
Hao Wu | 77c05b0 | 2021-01-08 11:09:42 -0800 | [diff] [blame] | 376 | |
| 377 | return g_test_run(); |
| 378 | } |