meson: avoid unused arguments of main() in compiler tests
[qemu.git] / hw / timer / sifive_pwm.c
blobc664480ccf526b9e34c36b3692dd4198e7e60469
1 /*
2 * SiFive PWM
4 * Copyright (c) 2020 Western Digital
6 * Author: Alistair Francis <alistair.francis@wdc.com>
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
27 #include "qemu/osdep.h"
28 #include "trace.h"
29 #include "hw/irq.h"
30 #include "hw/timer/sifive_pwm.h"
31 #include "hw/qdev-properties.h"
32 #include "hw/registerfields.h"
33 #include "migration/vmstate.h"
34 #include "qemu/log.h"
35 #include "qemu/module.h"
37 #define HAS_PWM_EN_BITS(cfg) ((cfg & R_CONFIG_ENONESHOT_MASK) || \
38 (cfg & R_CONFIG_ENALWAYS_MASK))
40 #define PWMCMP_MASK 0xFFFF
41 #define PWMCOUNT_MASK 0x7FFFFFFF
43 REG32(CONFIG, 0x00)
44 FIELD(CONFIG, SCALE, 0, 4)
45 FIELD(CONFIG, STICKY, 8, 1)
46 FIELD(CONFIG, ZEROCMP, 9, 1)
47 FIELD(CONFIG, DEGLITCH, 10, 1)
48 FIELD(CONFIG, ENALWAYS, 12, 1)
49 FIELD(CONFIG, ENONESHOT, 13, 1)
50 FIELD(CONFIG, CMP0CENTER, 16, 1)
51 FIELD(CONFIG, CMP1CENTER, 17, 1)
52 FIELD(CONFIG, CMP2CENTER, 18, 1)
53 FIELD(CONFIG, CMP3CENTER, 19, 1)
54 FIELD(CONFIG, CMP0GANG, 24, 1)
55 FIELD(CONFIG, CMP1GANG, 25, 1)
56 FIELD(CONFIG, CMP2GANG, 26, 1)
57 FIELD(CONFIG, CMP3GANG, 27, 1)
58 FIELD(CONFIG, CMP0IP, 28, 1)
59 FIELD(CONFIG, CMP1IP, 29, 1)
60 FIELD(CONFIG, CMP2IP, 30, 1)
61 FIELD(CONFIG, CMP3IP, 31, 1)
62 REG32(COUNT, 0x08)
63 REG32(PWMS, 0x10)
64 REG32(PWMCMP0, 0x20)
65 REG32(PWMCMP1, 0x24)
66 REG32(PWMCMP2, 0x28)
67 REG32(PWMCMP3, 0x2C)
69 static inline uint64_t sifive_pwm_ns_to_ticks(SiFivePwmState *s,
70 uint64_t time)
72 return muldiv64(time, s->freq_hz, NANOSECONDS_PER_SECOND);
75 static inline uint64_t sifive_pwm_ticks_to_ns(SiFivePwmState *s,
76 uint64_t ticks)
78 return muldiv64(ticks, NANOSECONDS_PER_SECOND, s->freq_hz);
81 static inline uint64_t sifive_pwm_compute_scale(SiFivePwmState *s)
83 return s->pwmcfg & R_CONFIG_SCALE_MASK;
86 static void sifive_pwm_set_alarms(SiFivePwmState *s)
88 uint64_t now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
90 if (HAS_PWM_EN_BITS(s->pwmcfg)) {
92 * Subtract ticks from number of ticks when the timer was zero
93 * and mask to the register width.
95 uint64_t pwmcount = (sifive_pwm_ns_to_ticks(s, now_ns) -
96 s->tick_offset) & PWMCOUNT_MASK;
97 uint64_t scale = sifive_pwm_compute_scale(s);
98 /* PWMs only contains PWMCMP_MASK bits starting at scale */
99 uint64_t pwms = (pwmcount & (PWMCMP_MASK << scale)) >> scale;
101 for (int i = 0; i < SIFIVE_PWM_CHANS; i++) {
102 uint64_t pwmcmp = s->pwmcmp[i] & PWMCMP_MASK;
103 uint64_t pwmcmp_ticks = pwmcmp << scale;
106 * Per circuit diagram and spec, both cases raises corresponding
107 * IP bit one clock cycle after time expires.
109 if (pwmcmp > pwms) {
110 uint64_t offset = pwmcmp_ticks - pwmcount + 1;
111 uint64_t when_to_fire = now_ns +
112 sifive_pwm_ticks_to_ns(s, offset);
114 trace_sifive_pwm_set_alarm(when_to_fire, now_ns);
115 timer_mod(&s->timer[i], when_to_fire);
116 } else {
117 /* Schedule interrupt for next cycle */
118 trace_sifive_pwm_set_alarm(now_ns + 1, now_ns);
119 timer_mod(&s->timer[i], now_ns + 1);
123 } else {
125 * If timer incrementing disabled, just do pwms > pwmcmp check since
126 * a write may have happened to PWMs.
128 uint64_t pwmcount = (s->tick_offset) & PWMCOUNT_MASK;
129 uint64_t scale = sifive_pwm_compute_scale(s);
130 uint64_t pwms = (pwmcount & (PWMCMP_MASK << scale)) >> scale;
132 for (int i = 0; i < SIFIVE_PWM_CHANS; i++) {
133 uint64_t pwmcmp = s->pwmcmp[i] & PWMCMP_MASK;
135 if (pwms >= pwmcmp) {
136 trace_sifive_pwm_set_alarm(now_ns + 1, now_ns);
137 timer_mod(&s->timer[i], now_ns + 1);
138 } else {
139 /* Effectively disable timer by scheduling far in future. */
140 trace_sifive_pwm_set_alarm(0xFFFFFFFFFFFFFF, now_ns);
141 timer_mod(&s->timer[i], 0xFFFFFFFFFFFFFF);
147 static void sifive_pwm_interrupt(SiFivePwmState *s, int num)
149 uint64_t now = sifive_pwm_ns_to_ticks(s,
150 qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
151 bool was_incrementing = HAS_PWM_EN_BITS(s->pwmcfg);
153 trace_sifive_pwm_interrupt(num);
155 s->pwmcfg |= R_CONFIG_CMP0IP_MASK << num;
156 qemu_irq_raise(s->irqs[num]);
159 * If the zerocmp is set and pwmcmp0 raised the interrupt
160 * reset the zero ticks.
162 if ((s->pwmcfg & R_CONFIG_ZEROCMP_MASK) && (num == 0)) {
163 /* If reset signal conditions, disable ENONESHOT. */
164 s->pwmcfg &= ~R_CONFIG_ENONESHOT_MASK;
166 if (was_incrementing) {
167 /* If incrementing, time in ticks is when pwmcount is zero */
168 s->tick_offset = now;
169 } else {
170 /* If not incrementing, pwmcount = 0 */
171 s->tick_offset = 0;
176 * If carryout bit set, which we discern via looking for overflow,
177 * also reset ENONESHOT.
179 if (was_incrementing &&
180 ((now & PWMCOUNT_MASK) < (s->tick_offset & PWMCOUNT_MASK))) {
181 s->pwmcfg &= ~R_CONFIG_ENONESHOT_MASK;
184 /* Schedule or disable interrupts */
185 sifive_pwm_set_alarms(s);
187 /* If was enabled, and now not enabled, switch tick rep */
188 if (was_incrementing && !HAS_PWM_EN_BITS(s->pwmcfg)) {
189 s->tick_offset = (now - s->tick_offset) & PWMCOUNT_MASK;
193 static void sifive_pwm_interrupt_0(void *opaque)
195 SiFivePwmState *s = opaque;
197 sifive_pwm_interrupt(s, 0);
200 static void sifive_pwm_interrupt_1(void *opaque)
202 SiFivePwmState *s = opaque;
204 sifive_pwm_interrupt(s, 1);
207 static void sifive_pwm_interrupt_2(void *opaque)
209 SiFivePwmState *s = opaque;
211 sifive_pwm_interrupt(s, 2);
214 static void sifive_pwm_interrupt_3(void *opaque)
216 SiFivePwmState *s = opaque;
218 sifive_pwm_interrupt(s, 3);
221 static uint64_t sifive_pwm_read(void *opaque, hwaddr addr,
222 unsigned int size)
224 SiFivePwmState *s = opaque;
225 uint64_t cur_time, scale;
226 uint64_t now = sifive_pwm_ns_to_ticks(s,
227 qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
229 trace_sifive_pwm_read(addr);
231 switch (addr) {
232 case A_CONFIG:
233 return s->pwmcfg;
234 case A_COUNT:
235 cur_time = s->tick_offset;
237 if (HAS_PWM_EN_BITS(s->pwmcfg)) {
238 cur_time = now - cur_time;
242 * Return the value in the counter with bit 31 always 0
243 * This is allowed to wrap around so we don't need to check that.
245 return cur_time & PWMCOUNT_MASK;
246 case A_PWMS:
247 cur_time = s->tick_offset;
248 scale = sifive_pwm_compute_scale(s);
250 if (HAS_PWM_EN_BITS(s->pwmcfg)) {
251 cur_time = now - cur_time;
254 return ((cur_time & PWMCOUNT_MASK) >> scale) & PWMCMP_MASK;
255 case A_PWMCMP0:
256 return s->pwmcmp[0] & PWMCMP_MASK;
257 case A_PWMCMP1:
258 return s->pwmcmp[1] & PWMCMP_MASK;
259 case A_PWMCMP2:
260 return s->pwmcmp[2] & PWMCMP_MASK;
261 case A_PWMCMP3:
262 return s->pwmcmp[3] & PWMCMP_MASK;
263 default:
264 qemu_log_mask(LOG_GUEST_ERROR,
265 "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
266 return 0;
269 return 0;
272 static void sifive_pwm_write(void *opaque, hwaddr addr,
273 uint64_t val64, unsigned int size)
275 SiFivePwmState *s = opaque;
276 uint32_t value = val64;
277 uint64_t new_offset, scale;
278 uint64_t now = sifive_pwm_ns_to_ticks(s,
279 qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
281 trace_sifive_pwm_write(value, addr);
283 switch (addr) {
284 case A_CONFIG:
285 if (value & (R_CONFIG_CMP0CENTER_MASK | R_CONFIG_CMP1CENTER_MASK |
286 R_CONFIG_CMP2CENTER_MASK | R_CONFIG_CMP3CENTER_MASK)) {
287 qemu_log_mask(LOG_UNIMP, "%s: CMPxCENTER is not supported\n",
288 __func__);
291 if (value & (R_CONFIG_CMP0GANG_MASK | R_CONFIG_CMP1GANG_MASK |
292 R_CONFIG_CMP2GANG_MASK | R_CONFIG_CMP3GANG_MASK)) {
293 qemu_log_mask(LOG_UNIMP, "%s: CMPxGANG is not supported\n",
294 __func__);
297 if (value & (R_CONFIG_CMP0IP_MASK | R_CONFIG_CMP1IP_MASK |
298 R_CONFIG_CMP2IP_MASK | R_CONFIG_CMP3IP_MASK)) {
299 qemu_log_mask(LOG_UNIMP, "%s: CMPxIP is not supported\n",
300 __func__);
303 if (!(value & R_CONFIG_CMP0IP_MASK)) {
304 qemu_irq_lower(s->irqs[0]);
307 if (!(value & R_CONFIG_CMP1IP_MASK)) {
308 qemu_irq_lower(s->irqs[1]);
311 if (!(value & R_CONFIG_CMP2IP_MASK)) {
312 qemu_irq_lower(s->irqs[2]);
315 if (!(value & R_CONFIG_CMP3IP_MASK)) {
316 qemu_irq_lower(s->irqs[3]);
320 * If this write enables the timer increment
321 * set the time when pwmcount was zero to be cur_time - pwmcount.
322 * If this write disables the timer increment
323 * convert back from pwmcount to the time in ticks
324 * when pwmcount was zero.
326 if ((!HAS_PWM_EN_BITS(s->pwmcfg) && HAS_PWM_EN_BITS(value)) ||
327 (HAS_PWM_EN_BITS(s->pwmcfg) && !HAS_PWM_EN_BITS(value))) {
328 s->tick_offset = (now - s->tick_offset) & PWMCOUNT_MASK;
331 s->pwmcfg = value;
332 break;
333 case A_COUNT:
334 /* The guest changed the counter, updated the offset value. */
335 new_offset = value;
337 if (HAS_PWM_EN_BITS(s->pwmcfg)) {
338 new_offset = now - new_offset;
341 s->tick_offset = new_offset;
342 break;
343 case A_PWMS:
344 scale = sifive_pwm_compute_scale(s);
345 new_offset = (((value & PWMCMP_MASK) << scale) & PWMCOUNT_MASK);
347 if (HAS_PWM_EN_BITS(s->pwmcfg)) {
348 new_offset = now - new_offset;
351 s->tick_offset = new_offset;
352 break;
353 case A_PWMCMP0:
354 s->pwmcmp[0] = value & PWMCMP_MASK;
355 break;
356 case A_PWMCMP1:
357 s->pwmcmp[1] = value & PWMCMP_MASK;
358 break;
359 case A_PWMCMP2:
360 s->pwmcmp[2] = value & PWMCMP_MASK;
361 break;
362 case A_PWMCMP3:
363 s->pwmcmp[3] = value & PWMCMP_MASK;
364 break;
365 default:
366 qemu_log_mask(LOG_GUEST_ERROR,
367 "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
370 /* Update the alarms to reflect possible updated values */
371 sifive_pwm_set_alarms(s);
374 static void sifive_pwm_reset(DeviceState *dev)
376 SiFivePwmState *s = SIFIVE_PWM(dev);
377 uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
379 s->pwmcfg = 0x00000000;
380 s->pwmcmp[0] = 0x00000000;
381 s->pwmcmp[1] = 0x00000000;
382 s->pwmcmp[2] = 0x00000000;
383 s->pwmcmp[3] = 0x00000000;
385 s->tick_offset = sifive_pwm_ns_to_ticks(s, now);
388 static const MemoryRegionOps sifive_pwm_ops = {
389 .read = sifive_pwm_read,
390 .write = sifive_pwm_write,
391 .endianness = DEVICE_NATIVE_ENDIAN,
394 static const VMStateDescription vmstate_sifive_pwm = {
395 .name = TYPE_SIFIVE_PWM,
396 .version_id = 1,
397 .minimum_version_id = 1,
398 .fields = (VMStateField[]) {
399 VMSTATE_TIMER_ARRAY(timer, SiFivePwmState, 4),
400 VMSTATE_UINT64(tick_offset, SiFivePwmState),
401 VMSTATE_UINT32(pwmcfg, SiFivePwmState),
402 VMSTATE_UINT32_ARRAY(pwmcmp, SiFivePwmState, 4),
403 VMSTATE_END_OF_LIST()
407 static Property sifive_pwm_properties[] = {
408 /* 0.5Ghz per spec after FSBL */
409 DEFINE_PROP_UINT64("clock-frequency", struct SiFivePwmState,
410 freq_hz, 500000000ULL),
411 DEFINE_PROP_END_OF_LIST(),
414 static void sifive_pwm_init(Object *obj)
416 SiFivePwmState *s = SIFIVE_PWM(obj);
417 int i;
419 for (i = 0; i < SIFIVE_PWM_IRQS; i++) {
420 sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irqs[i]);
423 memory_region_init_io(&s->mmio, obj, &sifive_pwm_ops, s,
424 TYPE_SIFIVE_PWM, 0x100);
425 sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
428 static void sifive_pwm_realize(DeviceState *dev, Error **errp)
430 SiFivePwmState *s = SIFIVE_PWM(dev);
432 timer_init_ns(&s->timer[0], QEMU_CLOCK_VIRTUAL,
433 sifive_pwm_interrupt_0, s);
435 timer_init_ns(&s->timer[1], QEMU_CLOCK_VIRTUAL,
436 sifive_pwm_interrupt_1, s);
438 timer_init_ns(&s->timer[2], QEMU_CLOCK_VIRTUAL,
439 sifive_pwm_interrupt_2, s);
441 timer_init_ns(&s->timer[3], QEMU_CLOCK_VIRTUAL,
442 sifive_pwm_interrupt_3, s);
445 static void sifive_pwm_class_init(ObjectClass *klass, void *data)
447 DeviceClass *dc = DEVICE_CLASS(klass);
449 dc->reset = sifive_pwm_reset;
450 device_class_set_props(dc, sifive_pwm_properties);
451 dc->vmsd = &vmstate_sifive_pwm;
452 dc->realize = sifive_pwm_realize;
455 static const TypeInfo sifive_pwm_info = {
456 .name = TYPE_SIFIVE_PWM,
457 .parent = TYPE_SYS_BUS_DEVICE,
458 .instance_size = sizeof(SiFivePwmState),
459 .instance_init = sifive_pwm_init,
460 .class_init = sifive_pwm_class_init,
463 static void sifive_pwm_register_types(void)
465 type_register_static(&sifive_pwm_info);
468 type_init(sifive_pwm_register_types)