xref: /qemu/hw/timer/sifive_pwm.c (revision ba324b3f)
15bf6f1acSAlistair Francis /*
25bf6f1acSAlistair Francis  * SiFive PWM
35bf6f1acSAlistair Francis  *
45bf6f1acSAlistair Francis  * Copyright (c) 2020 Western Digital
55bf6f1acSAlistair Francis  *
65bf6f1acSAlistair Francis  * Author:  Alistair Francis <alistair.francis@wdc.com>
75bf6f1acSAlistair Francis  *
85bf6f1acSAlistair Francis  * Permission is hereby granted, free of charge, to any person obtaining a copy
95bf6f1acSAlistair Francis  * of this software and associated documentation files (the "Software"), to deal
105bf6f1acSAlistair Francis  * in the Software without restriction, including without limitation the rights
115bf6f1acSAlistair Francis  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
125bf6f1acSAlistair Francis  * copies of the Software, and to permit persons to whom the Software is
135bf6f1acSAlistair Francis  * furnished to do so, subject to the following conditions:
145bf6f1acSAlistair Francis  *
155bf6f1acSAlistair Francis  * The above copyright notice and this permission notice shall be included in
165bf6f1acSAlistair Francis  * all copies or substantial portions of the Software.
175bf6f1acSAlistair Francis  *
185bf6f1acSAlistair Francis  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
195bf6f1acSAlistair Francis  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
205bf6f1acSAlistair Francis  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
215bf6f1acSAlistair Francis  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
225bf6f1acSAlistair Francis  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
235bf6f1acSAlistair Francis  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
245bf6f1acSAlistair Francis  * THE SOFTWARE.
255bf6f1acSAlistair Francis  */
265bf6f1acSAlistair Francis 
275bf6f1acSAlistair Francis #include "qemu/osdep.h"
285bf6f1acSAlistair Francis #include "trace.h"
295bf6f1acSAlistair Francis #include "hw/irq.h"
305bf6f1acSAlistair Francis #include "hw/timer/sifive_pwm.h"
315bf6f1acSAlistair Francis #include "hw/qdev-properties.h"
325bf6f1acSAlistair Francis #include "hw/registerfields.h"
335bf6f1acSAlistair Francis #include "migration/vmstate.h"
345bf6f1acSAlistair Francis #include "qemu/log.h"
355bf6f1acSAlistair Francis #include "qemu/module.h"
365bf6f1acSAlistair Francis 
375bf6f1acSAlistair Francis #define HAS_PWM_EN_BITS(cfg) ((cfg & R_CONFIG_ENONESHOT_MASK) || \
385bf6f1acSAlistair Francis                               (cfg & R_CONFIG_ENALWAYS_MASK))
395bf6f1acSAlistair Francis 
405bf6f1acSAlistair Francis #define PWMCMP_MASK 0xFFFF
415bf6f1acSAlistair Francis #define PWMCOUNT_MASK 0x7FFFFFFF
425bf6f1acSAlistair Francis 
435bf6f1acSAlistair Francis REG32(CONFIG,                   0x00)
445bf6f1acSAlistair Francis     FIELD(CONFIG, SCALE,            0, 4)
455bf6f1acSAlistair Francis     FIELD(CONFIG, STICKY,           8, 1)
465bf6f1acSAlistair Francis     FIELD(CONFIG, ZEROCMP,          9, 1)
475bf6f1acSAlistair Francis     FIELD(CONFIG, DEGLITCH,         10, 1)
485bf6f1acSAlistair Francis     FIELD(CONFIG, ENALWAYS,         12, 1)
495bf6f1acSAlistair Francis     FIELD(CONFIG, ENONESHOT,        13, 1)
505bf6f1acSAlistair Francis     FIELD(CONFIG, CMP0CENTER,       16, 1)
515bf6f1acSAlistair Francis     FIELD(CONFIG, CMP1CENTER,       17, 1)
525bf6f1acSAlistair Francis     FIELD(CONFIG, CMP2CENTER,       18, 1)
535bf6f1acSAlistair Francis     FIELD(CONFIG, CMP3CENTER,       19, 1)
545bf6f1acSAlistair Francis     FIELD(CONFIG, CMP0GANG,         24, 1)
555bf6f1acSAlistair Francis     FIELD(CONFIG, CMP1GANG,         25, 1)
565bf6f1acSAlistair Francis     FIELD(CONFIG, CMP2GANG,         26, 1)
575bf6f1acSAlistair Francis     FIELD(CONFIG, CMP3GANG,         27, 1)
585bf6f1acSAlistair Francis     FIELD(CONFIG, CMP0IP,           28, 1)
595bf6f1acSAlistair Francis     FIELD(CONFIG, CMP1IP,           29, 1)
605bf6f1acSAlistair Francis     FIELD(CONFIG, CMP2IP,           30, 1)
615bf6f1acSAlistair Francis     FIELD(CONFIG, CMP3IP,           31, 1)
625bf6f1acSAlistair Francis REG32(COUNT,                    0x08)
635bf6f1acSAlistair Francis REG32(PWMS,                     0x10)
645bf6f1acSAlistair Francis REG32(PWMCMP0,                  0x20)
655bf6f1acSAlistair Francis REG32(PWMCMP1,                  0x24)
665bf6f1acSAlistair Francis REG32(PWMCMP2,                  0x28)
675bf6f1acSAlistair Francis REG32(PWMCMP3,                  0x2C)
685bf6f1acSAlistair Francis 
sifive_pwm_ns_to_ticks(SiFivePwmState * s,uint64_t time)695bf6f1acSAlistair Francis static inline uint64_t sifive_pwm_ns_to_ticks(SiFivePwmState *s,
705bf6f1acSAlistair Francis                                                 uint64_t time)
715bf6f1acSAlistair Francis {
725bf6f1acSAlistair Francis     return muldiv64(time, s->freq_hz, NANOSECONDS_PER_SECOND);
735bf6f1acSAlistair Francis }
745bf6f1acSAlistair Francis 
sifive_pwm_ticks_to_ns(SiFivePwmState * s,uint64_t ticks)755bf6f1acSAlistair Francis static inline uint64_t sifive_pwm_ticks_to_ns(SiFivePwmState *s,
765bf6f1acSAlistair Francis                                                 uint64_t ticks)
775bf6f1acSAlistair Francis {
785bf6f1acSAlistair Francis     return muldiv64(ticks, NANOSECONDS_PER_SECOND, s->freq_hz);
795bf6f1acSAlistair Francis }
805bf6f1acSAlistair Francis 
sifive_pwm_compute_scale(SiFivePwmState * s)815bf6f1acSAlistair Francis static inline uint64_t sifive_pwm_compute_scale(SiFivePwmState *s)
825bf6f1acSAlistair Francis {
835bf6f1acSAlistair Francis     return s->pwmcfg & R_CONFIG_SCALE_MASK;
845bf6f1acSAlistair Francis }
855bf6f1acSAlistair Francis 
sifive_pwm_set_alarms(SiFivePwmState * s)865bf6f1acSAlistair Francis static void sifive_pwm_set_alarms(SiFivePwmState *s)
875bf6f1acSAlistair Francis {
885bf6f1acSAlistair Francis     uint64_t now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
895bf6f1acSAlistair Francis 
905bf6f1acSAlistair Francis     if (HAS_PWM_EN_BITS(s->pwmcfg)) {
915bf6f1acSAlistair Francis         /*
925bf6f1acSAlistair Francis          * Subtract ticks from number of ticks when the timer was zero
935bf6f1acSAlistair Francis          * and mask to the register width.
945bf6f1acSAlistair Francis          */
955bf6f1acSAlistair Francis         uint64_t pwmcount = (sifive_pwm_ns_to_ticks(s, now_ns) -
965bf6f1acSAlistair Francis                              s->tick_offset) & PWMCOUNT_MASK;
975bf6f1acSAlistair Francis         uint64_t scale = sifive_pwm_compute_scale(s);
985bf6f1acSAlistair Francis         /* PWMs only contains PWMCMP_MASK bits starting at scale */
995bf6f1acSAlistair Francis         uint64_t pwms = (pwmcount & (PWMCMP_MASK << scale)) >> scale;
1005bf6f1acSAlistair Francis 
1015bf6f1acSAlistair Francis         for (int i = 0; i < SIFIVE_PWM_CHANS; i++) {
1025bf6f1acSAlistair Francis             uint64_t pwmcmp = s->pwmcmp[i] & PWMCMP_MASK;
1035bf6f1acSAlistair Francis             uint64_t pwmcmp_ticks = pwmcmp << scale;
1045bf6f1acSAlistair Francis 
1055bf6f1acSAlistair Francis             /*
1065bf6f1acSAlistair Francis              * Per circuit diagram and spec, both cases raises corresponding
1075bf6f1acSAlistair Francis              * IP bit one clock cycle after time expires.
1085bf6f1acSAlistair Francis              */
1095bf6f1acSAlistair Francis             if (pwmcmp > pwms) {
1105bf6f1acSAlistair Francis                 uint64_t offset = pwmcmp_ticks - pwmcount + 1;
1115bf6f1acSAlistair Francis                 uint64_t when_to_fire = now_ns +
1125bf6f1acSAlistair Francis                                           sifive_pwm_ticks_to_ns(s, offset);
1135bf6f1acSAlistair Francis 
1145bf6f1acSAlistair Francis                 trace_sifive_pwm_set_alarm(when_to_fire, now_ns);
1155bf6f1acSAlistair Francis                 timer_mod(&s->timer[i], when_to_fire);
1165bf6f1acSAlistair Francis             } else {
1175bf6f1acSAlistair Francis                 /* Schedule interrupt for next cycle */
1185bf6f1acSAlistair Francis                 trace_sifive_pwm_set_alarm(now_ns + 1, now_ns);
1195bf6f1acSAlistair Francis                 timer_mod(&s->timer[i], now_ns + 1);
1205bf6f1acSAlistair Francis             }
1215bf6f1acSAlistair Francis 
1225bf6f1acSAlistair Francis         }
1235bf6f1acSAlistair Francis     } else {
1245bf6f1acSAlistair Francis         /*
1255bf6f1acSAlistair Francis          * If timer incrementing disabled, just do pwms > pwmcmp check since
1265bf6f1acSAlistair Francis          * a write may have happened to PWMs.
1275bf6f1acSAlistair Francis          */
1285bf6f1acSAlistair Francis         uint64_t pwmcount = (s->tick_offset) & PWMCOUNT_MASK;
1295bf6f1acSAlistair Francis         uint64_t scale = sifive_pwm_compute_scale(s);
1305bf6f1acSAlistair Francis         uint64_t pwms = (pwmcount & (PWMCMP_MASK << scale)) >> scale;
1315bf6f1acSAlistair Francis 
1325bf6f1acSAlistair Francis         for (int i = 0; i < SIFIVE_PWM_CHANS; i++) {
1335bf6f1acSAlistair Francis             uint64_t pwmcmp = s->pwmcmp[i] & PWMCMP_MASK;
1345bf6f1acSAlistair Francis 
1355bf6f1acSAlistair Francis             if (pwms >= pwmcmp) {
1365bf6f1acSAlistair Francis                 trace_sifive_pwm_set_alarm(now_ns + 1, now_ns);
1375bf6f1acSAlistair Francis                 timer_mod(&s->timer[i], now_ns + 1);
1385bf6f1acSAlistair Francis             } else {
1395bf6f1acSAlistair Francis                 /* Effectively disable timer by scheduling far in future. */
1405bf6f1acSAlistair Francis                 trace_sifive_pwm_set_alarm(0xFFFFFFFFFFFFFF, now_ns);
1415bf6f1acSAlistair Francis                 timer_mod(&s->timer[i], 0xFFFFFFFFFFFFFF);
1425bf6f1acSAlistair Francis             }
1435bf6f1acSAlistair Francis         }
1445bf6f1acSAlistair Francis     }
1455bf6f1acSAlistair Francis }
1465bf6f1acSAlistair Francis 
sifive_pwm_interrupt(SiFivePwmState * s,int num)1475bf6f1acSAlistair Francis static void sifive_pwm_interrupt(SiFivePwmState *s, int num)
1485bf6f1acSAlistair Francis {
1495bf6f1acSAlistair Francis     uint64_t now = sifive_pwm_ns_to_ticks(s,
1505bf6f1acSAlistair Francis                                         qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
1515bf6f1acSAlistair Francis     bool was_incrementing = HAS_PWM_EN_BITS(s->pwmcfg);
1525bf6f1acSAlistair Francis 
1535bf6f1acSAlistair Francis     trace_sifive_pwm_interrupt(num);
1545bf6f1acSAlistair Francis 
1555bf6f1acSAlistair Francis     s->pwmcfg |= R_CONFIG_CMP0IP_MASK << num;
1565bf6f1acSAlistair Francis     qemu_irq_raise(s->irqs[num]);
1575bf6f1acSAlistair Francis 
1585bf6f1acSAlistair Francis     /*
1595bf6f1acSAlistair Francis      * If the zerocmp is set and pwmcmp0 raised the interrupt
1605bf6f1acSAlistair Francis      * reset the zero ticks.
1615bf6f1acSAlistair Francis      */
1625bf6f1acSAlistair Francis     if ((s->pwmcfg & R_CONFIG_ZEROCMP_MASK) && (num == 0)) {
1635bf6f1acSAlistair Francis         /* If reset signal conditions, disable ENONESHOT. */
1645bf6f1acSAlistair Francis         s->pwmcfg &= ~R_CONFIG_ENONESHOT_MASK;
1655bf6f1acSAlistair Francis 
1665bf6f1acSAlistair Francis         if (was_incrementing) {
1675bf6f1acSAlistair Francis             /* If incrementing, time in ticks is when pwmcount is zero */
1685bf6f1acSAlistair Francis             s->tick_offset = now;
1695bf6f1acSAlistair Francis         } else {
1705bf6f1acSAlistair Francis             /* If not incrementing, pwmcount = 0 */
1715bf6f1acSAlistair Francis             s->tick_offset = 0;
1725bf6f1acSAlistair Francis         }
1735bf6f1acSAlistair Francis     }
1745bf6f1acSAlistair Francis 
1755bf6f1acSAlistair Francis     /*
1765bf6f1acSAlistair Francis      * If carryout bit set, which we discern via looking for overflow,
1775bf6f1acSAlistair Francis      * also reset ENONESHOT.
1785bf6f1acSAlistair Francis      */
1795bf6f1acSAlistair Francis     if (was_incrementing &&
1805bf6f1acSAlistair Francis         ((now & PWMCOUNT_MASK) < (s->tick_offset & PWMCOUNT_MASK))) {
1815bf6f1acSAlistair Francis         s->pwmcfg &= ~R_CONFIG_ENONESHOT_MASK;
1825bf6f1acSAlistair Francis     }
1835bf6f1acSAlistair Francis 
1845bf6f1acSAlistair Francis     /* Schedule or disable interrupts */
1855bf6f1acSAlistair Francis     sifive_pwm_set_alarms(s);
1865bf6f1acSAlistair Francis 
1875bf6f1acSAlistair Francis     /* If was enabled, and now not enabled, switch tick rep */
1885bf6f1acSAlistair Francis     if (was_incrementing && !HAS_PWM_EN_BITS(s->pwmcfg)) {
1895bf6f1acSAlistair Francis         s->tick_offset = (now - s->tick_offset) & PWMCOUNT_MASK;
1905bf6f1acSAlistair Francis     }
1915bf6f1acSAlistair Francis }
1925bf6f1acSAlistair Francis 
sifive_pwm_interrupt_0(void * opaque)1935bf6f1acSAlistair Francis static void sifive_pwm_interrupt_0(void *opaque)
1945bf6f1acSAlistair Francis {
1955bf6f1acSAlistair Francis     SiFivePwmState *s = opaque;
1965bf6f1acSAlistair Francis 
1975bf6f1acSAlistair Francis     sifive_pwm_interrupt(s, 0);
1985bf6f1acSAlistair Francis }
1995bf6f1acSAlistair Francis 
sifive_pwm_interrupt_1(void * opaque)2005bf6f1acSAlistair Francis static void sifive_pwm_interrupt_1(void *opaque)
2015bf6f1acSAlistair Francis {
2025bf6f1acSAlistair Francis     SiFivePwmState *s = opaque;
2035bf6f1acSAlistair Francis 
2045bf6f1acSAlistair Francis     sifive_pwm_interrupt(s, 1);
2055bf6f1acSAlistair Francis }
2065bf6f1acSAlistair Francis 
sifive_pwm_interrupt_2(void * opaque)2075bf6f1acSAlistair Francis static void sifive_pwm_interrupt_2(void *opaque)
2085bf6f1acSAlistair Francis {
2095bf6f1acSAlistair Francis     SiFivePwmState *s = opaque;
2105bf6f1acSAlistair Francis 
2115bf6f1acSAlistair Francis     sifive_pwm_interrupt(s, 2);
2125bf6f1acSAlistair Francis }
2135bf6f1acSAlistair Francis 
sifive_pwm_interrupt_3(void * opaque)2145bf6f1acSAlistair Francis static void sifive_pwm_interrupt_3(void *opaque)
2155bf6f1acSAlistair Francis {
2165bf6f1acSAlistair Francis     SiFivePwmState *s = opaque;
2175bf6f1acSAlistair Francis 
2185bf6f1acSAlistair Francis     sifive_pwm_interrupt(s, 3);
2195bf6f1acSAlistair Francis }
2205bf6f1acSAlistair Francis 
sifive_pwm_read(void * opaque,hwaddr addr,unsigned int size)2215bf6f1acSAlistair Francis static uint64_t sifive_pwm_read(void *opaque, hwaddr addr,
2225bf6f1acSAlistair Francis                                   unsigned int size)
2235bf6f1acSAlistair Francis {
2245bf6f1acSAlistair Francis     SiFivePwmState *s = opaque;
2255bf6f1acSAlistair Francis     uint64_t cur_time, scale;
2265bf6f1acSAlistair Francis     uint64_t now = sifive_pwm_ns_to_ticks(s,
2275bf6f1acSAlistair Francis                                         qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
2285bf6f1acSAlistair Francis 
2295bf6f1acSAlistair Francis     trace_sifive_pwm_read(addr);
2305bf6f1acSAlistair Francis 
2315bf6f1acSAlistair Francis     switch (addr) {
2325bf6f1acSAlistair Francis     case A_CONFIG:
2335bf6f1acSAlistair Francis         return s->pwmcfg;
2345bf6f1acSAlistair Francis     case A_COUNT:
2355bf6f1acSAlistair Francis         cur_time = s->tick_offset;
2365bf6f1acSAlistair Francis 
2375bf6f1acSAlistair Francis         if (HAS_PWM_EN_BITS(s->pwmcfg)) {
2385bf6f1acSAlistair Francis             cur_time = now - cur_time;
2395bf6f1acSAlistair Francis         }
2405bf6f1acSAlistair Francis 
2415bf6f1acSAlistair Francis         /*
2425bf6f1acSAlistair Francis          * Return the value in the counter with bit 31 always 0
2435bf6f1acSAlistair Francis          * This is allowed to wrap around so we don't need to check that.
2445bf6f1acSAlistair Francis          */
2455bf6f1acSAlistair Francis         return cur_time & PWMCOUNT_MASK;
2465bf6f1acSAlistair Francis     case A_PWMS:
2475bf6f1acSAlistair Francis         cur_time = s->tick_offset;
2485bf6f1acSAlistair Francis         scale = sifive_pwm_compute_scale(s);
2495bf6f1acSAlistair Francis 
2505bf6f1acSAlistair Francis         if (HAS_PWM_EN_BITS(s->pwmcfg)) {
2515bf6f1acSAlistair Francis             cur_time = now - cur_time;
2525bf6f1acSAlistair Francis         }
2535bf6f1acSAlistair Francis 
2545bf6f1acSAlistair Francis         return ((cur_time & PWMCOUNT_MASK) >> scale) & PWMCMP_MASK;
2555bf6f1acSAlistair Francis     case A_PWMCMP0:
2565bf6f1acSAlistair Francis         return s->pwmcmp[0] & PWMCMP_MASK;
2575bf6f1acSAlistair Francis     case A_PWMCMP1:
2585bf6f1acSAlistair Francis         return s->pwmcmp[1] & PWMCMP_MASK;
2595bf6f1acSAlistair Francis     case A_PWMCMP2:
2605bf6f1acSAlistair Francis         return s->pwmcmp[2] & PWMCMP_MASK;
2615bf6f1acSAlistair Francis     case A_PWMCMP3:
2625bf6f1acSAlistair Francis         return s->pwmcmp[3] & PWMCMP_MASK;
2635bf6f1acSAlistair Francis     default:
2645bf6f1acSAlistair Francis         qemu_log_mask(LOG_GUEST_ERROR,
2655bf6f1acSAlistair Francis                       "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
2665bf6f1acSAlistair Francis         return 0;
2675bf6f1acSAlistair Francis     }
2685bf6f1acSAlistair Francis 
2695bf6f1acSAlistair Francis     return 0;
2705bf6f1acSAlistair Francis }
2715bf6f1acSAlistair Francis 
sifive_pwm_write(void * opaque,hwaddr addr,uint64_t val64,unsigned int size)2725bf6f1acSAlistair Francis static void sifive_pwm_write(void *opaque, hwaddr addr,
2735bf6f1acSAlistair Francis                                uint64_t val64, unsigned int size)
2745bf6f1acSAlistair Francis {
2755bf6f1acSAlistair Francis     SiFivePwmState *s = opaque;
2765bf6f1acSAlistair Francis     uint32_t value = val64;
2775bf6f1acSAlistair Francis     uint64_t new_offset, scale;
2785bf6f1acSAlistair Francis     uint64_t now = sifive_pwm_ns_to_ticks(s,
2795bf6f1acSAlistair Francis                                         qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
2805bf6f1acSAlistair Francis 
2815bf6f1acSAlistair Francis     trace_sifive_pwm_write(value, addr);
2825bf6f1acSAlistair Francis 
2835bf6f1acSAlistair Francis     switch (addr) {
2845bf6f1acSAlistair Francis     case A_CONFIG:
2855bf6f1acSAlistair Francis         if (value & (R_CONFIG_CMP0CENTER_MASK | R_CONFIG_CMP1CENTER_MASK |
2865bf6f1acSAlistair Francis                      R_CONFIG_CMP2CENTER_MASK | R_CONFIG_CMP3CENTER_MASK)) {
2875bf6f1acSAlistair Francis             qemu_log_mask(LOG_UNIMP, "%s: CMPxCENTER is not supported\n",
2885bf6f1acSAlistair Francis                           __func__);
2895bf6f1acSAlistair Francis         }
2905bf6f1acSAlistair Francis 
2915bf6f1acSAlistair Francis         if (value & (R_CONFIG_CMP0GANG_MASK | R_CONFIG_CMP1GANG_MASK |
2925bf6f1acSAlistair Francis                      R_CONFIG_CMP2GANG_MASK | R_CONFIG_CMP3GANG_MASK)) {
2935bf6f1acSAlistair Francis             qemu_log_mask(LOG_UNIMP, "%s: CMPxGANG is not supported\n",
2945bf6f1acSAlistair Francis                           __func__);
2955bf6f1acSAlistair Francis         }
2965bf6f1acSAlistair Francis 
2975bf6f1acSAlistair Francis         if (value & (R_CONFIG_CMP0IP_MASK | R_CONFIG_CMP1IP_MASK |
2985bf6f1acSAlistair Francis                      R_CONFIG_CMP2IP_MASK | R_CONFIG_CMP3IP_MASK)) {
2995bf6f1acSAlistair Francis             qemu_log_mask(LOG_UNIMP, "%s: CMPxIP is not supported\n",
3005bf6f1acSAlistair Francis                           __func__);
3015bf6f1acSAlistair Francis         }
3025bf6f1acSAlistair Francis 
3035bf6f1acSAlistair Francis         if (!(value & R_CONFIG_CMP0IP_MASK)) {
3045bf6f1acSAlistair Francis             qemu_irq_lower(s->irqs[0]);
3055bf6f1acSAlistair Francis         }
3065bf6f1acSAlistair Francis 
3075bf6f1acSAlistair Francis         if (!(value & R_CONFIG_CMP1IP_MASK)) {
3085bf6f1acSAlistair Francis             qemu_irq_lower(s->irqs[1]);
3095bf6f1acSAlistair Francis         }
3105bf6f1acSAlistair Francis 
3115bf6f1acSAlistair Francis         if (!(value & R_CONFIG_CMP2IP_MASK)) {
3125bf6f1acSAlistair Francis             qemu_irq_lower(s->irqs[2]);
3135bf6f1acSAlistair Francis         }
3145bf6f1acSAlistair Francis 
3155bf6f1acSAlistair Francis         if (!(value & R_CONFIG_CMP3IP_MASK)) {
3165bf6f1acSAlistair Francis             qemu_irq_lower(s->irqs[3]);
3175bf6f1acSAlistair Francis         }
3185bf6f1acSAlistair Francis 
3195bf6f1acSAlistair Francis         /*
3205bf6f1acSAlistair Francis          * If this write enables the timer increment
3215bf6f1acSAlistair Francis          * set the time when pwmcount was zero to be cur_time - pwmcount.
3225bf6f1acSAlistair Francis          * If this write disables the timer increment
3235bf6f1acSAlistair Francis          * convert back from pwmcount to the time in ticks
3245bf6f1acSAlistair Francis          * when pwmcount was zero.
3255bf6f1acSAlistair Francis          */
3265bf6f1acSAlistair Francis         if ((!HAS_PWM_EN_BITS(s->pwmcfg) && HAS_PWM_EN_BITS(value)) ||
3275bf6f1acSAlistair Francis             (HAS_PWM_EN_BITS(s->pwmcfg) && !HAS_PWM_EN_BITS(value))) {
3285bf6f1acSAlistair Francis             s->tick_offset = (now - s->tick_offset) & PWMCOUNT_MASK;
3295bf6f1acSAlistair Francis         }
3305bf6f1acSAlistair Francis 
3315bf6f1acSAlistair Francis         s->pwmcfg = value;
3325bf6f1acSAlistair Francis         break;
3335bf6f1acSAlistair Francis     case A_COUNT:
3345bf6f1acSAlistair Francis         /* The guest changed the counter, updated the offset value. */
3355bf6f1acSAlistair Francis         new_offset = value;
3365bf6f1acSAlistair Francis 
3375bf6f1acSAlistair Francis         if (HAS_PWM_EN_BITS(s->pwmcfg)) {
3385bf6f1acSAlistair Francis             new_offset = now - new_offset;
3395bf6f1acSAlistair Francis         }
3405bf6f1acSAlistair Francis 
3415bf6f1acSAlistair Francis         s->tick_offset = new_offset;
3425bf6f1acSAlistair Francis         break;
3435bf6f1acSAlistair Francis     case A_PWMS:
3445bf6f1acSAlistair Francis         scale = sifive_pwm_compute_scale(s);
3455bf6f1acSAlistair Francis         new_offset = (((value & PWMCMP_MASK) << scale) & PWMCOUNT_MASK);
3465bf6f1acSAlistair Francis 
3475bf6f1acSAlistair Francis         if (HAS_PWM_EN_BITS(s->pwmcfg)) {
3485bf6f1acSAlistair Francis             new_offset = now - new_offset;
3495bf6f1acSAlistair Francis         }
3505bf6f1acSAlistair Francis 
3515bf6f1acSAlistair Francis         s->tick_offset = new_offset;
3525bf6f1acSAlistair Francis         break;
3535bf6f1acSAlistair Francis     case A_PWMCMP0:
3545bf6f1acSAlistair Francis         s->pwmcmp[0] = value & PWMCMP_MASK;
3555bf6f1acSAlistair Francis         break;
3565bf6f1acSAlistair Francis     case A_PWMCMP1:
3575bf6f1acSAlistair Francis         s->pwmcmp[1] = value & PWMCMP_MASK;
3585bf6f1acSAlistair Francis         break;
3595bf6f1acSAlistair Francis     case A_PWMCMP2:
3605bf6f1acSAlistair Francis         s->pwmcmp[2] = value & PWMCMP_MASK;
3615bf6f1acSAlistair Francis         break;
3625bf6f1acSAlistair Francis     case A_PWMCMP3:
3635bf6f1acSAlistair Francis         s->pwmcmp[3] = value & PWMCMP_MASK;
3645bf6f1acSAlistair Francis         break;
3655bf6f1acSAlistair Francis     default:
3665bf6f1acSAlistair Francis         qemu_log_mask(LOG_GUEST_ERROR,
3675bf6f1acSAlistair Francis                       "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
3685bf6f1acSAlistair Francis     }
3695bf6f1acSAlistair Francis 
3705bf6f1acSAlistair Francis     /* Update the alarms to reflect possible updated values */
3715bf6f1acSAlistair Francis     sifive_pwm_set_alarms(s);
3725bf6f1acSAlistair Francis }
3735bf6f1acSAlistair Francis 
sifive_pwm_reset(DeviceState * dev)3745bf6f1acSAlistair Francis static void sifive_pwm_reset(DeviceState *dev)
3755bf6f1acSAlistair Francis {
3765bf6f1acSAlistair Francis     SiFivePwmState *s = SIFIVE_PWM(dev);
3775bf6f1acSAlistair Francis     uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
3785bf6f1acSAlistair Francis 
3795bf6f1acSAlistair Francis     s->pwmcfg = 0x00000000;
3805bf6f1acSAlistair Francis     s->pwmcmp[0] = 0x00000000;
3815bf6f1acSAlistair Francis     s->pwmcmp[1] = 0x00000000;
3825bf6f1acSAlistair Francis     s->pwmcmp[2] = 0x00000000;
3835bf6f1acSAlistair Francis     s->pwmcmp[3] = 0x00000000;
3845bf6f1acSAlistair Francis 
3855bf6f1acSAlistair Francis     s->tick_offset = sifive_pwm_ns_to_ticks(s, now);
3865bf6f1acSAlistair Francis }
3875bf6f1acSAlistair Francis 
3885bf6f1acSAlistair Francis static const MemoryRegionOps sifive_pwm_ops = {
3895bf6f1acSAlistair Francis     .read = sifive_pwm_read,
3905bf6f1acSAlistair Francis     .write = sifive_pwm_write,
3915bf6f1acSAlistair Francis     .endianness = DEVICE_NATIVE_ENDIAN,
3925bf6f1acSAlistair Francis };
3935bf6f1acSAlistair Francis 
3945bf6f1acSAlistair Francis static const VMStateDescription vmstate_sifive_pwm = {
3955bf6f1acSAlistair Francis     .name = TYPE_SIFIVE_PWM,
3965bf6f1acSAlistair Francis     .version_id = 1,
3975bf6f1acSAlistair Francis     .minimum_version_id = 1,
398*ba324b3fSRichard Henderson     .fields = (const VMStateField[]) {
3995bf6f1acSAlistair Francis         VMSTATE_TIMER_ARRAY(timer, SiFivePwmState, 4),
4005bf6f1acSAlistair Francis         VMSTATE_UINT64(tick_offset, SiFivePwmState),
4015bf6f1acSAlistair Francis         VMSTATE_UINT32(pwmcfg, SiFivePwmState),
4025bf6f1acSAlistair Francis         VMSTATE_UINT32_ARRAY(pwmcmp, SiFivePwmState, 4),
4035bf6f1acSAlistair Francis         VMSTATE_END_OF_LIST()
4045bf6f1acSAlistair Francis     }
4055bf6f1acSAlistair Francis };
4065bf6f1acSAlistair Francis 
4075bf6f1acSAlistair Francis static Property sifive_pwm_properties[] = {
4085bf6f1acSAlistair Francis     /* 0.5Ghz per spec after FSBL */
4095bf6f1acSAlistair Francis     DEFINE_PROP_UINT64("clock-frequency", struct SiFivePwmState,
4105bf6f1acSAlistair Francis                        freq_hz, 500000000ULL),
4115bf6f1acSAlistair Francis     DEFINE_PROP_END_OF_LIST(),
4125bf6f1acSAlistair Francis };
4135bf6f1acSAlistair Francis 
sifive_pwm_init(Object * obj)4145bf6f1acSAlistair Francis static void sifive_pwm_init(Object *obj)
4155bf6f1acSAlistair Francis {
4165bf6f1acSAlistair Francis     SiFivePwmState *s = SIFIVE_PWM(obj);
4175bf6f1acSAlistair Francis     int i;
4185bf6f1acSAlistair Francis 
4195bf6f1acSAlistair Francis     for (i = 0; i < SIFIVE_PWM_IRQS; i++) {
4205bf6f1acSAlistair Francis         sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irqs[i]);
4215bf6f1acSAlistair Francis     }
4225bf6f1acSAlistair Francis 
4235bf6f1acSAlistair Francis     memory_region_init_io(&s->mmio, obj, &sifive_pwm_ops, s,
4245bf6f1acSAlistair Francis                           TYPE_SIFIVE_PWM, 0x100);
4255bf6f1acSAlistair Francis     sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
4265bf6f1acSAlistair Francis }
4275bf6f1acSAlistair Francis 
sifive_pwm_realize(DeviceState * dev,Error ** errp)4285bf6f1acSAlistair Francis static void sifive_pwm_realize(DeviceState *dev, Error **errp)
4295bf6f1acSAlistair Francis {
4305bf6f1acSAlistair Francis     SiFivePwmState *s = SIFIVE_PWM(dev);
4315bf6f1acSAlistair Francis 
4325bf6f1acSAlistair Francis     timer_init_ns(&s->timer[0], QEMU_CLOCK_VIRTUAL,
4335bf6f1acSAlistair Francis                   sifive_pwm_interrupt_0, s);
4345bf6f1acSAlistair Francis 
4355bf6f1acSAlistair Francis     timer_init_ns(&s->timer[1], QEMU_CLOCK_VIRTUAL,
4365bf6f1acSAlistair Francis                   sifive_pwm_interrupt_1, s);
4375bf6f1acSAlistair Francis 
4385bf6f1acSAlistair Francis     timer_init_ns(&s->timer[2], QEMU_CLOCK_VIRTUAL,
4395bf6f1acSAlistair Francis                   sifive_pwm_interrupt_2, s);
4405bf6f1acSAlistair Francis 
4415bf6f1acSAlistair Francis     timer_init_ns(&s->timer[3], QEMU_CLOCK_VIRTUAL,
4425bf6f1acSAlistair Francis                   sifive_pwm_interrupt_3, s);
4435bf6f1acSAlistair Francis }
4445bf6f1acSAlistair Francis 
sifive_pwm_class_init(ObjectClass * klass,void * data)4455bf6f1acSAlistair Francis static void sifive_pwm_class_init(ObjectClass *klass, void *data)
4465bf6f1acSAlistair Francis {
4475bf6f1acSAlistair Francis     DeviceClass *dc = DEVICE_CLASS(klass);
4485bf6f1acSAlistair Francis 
4495bf6f1acSAlistair Francis     dc->reset = sifive_pwm_reset;
4505bf6f1acSAlistair Francis     device_class_set_props(dc, sifive_pwm_properties);
4515bf6f1acSAlistair Francis     dc->vmsd = &vmstate_sifive_pwm;
4525bf6f1acSAlistair Francis     dc->realize = sifive_pwm_realize;
4535bf6f1acSAlistair Francis }
4545bf6f1acSAlistair Francis 
4555bf6f1acSAlistair Francis static const TypeInfo sifive_pwm_info = {
4565bf6f1acSAlistair Francis     .name          = TYPE_SIFIVE_PWM,
4575bf6f1acSAlistair Francis     .parent        = TYPE_SYS_BUS_DEVICE,
4585bf6f1acSAlistair Francis     .instance_size = sizeof(SiFivePwmState),
4595bf6f1acSAlistair Francis     .instance_init = sifive_pwm_init,
4605bf6f1acSAlistair Francis     .class_init    = sifive_pwm_class_init,
4615bf6f1acSAlistair Francis };
4625bf6f1acSAlistair Francis 
sifive_pwm_register_types(void)4635bf6f1acSAlistair Francis static void sifive_pwm_register_types(void)
4645bf6f1acSAlistair Francis {
4655bf6f1acSAlistair Francis     type_register_static(&sifive_pwm_info);
4665bf6f1acSAlistair Francis }
4675bf6f1acSAlistair Francis 
4685bf6f1acSAlistair Francis type_init(sifive_pwm_register_types)
469