qemu/hw/timer/stellaris-gptm.c
<<
>>
Prefs
   1/*
   2 * Luminary Micro Stellaris General Purpose Timer Module
   3 *
   4 * Copyright (c) 2006 CodeSourcery.
   5 * Written by Paul Brook
   6 *
   7 * This code is licensed under the GPL.
   8 */
   9
  10#include "qemu/osdep.h"
  11#include "qemu/log.h"
  12#include "qemu/timer.h"
  13#include "qapi/error.h"
  14#include "migration/vmstate.h"
  15#include "hw/qdev-clock.h"
  16#include "hw/timer/stellaris-gptm.h"
  17
  18static void gptm_update_irq(gptm_state *s)
  19{
  20    int level;
  21    level = (s->state & s->mask) != 0;
  22    qemu_set_irq(s->irq, level);
  23}
  24
  25static void gptm_stop(gptm_state *s, int n)
  26{
  27    timer_del(s->timer[n]);
  28}
  29
  30static void gptm_reload(gptm_state *s, int n, int reset)
  31{
  32    int64_t tick;
  33    if (reset) {
  34        tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
  35    } else {
  36        tick = s->tick[n];
  37    }
  38
  39    if (s->config == 0) {
  40        /* 32-bit CountDown.  */
  41        uint32_t count;
  42        count = s->load[0] | (s->load[1] << 16);
  43        tick += clock_ticks_to_ns(s->clk, count);
  44    } else if (s->config == 1) {
  45        /* 32-bit RTC.  1Hz tick.  */
  46        tick += NANOSECONDS_PER_SECOND;
  47    } else if (s->mode[n] == 0xa) {
  48        /* PWM mode.  Not implemented.  */
  49    } else {
  50        qemu_log_mask(LOG_UNIMP,
  51                      "GPTM: 16-bit timer mode unimplemented: 0x%x\n",
  52                      s->mode[n]);
  53        return;
  54    }
  55    s->tick[n] = tick;
  56    timer_mod(s->timer[n], tick);
  57}
  58
  59static void gptm_tick(void *opaque)
  60{
  61    gptm_state **p = (gptm_state **)opaque;
  62    gptm_state *s;
  63    int n;
  64
  65    s = *p;
  66    n = p - s->opaque;
  67    if (s->config == 0) {
  68        s->state |= 1;
  69        if ((s->control & 0x20)) {
  70            /* Output trigger.  */
  71            qemu_irq_pulse(s->trigger);
  72        }
  73        if (s->mode[0] & 1) {
  74            /* One-shot.  */
  75            s->control &= ~1;
  76        } else {
  77            /* Periodic.  */
  78            gptm_reload(s, 0, 0);
  79        }
  80    } else if (s->config == 1) {
  81        /* RTC.  */
  82        uint32_t match;
  83        s->rtc++;
  84        match = s->match[0] | (s->match[1] << 16);
  85        if (s->rtc > match)
  86            s->rtc = 0;
  87        if (s->rtc == 0) {
  88            s->state |= 8;
  89        }
  90        gptm_reload(s, 0, 0);
  91    } else if (s->mode[n] == 0xa) {
  92        /* PWM mode.  Not implemented.  */
  93    } else {
  94        qemu_log_mask(LOG_UNIMP,
  95                      "GPTM: 16-bit timer mode unimplemented: 0x%x\n",
  96                      s->mode[n]);
  97    }
  98    gptm_update_irq(s);
  99}
 100
 101static uint64_t gptm_read(void *opaque, hwaddr offset,
 102                          unsigned size)
 103{
 104    gptm_state *s = (gptm_state *)opaque;
 105
 106    switch (offset) {
 107    case 0x00: /* CFG */
 108        return s->config;
 109    case 0x04: /* TAMR */
 110        return s->mode[0];
 111    case 0x08: /* TBMR */
 112        return s->mode[1];
 113    case 0x0c: /* CTL */
 114        return s->control;
 115    case 0x18: /* IMR */
 116        return s->mask;
 117    case 0x1c: /* RIS */
 118        return s->state;
 119    case 0x20: /* MIS */
 120        return s->state & s->mask;
 121    case 0x24: /* CR */
 122        return 0;
 123    case 0x28: /* TAILR */
 124        return s->load[0] | ((s->config < 4) ? (s->load[1] << 16) : 0);
 125    case 0x2c: /* TBILR */
 126        return s->load[1];
 127    case 0x30: /* TAMARCHR */
 128        return s->match[0] | ((s->config < 4) ? (s->match[1] << 16) : 0);
 129    case 0x34: /* TBMATCHR */
 130        return s->match[1];
 131    case 0x38: /* TAPR */
 132        return s->prescale[0];
 133    case 0x3c: /* TBPR */
 134        return s->prescale[1];
 135    case 0x40: /* TAPMR */
 136        return s->match_prescale[0];
 137    case 0x44: /* TBPMR */
 138        return s->match_prescale[1];
 139    case 0x48: /* TAR */
 140        if (s->config == 1) {
 141            return s->rtc;
 142        }
 143        qemu_log_mask(LOG_UNIMP,
 144                      "GPTM: read of TAR but timer read not supported\n");
 145        return 0;
 146    case 0x4c: /* TBR */
 147        qemu_log_mask(LOG_UNIMP,
 148                      "GPTM: read of TBR but timer read not supported\n");
 149        return 0;
 150    default:
 151        qemu_log_mask(LOG_GUEST_ERROR,
 152                      "GPTM: read at bad offset 0x02%" HWADDR_PRIx "\n",
 153                      offset);
 154        return 0;
 155    }
 156}
 157
 158static void gptm_write(void *opaque, hwaddr offset,
 159                       uint64_t value, unsigned size)
 160{
 161    gptm_state *s = (gptm_state *)opaque;
 162    uint32_t oldval;
 163
 164    /*
 165     * The timers should be disabled before changing the configuration.
 166     * We take advantage of this and defer everything until the timer
 167     * is enabled.
 168     */
 169    switch (offset) {
 170    case 0x00: /* CFG */
 171        s->config = value;
 172        break;
 173    case 0x04: /* TAMR */
 174        s->mode[0] = value;
 175        break;
 176    case 0x08: /* TBMR */
 177        s->mode[1] = value;
 178        break;
 179    case 0x0c: /* CTL */
 180        oldval = s->control;
 181        s->control = value;
 182        /* TODO: Implement pause.  */
 183        if ((oldval ^ value) & 1) {
 184            if (value & 1) {
 185                gptm_reload(s, 0, 1);
 186            } else {
 187                gptm_stop(s, 0);
 188            }
 189        }
 190        if (((oldval ^ value) & 0x100) && s->config >= 4) {
 191            if (value & 0x100) {
 192                gptm_reload(s, 1, 1);
 193            } else {
 194                gptm_stop(s, 1);
 195            }
 196        }
 197        break;
 198    case 0x18: /* IMR */
 199        s->mask = value & 0x77;
 200        gptm_update_irq(s);
 201        break;
 202    case 0x24: /* CR */
 203        s->state &= ~value;
 204        break;
 205    case 0x28: /* TAILR */
 206        s->load[0] = value & 0xffff;
 207        if (s->config < 4) {
 208            s->load[1] = value >> 16;
 209        }
 210        break;
 211    case 0x2c: /* TBILR */
 212        s->load[1] = value & 0xffff;
 213        break;
 214    case 0x30: /* TAMARCHR */
 215        s->match[0] = value & 0xffff;
 216        if (s->config < 4) {
 217            s->match[1] = value >> 16;
 218        }
 219        break;
 220    case 0x34: /* TBMATCHR */
 221        s->match[1] = value >> 16;
 222        break;
 223    case 0x38: /* TAPR */
 224        s->prescale[0] = value;
 225        break;
 226    case 0x3c: /* TBPR */
 227        s->prescale[1] = value;
 228        break;
 229    case 0x40: /* TAPMR */
 230        s->match_prescale[0] = value;
 231        break;
 232    case 0x44: /* TBPMR */
 233        s->match_prescale[0] = value;
 234        break;
 235    default:
 236        qemu_log_mask(LOG_GUEST_ERROR,
 237                      "GPTM: write at bad offset 0x02%" HWADDR_PRIx "\n",
 238                      offset);
 239    }
 240    gptm_update_irq(s);
 241}
 242
 243static const MemoryRegionOps gptm_ops = {
 244    .read = gptm_read,
 245    .write = gptm_write,
 246    .endianness = DEVICE_NATIVE_ENDIAN,
 247};
 248
 249static const VMStateDescription vmstate_stellaris_gptm = {
 250    .name = "stellaris_gptm",
 251    .version_id = 2,
 252    .minimum_version_id = 2,
 253    .fields = (VMStateField[]) {
 254        VMSTATE_UINT32(config, gptm_state),
 255        VMSTATE_UINT32_ARRAY(mode, gptm_state, 2),
 256        VMSTATE_UINT32(control, gptm_state),
 257        VMSTATE_UINT32(state, gptm_state),
 258        VMSTATE_UINT32(mask, gptm_state),
 259        VMSTATE_UNUSED(8),
 260        VMSTATE_UINT32_ARRAY(load, gptm_state, 2),
 261        VMSTATE_UINT32_ARRAY(match, gptm_state, 2),
 262        VMSTATE_UINT32_ARRAY(prescale, gptm_state, 2),
 263        VMSTATE_UINT32_ARRAY(match_prescale, gptm_state, 2),
 264        VMSTATE_UINT32(rtc, gptm_state),
 265        VMSTATE_INT64_ARRAY(tick, gptm_state, 2),
 266        VMSTATE_TIMER_PTR_ARRAY(timer, gptm_state, 2),
 267        VMSTATE_CLOCK(clk, gptm_state),
 268        VMSTATE_END_OF_LIST()
 269    }
 270};
 271
 272static void stellaris_gptm_init(Object *obj)
 273{
 274    DeviceState *dev = DEVICE(obj);
 275    gptm_state *s = STELLARIS_GPTM(obj);
 276    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
 277
 278    sysbus_init_irq(sbd, &s->irq);
 279    qdev_init_gpio_out(dev, &s->trigger, 1);
 280
 281    memory_region_init_io(&s->iomem, obj, &gptm_ops, s,
 282                          "gptm", 0x1000);
 283    sysbus_init_mmio(sbd, &s->iomem);
 284
 285    s->opaque[0] = s->opaque[1] = s;
 286
 287    /*
 288     * TODO: in an ideal world we would model the effects of changing
 289     * the input clock frequency while the countdown timer is active.
 290     * The best way to do this would be to convert the device to use
 291     * ptimer instead of hand-rolling its own timer. This would also
 292     * make it easy to implement reading the current count from the
 293     * TAR and TBR registers.
 294     */
 295    s->clk = qdev_init_clock_in(dev, "clk", NULL, NULL, 0);
 296}
 297
 298static void stellaris_gptm_realize(DeviceState *dev, Error **errp)
 299{
 300    gptm_state *s = STELLARIS_GPTM(dev);
 301
 302    if (!clock_has_source(s->clk)) {
 303        error_setg(errp, "stellaris-gptm: clk must be connected");
 304        return;
 305    }
 306
 307    s->timer[0] = timer_new_ns(QEMU_CLOCK_VIRTUAL, gptm_tick, &s->opaque[0]);
 308    s->timer[1] = timer_new_ns(QEMU_CLOCK_VIRTUAL, gptm_tick, &s->opaque[1]);
 309}
 310
 311static void stellaris_gptm_class_init(ObjectClass *klass, void *data)
 312{
 313    DeviceClass *dc = DEVICE_CLASS(klass);
 314
 315    dc->vmsd = &vmstate_stellaris_gptm;
 316    dc->realize = stellaris_gptm_realize;
 317}
 318
 319static const TypeInfo stellaris_gptm_info = {
 320    .name          = TYPE_STELLARIS_GPTM,
 321    .parent        = TYPE_SYS_BUS_DEVICE,
 322    .instance_size = sizeof(gptm_state),
 323    .instance_init = stellaris_gptm_init,
 324    .class_init    = stellaris_gptm_class_init,
 325};
 326
 327static void stellaris_gptm_register_types(void)
 328{
 329    type_register_static(&stellaris_gptm_info);
 330}
 331
 332type_init(stellaris_gptm_register_types)
 333