qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

sifive_pwm.c (14851B)


      1 /*
      2  * SiFive PWM
      3  *
      4  * Copyright (c) 2020 Western Digital
      5  *
      6  * Author:  Alistair Francis <alistair.francis@wdc.com>
      7  *
      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:
     14  *
     15  * The above copyright notice and this permission notice shall be included in
     16  * all copies or substantial portions of the Software.
     17  *
     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.
     25  */
     26 
     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"
     36 
     37 #define HAS_PWM_EN_BITS(cfg) ((cfg & R_CONFIG_ENONESHOT_MASK) || \
     38                               (cfg & R_CONFIG_ENALWAYS_MASK))
     39 
     40 #define PWMCMP_MASK 0xFFFF
     41 #define PWMCOUNT_MASK 0x7FFFFFFF
     42 
     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)
     68 
     69 static inline uint64_t sifive_pwm_ns_to_ticks(SiFivePwmState *s,
     70                                                 uint64_t time)
     71 {
     72     return muldiv64(time, s->freq_hz, NANOSECONDS_PER_SECOND);
     73 }
     74 
     75 static inline uint64_t sifive_pwm_ticks_to_ns(SiFivePwmState *s,
     76                                                 uint64_t ticks)
     77 {
     78     return muldiv64(ticks, NANOSECONDS_PER_SECOND, s->freq_hz);
     79 }
     80 
     81 static inline uint64_t sifive_pwm_compute_scale(SiFivePwmState *s)
     82 {
     83     return s->pwmcfg & R_CONFIG_SCALE_MASK;
     84 }
     85 
     86 static void sifive_pwm_set_alarms(SiFivePwmState *s)
     87 {
     88     uint64_t now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
     89 
     90     if (HAS_PWM_EN_BITS(s->pwmcfg)) {
     91         /*
     92          * Subtract ticks from number of ticks when the timer was zero
     93          * and mask to the register width.
     94          */
     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;
    100 
    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;
    104 
    105             /*
    106              * Per circuit diagram and spec, both cases raises corresponding
    107              * IP bit one clock cycle after time expires.
    108              */
    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);
    113 
    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);
    120             }
    121 
    122         }
    123     } else {
    124         /*
    125          * If timer incrementing disabled, just do pwms > pwmcmp check since
    126          * a write may have happened to PWMs.
    127          */
    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;
    131 
    132         for (int i = 0; i < SIFIVE_PWM_CHANS; i++) {
    133             uint64_t pwmcmp = s->pwmcmp[i] & PWMCMP_MASK;
    134 
    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);
    142             }
    143         }
    144     }
    145 }
    146 
    147 static void sifive_pwm_interrupt(SiFivePwmState *s, int num)
    148 {
    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);
    152 
    153     trace_sifive_pwm_interrupt(num);
    154 
    155     s->pwmcfg |= R_CONFIG_CMP0IP_MASK << num;
    156     qemu_irq_raise(s->irqs[num]);
    157 
    158     /*
    159      * If the zerocmp is set and pwmcmp0 raised the interrupt
    160      * reset the zero ticks.
    161      */
    162     if ((s->pwmcfg & R_CONFIG_ZEROCMP_MASK) && (num == 0)) {
    163         /* If reset signal conditions, disable ENONESHOT. */
    164         s->pwmcfg &= ~R_CONFIG_ENONESHOT_MASK;
    165 
    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;
    172         }
    173     }
    174 
    175     /*
    176      * If carryout bit set, which we discern via looking for overflow,
    177      * also reset ENONESHOT.
    178      */
    179     if (was_incrementing &&
    180         ((now & PWMCOUNT_MASK) < (s->tick_offset & PWMCOUNT_MASK))) {
    181         s->pwmcfg &= ~R_CONFIG_ENONESHOT_MASK;
    182     }
    183 
    184     /* Schedule or disable interrupts */
    185     sifive_pwm_set_alarms(s);
    186 
    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;
    190     }
    191 }
    192 
    193 static void sifive_pwm_interrupt_0(void *opaque)
    194 {
    195     SiFivePwmState *s = opaque;
    196 
    197     sifive_pwm_interrupt(s, 0);
    198 }
    199 
    200 static void sifive_pwm_interrupt_1(void *opaque)
    201 {
    202     SiFivePwmState *s = opaque;
    203 
    204     sifive_pwm_interrupt(s, 1);
    205 }
    206 
    207 static void sifive_pwm_interrupt_2(void *opaque)
    208 {
    209     SiFivePwmState *s = opaque;
    210 
    211     sifive_pwm_interrupt(s, 2);
    212 }
    213 
    214 static void sifive_pwm_interrupt_3(void *opaque)
    215 {
    216     SiFivePwmState *s = opaque;
    217 
    218     sifive_pwm_interrupt(s, 3);
    219 }
    220 
    221 static uint64_t sifive_pwm_read(void *opaque, hwaddr addr,
    222                                   unsigned int size)
    223 {
    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));
    228 
    229     trace_sifive_pwm_read(addr);
    230 
    231     switch (addr) {
    232     case A_CONFIG:
    233         return s->pwmcfg;
    234     case A_COUNT:
    235         cur_time = s->tick_offset;
    236 
    237         if (HAS_PWM_EN_BITS(s->pwmcfg)) {
    238             cur_time = now - cur_time;
    239         }
    240 
    241         /*
    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.
    244          */
    245         return cur_time & PWMCOUNT_MASK;
    246     case A_PWMS:
    247         cur_time = s->tick_offset;
    248         scale = sifive_pwm_compute_scale(s);
    249 
    250         if (HAS_PWM_EN_BITS(s->pwmcfg)) {
    251             cur_time = now - cur_time;
    252         }
    253 
    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;
    267     }
    268 
    269     return 0;
    270 }
    271 
    272 static void sifive_pwm_write(void *opaque, hwaddr addr,
    273                                uint64_t val64, unsigned int size)
    274 {
    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));
    280 
    281     trace_sifive_pwm_write(value, addr);
    282 
    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__);
    289         }
    290 
    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__);
    295         }
    296 
    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__);
    301         }
    302 
    303         if (!(value & R_CONFIG_CMP0IP_MASK)) {
    304             qemu_irq_lower(s->irqs[0]);
    305         }
    306 
    307         if (!(value & R_CONFIG_CMP1IP_MASK)) {
    308             qemu_irq_lower(s->irqs[1]);
    309         }
    310 
    311         if (!(value & R_CONFIG_CMP2IP_MASK)) {
    312             qemu_irq_lower(s->irqs[2]);
    313         }
    314 
    315         if (!(value & R_CONFIG_CMP3IP_MASK)) {
    316             qemu_irq_lower(s->irqs[3]);
    317         }
    318 
    319         /*
    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.
    325          */
    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;
    329         }
    330 
    331         s->pwmcfg = value;
    332         break;
    333     case A_COUNT:
    334         /* The guest changed the counter, updated the offset value. */
    335         new_offset = value;
    336 
    337         if (HAS_PWM_EN_BITS(s->pwmcfg)) {
    338             new_offset = now - new_offset;
    339         }
    340 
    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);
    346 
    347         if (HAS_PWM_EN_BITS(s->pwmcfg)) {
    348             new_offset = now - new_offset;
    349         }
    350 
    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);
    368     }
    369 
    370     /* Update the alarms to reflect possible updated values */
    371     sifive_pwm_set_alarms(s);
    372 }
    373 
    374 static void sifive_pwm_reset(DeviceState *dev)
    375 {
    376     SiFivePwmState *s = SIFIVE_PWM(dev);
    377     uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
    378 
    379     s->pwmcfg = 0x00000000;
    380     s->pwmcmp[0] = 0x00000000;
    381     s->pwmcmp[1] = 0x00000000;
    382     s->pwmcmp[2] = 0x00000000;
    383     s->pwmcmp[3] = 0x00000000;
    384 
    385     s->tick_offset = sifive_pwm_ns_to_ticks(s, now);
    386 }
    387 
    388 static const MemoryRegionOps sifive_pwm_ops = {
    389     .read = sifive_pwm_read,
    390     .write = sifive_pwm_write,
    391     .endianness = DEVICE_NATIVE_ENDIAN,
    392 };
    393 
    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()
    404     }
    405 };
    406 
    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(),
    412 };
    413 
    414 static void sifive_pwm_init(Object *obj)
    415 {
    416     SiFivePwmState *s = SIFIVE_PWM(obj);
    417     int i;
    418 
    419     for (i = 0; i < SIFIVE_PWM_IRQS; i++) {
    420         sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irqs[i]);
    421     }
    422 
    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);
    426 }
    427 
    428 static void sifive_pwm_realize(DeviceState *dev, Error **errp)
    429 {
    430     SiFivePwmState *s = SIFIVE_PWM(dev);
    431 
    432     timer_init_ns(&s->timer[0], QEMU_CLOCK_VIRTUAL,
    433                   sifive_pwm_interrupt_0, s);
    434 
    435     timer_init_ns(&s->timer[1], QEMU_CLOCK_VIRTUAL,
    436                   sifive_pwm_interrupt_1, s);
    437 
    438     timer_init_ns(&s->timer[2], QEMU_CLOCK_VIRTUAL,
    439                   sifive_pwm_interrupt_2, s);
    440 
    441     timer_init_ns(&s->timer[3], QEMU_CLOCK_VIRTUAL,
    442                   sifive_pwm_interrupt_3, s);
    443 }
    444 
    445 static void sifive_pwm_class_init(ObjectClass *klass, void *data)
    446 {
    447     DeviceClass *dc = DEVICE_CLASS(klass);
    448 
    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;
    453 }
    454 
    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,
    461 };
    462 
    463 static void sifive_pwm_register_types(void)
    464 {
    465     type_register_static(&sifive_pwm_info);
    466 }
    467 
    468 type_init(sifive_pwm_register_types)