qemu/hw/timer/allwinner-a10-pit.c
<<
>>
Prefs
   1/*
   2 * Allwinner A10 timer device emulation
   3 *
   4 * Copyright (C) 2013 Li Guang
   5 * Written by Li Guang <lig.fnst@cn.fujitsu.com>
   6 *
   7 * This program is free software; you can redistribute it and/or modify it
   8 * under the terms of the GNU General Public License as published by the
   9 * Free Software Foundation; either version 2 of the License, or
  10 * (at your option) any later version.
  11 *
  12 * This program is distributed in the hope that it will be useful, but WITHOUT
  13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  15 * for more details.
  16 */
  17
  18#include "qemu/osdep.h"
  19#include "hw/sysbus.h"
  20#include "sysemu/sysemu.h"
  21#include "hw/timer/allwinner-a10-pit.h"
  22#include "qemu/log.h"
  23#include "qemu/module.h"
  24
  25static void a10_pit_update_irq(AwA10PITState *s)
  26{
  27    int i;
  28
  29    for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
  30        qemu_set_irq(s->irq[i], !!(s->irq_status & s->irq_enable & (1 << i)));
  31    }
  32}
  33
  34static uint64_t a10_pit_read(void *opaque, hwaddr offset, unsigned size)
  35{
  36    AwA10PITState *s = AW_A10_PIT(opaque);
  37    uint8_t index;
  38
  39    switch (offset) {
  40    case AW_A10_PIT_TIMER_IRQ_EN:
  41        return s->irq_enable;
  42    case AW_A10_PIT_TIMER_IRQ_ST:
  43        return s->irq_status;
  44    case AW_A10_PIT_TIMER_BASE ... AW_A10_PIT_TIMER_BASE_END:
  45        index = offset & 0xf0;
  46        index >>= 4;
  47        index -= 1;
  48        switch (offset & 0x0f) {
  49        case AW_A10_PIT_TIMER_CONTROL:
  50            return s->control[index];
  51        case AW_A10_PIT_TIMER_INTERVAL:
  52            return s->interval[index];
  53        case AW_A10_PIT_TIMER_COUNT:
  54            s->count[index] = ptimer_get_count(s->timer[index]);
  55            return s->count[index];
  56        default:
  57            qemu_log_mask(LOG_GUEST_ERROR,
  58                          "%s: Bad offset 0x%x\n",  __func__, (int)offset);
  59            break;
  60        }
  61    case AW_A10_PIT_WDOG_CONTROL:
  62        break;
  63    case AW_A10_PIT_WDOG_MODE:
  64        break;
  65    case AW_A10_PIT_COUNT_LO:
  66        return s->count_lo;
  67    case AW_A10_PIT_COUNT_HI:
  68        return s->count_hi;
  69    case AW_A10_PIT_COUNT_CTL:
  70        return s->count_ctl;
  71    default:
  72        qemu_log_mask(LOG_GUEST_ERROR,
  73                      "%s: Bad offset 0x%x\n",  __func__, (int)offset);
  74        break;
  75    }
  76
  77    return 0;
  78}
  79
  80static void a10_pit_set_freq(AwA10PITState *s, int index)
  81{
  82    uint32_t prescaler, source, source_freq;
  83
  84    prescaler = 1 << extract32(s->control[index], 4, 3);
  85    source = extract32(s->control[index], 2, 2);
  86    source_freq = s->clk_freq[source];
  87
  88    if (source_freq) {
  89        ptimer_set_freq(s->timer[index], source_freq / prescaler);
  90    } else {
  91        qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid clock source %u\n",
  92                      __func__, source);
  93    }
  94}
  95
  96static void a10_pit_write(void *opaque, hwaddr offset, uint64_t value,
  97                            unsigned size)
  98{
  99     AwA10PITState *s = AW_A10_PIT(opaque);
 100     uint8_t index;
 101
 102    switch (offset) {
 103    case AW_A10_PIT_TIMER_IRQ_EN:
 104        s->irq_enable = value;
 105        a10_pit_update_irq(s);
 106        break;
 107    case AW_A10_PIT_TIMER_IRQ_ST:
 108        s->irq_status &= ~value;
 109        a10_pit_update_irq(s);
 110        break;
 111    case AW_A10_PIT_TIMER_BASE ... AW_A10_PIT_TIMER_BASE_END:
 112        index = offset & 0xf0;
 113        index >>= 4;
 114        index -= 1;
 115        switch (offset & 0x0f) {
 116        case AW_A10_PIT_TIMER_CONTROL:
 117            s->control[index] = value;
 118            a10_pit_set_freq(s, index);
 119            if (s->control[index] & AW_A10_PIT_TIMER_RELOAD) {
 120                ptimer_set_count(s->timer[index], s->interval[index]);
 121            }
 122            if (s->control[index] & AW_A10_PIT_TIMER_EN) {
 123                int oneshot = 0;
 124                if (s->control[index] & AW_A10_PIT_TIMER_MODE) {
 125                    oneshot = 1;
 126                }
 127                ptimer_run(s->timer[index], oneshot);
 128            } else {
 129                ptimer_stop(s->timer[index]);
 130            }
 131            break;
 132        case AW_A10_PIT_TIMER_INTERVAL:
 133            s->interval[index] = value;
 134            ptimer_set_limit(s->timer[index], s->interval[index], 1);
 135            break;
 136        case AW_A10_PIT_TIMER_COUNT:
 137            s->count[index] = value;
 138            break;
 139        default:
 140            qemu_log_mask(LOG_GUEST_ERROR,
 141                          "%s: Bad offset 0x%x\n",  __func__, (int)offset);
 142        }
 143        break;
 144    case AW_A10_PIT_WDOG_CONTROL:
 145        s->watch_dog_control = value;
 146        break;
 147    case AW_A10_PIT_WDOG_MODE:
 148        s->watch_dog_mode = value;
 149        break;
 150    case AW_A10_PIT_COUNT_LO:
 151        s->count_lo = value;
 152        break;
 153    case AW_A10_PIT_COUNT_HI:
 154        s->count_hi = value;
 155        break;
 156    case AW_A10_PIT_COUNT_CTL:
 157        s->count_ctl = value;
 158        if (s->count_ctl & AW_A10_PIT_COUNT_RL_EN) {
 159            uint64_t  tmp_count = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
 160
 161            s->count_lo = tmp_count;
 162            s->count_hi = tmp_count >> 32;
 163            s->count_ctl &= ~AW_A10_PIT_COUNT_RL_EN;
 164        }
 165        if (s->count_ctl & AW_A10_PIT_COUNT_CLR_EN) {
 166            s->count_lo = 0;
 167            s->count_hi = 0;
 168            s->count_ctl &= ~AW_A10_PIT_COUNT_CLR_EN;
 169        }
 170        break;
 171    default:
 172        qemu_log_mask(LOG_GUEST_ERROR,
 173                      "%s: Bad offset 0x%x\n",  __func__, (int)offset);
 174        break;
 175    }
 176}
 177
 178static const MemoryRegionOps a10_pit_ops = {
 179    .read = a10_pit_read,
 180    .write = a10_pit_write,
 181    .endianness = DEVICE_NATIVE_ENDIAN,
 182};
 183
 184static Property a10_pit_properties[] = {
 185    DEFINE_PROP_UINT32("clk0-freq", AwA10PITState, clk_freq[0], 0),
 186    DEFINE_PROP_UINT32("clk1-freq", AwA10PITState, clk_freq[1], 0),
 187    DEFINE_PROP_UINT32("clk2-freq", AwA10PITState, clk_freq[2], 0),
 188    DEFINE_PROP_UINT32("clk3-freq", AwA10PITState, clk_freq[3], 0),
 189    DEFINE_PROP_END_OF_LIST(),
 190};
 191
 192static const VMStateDescription vmstate_a10_pit = {
 193    .name = "a10.pit",
 194    .version_id = 1,
 195    .minimum_version_id = 1,
 196    .fields = (VMStateField[]) {
 197        VMSTATE_UINT32(irq_enable, AwA10PITState),
 198        VMSTATE_UINT32(irq_status, AwA10PITState),
 199        VMSTATE_UINT32_ARRAY(control, AwA10PITState, AW_A10_PIT_TIMER_NR),
 200        VMSTATE_UINT32_ARRAY(interval, AwA10PITState, AW_A10_PIT_TIMER_NR),
 201        VMSTATE_UINT32_ARRAY(count, AwA10PITState, AW_A10_PIT_TIMER_NR),
 202        VMSTATE_UINT32(watch_dog_mode, AwA10PITState),
 203        VMSTATE_UINT32(watch_dog_control, AwA10PITState),
 204        VMSTATE_UINT32(count_lo, AwA10PITState),
 205        VMSTATE_UINT32(count_hi, AwA10PITState),
 206        VMSTATE_UINT32(count_ctl, AwA10PITState),
 207        VMSTATE_PTIMER_ARRAY(timer, AwA10PITState, AW_A10_PIT_TIMER_NR),
 208        VMSTATE_END_OF_LIST()
 209    }
 210};
 211
 212static void a10_pit_reset(DeviceState *dev)
 213{
 214    AwA10PITState *s = AW_A10_PIT(dev);
 215    uint8_t i;
 216
 217    s->irq_enable = 0;
 218    s->irq_status = 0;
 219    a10_pit_update_irq(s);
 220
 221    for (i = 0; i < 6; i++) {
 222        s->control[i] = AW_A10_PIT_DEFAULT_CLOCK;
 223        s->interval[i] = 0;
 224        s->count[i] = 0;
 225        ptimer_stop(s->timer[i]);
 226        a10_pit_set_freq(s, i);
 227    }
 228    s->watch_dog_mode = 0;
 229    s->watch_dog_control = 0;
 230    s->count_lo = 0;
 231    s->count_hi = 0;
 232    s->count_ctl = 0;
 233}
 234
 235static void a10_pit_timer_cb(void *opaque)
 236{
 237    AwA10TimerContext *tc = opaque;
 238    AwA10PITState *s = tc->container;
 239    uint8_t i = tc->index;
 240
 241    if (s->control[i] & AW_A10_PIT_TIMER_EN) {
 242        s->irq_status |= 1 << i;
 243        if (s->control[i] & AW_A10_PIT_TIMER_MODE) {
 244            ptimer_stop(s->timer[i]);
 245            s->control[i] &= ~AW_A10_PIT_TIMER_EN;
 246        }
 247        a10_pit_update_irq(s);
 248    }
 249}
 250
 251static void a10_pit_init(Object *obj)
 252{
 253    AwA10PITState *s = AW_A10_PIT(obj);
 254    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
 255    QEMUBH * bh[AW_A10_PIT_TIMER_NR];
 256    uint8_t i;
 257
 258    for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
 259        sysbus_init_irq(sbd, &s->irq[i]);
 260    }
 261    memory_region_init_io(&s->iomem, OBJECT(s), &a10_pit_ops, s,
 262                          TYPE_AW_A10_PIT, 0x400);
 263    sysbus_init_mmio(sbd, &s->iomem);
 264
 265    for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
 266        AwA10TimerContext *tc = &s->timer_context[i];
 267
 268        tc->container = s;
 269        tc->index = i;
 270        bh[i] = qemu_bh_new(a10_pit_timer_cb, tc);
 271        s->timer[i] = ptimer_init(bh[i], PTIMER_POLICY_DEFAULT);
 272    }
 273}
 274
 275static void a10_pit_class_init(ObjectClass *klass, void *data)
 276{
 277    DeviceClass *dc = DEVICE_CLASS(klass);
 278
 279    dc->reset = a10_pit_reset;
 280    dc->props = a10_pit_properties;
 281    dc->desc = "allwinner a10 timer";
 282    dc->vmsd = &vmstate_a10_pit;
 283}
 284
 285static const TypeInfo a10_pit_info = {
 286    .name = TYPE_AW_A10_PIT,
 287    .parent = TYPE_SYS_BUS_DEVICE,
 288    .instance_size = sizeof(AwA10PITState),
 289    .instance_init = a10_pit_init,
 290    .class_init = a10_pit_class_init,
 291};
 292
 293static void a10_register_types(void)
 294{
 295    type_register_static(&a10_pit_info);
 296}
 297
 298type_init(a10_register_types);
 299