| /* |
| * QEMU GRLIB GPTimer Emulator |
| * |
| * SPDX-License-Identifier: MIT |
| * |
| * Copyright (c) 2010-2024 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 "qemu/osdep.h" |
| #include "hw/timer/grlib_gptimer.h" |
| #include "hw/sysbus.h" |
| #include "qemu/timer.h" |
| #include "hw/irq.h" |
| #include "hw/ptimer.h" |
| #include "hw/qdev-properties.h" |
| #include "qemu/module.h" |
| |
| #include "trace.h" |
| #include "qom/object.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 |
| |
| OBJECT_DECLARE_SIMPLE_TYPE(GPTimerUnit, GRLIB_GPTIMER) |
| |
| typedef struct GPTimer GPTimer; |
| |
| struct GPTimer { |
| struct ptimer_state *ptimer; |
| |
| qemu_irq irq; |
| int id; |
| GPTimerUnit *unit; |
| |
| /* registers */ |
| uint32_t counter; |
| uint32_t reload; |
| uint32_t config; |
| }; |
| |
| struct GPTimerUnit { |
| SysBusDevice parent_obj; |
| |
| MemoryRegion iomem; |
| |
| 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_tx_begin(GPTimer *timer) |
| { |
| ptimer_transaction_begin(timer->ptimer); |
| } |
| |
| static void grlib_gptimer_tx_commit(GPTimer *timer) |
| { |
| ptimer_transaction_commit(timer->ptimer); |
| } |
| |
| /* Must be called within grlib_gptimer_tx_begin/commit block */ |
| 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); |
| |
| ptimer_set_count(timer->ptimer, (uint64_t)timer->counter + 1); |
| ptimer_run(timer->ptimer, 1); |
| } |
| |
| /* Must be called within grlib_gptimer_tx_begin/commit block */ |
| 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_transaction_begin(unit->timers[i].ptimer); |
| ptimer_set_freq(unit->timers[i].ptimer, value); |
| ptimer_transaction_commit(unit->timers[i].ptimer); |
| } |
| } |
| |
| 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 uint64_t grlib_gptimer_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| GPTimerUnit *unit = opaque; |
| hwaddr timer_addr; |
| int id; |
| uint32_t value = 0; |
| |
| addr &= 0xff; |
| |
| /* Unit registers */ |
| switch (addr) { |
| case SCALER_OFFSET: |
| trace_grlib_gptimer_readl(-1, addr, unit->scaler); |
| return unit->scaler; |
| |
| case SCALER_RELOAD_OFFSET: |
| trace_grlib_gptimer_readl(-1, addr, unit->reload); |
| return unit->reload; |
| |
| case CONFIG_OFFSET: |
| trace_grlib_gptimer_readl(-1, addr, 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, addr, value); |
| return value; |
| |
| case COUNTER_RELOAD_OFFSET: |
| value = unit->timers[id].reload; |
| trace_grlib_gptimer_readl(id, addr, value); |
| return value; |
| |
| case CONFIG_OFFSET: |
| trace_grlib_gptimer_readl(id, addr, unit->timers[id].config); |
| return unit->timers[id].config; |
| |
| default: |
| break; |
| } |
| |
| } |
| |
| trace_grlib_gptimer_readl(-1, addr, 0); |
| return 0; |
| } |
| |
| static void grlib_gptimer_write(void *opaque, hwaddr addr, |
| uint64_t value, unsigned size) |
| { |
| GPTimerUnit *unit = opaque; |
| hwaddr 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, addr, unit->scaler); |
| return; |
| |
| case SCALER_RELOAD_OFFSET: |
| value &= 0xFFFF; /* clean up the value */ |
| unit->reload = value; |
| trace_grlib_gptimer_writel(-1, addr, unit->reload); |
| grlib_gptimer_set_scaler(unit, value); |
| return; |
| |
| case CONFIG_OFFSET: |
| /* Read Only (disable timer freeze not supported) */ |
| trace_grlib_gptimer_writel(-1, addr, 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, addr, value); |
| grlib_gptimer_tx_begin(&unit->timers[id]); |
| unit->timers[id].counter = value; |
| grlib_gptimer_enable(&unit->timers[id]); |
| grlib_gptimer_tx_commit(&unit->timers[id]); |
| return; |
| |
| case COUNTER_RELOAD_OFFSET: |
| trace_grlib_gptimer_writel(id, addr, value); |
| unit->timers[id].reload = value; |
| return; |
| |
| case CONFIG_OFFSET: |
| trace_grlib_gptimer_writel(id, addr, 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. */ |
| |
| grlib_gptimer_tx_begin(&unit->timers[id]); |
| 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; |
| grlib_gptimer_tx_commit(&unit->timers[id]); |
| return; |
| |
| default: |
| break; |
| } |
| |
| } |
| |
| trace_grlib_gptimer_writel(-1, addr, value); |
| } |
| |
| static const MemoryRegionOps grlib_gptimer_ops = { |
| .read = grlib_gptimer_read, |
| .write = grlib_gptimer_write, |
| .endianness = DEVICE_NATIVE_ENDIAN, |
| .valid = { |
| .min_access_size = 4, |
| .max_access_size = 4, |
| }, |
| }; |
| |
| static void grlib_gptimer_reset(DeviceState *d) |
| { |
| GPTimerUnit *unit = GRLIB_GPTIMER(d); |
| int i = 0; |
| |
| assert(unit != NULL); |
| |
| unit->scaler = 0; |
| unit->reload = 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_transaction_begin(timer->ptimer); |
| ptimer_stop(timer->ptimer); |
| ptimer_set_count(timer->ptimer, 0); |
| ptimer_set_freq(timer->ptimer, unit->freq_hz); |
| ptimer_transaction_commit(timer->ptimer); |
| } |
| } |
| |
| static void grlib_gptimer_realize(DeviceState *dev, Error **errp) |
| { |
| GPTimerUnit *unit = GRLIB_GPTIMER(dev); |
| unsigned int i; |
| SysBusDevice *sbd = SYS_BUS_DEVICE(dev); |
| |
| assert(unit->nr_timers > 0); |
| assert(unit->nr_timers <= GPTIMER_MAX_TIMERS); |
| |
| unit->timers = g_malloc0(sizeof unit->timers[0] * unit->nr_timers); |
| |
| for (i = 0; i < unit->nr_timers; i++) { |
| GPTimer *timer = &unit->timers[i]; |
| |
| timer->unit = unit; |
| timer->ptimer = ptimer_init(grlib_gptimer_hit, timer, |
| PTIMER_POLICY_LEGACY); |
| timer->id = i; |
| |
| /* One IRQ line for each timer */ |
| sysbus_init_irq(sbd, &timer->irq); |
| |
| ptimer_transaction_begin(timer->ptimer); |
| ptimer_set_freq(timer->ptimer, unit->freq_hz); |
| ptimer_transaction_commit(timer->ptimer); |
| } |
| |
| memory_region_init_io(&unit->iomem, OBJECT(unit), &grlib_gptimer_ops, |
| unit, "gptimer", |
| UNIT_REG_SIZE + GPTIMER_REG_SIZE * unit->nr_timers); |
| |
| sysbus_init_mmio(sbd, &unit->iomem); |
| } |
| |
| static Property grlib_gptimer_properties[] = { |
| 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_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->realize = grlib_gptimer_realize; |
| device_class_set_legacy_reset(dc, grlib_gptimer_reset); |
| device_class_set_props(dc, grlib_gptimer_properties); |
| } |
| |
| static const TypeInfo grlib_gptimer_info = { |
| .name = TYPE_GRLIB_GPTIMER, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(GPTimerUnit), |
| .class_init = grlib_gptimer_class_init, |
| }; |
| |
| static void grlib_gptimer_register_types(void) |
| { |
| type_register_static(&grlib_gptimer_info); |
| } |
| |
| type_init(grlib_gptimer_register_types) |