diff options
Diffstat (limited to 'hw/grlib_gptimer.c')
-rw-r--r-- | hw/grlib_gptimer.c | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/hw/grlib_gptimer.c b/hw/grlib_gptimer.c new file mode 100644 index 0000000000..596a9000a1 --- /dev/null +++ b/hw/grlib_gptimer.c @@ -0,0 +1,395 @@ +/* + * QEMU GRLIB GPTimer Emulator + * + * Copyright (c) 2010-2011 AdaCore + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "sysbus.h" +#include "qemu-timer.h" + +#include "trace.h" + +#define UNIT_REG_SIZE 16 /* Size of memory mapped regs for the unit */ +#define GPTIMER_REG_SIZE 16 /* Size of memory mapped regs for a GPTimer */ + +#define GPTIMER_MAX_TIMERS 8 + +/* GPTimer Config register fields */ +#define GPTIMER_ENABLE (1 << 0) +#define GPTIMER_RESTART (1 << 1) +#define GPTIMER_LOAD (1 << 2) +#define GPTIMER_INT_ENABLE (1 << 3) +#define GPTIMER_INT_PENDING (1 << 4) +#define GPTIMER_CHAIN (1 << 5) /* Not supported */ +#define GPTIMER_DEBUG_HALT (1 << 6) /* Not supported */ + +/* Memory mapped register offsets */ +#define SCALER_OFFSET 0x00 +#define SCALER_RELOAD_OFFSET 0x04 +#define CONFIG_OFFSET 0x08 +#define COUNTER_OFFSET 0x00 +#define COUNTER_RELOAD_OFFSET 0x04 +#define TIMER_BASE 0x10 + +typedef struct GPTimer GPTimer; +typedef struct GPTimerUnit GPTimerUnit; + +struct GPTimer { + QEMUBH *bh; + struct ptimer_state *ptimer; + + qemu_irq irq; + int id; + GPTimerUnit *unit; + + /* registers */ + uint32_t counter; + uint32_t reload; + uint32_t config; +}; + +struct GPTimerUnit { + SysBusDevice busdev; + + uint32_t nr_timers; /* Number of timers available */ + uint32_t freq_hz; /* System frequency */ + uint32_t irq_line; /* Base irq line */ + + GPTimer *timers; + + /* registers */ + uint32_t scaler; + uint32_t reload; + uint32_t config; +}; + +static void grlib_gptimer_enable(GPTimer *timer) +{ + assert(timer != NULL); + + + ptimer_stop(timer->ptimer); + + if (!(timer->config & GPTIMER_ENABLE)) { + /* Timer disabled */ + trace_grlib_gptimer_disabled(timer->id, timer->config); + return; + } + + /* ptimer is triggered when the counter reach 0 but GPTimer is triggered at + underflow. Set count + 1 to simulate the GPTimer behavior. */ + + trace_grlib_gptimer_enable(timer->id, timer->counter + 1); + + ptimer_set_count(timer->ptimer, timer->counter + 1); + ptimer_run(timer->ptimer, 1); +} + +static void grlib_gptimer_restart(GPTimer *timer) +{ + assert(timer != NULL); + + trace_grlib_gptimer_restart(timer->id, timer->reload); + + timer->counter = timer->reload; + grlib_gptimer_enable(timer); +} + +static void grlib_gptimer_set_scaler(GPTimerUnit *unit, uint32_t scaler) +{ + int i = 0; + uint32_t value = 0; + + assert(unit != NULL); + + if (scaler > 0) { + value = unit->freq_hz / (scaler + 1); + } else { + value = unit->freq_hz; + } + + trace_grlib_gptimer_set_scaler(scaler, value); + + for (i = 0; i < unit->nr_timers; i++) { + ptimer_set_freq(unit->timers[i].ptimer, value); + } +} + +static void grlib_gptimer_hit(void *opaque) +{ + GPTimer *timer = opaque; + assert(timer != NULL); + + trace_grlib_gptimer_hit(timer->id); + + /* Timer expired */ + + if (timer->config & GPTIMER_INT_ENABLE) { + /* Set the pending bit (only unset by write in the config register) */ + timer->config |= GPTIMER_INT_PENDING; + qemu_irq_pulse(timer->irq); + } + + if (timer->config & GPTIMER_RESTART) { + grlib_gptimer_restart(timer); + } +} + +static uint32_t grlib_gptimer_readl(void *opaque, target_phys_addr_t addr) +{ + GPTimerUnit *unit = opaque; + target_phys_addr_t timer_addr; + int id; + uint32_t value = 0; + + addr &= 0xff; + + /* Unit registers */ + switch (addr) { + case SCALER_OFFSET: + trace_grlib_gptimer_readl(-1, "scaler:", unit->scaler); + return unit->scaler; + + case SCALER_RELOAD_OFFSET: + trace_grlib_gptimer_readl(-1, "reload:", unit->reload); + return unit->reload; + + case CONFIG_OFFSET: + trace_grlib_gptimer_readl(-1, "config:", unit->config); + return unit->config; + + default: + break; + } + + timer_addr = (addr % TIMER_BASE); + id = (addr - TIMER_BASE) / TIMER_BASE; + + if (id >= 0 && id < unit->nr_timers) { + + /* GPTimer registers */ + switch (timer_addr) { + case COUNTER_OFFSET: + value = ptimer_get_count(unit->timers[id].ptimer); + trace_grlib_gptimer_readl(id, "counter value:", value); + return value; + + case COUNTER_RELOAD_OFFSET: + value = unit->timers[id].reload; + trace_grlib_gptimer_readl(id, "reload value:", value); + return value; + + case CONFIG_OFFSET: + trace_grlib_gptimer_readl(id, "scaler value:", + unit->timers[id].config); + return unit->timers[id].config; + + default: + break; + } + + } + + trace_grlib_gptimer_unknown_register("read", addr); + return 0; +} + +static void +grlib_gptimer_writel(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + GPTimerUnit *unit = opaque; + target_phys_addr_t timer_addr; + int id; + + addr &= 0xff; + + /* Unit registers */ + switch (addr) { + case SCALER_OFFSET: + value &= 0xFFFF; /* clean up the value */ + unit->scaler = value; + trace_grlib_gptimer_writel(-1, "scaler:", unit->scaler); + return; + + case SCALER_RELOAD_OFFSET: + value &= 0xFFFF; /* clean up the value */ + unit->reload = value; + trace_grlib_gptimer_writel(-1, "reload:", unit->reload); + grlib_gptimer_set_scaler(unit, value); + return; + + case CONFIG_OFFSET: + /* Read Only (disable timer freeze not supported) */ + trace_grlib_gptimer_writel(-1, "config (Read Only):", 0); + return; + + default: + break; + } + + timer_addr = (addr % TIMER_BASE); + id = (addr - TIMER_BASE) / TIMER_BASE; + + if (id >= 0 && id < unit->nr_timers) { + + /* GPTimer registers */ + switch (timer_addr) { + case COUNTER_OFFSET: + trace_grlib_gptimer_writel(id, "counter:", value); + unit->timers[id].counter = value; + grlib_gptimer_enable(&unit->timers[id]); + return; + + case COUNTER_RELOAD_OFFSET: + trace_grlib_gptimer_writel(id, "reload:", value); + unit->timers[id].reload = value; + return; + + case CONFIG_OFFSET: + trace_grlib_gptimer_writel(id, "config:", value); + + if (value & GPTIMER_INT_PENDING) { + /* clear pending bit */ + value &= ~GPTIMER_INT_PENDING; + } else { + /* keep pending bit */ + value |= unit->timers[id].config & GPTIMER_INT_PENDING; + } + + unit->timers[id].config = value; + + /* gptimer_restart calls gptimer_enable, so if "enable" and "load" + bits are present, we just have to call restart. */ + + if (value & GPTIMER_LOAD) { + grlib_gptimer_restart(&unit->timers[id]); + } else if (value & GPTIMER_ENABLE) { + grlib_gptimer_enable(&unit->timers[id]); + } + + /* These fields must always be read as 0 */ + value &= ~(GPTIMER_LOAD & GPTIMER_DEBUG_HALT); + + unit->timers[id].config = value; + return; + + default: + break; + } + + } + + trace_grlib_gptimer_unknown_register("write", addr); +} + +static CPUReadMemoryFunc * const grlib_gptimer_read[] = { + NULL, NULL, grlib_gptimer_readl, +}; + +static CPUWriteMemoryFunc * const grlib_gptimer_write[] = { + NULL, NULL, grlib_gptimer_writel, +}; + +static void grlib_gptimer_reset(DeviceState *d) +{ + GPTimerUnit *unit = container_of(d, GPTimerUnit, busdev.qdev); + int i = 0; + + assert(unit != NULL); + + unit->scaler = 0; + unit->reload = 0; + unit->config = 0; + + unit->config = unit->nr_timers; + unit->config |= unit->irq_line << 3; + unit->config |= 1 << 8; /* separate interrupt */ + unit->config |= 1 << 9; /* Disable timer freeze */ + + + for (i = 0; i < unit->nr_timers; i++) { + GPTimer *timer = &unit->timers[i]; + + timer->counter = 0; + timer->reload = 0; + timer->config = 0; + ptimer_stop(timer->ptimer); + ptimer_set_count(timer->ptimer, 0); + ptimer_set_freq(timer->ptimer, unit->freq_hz); + } +} + +static int grlib_gptimer_init(SysBusDevice *dev) +{ + GPTimerUnit *unit = FROM_SYSBUS(typeof(*unit), dev); + unsigned int i; + int timer_regs; + + assert(unit->nr_timers > 0); + assert(unit->nr_timers <= GPTIMER_MAX_TIMERS); + + unit->timers = qemu_mallocz(sizeof unit->timers[0] * unit->nr_timers); + + for (i = 0; i < unit->nr_timers; i++) { + GPTimer *timer = &unit->timers[i]; + + timer->unit = unit; + timer->bh = qemu_bh_new(grlib_gptimer_hit, timer); + timer->ptimer = ptimer_init(timer->bh); + timer->id = i; + + /* One IRQ line for each timer */ + sysbus_init_irq(dev, &timer->irq); + + ptimer_set_freq(timer->ptimer, unit->freq_hz); + } + + timer_regs = cpu_register_io_memory(grlib_gptimer_read, + grlib_gptimer_write, + unit, DEVICE_NATIVE_ENDIAN); + if (timer_regs < 0) { + return -1; + } + + sysbus_init_mmio(dev, UNIT_REG_SIZE + GPTIMER_REG_SIZE * unit->nr_timers, + timer_regs); + return 0; +} + +static SysBusDeviceInfo grlib_gptimer_info = { + .init = grlib_gptimer_init, + .qdev.name = "grlib,gptimer", + .qdev.reset = grlib_gptimer_reset, + .qdev.size = sizeof(GPTimerUnit), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("frequency", GPTimerUnit, freq_hz, 40000000), + DEFINE_PROP_UINT32("irq-line", GPTimerUnit, irq_line, 8), + DEFINE_PROP_UINT32("nr-timers", GPTimerUnit, nr_timers, 2), + DEFINE_PROP_END_OF_LIST() + } +}; + +static void grlib_gptimer_register(void) +{ + sysbus_register_withprop(&grlib_gptimer_info); +} + +device_init(grlib_gptimer_register) |