xref: /qemu/tests/qtest/npcm7xx_pwm-test.c (revision 2e8f72ac)
1 /*
2  * QTests for Nuvoton NPCM7xx PWM Modules.
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 "libqos/libqtest.h"
20 #include "qapi/qmp/qdict.h"
21 #include "qapi/qmp/qnum.h"
22 
23 #define REF_HZ          25000000
24 
25 /* Register field definitions. */
26 #define CH_EN           BIT(0)
27 #define CH_INV          BIT(2)
28 #define CH_MOD          BIT(3)
29 
30 /* Registers shared between all PWMs in a module */
31 #define PPR             0x00
32 #define CSR             0x04
33 #define PCR             0x08
34 #define PIER            0x3c
35 #define PIIR            0x40
36 
37 /* CLK module related */
38 #define CLK_BA          0xf0801000
39 #define CLKSEL          0x04
40 #define CLKDIV1         0x08
41 #define CLKDIV2         0x2c
42 #define PLLCON0         0x0c
43 #define PLLCON1         0x10
44 #define PLL_INDV(rv)    extract32((rv), 0, 6)
45 #define PLL_FBDV(rv)    extract32((rv), 16, 12)
46 #define PLL_OTDV1(rv)   extract32((rv), 8, 3)
47 #define PLL_OTDV2(rv)   extract32((rv), 13, 3)
48 #define APB3CKDIV(rv)   extract32((rv), 28, 2)
49 #define CLK2CKDIV(rv)   extract32((rv), 0, 1)
50 #define CLK4CKDIV(rv)   extract32((rv), 26, 2)
51 #define CPUCKSEL(rv)    extract32((rv), 0, 2)
52 
53 #define MAX_DUTY        1000000
54 
55 typedef struct PWMModule {
56     int irq;
57     uint64_t base_addr;
58 } PWMModule;
59 
60 typedef struct PWM {
61     uint32_t cnr_offset;
62     uint32_t cmr_offset;
63     uint32_t pdr_offset;
64     uint32_t pwdr_offset;
65 } PWM;
66 
67 typedef struct TestData {
68     const PWMModule *module;
69     const PWM *pwm;
70 } TestData;
71 
72 static const PWMModule pwm_module_list[] = {
73     {
74         .irq        = 93,
75         .base_addr  = 0xf0103000
76     },
77     {
78         .irq        = 94,
79         .base_addr  = 0xf0104000
80     }
81 };
82 
83 static const PWM pwm_list[] = {
84     {
85         .cnr_offset     = 0x0c,
86         .cmr_offset     = 0x10,
87         .pdr_offset     = 0x14,
88         .pwdr_offset    = 0x44,
89     },
90     {
91         .cnr_offset     = 0x18,
92         .cmr_offset     = 0x1c,
93         .pdr_offset     = 0x20,
94         .pwdr_offset    = 0x48,
95     },
96     {
97         .cnr_offset     = 0x24,
98         .cmr_offset     = 0x28,
99         .pdr_offset     = 0x2c,
100         .pwdr_offset    = 0x4c,
101     },
102     {
103         .cnr_offset     = 0x30,
104         .cmr_offset     = 0x34,
105         .pdr_offset     = 0x38,
106         .pwdr_offset    = 0x50,
107     },
108 };
109 
110 static const int ppr_base[] = { 0, 0, 8, 8 };
111 static const int csr_base[] = { 0, 4, 8, 12 };
112 static const int pcr_base[] = { 0, 8, 12, 16 };
113 
114 static const uint32_t ppr_list[] = {
115     0,
116     1,
117     10,
118     100,
119     255, /* Max possible value. */
120 };
121 
122 static const uint32_t csr_list[] = {
123     0,
124     1,
125     2,
126     3,
127     4, /* Max possible value. */
128 };
129 
130 static const uint32_t cnr_list[] = {
131     0,
132     1,
133     50,
134     100,
135     150,
136     200,
137     1000,
138     10000,
139     65535, /* Max possible value. */
140 };
141 
142 static const uint32_t cmr_list[] = {
143     0,
144     1,
145     10,
146     50,
147     100,
148     150,
149     200,
150     1000,
151     10000,
152     65535, /* Max possible value. */
153 };
154 
155 /* Returns the index of the PWM module. */
156 static int pwm_module_index(const PWMModule *module)
157 {
158     ptrdiff_t diff = module - pwm_module_list;
159 
160     g_assert_true(diff >= 0 && diff < ARRAY_SIZE(pwm_module_list));
161 
162     return diff;
163 }
164 
165 /* Returns the index of the PWM entry. */
166 static int pwm_index(const PWM *pwm)
167 {
168     ptrdiff_t diff = pwm - pwm_list;
169 
170     g_assert_true(diff >= 0 && diff < ARRAY_SIZE(pwm_list));
171 
172     return diff;
173 }
174 
175 static uint64_t pwm_qom_get(QTestState *qts, const char *path, const char *name)
176 {
177     QDict *response;
178     uint64_t val;
179 
180     g_test_message("Getting properties %s from %s", name, path);
181     response = qtest_qmp(qts, "{ 'execute': 'qom-get',"
182             " 'arguments': { 'path': %s, 'property': %s}}",
183             path, name);
184     /* The qom set message returns successfully. */
185     g_assert_true(qdict_haskey(response, "return"));
186     val = qnum_get_uint(qobject_to(QNum, qdict_get(response, "return")));
187     qobject_unref(response);
188     return val;
189 }
190 
191 static uint64_t pwm_get_freq(QTestState *qts, int module_index, int pwm_index)
192 {
193     char path[100];
194     char name[100];
195 
196     sprintf(path, "/machine/soc/pwm[%d]", module_index);
197     sprintf(name, "freq[%d]", pwm_index);
198 
199     return pwm_qom_get(qts, path, name);
200 }
201 
202 static uint64_t pwm_get_duty(QTestState *qts, int module_index, int pwm_index)
203 {
204     char path[100];
205     char name[100];
206 
207     sprintf(path, "/machine/soc/pwm[%d]", module_index);
208     sprintf(name, "duty[%d]", pwm_index);
209 
210     return pwm_qom_get(qts, path, name);
211 }
212 
213 static uint32_t get_pll(uint32_t con)
214 {
215     return REF_HZ * PLL_FBDV(con) / (PLL_INDV(con) * PLL_OTDV1(con)
216             * PLL_OTDV2(con));
217 }
218 
219 static uint64_t read_pclk(QTestState *qts)
220 {
221     uint64_t freq = REF_HZ;
222     uint32_t clksel = qtest_readl(qts, CLK_BA + CLKSEL);
223     uint32_t pllcon;
224     uint32_t clkdiv1 = qtest_readl(qts, CLK_BA + CLKDIV1);
225     uint32_t clkdiv2 = qtest_readl(qts, CLK_BA + CLKDIV2);
226 
227     switch (CPUCKSEL(clksel)) {
228     case 0:
229         pllcon = qtest_readl(qts, CLK_BA + PLLCON0);
230         freq = get_pll(pllcon);
231         break;
232     case 1:
233         pllcon = qtest_readl(qts, CLK_BA + PLLCON1);
234         freq = get_pll(pllcon);
235         break;
236     case 2:
237         break;
238     case 3:
239         break;
240     default:
241         g_assert_not_reached();
242     }
243 
244     freq >>= (CLK2CKDIV(clkdiv1) + CLK4CKDIV(clkdiv1) + APB3CKDIV(clkdiv2));
245 
246     return freq;
247 }
248 
249 static uint32_t pwm_selector(uint32_t csr)
250 {
251     switch (csr) {
252     case 0:
253         return 2;
254     case 1:
255         return 4;
256     case 2:
257         return 8;
258     case 3:
259         return 16;
260     case 4:
261         return 1;
262     default:
263         g_assert_not_reached();
264     }
265 }
266 
267 static uint64_t pwm_compute_freq(QTestState *qts, uint32_t ppr, uint32_t csr,
268         uint32_t cnr)
269 {
270     return read_pclk(qts) / ((ppr + 1) * pwm_selector(csr) * (cnr + 1));
271 }
272 
273 static uint64_t pwm_compute_duty(uint32_t cnr, uint32_t cmr, bool inverted)
274 {
275     uint64_t duty;
276 
277     if (cnr == 0) {
278         /* PWM is stopped. */
279         duty = 0;
280     } else if (cmr >= cnr) {
281         duty = MAX_DUTY;
282     } else {
283         duty = MAX_DUTY * (cmr + 1) / (cnr + 1);
284     }
285 
286     if (inverted) {
287         duty = MAX_DUTY - duty;
288     }
289 
290     return duty;
291 }
292 
293 static uint32_t pwm_read(QTestState *qts, const TestData *td, unsigned offset)
294 {
295     return qtest_readl(qts, td->module->base_addr + offset);
296 }
297 
298 static void pwm_write(QTestState *qts, const TestData *td, unsigned offset,
299         uint32_t value)
300 {
301     qtest_writel(qts, td->module->base_addr + offset, value);
302 }
303 
304 static uint32_t pwm_read_ppr(QTestState *qts, const TestData *td)
305 {
306     return extract32(pwm_read(qts, td, PPR), ppr_base[pwm_index(td->pwm)], 8);
307 }
308 
309 static void pwm_write_ppr(QTestState *qts, const TestData *td, uint32_t value)
310 {
311     pwm_write(qts, td, PPR, value << ppr_base[pwm_index(td->pwm)]);
312 }
313 
314 static uint32_t pwm_read_csr(QTestState *qts, const TestData *td)
315 {
316     return extract32(pwm_read(qts, td, CSR), csr_base[pwm_index(td->pwm)], 3);
317 }
318 
319 static void pwm_write_csr(QTestState *qts, const TestData *td, uint32_t value)
320 {
321     pwm_write(qts, td, CSR, value << csr_base[pwm_index(td->pwm)]);
322 }
323 
324 static uint32_t pwm_read_pcr(QTestState *qts, const TestData *td)
325 {
326     return extract32(pwm_read(qts, td, PCR), pcr_base[pwm_index(td->pwm)], 4);
327 }
328 
329 static void pwm_write_pcr(QTestState *qts, const TestData *td, uint32_t value)
330 {
331     pwm_write(qts, td, PCR, value << pcr_base[pwm_index(td->pwm)]);
332 }
333 
334 static uint32_t pwm_read_cnr(QTestState *qts, const TestData *td)
335 {
336     return pwm_read(qts, td, td->pwm->cnr_offset);
337 }
338 
339 static void pwm_write_cnr(QTestState *qts, const TestData *td, uint32_t value)
340 {
341     pwm_write(qts, td, td->pwm->cnr_offset, value);
342 }
343 
344 static uint32_t pwm_read_cmr(QTestState *qts, const TestData *td)
345 {
346     return pwm_read(qts, td, td->pwm->cmr_offset);
347 }
348 
349 static void pwm_write_cmr(QTestState *qts, const TestData *td, uint32_t value)
350 {
351     pwm_write(qts, td, td->pwm->cmr_offset, value);
352 }
353 
354 /* Check pwm registers can be reset to default value */
355 static void test_init(gconstpointer test_data)
356 {
357     const TestData *td = test_data;
358     QTestState *qts = qtest_init("-machine quanta-gsj");
359     int module = pwm_module_index(td->module);
360     int pwm = pwm_index(td->pwm);
361 
362     g_assert_cmpuint(pwm_get_freq(qts, module, pwm), ==, 0);
363     g_assert_cmpuint(pwm_get_duty(qts, module, pwm), ==, 0);
364 
365     qtest_quit(qts);
366 }
367 
368 /* One-shot mode should not change frequency and duty cycle. */
369 static void test_oneshot(gconstpointer test_data)
370 {
371     const TestData *td = test_data;
372     QTestState *qts = qtest_init("-machine quanta-gsj");
373     int module = pwm_module_index(td->module);
374     int pwm = pwm_index(td->pwm);
375     uint32_t ppr, csr, pcr;
376     int i, j;
377 
378     pcr = CH_EN;
379     for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) {
380         ppr = ppr_list[i];
381         pwm_write_ppr(qts, td, ppr);
382 
383         for (j = 0; j < ARRAY_SIZE(csr_list); ++j) {
384             csr = csr_list[j];
385             pwm_write_csr(qts, td, csr);
386             pwm_write_pcr(qts, td, pcr);
387 
388             g_assert_cmpuint(pwm_read_ppr(qts, td), ==, ppr);
389             g_assert_cmpuint(pwm_read_csr(qts, td), ==, csr);
390             g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr);
391             g_assert_cmpuint(pwm_get_freq(qts, module, pwm), ==, 0);
392             g_assert_cmpuint(pwm_get_duty(qts, module, pwm), ==, 0);
393         }
394     }
395 
396     qtest_quit(qts);
397 }
398 
399 /* In toggle mode, the PWM generates correct outputs. */
400 static void test_toggle(gconstpointer test_data)
401 {
402     const TestData *td = test_data;
403     QTestState *qts = qtest_init("-machine quanta-gsj");
404     int module = pwm_module_index(td->module);
405     int pwm = pwm_index(td->pwm);
406     uint32_t ppr, csr, pcr, cnr, cmr;
407     int i, j, k, l;
408     uint64_t expected_freq, expected_duty;
409 
410     pcr = CH_EN | CH_MOD;
411     for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) {
412         ppr = ppr_list[i];
413         pwm_write_ppr(qts, td, ppr);
414 
415         for (j = 0; j < ARRAY_SIZE(csr_list); ++j) {
416             csr = csr_list[j];
417             pwm_write_csr(qts, td, csr);
418 
419             for (k = 0; k < ARRAY_SIZE(cnr_list); ++k) {
420                 cnr = cnr_list[k];
421                 pwm_write_cnr(qts, td, cnr);
422 
423                 for (l = 0; l < ARRAY_SIZE(cmr_list); ++l) {
424                     cmr = cmr_list[l];
425                     pwm_write_cmr(qts, td, cmr);
426                     expected_freq = pwm_compute_freq(qts, ppr, csr, cnr);
427                     expected_duty = pwm_compute_duty(cnr, cmr, false);
428 
429                     pwm_write_pcr(qts, td, pcr);
430                     g_assert_cmpuint(pwm_read_ppr(qts, td), ==, ppr);
431                     g_assert_cmpuint(pwm_read_csr(qts, td), ==, csr);
432                     g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr);
433                     g_assert_cmpuint(pwm_read_cnr(qts, td), ==, cnr);
434                     g_assert_cmpuint(pwm_read_cmr(qts, td), ==, cmr);
435                     g_assert_cmpuint(pwm_get_duty(qts, module, pwm),
436                             ==, expected_duty);
437                     if (expected_duty != 0 && expected_duty != 100) {
438                         /* Duty cycle with 0 or 100 doesn't need frequency. */
439                         g_assert_cmpuint(pwm_get_freq(qts, module, pwm),
440                                 ==, expected_freq);
441                     }
442 
443                     /* Test inverted mode */
444                     expected_duty = pwm_compute_duty(cnr, cmr, true);
445                     pwm_write_pcr(qts, td, pcr | CH_INV);
446                     g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr | CH_INV);
447                     g_assert_cmpuint(pwm_get_duty(qts, module, pwm),
448                             ==, expected_duty);
449                     if (expected_duty != 0 && expected_duty != 100) {
450                         /* Duty cycle with 0 or 100 doesn't need frequency. */
451                         g_assert_cmpuint(pwm_get_freq(qts, module, pwm),
452                                 ==, expected_freq);
453                     }
454 
455                 }
456             }
457         }
458     }
459 
460     qtest_quit(qts);
461 }
462 
463 static void pwm_add_test(const char *name, const TestData* td,
464         GTestDataFunc fn)
465 {
466     g_autofree char *full_name = g_strdup_printf(
467             "npcm7xx_pwm/module[%d]/pwm[%d]/%s", pwm_module_index(td->module),
468             pwm_index(td->pwm), name);
469     qtest_add_data_func(full_name, td, fn);
470 }
471 #define add_test(name, td) pwm_add_test(#name, td, test_##name)
472 
473 int main(int argc, char **argv)
474 {
475     TestData test_data_list[ARRAY_SIZE(pwm_module_list) * ARRAY_SIZE(pwm_list)];
476 
477     g_test_init(&argc, &argv, NULL);
478 
479     for (int i = 0; i < ARRAY_SIZE(pwm_module_list); ++i) {
480         for (int j = 0; j < ARRAY_SIZE(pwm_list); ++j) {
481             TestData *td = &test_data_list[i * ARRAY_SIZE(pwm_list) + j];
482 
483             td->module = &pwm_module_list[i];
484             td->pwm = &pwm_list[j];
485 
486             add_test(init, td);
487             add_test(oneshot, td);
488             add_test(toggle, td);
489         }
490     }
491 
492     return g_test_run();
493 }
494