|  | /* | 
|  | *  QEMU model of the LatticeMico32 timer block. | 
|  | * | 
|  | *  Copyright (c) 2010 Michael Walle <michael@walle.cc> | 
|  | * | 
|  | * This library is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU Lesser General Public | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2 of the License, or (at your option) any later version. | 
|  | * | 
|  | * This library is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | * Lesser General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU Lesser General Public | 
|  | * License along with this library; if not, see <http://www.gnu.org/licenses/>. | 
|  | * | 
|  | * | 
|  | * Specification available at: | 
|  | *   http://www.latticesemi.com/documents/mico32timer.pdf | 
|  | */ | 
|  |  | 
|  | #include "hw.h" | 
|  | #include "sysbus.h" | 
|  | #include "trace.h" | 
|  | #include "qemu/timer.h" | 
|  | #include "ptimer.h" | 
|  | #include "qemu/error-report.h" | 
|  |  | 
|  | #define DEFAULT_FREQUENCY (50*1000000) | 
|  |  | 
|  | enum { | 
|  | R_SR = 0, | 
|  | R_CR, | 
|  | R_PERIOD, | 
|  | R_SNAPSHOT, | 
|  | R_MAX | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | SR_TO    = (1 << 0), | 
|  | SR_RUN   = (1 << 1), | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | CR_ITO   = (1 << 0), | 
|  | CR_CONT  = (1 << 1), | 
|  | CR_START = (1 << 2), | 
|  | CR_STOP  = (1 << 3), | 
|  | }; | 
|  |  | 
|  | struct LM32TimerState { | 
|  | SysBusDevice busdev; | 
|  | MemoryRegion iomem; | 
|  |  | 
|  | QEMUBH *bh; | 
|  | ptimer_state *ptimer; | 
|  |  | 
|  | qemu_irq irq; | 
|  | uint32_t freq_hz; | 
|  |  | 
|  | uint32_t regs[R_MAX]; | 
|  | }; | 
|  | typedef struct LM32TimerState LM32TimerState; | 
|  |  | 
|  | static void timer_update_irq(LM32TimerState *s) | 
|  | { | 
|  | int state = (s->regs[R_SR] & SR_TO) && (s->regs[R_CR] & CR_ITO); | 
|  |  | 
|  | trace_lm32_timer_irq_state(state); | 
|  | qemu_set_irq(s->irq, state); | 
|  | } | 
|  |  | 
|  | static uint64_t timer_read(void *opaque, hwaddr addr, unsigned size) | 
|  | { | 
|  | LM32TimerState *s = opaque; | 
|  | uint32_t r = 0; | 
|  |  | 
|  | addr >>= 2; | 
|  | switch (addr) { | 
|  | case R_SR: | 
|  | case R_CR: | 
|  | case R_PERIOD: | 
|  | r = s->regs[addr]; | 
|  | break; | 
|  | case R_SNAPSHOT: | 
|  | r = (uint32_t)ptimer_get_count(s->ptimer); | 
|  | break; | 
|  | default: | 
|  | error_report("lm32_timer: read access to unknown register 0x" | 
|  | TARGET_FMT_plx, addr << 2); | 
|  | break; | 
|  | } | 
|  |  | 
|  | trace_lm32_timer_memory_read(addr << 2, r); | 
|  | return r; | 
|  | } | 
|  |  | 
|  | static void timer_write(void *opaque, hwaddr addr, | 
|  | uint64_t value, unsigned size) | 
|  | { | 
|  | LM32TimerState *s = opaque; | 
|  |  | 
|  | trace_lm32_timer_memory_write(addr, value); | 
|  |  | 
|  | addr >>= 2; | 
|  | switch (addr) { | 
|  | case R_SR: | 
|  | s->regs[R_SR] &= ~SR_TO; | 
|  | break; | 
|  | case R_CR: | 
|  | s->regs[R_CR] = value; | 
|  | if (s->regs[R_CR] & CR_START) { | 
|  | ptimer_run(s->ptimer, 1); | 
|  | } | 
|  | if (s->regs[R_CR] & CR_STOP) { | 
|  | ptimer_stop(s->ptimer); | 
|  | } | 
|  | break; | 
|  | case R_PERIOD: | 
|  | s->regs[R_PERIOD] = value; | 
|  | ptimer_set_count(s->ptimer, value); | 
|  | break; | 
|  | case R_SNAPSHOT: | 
|  | error_report("lm32_timer: write access to read only register 0x" | 
|  | TARGET_FMT_plx, addr << 2); | 
|  | break; | 
|  | default: | 
|  | error_report("lm32_timer: write access to unknown register 0x" | 
|  | TARGET_FMT_plx, addr << 2); | 
|  | break; | 
|  | } | 
|  | timer_update_irq(s); | 
|  | } | 
|  |  | 
|  | static const MemoryRegionOps timer_ops = { | 
|  | .read = timer_read, | 
|  | .write = timer_write, | 
|  | .endianness = DEVICE_NATIVE_ENDIAN, | 
|  | .valid = { | 
|  | .min_access_size = 4, | 
|  | .max_access_size = 4, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static void timer_hit(void *opaque) | 
|  | { | 
|  | LM32TimerState *s = opaque; | 
|  |  | 
|  | trace_lm32_timer_hit(); | 
|  |  | 
|  | s->regs[R_SR] |= SR_TO; | 
|  |  | 
|  | if (s->regs[R_CR] & CR_CONT) { | 
|  | ptimer_set_count(s->ptimer, s->regs[R_PERIOD]); | 
|  | ptimer_run(s->ptimer, 1); | 
|  | } | 
|  | timer_update_irq(s); | 
|  | } | 
|  |  | 
|  | static void timer_reset(DeviceState *d) | 
|  | { | 
|  | LM32TimerState *s = container_of(d, LM32TimerState, busdev.qdev); | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < R_MAX; i++) { | 
|  | s->regs[i] = 0; | 
|  | } | 
|  | ptimer_stop(s->ptimer); | 
|  | } | 
|  |  | 
|  | static int lm32_timer_init(SysBusDevice *dev) | 
|  | { | 
|  | LM32TimerState *s = FROM_SYSBUS(typeof(*s), dev); | 
|  |  | 
|  | sysbus_init_irq(dev, &s->irq); | 
|  |  | 
|  | s->bh = qemu_bh_new(timer_hit, s); | 
|  | s->ptimer = ptimer_init(s->bh); | 
|  | ptimer_set_freq(s->ptimer, s->freq_hz); | 
|  |  | 
|  | memory_region_init_io(&s->iomem, &timer_ops, s, "timer", R_MAX * 4); | 
|  | sysbus_init_mmio(dev, &s->iomem); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const VMStateDescription vmstate_lm32_timer = { | 
|  | .name = "lm32-timer", | 
|  | .version_id = 1, | 
|  | .minimum_version_id = 1, | 
|  | .minimum_version_id_old = 1, | 
|  | .fields      = (VMStateField[]) { | 
|  | VMSTATE_PTIMER(ptimer, LM32TimerState), | 
|  | VMSTATE_UINT32(freq_hz, LM32TimerState), | 
|  | VMSTATE_UINT32_ARRAY(regs, LM32TimerState, R_MAX), | 
|  | VMSTATE_END_OF_LIST() | 
|  | } | 
|  | }; | 
|  |  | 
|  | static Property lm32_timer_properties[] = { | 
|  | DEFINE_PROP_UINT32("frequency", LM32TimerState, freq_hz, DEFAULT_FREQUENCY), | 
|  | DEFINE_PROP_END_OF_LIST(), | 
|  | }; | 
|  |  | 
|  | static void lm32_timer_class_init(ObjectClass *klass, void *data) | 
|  | { | 
|  | DeviceClass *dc = DEVICE_CLASS(klass); | 
|  | SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); | 
|  |  | 
|  | k->init = lm32_timer_init; | 
|  | dc->reset = timer_reset; | 
|  | dc->vmsd = &vmstate_lm32_timer; | 
|  | dc->props = lm32_timer_properties; | 
|  | } | 
|  |  | 
|  | static const TypeInfo lm32_timer_info = { | 
|  | .name          = "lm32-timer", | 
|  | .parent        = TYPE_SYS_BUS_DEVICE, | 
|  | .instance_size = sizeof(LM32TimerState), | 
|  | .class_init    = lm32_timer_class_init, | 
|  | }; | 
|  |  | 
|  | static void lm32_timer_register_types(void) | 
|  | { | 
|  | type_register_static(&lm32_timer_info); | 
|  | } | 
|  |  | 
|  | type_init(lm32_timer_register_types) |