blob: e751a72e36732608ffe76225f96f4b784d705f15 [file] [log] [blame]
Hao Wu77c05b02021-01-08 11:09:42 -08001/*
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é Lureau907b5102022-03-30 13:39:05 +040020#include "libqtest.h"
Hao Wu77c05b02021-01-08 11:09:42 -080021#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 Wu4a84e852022-07-14 11:28:31 -070053#define CON_CONV BIT(13)
Hao Wu77c05b02021-01-08 11:09:42 -080054#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
Philippe Mathieu-Daudéec3ad0f2023-10-09 12:02:48 +020093ADC adc_defs = {
Hao Wu77c05b02021-01-08 11:09:42 -080094 .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"));
Gan Qixin73624e02021-01-18 14:56:27 +0800132 qobject_unref(response);
Hao Wu77c05b02021-01-08 11:09:42 -0800133}
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
Philippe Mathieu-Daudéec3ad0f2023-10-09 12:02:48 +0200370 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 Wu77c05b02021-01-08 11:09:42 -0800376
377 return g_test_run();
378}