diff options
Diffstat (limited to 'hw/timer')
-rw-r--r-- | hw/timer/Makefile.objs | 10 | ||||
-rw-r--r-- | hw/timer/arm_timer.c | 399 | ||||
-rw-r--r-- | hw/timer/cadence_ttc.c | 489 | ||||
-rw-r--r-- | hw/timer/ds1338.c | 236 | ||||
-rw-r--r-- | hw/timer/hpet.c | 760 | ||||
-rw-r--r-- | hw/timer/i8254.c | 362 | ||||
-rw-r--r-- | hw/timer/i8254_common.c | 311 | ||||
-rw-r--r-- | hw/timer/m48t59.c | 778 | ||||
-rw-r--r-- | hw/timer/pl031.c | 265 | ||||
-rw-r--r-- | hw/timer/puv3_ost.c | 151 | ||||
-rw-r--r-- | hw/timer/twl92230.c | 882 | ||||
-rw-r--r-- | hw/timer/xilinx_timer.c | 255 |
12 files changed, 4898 insertions, 0 deletions
diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs index e69de29bb2..12781dd2a3 100644 --- a/hw/timer/Makefile.objs +++ b/hw/timer/Makefile.objs @@ -0,0 +1,10 @@ +common-obj-$(CONFIG_ARM_TIMER) += arm_timer.o +common-obj-$(CONFIG_CADENCE) += cadence_ttc.o +common-obj-$(CONFIG_DS1338) += ds1338.o +common-obj-$(CONFIG_HPET) += hpet.o +common-obj-$(CONFIG_I8254) += i8254_common.o i8254.o +common-obj-$(CONFIG_M48T59) += m48t59.o +common-obj-$(CONFIG_PL031) += pl031.o +common-obj-$(CONFIG_PUV3) += puv3_ost.o +common-obj-$(CONFIG_TWL92230) += twl92230.o +common-obj-$(CONFIG_XILINX) += xilinx_timer.o diff --git a/hw/timer/arm_timer.c b/hw/timer/arm_timer.c new file mode 100644 index 0000000000..644987046a --- /dev/null +++ b/hw/timer/arm_timer.c @@ -0,0 +1,399 @@ +/* + * ARM PrimeCell Timer modules. + * + * Copyright (c) 2005-2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +#include "hw/sysbus.h" +#include "qemu/timer.h" +#include "qemu-common.h" +#include "hw/qdev.h" +#include "hw/ptimer.h" + +/* Common timer implementation. */ + +#define TIMER_CTRL_ONESHOT (1 << 0) +#define TIMER_CTRL_32BIT (1 << 1) +#define TIMER_CTRL_DIV1 (0 << 2) +#define TIMER_CTRL_DIV16 (1 << 2) +#define TIMER_CTRL_DIV256 (2 << 2) +#define TIMER_CTRL_IE (1 << 5) +#define TIMER_CTRL_PERIODIC (1 << 6) +#define TIMER_CTRL_ENABLE (1 << 7) + +typedef struct { + ptimer_state *timer; + uint32_t control; + uint32_t limit; + int freq; + int int_level; + qemu_irq irq; +} arm_timer_state; + +/* Check all active timers, and schedule the next timer interrupt. */ + +static void arm_timer_update(arm_timer_state *s) +{ + /* Update interrupts. */ + if (s->int_level && (s->control & TIMER_CTRL_IE)) { + qemu_irq_raise(s->irq); + } else { + qemu_irq_lower(s->irq); + } +} + +static uint32_t arm_timer_read(void *opaque, hwaddr offset) +{ + arm_timer_state *s = (arm_timer_state *)opaque; + + switch (offset >> 2) { + case 0: /* TimerLoad */ + case 6: /* TimerBGLoad */ + return s->limit; + case 1: /* TimerValue */ + return ptimer_get_count(s->timer); + case 2: /* TimerControl */ + return s->control; + case 4: /* TimerRIS */ + return s->int_level; + case 5: /* TimerMIS */ + if ((s->control & TIMER_CTRL_IE) == 0) + return 0; + return s->int_level; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset %x\n", __func__, (int)offset); + return 0; + } +} + +/* Reset the timer limit after settings have changed. */ +static void arm_timer_recalibrate(arm_timer_state *s, int reload) +{ + uint32_t limit; + + if ((s->control & (TIMER_CTRL_PERIODIC | TIMER_CTRL_ONESHOT)) == 0) { + /* Free running. */ + if (s->control & TIMER_CTRL_32BIT) + limit = 0xffffffff; + else + limit = 0xffff; + } else { + /* Periodic. */ + limit = s->limit; + } + ptimer_set_limit(s->timer, limit, reload); +} + +static void arm_timer_write(void *opaque, hwaddr offset, + uint32_t value) +{ + arm_timer_state *s = (arm_timer_state *)opaque; + int freq; + + switch (offset >> 2) { + case 0: /* TimerLoad */ + s->limit = value; + arm_timer_recalibrate(s, 1); + break; + case 1: /* TimerValue */ + /* ??? Linux seems to want to write to this readonly register. + Ignore it. */ + break; + case 2: /* TimerControl */ + if (s->control & TIMER_CTRL_ENABLE) { + /* Pause the timer if it is running. This may cause some + inaccuracy dure to rounding, but avoids a whole lot of other + messyness. */ + ptimer_stop(s->timer); + } + s->control = value; + freq = s->freq; + /* ??? Need to recalculate expiry time after changing divisor. */ + switch ((value >> 2) & 3) { + case 1: freq >>= 4; break; + case 2: freq >>= 8; break; + } + arm_timer_recalibrate(s, s->control & TIMER_CTRL_ENABLE); + ptimer_set_freq(s->timer, freq); + if (s->control & TIMER_CTRL_ENABLE) { + /* Restart the timer if still enabled. */ + ptimer_run(s->timer, (s->control & TIMER_CTRL_ONESHOT) != 0); + } + break; + case 3: /* TimerIntClr */ + s->int_level = 0; + break; + case 6: /* TimerBGLoad */ + s->limit = value; + arm_timer_recalibrate(s, 0); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset %x\n", __func__, (int)offset); + } + arm_timer_update(s); +} + +static void arm_timer_tick(void *opaque) +{ + arm_timer_state *s = (arm_timer_state *)opaque; + s->int_level = 1; + arm_timer_update(s); +} + +static const VMStateDescription vmstate_arm_timer = { + .name = "arm_timer", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(control, arm_timer_state), + VMSTATE_UINT32(limit, arm_timer_state), + VMSTATE_INT32(int_level, arm_timer_state), + VMSTATE_PTIMER(timer, arm_timer_state), + VMSTATE_END_OF_LIST() + } +}; + +static arm_timer_state *arm_timer_init(uint32_t freq) +{ + arm_timer_state *s; + QEMUBH *bh; + + s = (arm_timer_state *)g_malloc0(sizeof(arm_timer_state)); + s->freq = freq; + s->control = TIMER_CTRL_IE; + + bh = qemu_bh_new(arm_timer_tick, s); + s->timer = ptimer_init(bh); + vmstate_register(NULL, -1, &vmstate_arm_timer, s); + return s; +} + +/* ARM PrimeCell SP804 dual timer module. + * Docs at + * http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0271d/index.html +*/ + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + arm_timer_state *timer[2]; + uint32_t freq0, freq1; + int level[2]; + qemu_irq irq; +} sp804_state; + +static const uint8_t sp804_ids[] = { + /* Timer ID */ + 0x04, 0x18, 0x14, 0, + /* PrimeCell ID */ + 0xd, 0xf0, 0x05, 0xb1 +}; + +/* Merge the IRQs from the two component devices. */ +static void sp804_set_irq(void *opaque, int irq, int level) +{ + sp804_state *s = (sp804_state *)opaque; + + s->level[irq] = level; + qemu_set_irq(s->irq, s->level[0] || s->level[1]); +} + +static uint64_t sp804_read(void *opaque, hwaddr offset, + unsigned size) +{ + sp804_state *s = (sp804_state *)opaque; + + if (offset < 0x20) { + return arm_timer_read(s->timer[0], offset); + } + if (offset < 0x40) { + return arm_timer_read(s->timer[1], offset - 0x20); + } + + /* TimerPeriphID */ + if (offset >= 0xfe0 && offset <= 0xffc) { + return sp804_ids[(offset - 0xfe0) >> 2]; + } + + switch (offset) { + /* Integration Test control registers, which we won't support */ + case 0xf00: /* TimerITCR */ + case 0xf04: /* TimerITOP (strictly write only but..) */ + qemu_log_mask(LOG_UNIMP, + "%s: integration test registers unimplemented\n", + __func__); + return 0; + } + + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset %x\n", __func__, (int)offset); + return 0; +} + +static void sp804_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + sp804_state *s = (sp804_state *)opaque; + + if (offset < 0x20) { + arm_timer_write(s->timer[0], offset, value); + return; + } + + if (offset < 0x40) { + arm_timer_write(s->timer[1], offset - 0x20, value); + return; + } + + /* Technically we could be writing to the Test Registers, but not likely */ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %x\n", + __func__, (int)offset); +} + +static const MemoryRegionOps sp804_ops = { + .read = sp804_read, + .write = sp804_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_sp804 = { + .name = "sp804", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_INT32_ARRAY(level, sp804_state, 2), + VMSTATE_END_OF_LIST() + } +}; + +static int sp804_init(SysBusDevice *dev) +{ + sp804_state *s = FROM_SYSBUS(sp804_state, dev); + qemu_irq *qi; + + qi = qemu_allocate_irqs(sp804_set_irq, s, 2); + sysbus_init_irq(dev, &s->irq); + s->timer[0] = arm_timer_init(s->freq0); + s->timer[1] = arm_timer_init(s->freq1); + s->timer[0]->irq = qi[0]; + s->timer[1]->irq = qi[1]; + memory_region_init_io(&s->iomem, &sp804_ops, s, "sp804", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + vmstate_register(&dev->qdev, -1, &vmstate_sp804, s); + return 0; +} + +/* Integrator/CP timer module. */ + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + arm_timer_state *timer[3]; +} icp_pit_state; + +static uint64_t icp_pit_read(void *opaque, hwaddr offset, + unsigned size) +{ + icp_pit_state *s = (icp_pit_state *)opaque; + int n; + + /* ??? Don't know the PrimeCell ID for this device. */ + n = offset >> 8; + if (n > 2) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad timer %d\n", __func__, n); + } + + return arm_timer_read(s->timer[n], offset & 0xff); +} + +static void icp_pit_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + icp_pit_state *s = (icp_pit_state *)opaque; + int n; + + n = offset >> 8; + if (n > 2) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad timer %d\n", __func__, n); + } + + arm_timer_write(s->timer[n], offset & 0xff, value); +} + +static const MemoryRegionOps icp_pit_ops = { + .read = icp_pit_read, + .write = icp_pit_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int icp_pit_init(SysBusDevice *dev) +{ + icp_pit_state *s = FROM_SYSBUS(icp_pit_state, dev); + + /* Timer 0 runs at the system clock speed (40MHz). */ + s->timer[0] = arm_timer_init(40000000); + /* The other two timers run at 1MHz. */ + s->timer[1] = arm_timer_init(1000000); + s->timer[2] = arm_timer_init(1000000); + + sysbus_init_irq(dev, &s->timer[0]->irq); + sysbus_init_irq(dev, &s->timer[1]->irq); + sysbus_init_irq(dev, &s->timer[2]->irq); + + memory_region_init_io(&s->iomem, &icp_pit_ops, s, "icp_pit", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + /* This device has no state to save/restore. The component timers will + save themselves. */ + return 0; +} + +static void icp_pit_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = icp_pit_init; +} + +static const TypeInfo icp_pit_info = { + .name = "integrator_pit", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(icp_pit_state), + .class_init = icp_pit_class_init, +}; + +static Property sp804_properties[] = { + DEFINE_PROP_UINT32("freq0", sp804_state, freq0, 1000000), + DEFINE_PROP_UINT32("freq1", sp804_state, freq1, 1000000), + DEFINE_PROP_END_OF_LIST(), +}; + +static void sp804_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + DeviceClass *k = DEVICE_CLASS(klass); + + sdc->init = sp804_init; + k->props = sp804_properties; +} + +static const TypeInfo sp804_info = { + .name = "sp804", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(sp804_state), + .class_init = sp804_class_init, +}; + +static void arm_timer_register_types(void) +{ + type_register_static(&icp_pit_info); + type_register_static(&sp804_info); +} + +type_init(arm_timer_register_types) diff --git a/hw/timer/cadence_ttc.c b/hw/timer/cadence_ttc.c new file mode 100644 index 0000000000..ba584f4719 --- /dev/null +++ b/hw/timer/cadence_ttc.c @@ -0,0 +1,489 @@ +/* + * Xilinx Zynq cadence TTC model + * + * Copyright (c) 2011 Xilinx Inc. + * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com) + * Copyright (c) 2012 PetaLogix Pty Ltd. + * Written By Haibing Ma + * M. Habib + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/sysbus.h" +#include "qemu/timer.h" + +#ifdef CADENCE_TTC_ERR_DEBUG +#define DB_PRINT(...) do { \ + fprintf(stderr, ": %s: ", __func__); \ + fprintf(stderr, ## __VA_ARGS__); \ + } while (0); +#else + #define DB_PRINT(...) +#endif + +#define COUNTER_INTR_IV 0x00000001 +#define COUNTER_INTR_M1 0x00000002 +#define COUNTER_INTR_M2 0x00000004 +#define COUNTER_INTR_M3 0x00000008 +#define COUNTER_INTR_OV 0x00000010 +#define COUNTER_INTR_EV 0x00000020 + +#define COUNTER_CTRL_DIS 0x00000001 +#define COUNTER_CTRL_INT 0x00000002 +#define COUNTER_CTRL_DEC 0x00000004 +#define COUNTER_CTRL_MATCH 0x00000008 +#define COUNTER_CTRL_RST 0x00000010 + +#define CLOCK_CTRL_PS_EN 0x00000001 +#define CLOCK_CTRL_PS_V 0x0000001e + +typedef struct { + QEMUTimer *timer; + int freq; + + uint32_t reg_clock; + uint32_t reg_count; + uint32_t reg_value; + uint16_t reg_interval; + uint16_t reg_match[3]; + uint32_t reg_intr; + uint32_t reg_intr_en; + uint32_t reg_event_ctrl; + uint32_t reg_event; + + uint64_t cpu_time; + unsigned int cpu_time_valid; + + qemu_irq irq; +} CadenceTimerState; + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + CadenceTimerState timer[3]; +} CadenceTTCState; + +static void cadence_timer_update(CadenceTimerState *s) +{ + qemu_set_irq(s->irq, !!(s->reg_intr & s->reg_intr_en)); +} + +static CadenceTimerState *cadence_timer_from_addr(void *opaque, + hwaddr offset) +{ + unsigned int index; + CadenceTTCState *s = (CadenceTTCState *)opaque; + + index = (offset >> 2) % 3; + + return &s->timer[index]; +} + +static uint64_t cadence_timer_get_ns(CadenceTimerState *s, uint64_t timer_steps) +{ + /* timer_steps has max value of 0x100000000. double check it + * (or overflow can happen below) */ + assert(timer_steps <= 1ULL << 32); + + uint64_t r = timer_steps * 1000000000ULL; + if (s->reg_clock & CLOCK_CTRL_PS_EN) { + r >>= 16 - (((s->reg_clock & CLOCK_CTRL_PS_V) >> 1) + 1); + } else { + r >>= 16; + } + r /= (uint64_t)s->freq; + return r; +} + +static uint64_t cadence_timer_get_steps(CadenceTimerState *s, uint64_t ns) +{ + uint64_t to_divide = 1000000000ULL; + + uint64_t r = ns; + /* for very large intervals (> 8s) do some division first to stop + * overflow (costs some prescision) */ + while (r >= 8ULL << 30 && to_divide > 1) { + r /= 1000; + to_divide /= 1000; + } + r <<= 16; + /* keep early-dividing as needed */ + while (r >= 8ULL << 30 && to_divide > 1) { + r /= 1000; + to_divide /= 1000; + } + r *= (uint64_t)s->freq; + if (s->reg_clock & CLOCK_CTRL_PS_EN) { + r /= 1 << (((s->reg_clock & CLOCK_CTRL_PS_V) >> 1) + 1); + } + + r /= to_divide; + return r; +} + +/* determine if x is in between a and b, exclusive of a, inclusive of b */ + +static inline int64_t is_between(int64_t x, int64_t a, int64_t b) +{ + if (a < b) { + return x > a && x <= b; + } + return x < a && x >= b; +} + +static void cadence_timer_run(CadenceTimerState *s) +{ + int i; + int64_t event_interval, next_value; + + assert(s->cpu_time_valid); /* cadence_timer_sync must be called first */ + + if (s->reg_count & COUNTER_CTRL_DIS) { + s->cpu_time_valid = 0; + return; + } + + { /* figure out what's going to happen next (rollover or match) */ + int64_t interval = (uint64_t)((s->reg_count & COUNTER_CTRL_INT) ? + (int64_t)s->reg_interval + 1 : 0x10000ULL) << 16; + next_value = (s->reg_count & COUNTER_CTRL_DEC) ? -1ULL : interval; + for (i = 0; i < 3; ++i) { + int64_t cand = (uint64_t)s->reg_match[i] << 16; + if (is_between(cand, (uint64_t)s->reg_value, next_value)) { + next_value = cand; + } + } + } + DB_PRINT("next timer event value: %09llx\n", + (unsigned long long)next_value); + + event_interval = next_value - (int64_t)s->reg_value; + event_interval = (event_interval < 0) ? -event_interval : event_interval; + + qemu_mod_timer(s->timer, s->cpu_time + + cadence_timer_get_ns(s, event_interval)); +} + +static void cadence_timer_sync(CadenceTimerState *s) +{ + int i; + int64_t r, x; + int64_t interval = ((s->reg_count & COUNTER_CTRL_INT) ? + (int64_t)s->reg_interval + 1 : 0x10000ULL) << 16; + uint64_t old_time = s->cpu_time; + + s->cpu_time = qemu_get_clock_ns(vm_clock); + DB_PRINT("cpu time: %lld ns\n", (long long)old_time); + + if (!s->cpu_time_valid || old_time == s->cpu_time) { + s->cpu_time_valid = 1; + return; + } + + r = (int64_t)cadence_timer_get_steps(s, s->cpu_time - old_time); + x = (int64_t)s->reg_value + ((s->reg_count & COUNTER_CTRL_DEC) ? -r : r); + + for (i = 0; i < 3; ++i) { + int64_t m = (int64_t)s->reg_match[i] << 16; + if (m > interval) { + continue; + } + /* check to see if match event has occurred. check m +/- interval + * to account for match events in wrap around cases */ + if (is_between(m, s->reg_value, x) || + is_between(m + interval, s->reg_value, x) || + is_between(m - interval, s->reg_value, x)) { + s->reg_intr |= (2 << i); + } + } + while (x < 0) { + x += interval; + } + s->reg_value = (uint32_t)(x % interval); + + if (s->reg_value != x) { + s->reg_intr |= (s->reg_count & COUNTER_CTRL_INT) ? + COUNTER_INTR_IV : COUNTER_INTR_OV; + } + cadence_timer_update(s); +} + +static void cadence_timer_tick(void *opaque) +{ + CadenceTimerState *s = opaque; + + DB_PRINT("\n"); + cadence_timer_sync(s); + cadence_timer_run(s); +} + +static uint32_t cadence_ttc_read_imp(void *opaque, hwaddr offset) +{ + CadenceTimerState *s = cadence_timer_from_addr(opaque, offset); + uint32_t value; + + cadence_timer_sync(s); + cadence_timer_run(s); + + switch (offset) { + case 0x00: /* clock control */ + case 0x04: + case 0x08: + return s->reg_clock; + + case 0x0c: /* counter control */ + case 0x10: + case 0x14: + return s->reg_count; + + case 0x18: /* counter value */ + case 0x1c: + case 0x20: + return (uint16_t)(s->reg_value >> 16); + + case 0x24: /* reg_interval counter */ + case 0x28: + case 0x2c: + return s->reg_interval; + + case 0x30: /* match 1 counter */ + case 0x34: + case 0x38: + return s->reg_match[0]; + + case 0x3c: /* match 2 counter */ + case 0x40: + case 0x44: + return s->reg_match[1]; + + case 0x48: /* match 3 counter */ + case 0x4c: + case 0x50: + return s->reg_match[2]; + + case 0x54: /* interrupt register */ + case 0x58: + case 0x5c: + /* cleared after read */ + value = s->reg_intr; + s->reg_intr = 0; + cadence_timer_update(s); + return value; + + case 0x60: /* interrupt enable */ + case 0x64: + case 0x68: + return s->reg_intr_en; + + case 0x6c: + case 0x70: + case 0x74: + return s->reg_event_ctrl; + + case 0x78: + case 0x7c: + case 0x80: + return s->reg_event; + + default: + return 0; + } +} + +static uint64_t cadence_ttc_read(void *opaque, hwaddr offset, + unsigned size) +{ + uint32_t ret = cadence_ttc_read_imp(opaque, offset); + + DB_PRINT("addr: %08x data: %08x\n", (unsigned)offset, (unsigned)ret); + return ret; +} + +static void cadence_ttc_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + CadenceTimerState *s = cadence_timer_from_addr(opaque, offset); + + DB_PRINT("addr: %08x data %08x\n", (unsigned)offset, (unsigned)value); + + cadence_timer_sync(s); + + switch (offset) { + case 0x00: /* clock control */ + case 0x04: + case 0x08: + s->reg_clock = value & 0x3F; + break; + + case 0x0c: /* counter control */ + case 0x10: + case 0x14: + if (value & COUNTER_CTRL_RST) { + s->reg_value = 0; + } + s->reg_count = value & 0x3f & ~COUNTER_CTRL_RST; + break; + + case 0x24: /* interval register */ + case 0x28: + case 0x2c: + s->reg_interval = value & 0xffff; + break; + + case 0x30: /* match register */ + case 0x34: + case 0x38: + s->reg_match[0] = value & 0xffff; + + case 0x3c: /* match register */ + case 0x40: + case 0x44: + s->reg_match[1] = value & 0xffff; + + case 0x48: /* match register */ + case 0x4c: + case 0x50: + s->reg_match[2] = value & 0xffff; + break; + + case 0x54: /* interrupt register */ + case 0x58: + case 0x5c: + break; + + case 0x60: /* interrupt enable */ + case 0x64: + case 0x68: + s->reg_intr_en = value & 0x3f; + break; + + case 0x6c: /* event control */ + case 0x70: + case 0x74: + s->reg_event_ctrl = value & 0x07; + break; + + default: + return; + } + + cadence_timer_run(s); + cadence_timer_update(s); +} + +static const MemoryRegionOps cadence_ttc_ops = { + .read = cadence_ttc_read, + .write = cadence_ttc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void cadence_timer_reset(CadenceTimerState *s) +{ + s->reg_count = 0x21; +} + +static void cadence_timer_init(uint32_t freq, CadenceTimerState *s) +{ + memset(s, 0, sizeof(CadenceTimerState)); + s->freq = freq; + + cadence_timer_reset(s); + + s->timer = qemu_new_timer_ns(vm_clock, cadence_timer_tick, s); +} + +static int cadence_ttc_init(SysBusDevice *dev) +{ + CadenceTTCState *s = FROM_SYSBUS(CadenceTTCState, dev); + int i; + + for (i = 0; i < 3; ++i) { + cadence_timer_init(133000000, &s->timer[i]); + sysbus_init_irq(dev, &s->timer[i].irq); + } + + memory_region_init_io(&s->iomem, &cadence_ttc_ops, s, "timer", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static void cadence_timer_pre_save(void *opaque) +{ + cadence_timer_sync((CadenceTimerState *)opaque); +} + +static int cadence_timer_post_load(void *opaque, int version_id) +{ + CadenceTimerState *s = opaque; + + s->cpu_time_valid = 0; + cadence_timer_sync(s); + cadence_timer_run(s); + cadence_timer_update(s); + return 0; +} + +static const VMStateDescription vmstate_cadence_timer = { + .name = "cadence_timer", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .pre_save = cadence_timer_pre_save, + .post_load = cadence_timer_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32(reg_clock, CadenceTimerState), + VMSTATE_UINT32(reg_count, CadenceTimerState), + VMSTATE_UINT32(reg_value, CadenceTimerState), + VMSTATE_UINT16(reg_interval, CadenceTimerState), + VMSTATE_UINT16_ARRAY(reg_match, CadenceTimerState, 3), + VMSTATE_UINT32(reg_intr, CadenceTimerState), + VMSTATE_UINT32(reg_intr_en, CadenceTimerState), + VMSTATE_UINT32(reg_event_ctrl, CadenceTimerState), + VMSTATE_UINT32(reg_event, CadenceTimerState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_cadence_ttc = { + .name = "cadence_TTC", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT_ARRAY(timer, CadenceTTCState, 3, 0, + vmstate_cadence_timer, + CadenceTimerState), + VMSTATE_END_OF_LIST() + } +}; + +static void cadence_ttc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = cadence_ttc_init; + dc->vmsd = &vmstate_cadence_ttc; +} + +static const TypeInfo cadence_ttc_info = { + .name = "cadence_ttc", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(CadenceTTCState), + .class_init = cadence_ttc_class_init, +}; + +static void cadence_ttc_register_types(void) +{ + type_register_static(&cadence_ttc_info); +} + +type_init(cadence_ttc_register_types) diff --git a/hw/timer/ds1338.c b/hw/timer/ds1338.c new file mode 100644 index 0000000000..8987cdc9e0 --- /dev/null +++ b/hw/timer/ds1338.c @@ -0,0 +1,236 @@ +/* + * MAXIM DS1338 I2C RTC+NVRAM + * + * Copyright (c) 2009 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "hw/i2c/i2c.h" + +/* Size of NVRAM including both the user-accessible area and the + * secondary register area. + */ +#define NVRAM_SIZE 64 + +/* Flags definitions */ +#define SECONDS_CH 0x80 +#define HOURS_12 0x40 +#define HOURS_PM 0x20 +#define CTRL_OSF 0x20 + +typedef struct { + I2CSlave i2c; + int64_t offset; + uint8_t wday_offset; + uint8_t nvram[NVRAM_SIZE]; + int32_t ptr; + bool addr_byte; +} DS1338State; + +static const VMStateDescription vmstate_ds1338 = { + .name = "ds1338", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_I2C_SLAVE(i2c, DS1338State), + VMSTATE_INT64(offset, DS1338State), + VMSTATE_UINT8_V(wday_offset, DS1338State, 2), + VMSTATE_UINT8_ARRAY(nvram, DS1338State, NVRAM_SIZE), + VMSTATE_INT32(ptr, DS1338State), + VMSTATE_BOOL(addr_byte, DS1338State), + VMSTATE_END_OF_LIST() + } +}; + +static void capture_current_time(DS1338State *s) +{ + /* Capture the current time into the secondary registers + * which will be actually read by the data transfer operation. + */ + struct tm now; + qemu_get_timedate(&now, s->offset); + s->nvram[0] = to_bcd(now.tm_sec); + s->nvram[1] = to_bcd(now.tm_min); + if (s->nvram[2] & HOURS_12) { + int tmp = now.tm_hour; + if (tmp % 12 == 0) { + tmp += 12; + } + if (tmp <= 12) { + s->nvram[2] = HOURS_12 | to_bcd(tmp); + } else { + s->nvram[2] = HOURS_12 | HOURS_PM | to_bcd(tmp - 12); + } + } else { + s->nvram[2] = to_bcd(now.tm_hour); + } + s->nvram[3] = (now.tm_wday + s->wday_offset) % 7 + 1; + s->nvram[4] = to_bcd(now.tm_mday); + s->nvram[5] = to_bcd(now.tm_mon + 1); + s->nvram[6] = to_bcd(now.tm_year - 100); +} + +static void inc_regptr(DS1338State *s) +{ + /* The register pointer wraps around after 0x3F; wraparound + * causes the current time/date to be retransferred into + * the secondary registers. + */ + s->ptr = (s->ptr + 1) & (NVRAM_SIZE - 1); + if (!s->ptr) { + capture_current_time(s); + } +} + +static void ds1338_event(I2CSlave *i2c, enum i2c_event event) +{ + DS1338State *s = FROM_I2C_SLAVE(DS1338State, i2c); + + switch (event) { + case I2C_START_RECV: + /* In h/w, capture happens on any START condition, not just a + * START_RECV, but there is no need to actually capture on + * START_SEND, because the guest can't get at that data + * without going through a START_RECV which would overwrite it. + */ + capture_current_time(s); + break; + case I2C_START_SEND: + s->addr_byte = true; + break; + default: + break; + } +} + +static int ds1338_recv(I2CSlave *i2c) +{ + DS1338State *s = FROM_I2C_SLAVE(DS1338State, i2c); + uint8_t res; + + res = s->nvram[s->ptr]; + inc_regptr(s); + return res; +} + +static int ds1338_send(I2CSlave *i2c, uint8_t data) +{ + DS1338State *s = FROM_I2C_SLAVE(DS1338State, i2c); + if (s->addr_byte) { + s->ptr = data & (NVRAM_SIZE - 1); + s->addr_byte = false; + return 0; + } + if (s->ptr < 7) { + /* Time register. */ + struct tm now; + qemu_get_timedate(&now, s->offset); + switch(s->ptr) { + case 0: + /* TODO: Implement CH (stop) bit. */ + now.tm_sec = from_bcd(data & 0x7f); + break; + case 1: + now.tm_min = from_bcd(data & 0x7f); + break; + case 2: + if (data & HOURS_12) { + int tmp = from_bcd(data & (HOURS_PM - 1)); + if (data & HOURS_PM) { + tmp += 12; + } + if (tmp % 12 == 0) { + tmp -= 12; + } + now.tm_hour = tmp; + } else { + now.tm_hour = from_bcd(data & (HOURS_12 - 1)); + } + break; + case 3: + { + /* The day field is supposed to contain a value in + the range 1-7. Otherwise behavior is undefined. + */ + int user_wday = (data & 7) - 1; + s->wday_offset = (user_wday - now.tm_wday + 7) % 7; + } + break; + case 4: + now.tm_mday = from_bcd(data & 0x3f); + break; + case 5: + now.tm_mon = from_bcd(data & 0x1f) - 1; + break; + case 6: + now.tm_year = from_bcd(data) + 100; + break; + } + s->offset = qemu_timedate_diff(&now); + } else if (s->ptr == 7) { + /* Control register. */ + + /* Ensure bits 2, 3 and 6 will read back as zero. */ + data &= 0xB3; + + /* Attempting to write the OSF flag to logic 1 leaves the + value unchanged. */ + data = (data & ~CTRL_OSF) | (data & s->nvram[s->ptr] & CTRL_OSF); + + s->nvram[s->ptr] = data; + } else { + s->nvram[s->ptr] = data; + } + inc_regptr(s); + return 0; +} + +static int ds1338_init(I2CSlave *i2c) +{ + return 0; +} + +static void ds1338_reset(DeviceState *dev) +{ + DS1338State *s = FROM_I2C_SLAVE(DS1338State, I2C_SLAVE(dev)); + + /* The clock is running and synchronized with the host */ + s->offset = 0; + s->wday_offset = 0; + memset(s->nvram, 0, NVRAM_SIZE); + s->ptr = 0; + s->addr_byte = false; +} + +static void ds1338_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); + + k->init = ds1338_init; + k->event = ds1338_event; + k->recv = ds1338_recv; + k->send = ds1338_send; + dc->reset = ds1338_reset; + dc->vmsd = &vmstate_ds1338; +} + +static const TypeInfo ds1338_info = { + .name = "ds1338", + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(DS1338State), + .class_init = ds1338_class_init, +}; + +static void ds1338_register_types(void) +{ + type_register_static(&ds1338_info); +} + +type_init(ds1338_register_types) diff --git a/hw/timer/hpet.c b/hw/timer/hpet.c new file mode 100644 index 0000000000..95dd01d147 --- /dev/null +++ b/hw/timer/hpet.c @@ -0,0 +1,760 @@ +/* + * High Precisition Event Timer emulation + * + * Copyright (c) 2007 Alexander Graf + * Copyright (c) 2008 IBM Corporation + * + * Authors: Beth Kon <bkon@us.ibm.com> + * + * 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/>. + * + * ***************************************************************** + * + * This driver attempts to emulate an HPET device in software. + */ + +#include "hw/hw.h" +#include "hw/i386/pc.h" +#include "ui/console.h" +#include "qemu/timer.h" +#include "hw/timer/hpet.h" +#include "hw/sysbus.h" +#include "hw/timer/mc146818rtc.h" +#include "hw/timer/i8254.h" + +//#define HPET_DEBUG +#ifdef HPET_DEBUG +#define DPRINTF printf +#else +#define DPRINTF(...) +#endif + +#define HPET_MSI_SUPPORT 0 + +struct HPETState; +typedef struct HPETTimer { /* timers */ + uint8_t tn; /*timer number*/ + QEMUTimer *qemu_timer; + struct HPETState *state; + /* Memory-mapped, software visible timer registers */ + uint64_t config; /* configuration/cap */ + uint64_t cmp; /* comparator */ + uint64_t fsb; /* FSB route */ + /* Hidden register state */ + uint64_t period; /* Last value written to comparator */ + uint8_t wrap_flag; /* timer pop will indicate wrap for one-shot 32-bit + * mode. Next pop will be actual timer expiration. + */ +} HPETTimer; + +typedef struct HPETState { + SysBusDevice busdev; + MemoryRegion iomem; + uint64_t hpet_offset; + qemu_irq irqs[HPET_NUM_IRQ_ROUTES]; + uint32_t flags; + uint8_t rtc_irq_level; + qemu_irq pit_enabled; + uint8_t num_timers; + HPETTimer timer[HPET_MAX_TIMERS]; + + /* Memory-mapped, software visible registers */ + uint64_t capability; /* capabilities */ + uint64_t config; /* configuration */ + uint64_t isr; /* interrupt status reg */ + uint64_t hpet_counter; /* main counter */ + uint8_t hpet_id; /* instance id */ +} HPETState; + +static uint32_t hpet_in_legacy_mode(HPETState *s) +{ + return s->config & HPET_CFG_LEGACY; +} + +static uint32_t timer_int_route(struct HPETTimer *timer) +{ + return (timer->config & HPET_TN_INT_ROUTE_MASK) >> HPET_TN_INT_ROUTE_SHIFT; +} + +static uint32_t timer_fsb_route(HPETTimer *t) +{ + return t->config & HPET_TN_FSB_ENABLE; +} + +static uint32_t hpet_enabled(HPETState *s) +{ + return s->config & HPET_CFG_ENABLE; +} + +static uint32_t timer_is_periodic(HPETTimer *t) +{ + return t->config & HPET_TN_PERIODIC; +} + +static uint32_t timer_enabled(HPETTimer *t) +{ + return t->config & HPET_TN_ENABLE; +} + +static uint32_t hpet_time_after(uint64_t a, uint64_t b) +{ + return ((int32_t)(b) - (int32_t)(a) < 0); +} + +static uint32_t hpet_time_after64(uint64_t a, uint64_t b) +{ + return ((int64_t)(b) - (int64_t)(a) < 0); +} + +static uint64_t ticks_to_ns(uint64_t value) +{ + return (muldiv64(value, HPET_CLK_PERIOD, FS_PER_NS)); +} + +static uint64_t ns_to_ticks(uint64_t value) +{ + return (muldiv64(value, FS_PER_NS, HPET_CLK_PERIOD)); +} + +static uint64_t hpet_fixup_reg(uint64_t new, uint64_t old, uint64_t mask) +{ + new &= mask; + new |= old & ~mask; + return new; +} + +static int activating_bit(uint64_t old, uint64_t new, uint64_t mask) +{ + return (!(old & mask) && (new & mask)); +} + +static int deactivating_bit(uint64_t old, uint64_t new, uint64_t mask) +{ + return ((old & mask) && !(new & mask)); +} + +static uint64_t hpet_get_ticks(HPETState *s) +{ + return ns_to_ticks(qemu_get_clock_ns(vm_clock) + s->hpet_offset); +} + +/* + * calculate diff between comparator value and current ticks + */ +static inline uint64_t hpet_calculate_diff(HPETTimer *t, uint64_t current) +{ + + if (t->config & HPET_TN_32BIT) { + uint32_t diff, cmp; + + cmp = (uint32_t)t->cmp; + diff = cmp - (uint32_t)current; + diff = (int32_t)diff > 0 ? diff : (uint32_t)1; + return (uint64_t)diff; + } else { + uint64_t diff, cmp; + + cmp = t->cmp; + diff = cmp - current; + diff = (int64_t)diff > 0 ? diff : (uint64_t)1; + return diff; + } +} + +static void update_irq(struct HPETTimer *timer, int set) +{ + uint64_t mask; + HPETState *s; + int route; + + if (timer->tn <= 1 && hpet_in_legacy_mode(timer->state)) { + /* if LegacyReplacementRoute bit is set, HPET specification requires + * timer0 be routed to IRQ0 in NON-APIC or IRQ2 in the I/O APIC, + * timer1 be routed to IRQ8 in NON-APIC or IRQ8 in the I/O APIC. + */ + route = (timer->tn == 0) ? 0 : RTC_ISA_IRQ; + } else { + route = timer_int_route(timer); + } + s = timer->state; + mask = 1 << timer->tn; + if (!set || !timer_enabled(timer) || !hpet_enabled(timer->state)) { + s->isr &= ~mask; + if (!timer_fsb_route(timer)) { + qemu_irq_lower(s->irqs[route]); + } + } else if (timer_fsb_route(timer)) { + stl_le_phys(timer->fsb >> 32, timer->fsb & 0xffffffff); + } else if (timer->config & HPET_TN_TYPE_LEVEL) { + s->isr |= mask; + qemu_irq_raise(s->irqs[route]); + } else { + s->isr &= ~mask; + qemu_irq_pulse(s->irqs[route]); + } +} + +static void hpet_pre_save(void *opaque) +{ + HPETState *s = opaque; + + /* save current counter value */ + s->hpet_counter = hpet_get_ticks(s); +} + +static int hpet_pre_load(void *opaque) +{ + HPETState *s = opaque; + + /* version 1 only supports 3, later versions will load the actual value */ + s->num_timers = HPET_MIN_TIMERS; + return 0; +} + +static int hpet_post_load(void *opaque, int version_id) +{ + HPETState *s = opaque; + + /* Recalculate the offset between the main counter and guest time */ + s->hpet_offset = ticks_to_ns(s->hpet_counter) - qemu_get_clock_ns(vm_clock); + + /* Push number of timers into capability returned via HPET_ID */ + s->capability &= ~HPET_ID_NUM_TIM_MASK; + s->capability |= (s->num_timers - 1) << HPET_ID_NUM_TIM_SHIFT; + hpet_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability; + + /* Derive HPET_MSI_SUPPORT from the capability of the first timer. */ + s->flags &= ~(1 << HPET_MSI_SUPPORT); + if (s->timer[0].config & HPET_TN_FSB_CAP) { + s->flags |= 1 << HPET_MSI_SUPPORT; + } + return 0; +} + +static bool hpet_rtc_irq_level_needed(void *opaque) +{ + HPETState *s = opaque; + + return s->rtc_irq_level != 0; +} + +static const VMStateDescription vmstate_hpet_rtc_irq_level = { + .name = "hpet/rtc_irq_level", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(rtc_irq_level, HPETState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_hpet_timer = { + .name = "hpet_timer", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(tn, HPETTimer), + VMSTATE_UINT64(config, HPETTimer), + VMSTATE_UINT64(cmp, HPETTimer), + VMSTATE_UINT64(fsb, HPETTimer), + VMSTATE_UINT64(period, HPETTimer), + VMSTATE_UINT8(wrap_flag, HPETTimer), + VMSTATE_TIMER(qemu_timer, HPETTimer), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_hpet = { + .name = "hpet", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .pre_save = hpet_pre_save, + .pre_load = hpet_pre_load, + .post_load = hpet_post_load, + .fields = (VMStateField []) { + VMSTATE_UINT64(config, HPETState), + VMSTATE_UINT64(isr, HPETState), + VMSTATE_UINT64(hpet_counter, HPETState), + VMSTATE_UINT8_V(num_timers, HPETState, 2), + VMSTATE_STRUCT_VARRAY_UINT8(timer, HPETState, num_timers, 0, + vmstate_hpet_timer, HPETTimer), + VMSTATE_END_OF_LIST() + }, + .subsections = (VMStateSubsection[]) { + { + .vmsd = &vmstate_hpet_rtc_irq_level, + .needed = hpet_rtc_irq_level_needed, + }, { + /* empty */ + } + } +}; + +/* + * timer expiration callback + */ +static void hpet_timer(void *opaque) +{ + HPETTimer *t = opaque; + uint64_t diff; + + uint64_t period = t->period; + uint64_t cur_tick = hpet_get_ticks(t->state); + + if (timer_is_periodic(t) && period != 0) { + if (t->config & HPET_TN_32BIT) { + while (hpet_time_after(cur_tick, t->cmp)) { + t->cmp = (uint32_t)(t->cmp + t->period); + } + } else { + while (hpet_time_after64(cur_tick, t->cmp)) { + t->cmp += period; + } + } + diff = hpet_calculate_diff(t, cur_tick); + qemu_mod_timer(t->qemu_timer, + qemu_get_clock_ns(vm_clock) + (int64_t)ticks_to_ns(diff)); + } else if (t->config & HPET_TN_32BIT && !timer_is_periodic(t)) { + if (t->wrap_flag) { + diff = hpet_calculate_diff(t, cur_tick); + qemu_mod_timer(t->qemu_timer, qemu_get_clock_ns(vm_clock) + + (int64_t)ticks_to_ns(diff)); + t->wrap_flag = 0; + } + } + update_irq(t, 1); +} + +static void hpet_set_timer(HPETTimer *t) +{ + uint64_t diff; + uint32_t wrap_diff; /* how many ticks until we wrap? */ + uint64_t cur_tick = hpet_get_ticks(t->state); + + /* whenever new timer is being set up, make sure wrap_flag is 0 */ + t->wrap_flag = 0; + diff = hpet_calculate_diff(t, cur_tick); + + /* hpet spec says in one-shot 32-bit mode, generate an interrupt when + * counter wraps in addition to an interrupt with comparator match. + */ + if (t->config & HPET_TN_32BIT && !timer_is_periodic(t)) { + wrap_diff = 0xffffffff - (uint32_t)cur_tick; + if (wrap_diff < (uint32_t)diff) { + diff = wrap_diff; + t->wrap_flag = 1; + } + } + qemu_mod_timer(t->qemu_timer, + qemu_get_clock_ns(vm_clock) + (int64_t)ticks_to_ns(diff)); +} + +static void hpet_del_timer(HPETTimer *t) +{ + qemu_del_timer(t->qemu_timer); + update_irq(t, 0); +} + +#ifdef HPET_DEBUG +static uint32_t hpet_ram_readb(void *opaque, hwaddr addr) +{ + printf("qemu: hpet_read b at %" PRIx64 "\n", addr); + return 0; +} + +static uint32_t hpet_ram_readw(void *opaque, hwaddr addr) +{ + printf("qemu: hpet_read w at %" PRIx64 "\n", addr); + return 0; +} +#endif + +static uint64_t hpet_ram_read(void *opaque, hwaddr addr, + unsigned size) +{ + HPETState *s = opaque; + uint64_t cur_tick, index; + + DPRINTF("qemu: Enter hpet_ram_readl at %" PRIx64 "\n", addr); + index = addr; + /*address range of all TN regs*/ + if (index >= 0x100 && index <= 0x3ff) { + uint8_t timer_id = (addr - 0x100) / 0x20; + HPETTimer *timer = &s->timer[timer_id]; + + if (timer_id > s->num_timers) { + DPRINTF("qemu: timer id out of range\n"); + return 0; + } + + switch ((addr - 0x100) % 0x20) { + case HPET_TN_CFG: + return timer->config; + case HPET_TN_CFG + 4: // Interrupt capabilities + return timer->config >> 32; + case HPET_TN_CMP: // comparator register + return timer->cmp; + case HPET_TN_CMP + 4: + return timer->cmp >> 32; + case HPET_TN_ROUTE: + return timer->fsb; + case HPET_TN_ROUTE + 4: + return timer->fsb >> 32; + default: + DPRINTF("qemu: invalid hpet_ram_readl\n"); + break; + } + } else { + switch (index) { + case HPET_ID: + return s->capability; + case HPET_PERIOD: + return s->capability >> 32; + case HPET_CFG: + return s->config; + case HPET_CFG + 4: + DPRINTF("qemu: invalid HPET_CFG + 4 hpet_ram_readl\n"); + return 0; + case HPET_COUNTER: + if (hpet_enabled(s)) { + cur_tick = hpet_get_ticks(s); + } else { + cur_tick = s->hpet_counter; + } + DPRINTF("qemu: reading counter = %" PRIx64 "\n", cur_tick); + return cur_tick; + case HPET_COUNTER + 4: + if (hpet_enabled(s)) { + cur_tick = hpet_get_ticks(s); + } else { + cur_tick = s->hpet_counter; + } + DPRINTF("qemu: reading counter + 4 = %" PRIx64 "\n", cur_tick); + return cur_tick >> 32; + case HPET_STATUS: + return s->isr; + default: + DPRINTF("qemu: invalid hpet_ram_readl\n"); + break; + } + } + return 0; +} + +static void hpet_ram_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + int i; + HPETState *s = opaque; + uint64_t old_val, new_val, val, index; + + DPRINTF("qemu: Enter hpet_ram_writel at %" PRIx64 " = %#x\n", addr, value); + index = addr; + old_val = hpet_ram_read(opaque, addr, 4); + new_val = value; + + /*address range of all TN regs*/ + if (index >= 0x100 && index <= 0x3ff) { + uint8_t timer_id = (addr - 0x100) / 0x20; + HPETTimer *timer = &s->timer[timer_id]; + + DPRINTF("qemu: hpet_ram_writel timer_id = %#x\n", timer_id); + if (timer_id > s->num_timers) { + DPRINTF("qemu: timer id out of range\n"); + return; + } + switch ((addr - 0x100) % 0x20) { + case HPET_TN_CFG: + DPRINTF("qemu: hpet_ram_writel HPET_TN_CFG\n"); + if (activating_bit(old_val, new_val, HPET_TN_FSB_ENABLE)) { + update_irq(timer, 0); + } + val = hpet_fixup_reg(new_val, old_val, HPET_TN_CFG_WRITE_MASK); + timer->config = (timer->config & 0xffffffff00000000ULL) | val; + if (new_val & HPET_TN_32BIT) { + timer->cmp = (uint32_t)timer->cmp; + timer->period = (uint32_t)timer->period; + } + if (activating_bit(old_val, new_val, HPET_TN_ENABLE)) { + hpet_set_timer(timer); + } else if (deactivating_bit(old_val, new_val, HPET_TN_ENABLE)) { + hpet_del_timer(timer); + } + break; + case HPET_TN_CFG + 4: // Interrupt capabilities + DPRINTF("qemu: invalid HPET_TN_CFG+4 write\n"); + break; + case HPET_TN_CMP: // comparator register + DPRINTF("qemu: hpet_ram_writel HPET_TN_CMP\n"); + if (timer->config & HPET_TN_32BIT) { + new_val = (uint32_t)new_val; + } + if (!timer_is_periodic(timer) + || (timer->config & HPET_TN_SETVAL)) { + timer->cmp = (timer->cmp & 0xffffffff00000000ULL) | new_val; + } + if (timer_is_periodic(timer)) { + /* + * FIXME: Clamp period to reasonable min value? + * Clamp period to reasonable max value + */ + new_val &= (timer->config & HPET_TN_32BIT ? ~0u : ~0ull) >> 1; + timer->period = + (timer->period & 0xffffffff00000000ULL) | new_val; + } + timer->config &= ~HPET_TN_SETVAL; + if (hpet_enabled(s)) { + hpet_set_timer(timer); + } + break; + case HPET_TN_CMP + 4: // comparator register high order + DPRINTF("qemu: hpet_ram_writel HPET_TN_CMP + 4\n"); + if (!timer_is_periodic(timer) + || (timer->config & HPET_TN_SETVAL)) { + timer->cmp = (timer->cmp & 0xffffffffULL) | new_val << 32; + } else { + /* + * FIXME: Clamp period to reasonable min value? + * Clamp period to reasonable max value + */ + new_val &= (timer->config & HPET_TN_32BIT ? ~0u : ~0ull) >> 1; + timer->period = + (timer->period & 0xffffffffULL) | new_val << 32; + } + timer->config &= ~HPET_TN_SETVAL; + if (hpet_enabled(s)) { + hpet_set_timer(timer); + } + break; + case HPET_TN_ROUTE: + timer->fsb = (timer->fsb & 0xffffffff00000000ULL) | new_val; + break; + case HPET_TN_ROUTE + 4: + timer->fsb = (new_val << 32) | (timer->fsb & 0xffffffff); + break; + default: + DPRINTF("qemu: invalid hpet_ram_writel\n"); + break; + } + return; + } else { + switch (index) { + case HPET_ID: + return; + case HPET_CFG: + val = hpet_fixup_reg(new_val, old_val, HPET_CFG_WRITE_MASK); + s->config = (s->config & 0xffffffff00000000ULL) | val; + if (activating_bit(old_val, new_val, HPET_CFG_ENABLE)) { + /* Enable main counter and interrupt generation. */ + s->hpet_offset = + ticks_to_ns(s->hpet_counter) - qemu_get_clock_ns(vm_clock); + for (i = 0; i < s->num_timers; i++) { + if ((&s->timer[i])->cmp != ~0ULL) { + hpet_set_timer(&s->timer[i]); + } + } + } else if (deactivating_bit(old_val, new_val, HPET_CFG_ENABLE)) { + /* Halt main counter and disable interrupt generation. */ + s->hpet_counter = hpet_get_ticks(s); + for (i = 0; i < s->num_timers; i++) { + hpet_del_timer(&s->timer[i]); + } + } + /* i8254 and RTC output pins are disabled + * when HPET is in legacy mode */ + if (activating_bit(old_val, new_val, HPET_CFG_LEGACY)) { + qemu_set_irq(s->pit_enabled, 0); + qemu_irq_lower(s->irqs[0]); + qemu_irq_lower(s->irqs[RTC_ISA_IRQ]); + } else if (deactivating_bit(old_val, new_val, HPET_CFG_LEGACY)) { + qemu_irq_lower(s->irqs[0]); + qemu_set_irq(s->pit_enabled, 1); + qemu_set_irq(s->irqs[RTC_ISA_IRQ], s->rtc_irq_level); + } + break; + case HPET_CFG + 4: + DPRINTF("qemu: invalid HPET_CFG+4 write\n"); + break; + case HPET_STATUS: + val = new_val & s->isr; + for (i = 0; i < s->num_timers; i++) { + if (val & (1 << i)) { + update_irq(&s->timer[i], 0); + } + } + break; + case HPET_COUNTER: + if (hpet_enabled(s)) { + DPRINTF("qemu: Writing counter while HPET enabled!\n"); + } + s->hpet_counter = + (s->hpet_counter & 0xffffffff00000000ULL) | value; + DPRINTF("qemu: HPET counter written. ctr = %#x -> %" PRIx64 "\n", + value, s->hpet_counter); + break; + case HPET_COUNTER + 4: + if (hpet_enabled(s)) { + DPRINTF("qemu: Writing counter while HPET enabled!\n"); + } + s->hpet_counter = + (s->hpet_counter & 0xffffffffULL) | (((uint64_t)value) << 32); + DPRINTF("qemu: HPET counter + 4 written. ctr = %#x -> %" PRIx64 "\n", + value, s->hpet_counter); + break; + default: + DPRINTF("qemu: invalid hpet_ram_writel\n"); + break; + } + } +} + +static const MemoryRegionOps hpet_ram_ops = { + .read = hpet_ram_read, + .write = hpet_ram_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void hpet_reset(DeviceState *d) +{ + HPETState *s = FROM_SYSBUS(HPETState, SYS_BUS_DEVICE(d)); + int i; + + for (i = 0; i < s->num_timers; i++) { + HPETTimer *timer = &s->timer[i]; + + hpet_del_timer(timer); + timer->cmp = ~0ULL; + timer->config = HPET_TN_PERIODIC_CAP | HPET_TN_SIZE_CAP; + if (s->flags & (1 << HPET_MSI_SUPPORT)) { + timer->config |= HPET_TN_FSB_CAP; + } + /* advertise availability of ioapic inti2 */ + timer->config |= 0x00000004ULL << 32; + timer->period = 0ULL; + timer->wrap_flag = 0; + } + + qemu_set_irq(s->pit_enabled, 1); + s->hpet_counter = 0ULL; + s->hpet_offset = 0ULL; + s->config = 0ULL; + hpet_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability; + hpet_cfg.hpet[s->hpet_id].address = SYS_BUS_DEVICE(d)->mmio[0].addr; + + /* to document that the RTC lowers its output on reset as well */ + s->rtc_irq_level = 0; +} + +static void hpet_handle_legacy_irq(void *opaque, int n, int level) +{ + HPETState *s = FROM_SYSBUS(HPETState, opaque); + + if (n == HPET_LEGACY_PIT_INT) { + if (!hpet_in_legacy_mode(s)) { + qemu_set_irq(s->irqs[0], level); + } + } else { + s->rtc_irq_level = level; + if (!hpet_in_legacy_mode(s)) { + qemu_set_irq(s->irqs[RTC_ISA_IRQ], level); + } + } +} + +static int hpet_init(SysBusDevice *dev) +{ + HPETState *s = FROM_SYSBUS(HPETState, dev); + int i; + HPETTimer *timer; + + if (hpet_cfg.count == UINT8_MAX) { + /* first instance */ + hpet_cfg.count = 0; + } + + if (hpet_cfg.count == 8) { + fprintf(stderr, "Only 8 instances of HPET is allowed\n"); + return -1; + } + + s->hpet_id = hpet_cfg.count++; + + for (i = 0; i < HPET_NUM_IRQ_ROUTES; i++) { + sysbus_init_irq(dev, &s->irqs[i]); + } + + if (s->num_timers < HPET_MIN_TIMERS) { + s->num_timers = HPET_MIN_TIMERS; + } else if (s->num_timers > HPET_MAX_TIMERS) { + s->num_timers = HPET_MAX_TIMERS; + } + for (i = 0; i < HPET_MAX_TIMERS; i++) { + timer = &s->timer[i]; + timer->qemu_timer = qemu_new_timer_ns(vm_clock, hpet_timer, timer); + timer->tn = i; + timer->state = s; + } + + /* 64-bit main counter; LegacyReplacementRoute. */ + s->capability = 0x8086a001ULL; + s->capability |= (s->num_timers - 1) << HPET_ID_NUM_TIM_SHIFT; + s->capability |= ((HPET_CLK_PERIOD) << 32); + + qdev_init_gpio_in(&dev->qdev, hpet_handle_legacy_irq, 2); + qdev_init_gpio_out(&dev->qdev, &s->pit_enabled, 1); + + /* HPET Area */ + memory_region_init_io(&s->iomem, &hpet_ram_ops, s, "hpet", 0x400); + sysbus_init_mmio(dev, &s->iomem); + return 0; +} + +static Property hpet_device_properties[] = { + DEFINE_PROP_UINT8("timers", HPETState, num_timers, HPET_MIN_TIMERS), + DEFINE_PROP_BIT("msi", HPETState, flags, HPET_MSI_SUPPORT, false), + DEFINE_PROP_END_OF_LIST(), +}; + +static void hpet_device_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = hpet_init; + dc->no_user = 1; + dc->reset = hpet_reset; + dc->vmsd = &vmstate_hpet; + dc->props = hpet_device_properties; +} + +static const TypeInfo hpet_device_info = { + .name = "hpet", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HPETState), + .class_init = hpet_device_class_init, +}; + +static void hpet_register_types(void) +{ + type_register_static(&hpet_device_info); +} + +type_init(hpet_register_types) diff --git a/hw/timer/i8254.c b/hw/timer/i8254.c new file mode 100644 index 0000000000..20c0c3601d --- /dev/null +++ b/hw/timer/i8254.c @@ -0,0 +1,362 @@ +/* + * QEMU 8253/8254 interval timer emulation + * + * Copyright (c) 2003-2004 Fabrice Bellard + * + * 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 "hw/hw.h" +#include "hw/i386/pc.h" +#include "hw/isa/isa.h" +#include "qemu/timer.h" +#include "hw/timer/i8254.h" +#include "hw/timer/i8254_internal.h" + +//#define DEBUG_PIT + +#define RW_STATE_LSB 1 +#define RW_STATE_MSB 2 +#define RW_STATE_WORD0 3 +#define RW_STATE_WORD1 4 + +static void pit_irq_timer_update(PITChannelState *s, int64_t current_time); + +static int pit_get_count(PITChannelState *s) +{ + uint64_t d; + int counter; + + d = muldiv64(qemu_get_clock_ns(vm_clock) - s->count_load_time, PIT_FREQ, + get_ticks_per_sec()); + switch(s->mode) { + case 0: + case 1: + case 4: + case 5: + counter = (s->count - d) & 0xffff; + break; + case 3: + /* XXX: may be incorrect for odd counts */ + counter = s->count - ((2 * d) % s->count); + break; + default: + counter = s->count - (d % s->count); + break; + } + return counter; +} + +/* val must be 0 or 1 */ +static void pit_set_channel_gate(PITCommonState *s, PITChannelState *sc, + int val) +{ + switch (sc->mode) { + default: + case 0: + case 4: + /* XXX: just disable/enable counting */ + break; + case 1: + case 5: + if (sc->gate < val) { + /* restart counting on rising edge */ + sc->count_load_time = qemu_get_clock_ns(vm_clock); + pit_irq_timer_update(sc, sc->count_load_time); + } + break; + case 2: + case 3: + if (sc->gate < val) { + /* restart counting on rising edge */ + sc->count_load_time = qemu_get_clock_ns(vm_clock); + pit_irq_timer_update(sc, sc->count_load_time); + } + /* XXX: disable/enable counting */ + break; + } + sc->gate = val; +} + +static inline void pit_load_count(PITChannelState *s, int val) +{ + if (val == 0) + val = 0x10000; + s->count_load_time = qemu_get_clock_ns(vm_clock); + s->count = val; + pit_irq_timer_update(s, s->count_load_time); +} + +/* if already latched, do not latch again */ +static void pit_latch_count(PITChannelState *s) +{ + if (!s->count_latched) { + s->latched_count = pit_get_count(s); + s->count_latched = s->rw_mode; + } +} + +static void pit_ioport_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + PITCommonState *pit = opaque; + int channel, access; + PITChannelState *s; + + addr &= 3; + if (addr == 3) { + channel = val >> 6; + if (channel == 3) { + /* read back command */ + for(channel = 0; channel < 3; channel++) { + s = &pit->channels[channel]; + if (val & (2 << channel)) { + if (!(val & 0x20)) { + pit_latch_count(s); + } + if (!(val & 0x10) && !s->status_latched) { + /* status latch */ + /* XXX: add BCD and null count */ + s->status = + (pit_get_out(s, + qemu_get_clock_ns(vm_clock)) << 7) | + (s->rw_mode << 4) | + (s->mode << 1) | + s->bcd; + s->status_latched = 1; + } + } + } + } else { + s = &pit->channels[channel]; + access = (val >> 4) & 3; + if (access == 0) { + pit_latch_count(s); + } else { + s->rw_mode = access; + s->read_state = access; + s->write_state = access; + + s->mode = (val >> 1) & 7; + s->bcd = val & 1; + /* XXX: update irq timer ? */ + } + } + } else { + s = &pit->channels[addr]; + switch(s->write_state) { + default: + case RW_STATE_LSB: + pit_load_count(s, val); + break; + case RW_STATE_MSB: + pit_load_count(s, val << 8); + break; + case RW_STATE_WORD0: + s->write_latch = val; + s->write_state = RW_STATE_WORD1; + break; + case RW_STATE_WORD1: + pit_load_count(s, s->write_latch | (val << 8)); + s->write_state = RW_STATE_WORD0; + break; + } + } +} + +static uint64_t pit_ioport_read(void *opaque, hwaddr addr, + unsigned size) +{ + PITCommonState *pit = opaque; + int ret, count; + PITChannelState *s; + + addr &= 3; + s = &pit->channels[addr]; + if (s->status_latched) { + s->status_latched = 0; + ret = s->status; + } else if (s->count_latched) { + switch(s->count_latched) { + default: + case RW_STATE_LSB: + ret = s->latched_count & 0xff; + s->count_latched = 0; + break; + case RW_STATE_MSB: + ret = s->latched_count >> 8; + s->count_latched = 0; + break; + case RW_STATE_WORD0: + ret = s->latched_count & 0xff; + s->count_latched = RW_STATE_MSB; + break; + } + } else { + switch(s->read_state) { + default: + case RW_STATE_LSB: + count = pit_get_count(s); + ret = count & 0xff; + break; + case RW_STATE_MSB: + count = pit_get_count(s); + ret = (count >> 8) & 0xff; + break; + case RW_STATE_WORD0: + count = pit_get_count(s); + ret = count & 0xff; + s->read_state = RW_STATE_WORD1; + break; + case RW_STATE_WORD1: + count = pit_get_count(s); + ret = (count >> 8) & 0xff; + s->read_state = RW_STATE_WORD0; + break; + } + } + return ret; +} + +static void pit_irq_timer_update(PITChannelState *s, int64_t current_time) +{ + int64_t expire_time; + int irq_level; + + if (!s->irq_timer || s->irq_disabled) { + return; + } + expire_time = pit_get_next_transition_time(s, current_time); + irq_level = pit_get_out(s, current_time); + qemu_set_irq(s->irq, irq_level); +#ifdef DEBUG_PIT + printf("irq_level=%d next_delay=%f\n", + irq_level, + (double)(expire_time - current_time) / get_ticks_per_sec()); +#endif + s->next_transition_time = expire_time; + if (expire_time != -1) + qemu_mod_timer(s->irq_timer, expire_time); + else + qemu_del_timer(s->irq_timer); +} + +static void pit_irq_timer(void *opaque) +{ + PITChannelState *s = opaque; + + pit_irq_timer_update(s, s->next_transition_time); +} + +static void pit_reset(DeviceState *dev) +{ + PITCommonState *pit = DO_UPCAST(PITCommonState, dev.qdev, dev); + PITChannelState *s; + + pit_reset_common(pit); + + s = &pit->channels[0]; + if (!s->irq_disabled) { + qemu_mod_timer(s->irq_timer, s->next_transition_time); + } +} + +/* When HPET is operating in legacy mode, suppress the ignored timer IRQ, + * reenable it when legacy mode is left again. */ +static void pit_irq_control(void *opaque, int n, int enable) +{ + PITCommonState *pit = opaque; + PITChannelState *s = &pit->channels[0]; + + if (enable) { + s->irq_disabled = 0; + pit_irq_timer_update(s, qemu_get_clock_ns(vm_clock)); + } else { + s->irq_disabled = 1; + qemu_del_timer(s->irq_timer); + } +} + +static const MemoryRegionOps pit_ioport_ops = { + .read = pit_ioport_read, + .write = pit_ioport_write, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void pit_post_load(PITCommonState *s) +{ + PITChannelState *sc = &s->channels[0]; + + if (sc->next_transition_time != -1) { + qemu_mod_timer(sc->irq_timer, sc->next_transition_time); + } else { + qemu_del_timer(sc->irq_timer); + } +} + +static int pit_initfn(PITCommonState *pit) +{ + PITChannelState *s; + + s = &pit->channels[0]; + /* the timer 0 is connected to an IRQ */ + s->irq_timer = qemu_new_timer_ns(vm_clock, pit_irq_timer, s); + qdev_init_gpio_out(&pit->dev.qdev, &s->irq, 1); + + memory_region_init_io(&pit->ioports, &pit_ioport_ops, pit, "pit", 4); + + qdev_init_gpio_in(&pit->dev.qdev, pit_irq_control, 1); + + return 0; +} + +static Property pit_properties[] = { + DEFINE_PROP_HEX32("iobase", PITCommonState, iobase, -1), + DEFINE_PROP_END_OF_LIST(), +}; + +static void pit_class_initfn(ObjectClass *klass, void *data) +{ + PITCommonClass *k = PIT_COMMON_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + k->init = pit_initfn; + k->set_channel_gate = pit_set_channel_gate; + k->get_channel_info = pit_get_channel_info_common; + k->post_load = pit_post_load; + dc->reset = pit_reset; + dc->props = pit_properties; +} + +static const TypeInfo pit_info = { + .name = "isa-pit", + .parent = TYPE_PIT_COMMON, + .instance_size = sizeof(PITCommonState), + .class_init = pit_class_initfn, +}; + +static void pit_register_types(void) +{ + type_register_static(&pit_info); +} + +type_init(pit_register_types) diff --git a/hw/timer/i8254_common.c b/hw/timer/i8254_common.c new file mode 100644 index 0000000000..5342df4a34 --- /dev/null +++ b/hw/timer/i8254_common.c @@ -0,0 +1,311 @@ +/* + * QEMU 8253/8254 - common bits of emulated and KVM kernel model + * + * Copyright (c) 2003-2004 Fabrice Bellard + * Copyright (c) 2012 Jan Kiszka, Siemens AG + * + * 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 "hw/hw.h" +#include "hw/i386/pc.h" +#include "hw/isa/isa.h" +#include "qemu/timer.h" +#include "hw/timer/i8254.h" +#include "hw/timer/i8254_internal.h" + +/* val must be 0 or 1 */ +void pit_set_gate(ISADevice *dev, int channel, int val) +{ + PITCommonState *pit = PIT_COMMON(dev); + PITChannelState *s = &pit->channels[channel]; + PITCommonClass *c = PIT_COMMON_GET_CLASS(pit); + + c->set_channel_gate(pit, s, val); +} + +/* get pit output bit */ +int pit_get_out(PITChannelState *s, int64_t current_time) +{ + uint64_t d; + int out; + + d = muldiv64(current_time - s->count_load_time, PIT_FREQ, + get_ticks_per_sec()); + switch (s->mode) { + default: + case 0: + out = (d >= s->count); + break; + case 1: + out = (d < s->count); + break; + case 2: + if ((d % s->count) == 0 && d != 0) { + out = 1; + } else { + out = 0; + } + break; + case 3: + out = (d % s->count) < ((s->count + 1) >> 1); + break; + case 4: + case 5: + out = (d == s->count); + break; + } + return out; +} + +/* return -1 if no transition will occur. */ +int64_t pit_get_next_transition_time(PITChannelState *s, int64_t current_time) +{ + uint64_t d, next_time, base; + int period2; + + d = muldiv64(current_time - s->count_load_time, PIT_FREQ, + get_ticks_per_sec()); + switch (s->mode) { + default: + case 0: + case 1: + if (d < s->count) { + next_time = s->count; + } else { + return -1; + } + break; + case 2: + base = (d / s->count) * s->count; + if ((d - base) == 0 && d != 0) { + next_time = base + s->count; + } else { + next_time = base + s->count + 1; + } + break; + case 3: + base = (d / s->count) * s->count; + period2 = ((s->count + 1) >> 1); + if ((d - base) < period2) { + next_time = base + period2; + } else { + next_time = base + s->count; + } + break; + case 4: + case 5: + if (d < s->count) { + next_time = s->count; + } else if (d == s->count) { + next_time = s->count + 1; + } else { + return -1; + } + break; + } + /* convert to timer units */ + next_time = s->count_load_time + muldiv64(next_time, get_ticks_per_sec(), + PIT_FREQ); + /* fix potential rounding problems */ + /* XXX: better solution: use a clock at PIT_FREQ Hz */ + if (next_time <= current_time) { + next_time = current_time + 1; + } + return next_time; +} + +void pit_get_channel_info_common(PITCommonState *s, PITChannelState *sc, + PITChannelInfo *info) +{ + info->gate = sc->gate; + info->mode = sc->mode; + info->initial_count = sc->count; + info->out = pit_get_out(sc, qemu_get_clock_ns(vm_clock)); +} + +void pit_get_channel_info(ISADevice *dev, int channel, PITChannelInfo *info) +{ + PITCommonState *pit = PIT_COMMON(dev); + PITChannelState *s = &pit->channels[channel]; + PITCommonClass *c = PIT_COMMON_GET_CLASS(pit); + + c->get_channel_info(pit, s, info); +} + +void pit_reset_common(PITCommonState *pit) +{ + PITChannelState *s; + int i; + + for (i = 0; i < 3; i++) { + s = &pit->channels[i]; + s->mode = 3; + s->gate = (i != 2); + s->count_load_time = qemu_get_clock_ns(vm_clock); + s->count = 0x10000; + if (i == 0 && !s->irq_disabled) { + s->next_transition_time = + pit_get_next_transition_time(s, s->count_load_time); + } + } +} + +static int pit_init_common(ISADevice *dev) +{ + PITCommonState *pit = PIT_COMMON(dev); + PITCommonClass *c = PIT_COMMON_GET_CLASS(pit); + int ret; + + ret = c->init(pit); + if (ret < 0) { + return ret; + } + + isa_register_ioport(dev, &pit->ioports, pit->iobase); + + qdev_set_legacy_instance_id(&dev->qdev, pit->iobase, 2); + + return 0; +} + +static const VMStateDescription vmstate_pit_channel = { + .name = "pit channel", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .fields = (VMStateField[]) { + VMSTATE_INT32(count, PITChannelState), + VMSTATE_UINT16(latched_count, PITChannelState), + VMSTATE_UINT8(count_latched, PITChannelState), + VMSTATE_UINT8(status_latched, PITChannelState), + VMSTATE_UINT8(status, PITChannelState), + VMSTATE_UINT8(read_state, PITChannelState), + VMSTATE_UINT8(write_state, PITChannelState), + VMSTATE_UINT8(write_latch, PITChannelState), + VMSTATE_UINT8(rw_mode, PITChannelState), + VMSTATE_UINT8(mode, PITChannelState), + VMSTATE_UINT8(bcd, PITChannelState), + VMSTATE_UINT8(gate, PITChannelState), + VMSTATE_INT64(count_load_time, PITChannelState), + VMSTATE_INT64(next_transition_time, PITChannelState), + VMSTATE_END_OF_LIST() + } +}; + +static int pit_load_old(QEMUFile *f, void *opaque, int version_id) +{ + PITCommonState *pit = opaque; + PITCommonClass *c = PIT_COMMON_GET_CLASS(pit); + PITChannelState *s; + int i; + + if (version_id != 1) { + return -EINVAL; + } + + for (i = 0; i < 3; i++) { + s = &pit->channels[i]; + s->count = qemu_get_be32(f); + qemu_get_be16s(f, &s->latched_count); + qemu_get_8s(f, &s->count_latched); + qemu_get_8s(f, &s->status_latched); + qemu_get_8s(f, &s->status); + qemu_get_8s(f, &s->read_state); + qemu_get_8s(f, &s->write_state); + qemu_get_8s(f, &s->write_latch); + qemu_get_8s(f, &s->rw_mode); + qemu_get_8s(f, &s->mode); + qemu_get_8s(f, &s->bcd); + qemu_get_8s(f, &s->gate); + s->count_load_time = qemu_get_be64(f); + s->irq_disabled = 0; + if (i == 0) { + s->next_transition_time = qemu_get_be64(f); + } + } + if (c->post_load) { + c->post_load(pit); + } + return 0; +} + +static void pit_dispatch_pre_save(void *opaque) +{ + PITCommonState *s = opaque; + PITCommonClass *c = PIT_COMMON_GET_CLASS(s); + + if (c->pre_save) { + c->pre_save(s); + } +} + +static int pit_dispatch_post_load(void *opaque, int version_id) +{ + PITCommonState *s = opaque; + PITCommonClass *c = PIT_COMMON_GET_CLASS(s); + + if (c->post_load) { + c->post_load(s); + } + return 0; +} + +static const VMStateDescription vmstate_pit_common = { + .name = "i8254", + .version_id = 3, + .minimum_version_id = 2, + .minimum_version_id_old = 1, + .load_state_old = pit_load_old, + .pre_save = pit_dispatch_pre_save, + .post_load = pit_dispatch_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32_V(channels[0].irq_disabled, PITCommonState, 3), + VMSTATE_STRUCT_ARRAY(channels, PITCommonState, 3, 2, + vmstate_pit_channel, PITChannelState), + VMSTATE_INT64(channels[0].next_transition_time, + PITCommonState), /* formerly irq_timer */ + VMSTATE_END_OF_LIST() + } +}; + +static void pit_common_class_init(ObjectClass *klass, void *data) +{ + ISADeviceClass *ic = ISA_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + ic->init = pit_init_common; + dc->vmsd = &vmstate_pit_common; + dc->no_user = 1; +} + +static const TypeInfo pit_common_type = { + .name = TYPE_PIT_COMMON, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(PITCommonState), + .class_size = sizeof(PITCommonClass), + .class_init = pit_common_class_init, + .abstract = true, +}; + +static void register_devices(void) +{ + type_register_static(&pit_common_type); +} + +type_init(register_devices); diff --git a/hw/timer/m48t59.c b/hw/timer/m48t59.c new file mode 100644 index 0000000000..5019e0632b --- /dev/null +++ b/hw/timer/m48t59.c @@ -0,0 +1,778 @@ +/* + * QEMU M48T59 and M48T08 NVRAM emulation for PPC PREP and Sparc platforms + * + * Copyright (c) 2003-2005, 2007 Jocelyn Mayer + * + * 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 "hw/hw.h" +#include "hw/timer/m48t59.h" +#include "qemu/timer.h" +#include "sysemu/sysemu.h" +#include "hw/sysbus.h" +#include "hw/isa/isa.h" +#include "exec/address-spaces.h" + +//#define DEBUG_NVRAM + +#if defined(DEBUG_NVRAM) +#define NVRAM_PRINTF(fmt, ...) do { printf(fmt , ## __VA_ARGS__); } while (0) +#else +#define NVRAM_PRINTF(fmt, ...) do { } while (0) +#endif + +/* + * The M48T02, M48T08 and M48T59 chips are very similar. The newer '59 has + * alarm and a watchdog timer and related control registers. In the + * PPC platform there is also a nvram lock function. + */ + +/* + * Chipset docs: + * http://www.st.com/stonline/products/literature/ds/2410/m48t02.pdf + * http://www.st.com/stonline/products/literature/ds/2411/m48t08.pdf + * http://www.st.com/stonline/products/literature/od/7001/m48t59y.pdf + */ + +struct M48t59State { + /* Hardware parameters */ + qemu_irq IRQ; + MemoryRegion iomem; + uint32_t io_base; + uint32_t size; + /* RTC management */ + time_t time_offset; + time_t stop_time; + /* Alarm & watchdog */ + struct tm alarm; + struct QEMUTimer *alrm_timer; + struct QEMUTimer *wd_timer; + /* NVRAM storage */ + uint8_t *buffer; + /* Model parameters */ + uint32_t model; /* 2 = m48t02, 8 = m48t08, 59 = m48t59 */ + /* NVRAM storage */ + uint16_t addr; + uint8_t lock; +}; + +typedef struct M48t59ISAState { + ISADevice busdev; + M48t59State state; + MemoryRegion io; +} M48t59ISAState; + +typedef struct M48t59SysBusState { + SysBusDevice busdev; + M48t59State state; + MemoryRegion io; +} M48t59SysBusState; + +/* Fake timer functions */ + +/* Alarm management */ +static void alarm_cb (void *opaque) +{ + struct tm tm; + uint64_t next_time; + M48t59State *NVRAM = opaque; + + qemu_set_irq(NVRAM->IRQ, 1); + if ((NVRAM->buffer[0x1FF5] & 0x80) == 0 && + (NVRAM->buffer[0x1FF4] & 0x80) == 0 && + (NVRAM->buffer[0x1FF3] & 0x80) == 0 && + (NVRAM->buffer[0x1FF2] & 0x80) == 0) { + /* Repeat once a month */ + qemu_get_timedate(&tm, NVRAM->time_offset); + tm.tm_mon++; + if (tm.tm_mon == 13) { + tm.tm_mon = 1; + tm.tm_year++; + } + next_time = qemu_timedate_diff(&tm) - NVRAM->time_offset; + } else if ((NVRAM->buffer[0x1FF5] & 0x80) != 0 && + (NVRAM->buffer[0x1FF4] & 0x80) == 0 && + (NVRAM->buffer[0x1FF3] & 0x80) == 0 && + (NVRAM->buffer[0x1FF2] & 0x80) == 0) { + /* Repeat once a day */ + next_time = 24 * 60 * 60; + } else if ((NVRAM->buffer[0x1FF5] & 0x80) != 0 && + (NVRAM->buffer[0x1FF4] & 0x80) != 0 && + (NVRAM->buffer[0x1FF3] & 0x80) == 0 && + (NVRAM->buffer[0x1FF2] & 0x80) == 0) { + /* Repeat once an hour */ + next_time = 60 * 60; + } else if ((NVRAM->buffer[0x1FF5] & 0x80) != 0 && + (NVRAM->buffer[0x1FF4] & 0x80) != 0 && + (NVRAM->buffer[0x1FF3] & 0x80) != 0 && + (NVRAM->buffer[0x1FF2] & 0x80) == 0) { + /* Repeat once a minute */ + next_time = 60; + } else { + /* Repeat once a second */ + next_time = 1; + } + qemu_mod_timer(NVRAM->alrm_timer, qemu_get_clock_ns(rtc_clock) + + next_time * 1000); + qemu_set_irq(NVRAM->IRQ, 0); +} + +static void set_alarm(M48t59State *NVRAM) +{ + int diff; + if (NVRAM->alrm_timer != NULL) { + qemu_del_timer(NVRAM->alrm_timer); + diff = qemu_timedate_diff(&NVRAM->alarm) - NVRAM->time_offset; + if (diff > 0) + qemu_mod_timer(NVRAM->alrm_timer, diff * 1000); + } +} + +/* RTC management helpers */ +static inline void get_time(M48t59State *NVRAM, struct tm *tm) +{ + qemu_get_timedate(tm, NVRAM->time_offset); +} + +static void set_time(M48t59State *NVRAM, struct tm *tm) +{ + NVRAM->time_offset = qemu_timedate_diff(tm); + set_alarm(NVRAM); +} + +/* Watchdog management */ +static void watchdog_cb (void *opaque) +{ + M48t59State *NVRAM = opaque; + + NVRAM->buffer[0x1FF0] |= 0x80; + if (NVRAM->buffer[0x1FF7] & 0x80) { + NVRAM->buffer[0x1FF7] = 0x00; + NVRAM->buffer[0x1FFC] &= ~0x40; + /* May it be a hw CPU Reset instead ? */ + qemu_system_reset_request(); + } else { + qemu_set_irq(NVRAM->IRQ, 1); + qemu_set_irq(NVRAM->IRQ, 0); + } +} + +static void set_up_watchdog(M48t59State *NVRAM, uint8_t value) +{ + uint64_t interval; /* in 1/16 seconds */ + + NVRAM->buffer[0x1FF0] &= ~0x80; + if (NVRAM->wd_timer != NULL) { + qemu_del_timer(NVRAM->wd_timer); + if (value != 0) { + interval = (1 << (2 * (value & 0x03))) * ((value >> 2) & 0x1F); + qemu_mod_timer(NVRAM->wd_timer, ((uint64_t)time(NULL) * 1000) + + ((interval * 1000) >> 4)); + } + } +} + +/* Direct access to NVRAM */ +void m48t59_write (void *opaque, uint32_t addr, uint32_t val) +{ + M48t59State *NVRAM = opaque; + struct tm tm; + int tmp; + + if (addr > 0x1FF8 && addr < 0x2000) + NVRAM_PRINTF("%s: 0x%08x => 0x%08x\n", __func__, addr, val); + + /* check for NVRAM access */ + if ((NVRAM->model == 2 && addr < 0x7f8) || + (NVRAM->model == 8 && addr < 0x1ff8) || + (NVRAM->model == 59 && addr < 0x1ff0)) { + goto do_write; + } + + /* TOD access */ + switch (addr) { + case 0x1FF0: + /* flags register : read-only */ + break; + case 0x1FF1: + /* unused */ + break; + case 0x1FF2: + /* alarm seconds */ + tmp = from_bcd(val & 0x7F); + if (tmp >= 0 && tmp <= 59) { + NVRAM->alarm.tm_sec = tmp; + NVRAM->buffer[0x1FF2] = val; + set_alarm(NVRAM); + } + break; + case 0x1FF3: + /* alarm minutes */ + tmp = from_bcd(val & 0x7F); + if (tmp >= 0 && tmp <= 59) { + NVRAM->alarm.tm_min = tmp; + NVRAM->buffer[0x1FF3] = val; + set_alarm(NVRAM); + } + break; + case 0x1FF4: + /* alarm hours */ + tmp = from_bcd(val & 0x3F); + if (tmp >= 0 && tmp <= 23) { + NVRAM->alarm.tm_hour = tmp; + NVRAM->buffer[0x1FF4] = val; + set_alarm(NVRAM); + } + break; + case 0x1FF5: + /* alarm date */ + tmp = from_bcd(val & 0x3F); + if (tmp != 0) { + NVRAM->alarm.tm_mday = tmp; + NVRAM->buffer[0x1FF5] = val; + set_alarm(NVRAM); + } + break; + case 0x1FF6: + /* interrupts */ + NVRAM->buffer[0x1FF6] = val; + break; + case 0x1FF7: + /* watchdog */ + NVRAM->buffer[0x1FF7] = val; + set_up_watchdog(NVRAM, val); + break; + case 0x1FF8: + case 0x07F8: + /* control */ + NVRAM->buffer[addr] = (val & ~0xA0) | 0x90; + break; + case 0x1FF9: + case 0x07F9: + /* seconds (BCD) */ + tmp = from_bcd(val & 0x7F); + if (tmp >= 0 && tmp <= 59) { + get_time(NVRAM, &tm); + tm.tm_sec = tmp; + set_time(NVRAM, &tm); + } + if ((val & 0x80) ^ (NVRAM->buffer[addr] & 0x80)) { + if (val & 0x80) { + NVRAM->stop_time = time(NULL); + } else { + NVRAM->time_offset += NVRAM->stop_time - time(NULL); + NVRAM->stop_time = 0; + } + } + NVRAM->buffer[addr] = val & 0x80; + break; + case 0x1FFA: + case 0x07FA: + /* minutes (BCD) */ + tmp = from_bcd(val & 0x7F); + if (tmp >= 0 && tmp <= 59) { + get_time(NVRAM, &tm); + tm.tm_min = tmp; + set_time(NVRAM, &tm); + } + break; + case 0x1FFB: + case 0x07FB: + /* hours (BCD) */ + tmp = from_bcd(val & 0x3F); + if (tmp >= 0 && tmp <= 23) { + get_time(NVRAM, &tm); + tm.tm_hour = tmp; + set_time(NVRAM, &tm); + } + break; + case 0x1FFC: + case 0x07FC: + /* day of the week / century */ + tmp = from_bcd(val & 0x07); + get_time(NVRAM, &tm); + tm.tm_wday = tmp; + set_time(NVRAM, &tm); + NVRAM->buffer[addr] = val & 0x40; + break; + case 0x1FFD: + case 0x07FD: + /* date (BCD) */ + tmp = from_bcd(val & 0x3F); + if (tmp != 0) { + get_time(NVRAM, &tm); + tm.tm_mday = tmp; + set_time(NVRAM, &tm); + } + break; + case 0x1FFE: + case 0x07FE: + /* month */ + tmp = from_bcd(val & 0x1F); + if (tmp >= 1 && tmp <= 12) { + get_time(NVRAM, &tm); + tm.tm_mon = tmp - 1; + set_time(NVRAM, &tm); + } + break; + case 0x1FFF: + case 0x07FF: + /* year */ + tmp = from_bcd(val); + if (tmp >= 0 && tmp <= 99) { + get_time(NVRAM, &tm); + if (NVRAM->model == 8) { + tm.tm_year = from_bcd(val) + 68; // Base year is 1968 + } else { + tm.tm_year = from_bcd(val); + } + set_time(NVRAM, &tm); + } + break; + default: + /* Check lock registers state */ + if (addr >= 0x20 && addr <= 0x2F && (NVRAM->lock & 1)) + break; + if (addr >= 0x30 && addr <= 0x3F && (NVRAM->lock & 2)) + break; + do_write: + if (addr < NVRAM->size) { + NVRAM->buffer[addr] = val & 0xFF; + } + break; + } +} + +uint32_t m48t59_read (void *opaque, uint32_t addr) +{ + M48t59State *NVRAM = opaque; + struct tm tm; + uint32_t retval = 0xFF; + + /* check for NVRAM access */ + if ((NVRAM->model == 2 && addr < 0x078f) || + (NVRAM->model == 8 && addr < 0x1ff8) || + (NVRAM->model == 59 && addr < 0x1ff0)) { + goto do_read; + } + + /* TOD access */ + switch (addr) { + case 0x1FF0: + /* flags register */ + goto do_read; + case 0x1FF1: + /* unused */ + retval = 0; + break; + case 0x1FF2: + /* alarm seconds */ + goto do_read; + case 0x1FF3: + /* alarm minutes */ + goto do_read; + case 0x1FF4: + /* alarm hours */ + goto do_read; + case 0x1FF5: + /* alarm date */ + goto do_read; + case 0x1FF6: + /* interrupts */ + goto do_read; + case 0x1FF7: + /* A read resets the watchdog */ + set_up_watchdog(NVRAM, NVRAM->buffer[0x1FF7]); + goto do_read; + case 0x1FF8: + case 0x07F8: + /* control */ + goto do_read; + case 0x1FF9: + case 0x07F9: + /* seconds (BCD) */ + get_time(NVRAM, &tm); + retval = (NVRAM->buffer[addr] & 0x80) | to_bcd(tm.tm_sec); + break; + case 0x1FFA: + case 0x07FA: + /* minutes (BCD) */ + get_time(NVRAM, &tm); + retval = to_bcd(tm.tm_min); + break; + case 0x1FFB: + case 0x07FB: + /* hours (BCD) */ + get_time(NVRAM, &tm); + retval = to_bcd(tm.tm_hour); + break; + case 0x1FFC: + case 0x07FC: + /* day of the week / century */ + get_time(NVRAM, &tm); + retval = NVRAM->buffer[addr] | tm.tm_wday; + break; + case 0x1FFD: + case 0x07FD: + /* date */ + get_time(NVRAM, &tm); + retval = to_bcd(tm.tm_mday); + break; + case 0x1FFE: + case 0x07FE: + /* month */ + get_time(NVRAM, &tm); + retval = to_bcd(tm.tm_mon + 1); + break; + case 0x1FFF: + case 0x07FF: + /* year */ + get_time(NVRAM, &tm); + if (NVRAM->model == 8) { + retval = to_bcd(tm.tm_year - 68); // Base year is 1968 + } else { + retval = to_bcd(tm.tm_year); + } + break; + default: + /* Check lock registers state */ + if (addr >= 0x20 && addr <= 0x2F && (NVRAM->lock & 1)) + break; + if (addr >= 0x30 && addr <= 0x3F && (NVRAM->lock & 2)) + break; + do_read: + if (addr < NVRAM->size) { + retval = NVRAM->buffer[addr]; + } + break; + } + if (addr > 0x1FF9 && addr < 0x2000) + NVRAM_PRINTF("%s: 0x%08x <= 0x%08x\n", __func__, addr, retval); + + return retval; +} + +void m48t59_toggle_lock (void *opaque, int lock) +{ + M48t59State *NVRAM = opaque; + + NVRAM->lock ^= 1 << lock; +} + +/* IO access to NVRAM */ +static void NVRAM_writeb(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + M48t59State *NVRAM = opaque; + + NVRAM_PRINTF("%s: 0x%08x => 0x%08x\n", __func__, addr, val); + switch (addr) { + case 0: + NVRAM->addr &= ~0x00FF; + NVRAM->addr |= val; + break; + case 1: + NVRAM->addr &= ~0xFF00; + NVRAM->addr |= val << 8; + break; + case 3: + m48t59_write(NVRAM, NVRAM->addr, val); + NVRAM->addr = 0x0000; + break; + default: + break; + } +} + +static uint64_t NVRAM_readb(void *opaque, hwaddr addr, unsigned size) +{ + M48t59State *NVRAM = opaque; + uint32_t retval; + + switch (addr) { + case 3: + retval = m48t59_read(NVRAM, NVRAM->addr); + break; + default: + retval = -1; + break; + } + NVRAM_PRINTF("%s: 0x%08x <= 0x%08x\n", __func__, addr, retval); + + return retval; +} + +static void nvram_writeb (void *opaque, hwaddr addr, uint32_t value) +{ + M48t59State *NVRAM = opaque; + + m48t59_write(NVRAM, addr, value & 0xff); +} + +static void nvram_writew (void *opaque, hwaddr addr, uint32_t value) +{ + M48t59State *NVRAM = opaque; + + m48t59_write(NVRAM, addr, (value >> 8) & 0xff); + m48t59_write(NVRAM, addr + 1, value & 0xff); +} + +static void nvram_writel (void *opaque, hwaddr addr, uint32_t value) +{ + M48t59State *NVRAM = opaque; + + m48t59_write(NVRAM, addr, (value >> 24) & 0xff); + m48t59_write(NVRAM, addr + 1, (value >> 16) & 0xff); + m48t59_write(NVRAM, addr + 2, (value >> 8) & 0xff); + m48t59_write(NVRAM, addr + 3, value & 0xff); +} + +static uint32_t nvram_readb (void *opaque, hwaddr addr) +{ + M48t59State *NVRAM = opaque; + uint32_t retval; + + retval = m48t59_read(NVRAM, addr); + return retval; +} + +static uint32_t nvram_readw (void *opaque, hwaddr addr) +{ + M48t59State *NVRAM = opaque; + uint32_t retval; + + retval = m48t59_read(NVRAM, addr) << 8; + retval |= m48t59_read(NVRAM, addr + 1); + return retval; +} + +static uint32_t nvram_readl (void *opaque, hwaddr addr) +{ + M48t59State *NVRAM = opaque; + uint32_t retval; + + retval = m48t59_read(NVRAM, addr) << 24; + retval |= m48t59_read(NVRAM, addr + 1) << 16; + retval |= m48t59_read(NVRAM, addr + 2) << 8; + retval |= m48t59_read(NVRAM, addr + 3); + return retval; +} + +static const MemoryRegionOps nvram_ops = { + .old_mmio = { + .read = { nvram_readb, nvram_readw, nvram_readl, }, + .write = { nvram_writeb, nvram_writew, nvram_writel, }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_m48t59 = { + .name = "m48t59", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(lock, M48t59State), + VMSTATE_UINT16(addr, M48t59State), + VMSTATE_VBUFFER_UINT32(buffer, M48t59State, 0, NULL, 0, size), + VMSTATE_END_OF_LIST() + } +}; + +static void m48t59_reset_common(M48t59State *NVRAM) +{ + NVRAM->addr = 0; + NVRAM->lock = 0; + if (NVRAM->alrm_timer != NULL) + qemu_del_timer(NVRAM->alrm_timer); + + if (NVRAM->wd_timer != NULL) + qemu_del_timer(NVRAM->wd_timer); +} + +static void m48t59_reset_isa(DeviceState *d) +{ + M48t59ISAState *isa = container_of(d, M48t59ISAState, busdev.qdev); + M48t59State *NVRAM = &isa->state; + + m48t59_reset_common(NVRAM); +} + +static void m48t59_reset_sysbus(DeviceState *d) +{ + M48t59SysBusState *sys = container_of(d, M48t59SysBusState, busdev.qdev); + M48t59State *NVRAM = &sys->state; + + m48t59_reset_common(NVRAM); +} + +static const MemoryRegionOps m48t59_io_ops = { + .read = NVRAM_readb, + .write = NVRAM_writeb, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +/* Initialisation routine */ +M48t59State *m48t59_init(qemu_irq IRQ, hwaddr mem_base, + uint32_t io_base, uint16_t size, int model) +{ + DeviceState *dev; + SysBusDevice *s; + M48t59SysBusState *d; + M48t59State *state; + + dev = qdev_create(NULL, "m48t59"); + qdev_prop_set_uint32(dev, "model", model); + qdev_prop_set_uint32(dev, "size", size); + qdev_prop_set_uint32(dev, "io_base", io_base); + qdev_init_nofail(dev); + s = SYS_BUS_DEVICE(dev); + d = FROM_SYSBUS(M48t59SysBusState, s); + state = &d->state; + sysbus_connect_irq(s, 0, IRQ); + memory_region_init_io(&d->io, &m48t59_io_ops, state, "m48t59", 4); + if (io_base != 0) { + memory_region_add_subregion(get_system_io(), io_base, &d->io); + } + if (mem_base != 0) { + sysbus_mmio_map(s, 0, mem_base); + } + + return state; +} + +M48t59State *m48t59_init_isa(ISABus *bus, uint32_t io_base, uint16_t size, + int model) +{ + M48t59ISAState *d; + ISADevice *dev; + M48t59State *s; + + dev = isa_create(bus, "m48t59_isa"); + qdev_prop_set_uint32(&dev->qdev, "model", model); + qdev_prop_set_uint32(&dev->qdev, "size", size); + qdev_prop_set_uint32(&dev->qdev, "io_base", io_base); + qdev_init_nofail(&dev->qdev); + d = DO_UPCAST(M48t59ISAState, busdev, dev); + s = &d->state; + + memory_region_init_io(&d->io, &m48t59_io_ops, s, "m48t59", 4); + if (io_base != 0) { + isa_register_ioport(dev, &d->io, io_base); + } + + return s; +} + +static void m48t59_init_common(M48t59State *s) +{ + s->buffer = g_malloc0(s->size); + if (s->model == 59) { + s->alrm_timer = qemu_new_timer_ns(rtc_clock, &alarm_cb, s); + s->wd_timer = qemu_new_timer_ns(vm_clock, &watchdog_cb, s); + } + qemu_get_timedate(&s->alarm, 0); + + vmstate_register(NULL, -1, &vmstate_m48t59, s); +} + +static int m48t59_init_isa1(ISADevice *dev) +{ + M48t59ISAState *d = DO_UPCAST(M48t59ISAState, busdev, dev); + M48t59State *s = &d->state; + + isa_init_irq(dev, &s->IRQ, 8); + m48t59_init_common(s); + + return 0; +} + +static int m48t59_init1(SysBusDevice *dev) +{ + M48t59SysBusState *d = FROM_SYSBUS(M48t59SysBusState, dev); + M48t59State *s = &d->state; + + sysbus_init_irq(dev, &s->IRQ); + + memory_region_init_io(&s->iomem, &nvram_ops, s, "m48t59.nvram", s->size); + sysbus_init_mmio(dev, &s->iomem); + m48t59_init_common(s); + + return 0; +} + +static Property m48t59_isa_properties[] = { + DEFINE_PROP_UINT32("size", M48t59ISAState, state.size, -1), + DEFINE_PROP_UINT32("model", M48t59ISAState, state.model, -1), + DEFINE_PROP_HEX32( "io_base", M48t59ISAState, state.io_base, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void m48t59_init_class_isa1(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ISADeviceClass *ic = ISA_DEVICE_CLASS(klass); + ic->init = m48t59_init_isa1; + dc->no_user = 1; + dc->reset = m48t59_reset_isa; + dc->props = m48t59_isa_properties; +} + +static const TypeInfo m48t59_isa_info = { + .name = "m48t59_isa", + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(M48t59ISAState), + .class_init = m48t59_init_class_isa1, +}; + +static Property m48t59_properties[] = { + DEFINE_PROP_UINT32("size", M48t59SysBusState, state.size, -1), + DEFINE_PROP_UINT32("model", M48t59SysBusState, state.model, -1), + DEFINE_PROP_HEX32( "io_base", M48t59SysBusState, state.io_base, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void m48t59_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = m48t59_init1; + dc->reset = m48t59_reset_sysbus; + dc->props = m48t59_properties; +} + +static const TypeInfo m48t59_info = { + .name = "m48t59", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(M48t59SysBusState), + .class_init = m48t59_class_init, +}; + +static void m48t59_register_types(void) +{ + type_register_static(&m48t59_info); + type_register_static(&m48t59_isa_info); +} + +type_init(m48t59_register_types) diff --git a/hw/timer/pl031.c b/hw/timer/pl031.c new file mode 100644 index 0000000000..764940be7e --- /dev/null +++ b/hw/timer/pl031.c @@ -0,0 +1,265 @@ +/* + * ARM AMBA PrimeCell PL031 RTC + * + * Copyright (c) 2007 CodeSourcery + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "hw/sysbus.h" +#include "qemu/timer.h" +#include "sysemu/sysemu.h" + +//#define DEBUG_PL031 + +#ifdef DEBUG_PL031 +#define DPRINTF(fmt, ...) \ +do { printf("pl031: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#endif + +#define RTC_DR 0x00 /* Data read register */ +#define RTC_MR 0x04 /* Match register */ +#define RTC_LR 0x08 /* Data load register */ +#define RTC_CR 0x0c /* Control register */ +#define RTC_IMSC 0x10 /* Interrupt mask and set register */ +#define RTC_RIS 0x14 /* Raw interrupt status register */ +#define RTC_MIS 0x18 /* Masked interrupt status register */ +#define RTC_ICR 0x1c /* Interrupt clear register */ + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + QEMUTimer *timer; + qemu_irq irq; + + /* Needed to preserve the tick_count across migration, even if the + * absolute value of the rtc_clock is different on the source and + * destination. + */ + uint32_t tick_offset_vmstate; + uint32_t tick_offset; + + uint32_t mr; + uint32_t lr; + uint32_t cr; + uint32_t im; + uint32_t is; +} pl031_state; + +static const unsigned char pl031_id[] = { + 0x31, 0x10, 0x14, 0x00, /* Device ID */ + 0x0d, 0xf0, 0x05, 0xb1 /* Cell ID */ +}; + +static void pl031_update(pl031_state *s) +{ + qemu_set_irq(s->irq, s->is & s->im); +} + +static void pl031_interrupt(void * opaque) +{ + pl031_state *s = (pl031_state *)opaque; + + s->is = 1; + DPRINTF("Alarm raised\n"); + pl031_update(s); +} + +static uint32_t pl031_get_count(pl031_state *s) +{ + int64_t now = qemu_get_clock_ns(rtc_clock); + return s->tick_offset + now / get_ticks_per_sec(); +} + +static void pl031_set_alarm(pl031_state *s) +{ + uint32_t ticks; + + /* The timer wraps around. This subtraction also wraps in the same way, + and gives correct results when alarm < now_ticks. */ + ticks = s->mr - pl031_get_count(s); + DPRINTF("Alarm set in %ud ticks\n", ticks); + if (ticks == 0) { + qemu_del_timer(s->timer); + pl031_interrupt(s); + } else { + int64_t now = qemu_get_clock_ns(rtc_clock); + qemu_mod_timer(s->timer, now + (int64_t)ticks * get_ticks_per_sec()); + } +} + +static uint64_t pl031_read(void *opaque, hwaddr offset, + unsigned size) +{ + pl031_state *s = (pl031_state *)opaque; + + if (offset >= 0xfe0 && offset < 0x1000) + return pl031_id[(offset - 0xfe0) >> 2]; + + switch (offset) { + case RTC_DR: + return pl031_get_count(s); + case RTC_MR: + return s->mr; + case RTC_IMSC: + return s->im; + case RTC_RIS: + return s->is; + case RTC_LR: + return s->lr; + case RTC_CR: + /* RTC is permanently enabled. */ + return 1; + case RTC_MIS: + return s->is & s->im; + case RTC_ICR: + qemu_log_mask(LOG_GUEST_ERROR, + "pl031: read of write-only register at offset 0x%x\n", + (int)offset); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pl031_read: Bad offset 0x%x\n", (int)offset); + break; + } + + return 0; +} + +static void pl031_write(void * opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + pl031_state *s = (pl031_state *)opaque; + + + switch (offset) { + case RTC_LR: + s->tick_offset += value - pl031_get_count(s); + pl031_set_alarm(s); + break; + case RTC_MR: + s->mr = value; + pl031_set_alarm(s); + break; + case RTC_IMSC: + s->im = value & 1; + DPRINTF("Interrupt mask %d\n", s->im); + pl031_update(s); + break; + case RTC_ICR: + /* The PL031 documentation (DDI0224B) states that the interrupt is + cleared when bit 0 of the written value is set. However the + arm926e documentation (DDI0287B) states that the interrupt is + cleared when any value is written. */ + DPRINTF("Interrupt cleared"); + s->is = 0; + pl031_update(s); + break; + case RTC_CR: + /* Written value is ignored. */ + break; + + case RTC_DR: + case RTC_MIS: + case RTC_RIS: + qemu_log_mask(LOG_GUEST_ERROR, + "pl031: write to read-only register at offset 0x%x\n", + (int)offset); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pl031_write: Bad offset 0x%x\n", (int)offset); + break; + } +} + +static const MemoryRegionOps pl031_ops = { + .read = pl031_read, + .write = pl031_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int pl031_init(SysBusDevice *dev) +{ + pl031_state *s = FROM_SYSBUS(pl031_state, dev); + struct tm tm; + + memory_region_init_io(&s->iomem, &pl031_ops, s, "pl031", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + + sysbus_init_irq(dev, &s->irq); + qemu_get_timedate(&tm, 0); + s->tick_offset = mktimegm(&tm) - qemu_get_clock_ns(rtc_clock) / get_ticks_per_sec(); + + s->timer = qemu_new_timer_ns(rtc_clock, pl031_interrupt, s); + return 0; +} + +static void pl031_pre_save(void *opaque) +{ + pl031_state *s = opaque; + + /* tick_offset is base_time - rtc_clock base time. Instead, we want to + * store the base time relative to the vm_clock for backwards-compatibility. */ + int64_t delta = qemu_get_clock_ns(rtc_clock) - qemu_get_clock_ns(vm_clock); + s->tick_offset_vmstate = s->tick_offset + delta / get_ticks_per_sec(); +} + +static int pl031_post_load(void *opaque, int version_id) +{ + pl031_state *s = opaque; + + int64_t delta = qemu_get_clock_ns(rtc_clock) - qemu_get_clock_ns(vm_clock); + s->tick_offset = s->tick_offset_vmstate - delta / get_ticks_per_sec(); + pl031_set_alarm(s); + return 0; +} + +static const VMStateDescription vmstate_pl031 = { + .name = "pl031", + .version_id = 1, + .minimum_version_id = 1, + .pre_save = pl031_pre_save, + .post_load = pl031_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32(tick_offset_vmstate, pl031_state), + VMSTATE_UINT32(mr, pl031_state), + VMSTATE_UINT32(lr, pl031_state), + VMSTATE_UINT32(cr, pl031_state), + VMSTATE_UINT32(im, pl031_state), + VMSTATE_UINT32(is, pl031_state), + VMSTATE_END_OF_LIST() + } +}; + +static void pl031_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = pl031_init; + dc->no_user = 1; + dc->vmsd = &vmstate_pl031; +} + +static const TypeInfo pl031_info = { + .name = "pl031", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(pl031_state), + .class_init = pl031_class_init, +}; + +static void pl031_register_types(void) +{ + type_register_static(&pl031_info); +} + +type_init(pl031_register_types) diff --git a/hw/timer/puv3_ost.c b/hw/timer/puv3_ost.c new file mode 100644 index 0000000000..0c3d827978 --- /dev/null +++ b/hw/timer/puv3_ost.c @@ -0,0 +1,151 @@ +/* + * OSTimer device simulation in PKUnity SoC + * + * Copyright (C) 2010-2012 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation, or any later version. + * See the COPYING file in the top-level directory. + */ +#include "hw/sysbus.h" +#include "hw/ptimer.h" + +#undef DEBUG_PUV3 +#include "hw/unicore32/puv3.h" + +/* puv3 ostimer implementation. */ +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + QEMUBH *bh; + qemu_irq irq; + ptimer_state *ptimer; + + uint32_t reg_OSMR0; + uint32_t reg_OSCR; + uint32_t reg_OSSR; + uint32_t reg_OIER; +} PUV3OSTState; + +static uint64_t puv3_ost_read(void *opaque, hwaddr offset, + unsigned size) +{ + PUV3OSTState *s = opaque; + uint32_t ret = 0; + + switch (offset) { + case 0x10: /* Counter Register */ + ret = s->reg_OSMR0 - (uint32_t)ptimer_get_count(s->ptimer); + break; + case 0x14: /* Status Register */ + ret = s->reg_OSSR; + break; + case 0x1c: /* Interrupt Enable Register */ + ret = s->reg_OIER; + break; + default: + DPRINTF("Bad offset %x\n", (int)offset); + } + DPRINTF("offset 0x%x, value 0x%x\n", offset, ret); + return ret; +} + +static void puv3_ost_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + PUV3OSTState *s = opaque; + + DPRINTF("offset 0x%x, value 0x%x\n", offset, value); + switch (offset) { + case 0x00: /* Match Register 0 */ + s->reg_OSMR0 = value; + if (s->reg_OSMR0 > s->reg_OSCR) { + ptimer_set_count(s->ptimer, s->reg_OSMR0 - s->reg_OSCR); + } else { + ptimer_set_count(s->ptimer, s->reg_OSMR0 + + (0xffffffff - s->reg_OSCR)); + } + ptimer_run(s->ptimer, 2); + break; + case 0x14: /* Status Register */ + assert(value == 0); + if (s->reg_OSSR) { + s->reg_OSSR = value; + qemu_irq_lower(s->irq); + } + break; + case 0x1c: /* Interrupt Enable Register */ + s->reg_OIER = value; + break; + default: + DPRINTF("Bad offset %x\n", (int)offset); + } +} + +static const MemoryRegionOps puv3_ost_ops = { + .read = puv3_ost_read, + .write = puv3_ost_write, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void puv3_ost_tick(void *opaque) +{ + PUV3OSTState *s = opaque; + + DPRINTF("ost hit when ptimer counter from 0x%x to 0x%x!\n", + s->reg_OSCR, s->reg_OSMR0); + + s->reg_OSCR = s->reg_OSMR0; + if (s->reg_OIER) { + s->reg_OSSR = 1; + qemu_irq_raise(s->irq); + } +} + +static int puv3_ost_init(SysBusDevice *dev) +{ + PUV3OSTState *s = FROM_SYSBUS(PUV3OSTState, dev); + + s->reg_OIER = 0; + s->reg_OSSR = 0; + s->reg_OSMR0 = 0; + s->reg_OSCR = 0; + + sysbus_init_irq(dev, &s->irq); + + s->bh = qemu_bh_new(puv3_ost_tick, s); + s->ptimer = ptimer_init(s->bh); + ptimer_set_freq(s->ptimer, 50 * 1000 * 1000); + + memory_region_init_io(&s->iomem, &puv3_ost_ops, s, "puv3_ost", + PUV3_REGS_OFFSET); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static void puv3_ost_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = puv3_ost_init; +} + +static const TypeInfo puv3_ost_info = { + .name = "puv3_ost", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PUV3OSTState), + .class_init = puv3_ost_class_init, +}; + +static void puv3_ost_register_type(void) +{ + type_register_static(&puv3_ost_info); +} + +type_init(puv3_ost_register_type) diff --git a/hw/timer/twl92230.c b/hw/timer/twl92230.c new file mode 100644 index 0000000000..b730d853f7 --- /dev/null +++ b/hw/timer/twl92230.c @@ -0,0 +1,882 @@ +/* + * TI TWL92230C energy-management companion device for the OMAP24xx. + * Aka. Menelaus (N4200 MENELAUS1_V2.2) + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski <andrew@openedhand.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/hw.h" +#include "qemu/timer.h" +#include "hw/i2c/i2c.h" +#include "sysemu/sysemu.h" +#include "ui/console.h" + +#define VERBOSE 1 + +typedef struct { + I2CSlave i2c; + + int firstbyte; + uint8_t reg; + + uint8_t vcore[5]; + uint8_t dcdc[3]; + uint8_t ldo[8]; + uint8_t sleep[2]; + uint8_t osc; + uint8_t detect; + uint16_t mask; + uint16_t status; + uint8_t dir; + uint8_t inputs; + uint8_t outputs; + uint8_t bbsms; + uint8_t pull[4]; + uint8_t mmc_ctrl[3]; + uint8_t mmc_debounce; + struct { + uint8_t ctrl; + uint16_t comp; + QEMUTimer *hz_tm; + int64_t next; + struct tm tm; + struct tm new; + struct tm alm; + int sec_offset; + int alm_sec; + int next_comp; + } rtc; + uint16_t rtc_next_vmstate; + qemu_irq out[4]; + uint8_t pwrbtn_state; +} MenelausState; + +static inline void menelaus_update(MenelausState *s) +{ + qemu_set_irq(s->out[3], s->status & ~s->mask); +} + +static inline void menelaus_rtc_start(MenelausState *s) +{ + s->rtc.next += qemu_get_clock_ms(rtc_clock); + qemu_mod_timer(s->rtc.hz_tm, s->rtc.next); +} + +static inline void menelaus_rtc_stop(MenelausState *s) +{ + qemu_del_timer(s->rtc.hz_tm); + s->rtc.next -= qemu_get_clock_ms(rtc_clock); + if (s->rtc.next < 1) + s->rtc.next = 1; +} + +static void menelaus_rtc_update(MenelausState *s) +{ + qemu_get_timedate(&s->rtc.tm, s->rtc.sec_offset); +} + +static void menelaus_alm_update(MenelausState *s) +{ + if ((s->rtc.ctrl & 3) == 3) + s->rtc.alm_sec = qemu_timedate_diff(&s->rtc.alm) - s->rtc.sec_offset; +} + +static void menelaus_rtc_hz(void *opaque) +{ + MenelausState *s = (MenelausState *) opaque; + + s->rtc.next_comp --; + s->rtc.alm_sec --; + s->rtc.next += 1000; + qemu_mod_timer(s->rtc.hz_tm, s->rtc.next); + if ((s->rtc.ctrl >> 3) & 3) { /* EVERY */ + menelaus_rtc_update(s); + if (((s->rtc.ctrl >> 3) & 3) == 1 && !s->rtc.tm.tm_sec) + s->status |= 1 << 8; /* RTCTMR */ + else if (((s->rtc.ctrl >> 3) & 3) == 2 && !s->rtc.tm.tm_min) + s->status |= 1 << 8; /* RTCTMR */ + else if (!s->rtc.tm.tm_hour) + s->status |= 1 << 8; /* RTCTMR */ + } else + s->status |= 1 << 8; /* RTCTMR */ + if ((s->rtc.ctrl >> 1) & 1) { /* RTC_AL_EN */ + if (s->rtc.alm_sec == 0) + s->status |= 1 << 9; /* RTCALM */ + /* TODO: wake-up */ + } + if (s->rtc.next_comp <= 0) { + s->rtc.next -= muldiv64((int16_t) s->rtc.comp, 1000, 0x8000); + s->rtc.next_comp = 3600; + } + menelaus_update(s); +} + +static void menelaus_reset(I2CSlave *i2c) +{ + MenelausState *s = (MenelausState *) i2c; + s->reg = 0x00; + + s->vcore[0] = 0x0c; /* XXX: X-loader needs 0x8c? check! */ + s->vcore[1] = 0x05; + s->vcore[2] = 0x02; + s->vcore[3] = 0x0c; + s->vcore[4] = 0x03; + s->dcdc[0] = 0x33; /* Depends on wiring */ + s->dcdc[1] = 0x03; + s->dcdc[2] = 0x00; + s->ldo[0] = 0x95; + s->ldo[1] = 0x7e; + s->ldo[2] = 0x00; + s->ldo[3] = 0x00; /* Depends on wiring */ + s->ldo[4] = 0x03; /* Depends on wiring */ + s->ldo[5] = 0x00; + s->ldo[6] = 0x00; + s->ldo[7] = 0x00; + s->sleep[0] = 0x00; + s->sleep[1] = 0x00; + s->osc = 0x01; + s->detect = 0x09; + s->mask = 0x0fff; + s->status = 0; + s->dir = 0x07; + s->outputs = 0x00; + s->bbsms = 0x00; + s->pull[0] = 0x00; + s->pull[1] = 0x00; + s->pull[2] = 0x00; + s->pull[3] = 0x00; + s->mmc_ctrl[0] = 0x03; + s->mmc_ctrl[1] = 0xc0; + s->mmc_ctrl[2] = 0x00; + s->mmc_debounce = 0x05; + + if (s->rtc.ctrl & 1) + menelaus_rtc_stop(s); + s->rtc.ctrl = 0x00; + s->rtc.comp = 0x0000; + s->rtc.next = 1000; + s->rtc.sec_offset = 0; + s->rtc.next_comp = 1800; + s->rtc.alm_sec = 1800; + s->rtc.alm.tm_sec = 0x00; + s->rtc.alm.tm_min = 0x00; + s->rtc.alm.tm_hour = 0x00; + s->rtc.alm.tm_mday = 0x01; + s->rtc.alm.tm_mon = 0x00; + s->rtc.alm.tm_year = 2004; + menelaus_update(s); +} + +static void menelaus_gpio_set(void *opaque, int line, int level) +{ + MenelausState *s = (MenelausState *) opaque; + + if (line < 3) { + /* No interrupt generated */ + s->inputs &= ~(1 << line); + s->inputs |= level << line; + return; + } + + if (!s->pwrbtn_state && level) { + s->status |= 1 << 11; /* PSHBTN */ + menelaus_update(s); + } + s->pwrbtn_state = level; +} + +#define MENELAUS_REV 0x01 +#define MENELAUS_VCORE_CTRL1 0x02 +#define MENELAUS_VCORE_CTRL2 0x03 +#define MENELAUS_VCORE_CTRL3 0x04 +#define MENELAUS_VCORE_CTRL4 0x05 +#define MENELAUS_VCORE_CTRL5 0x06 +#define MENELAUS_DCDC_CTRL1 0x07 +#define MENELAUS_DCDC_CTRL2 0x08 +#define MENELAUS_DCDC_CTRL3 0x09 +#define MENELAUS_LDO_CTRL1 0x0a +#define MENELAUS_LDO_CTRL2 0x0b +#define MENELAUS_LDO_CTRL3 0x0c +#define MENELAUS_LDO_CTRL4 0x0d +#define MENELAUS_LDO_CTRL5 0x0e +#define MENELAUS_LDO_CTRL6 0x0f +#define MENELAUS_LDO_CTRL7 0x10 +#define MENELAUS_LDO_CTRL8 0x11 +#define MENELAUS_SLEEP_CTRL1 0x12 +#define MENELAUS_SLEEP_CTRL2 0x13 +#define MENELAUS_DEVICE_OFF 0x14 +#define MENELAUS_OSC_CTRL 0x15 +#define MENELAUS_DETECT_CTRL 0x16 +#define MENELAUS_INT_MASK1 0x17 +#define MENELAUS_INT_MASK2 0x18 +#define MENELAUS_INT_STATUS1 0x19 +#define MENELAUS_INT_STATUS2 0x1a +#define MENELAUS_INT_ACK1 0x1b +#define MENELAUS_INT_ACK2 0x1c +#define MENELAUS_GPIO_CTRL 0x1d +#define MENELAUS_GPIO_IN 0x1e +#define MENELAUS_GPIO_OUT 0x1f +#define MENELAUS_BBSMS 0x20 +#define MENELAUS_RTC_CTRL 0x21 +#define MENELAUS_RTC_UPDATE 0x22 +#define MENELAUS_RTC_SEC 0x23 +#define MENELAUS_RTC_MIN 0x24 +#define MENELAUS_RTC_HR 0x25 +#define MENELAUS_RTC_DAY 0x26 +#define MENELAUS_RTC_MON 0x27 +#define MENELAUS_RTC_YR 0x28 +#define MENELAUS_RTC_WKDAY 0x29 +#define MENELAUS_RTC_AL_SEC 0x2a +#define MENELAUS_RTC_AL_MIN 0x2b +#define MENELAUS_RTC_AL_HR 0x2c +#define MENELAUS_RTC_AL_DAY 0x2d +#define MENELAUS_RTC_AL_MON 0x2e +#define MENELAUS_RTC_AL_YR 0x2f +#define MENELAUS_RTC_COMP_MSB 0x30 +#define MENELAUS_RTC_COMP_LSB 0x31 +#define MENELAUS_S1_PULL_EN 0x32 +#define MENELAUS_S1_PULL_DIR 0x33 +#define MENELAUS_S2_PULL_EN 0x34 +#define MENELAUS_S2_PULL_DIR 0x35 +#define MENELAUS_MCT_CTRL1 0x36 +#define MENELAUS_MCT_CTRL2 0x37 +#define MENELAUS_MCT_CTRL3 0x38 +#define MENELAUS_MCT_PIN_ST 0x39 +#define MENELAUS_DEBOUNCE1 0x3a + +static uint8_t menelaus_read(void *opaque, uint8_t addr) +{ + MenelausState *s = (MenelausState *) opaque; + int reg = 0; + + switch (addr) { + case MENELAUS_REV: + return 0x22; + + case MENELAUS_VCORE_CTRL5: reg ++; + case MENELAUS_VCORE_CTRL4: reg ++; + case MENELAUS_VCORE_CTRL3: reg ++; + case MENELAUS_VCORE_CTRL2: reg ++; + case MENELAUS_VCORE_CTRL1: + return s->vcore[reg]; + + case MENELAUS_DCDC_CTRL3: reg ++; + case MENELAUS_DCDC_CTRL2: reg ++; + case MENELAUS_DCDC_CTRL1: + return s->dcdc[reg]; + + case MENELAUS_LDO_CTRL8: reg ++; + case MENELAUS_LDO_CTRL7: reg ++; + case MENELAUS_LDO_CTRL6: reg ++; + case MENELAUS_LDO_CTRL5: reg ++; + case MENELAUS_LDO_CTRL4: reg ++; + case MENELAUS_LDO_CTRL3: reg ++; + case MENELAUS_LDO_CTRL2: reg ++; + case MENELAUS_LDO_CTRL1: + return s->ldo[reg]; + + case MENELAUS_SLEEP_CTRL2: reg ++; + case MENELAUS_SLEEP_CTRL1: + return s->sleep[reg]; + + case MENELAUS_DEVICE_OFF: + return 0; + + case MENELAUS_OSC_CTRL: + return s->osc | (1 << 7); /* CLK32K_GOOD */ + + case MENELAUS_DETECT_CTRL: + return s->detect; + + case MENELAUS_INT_MASK1: + return (s->mask >> 0) & 0xff; + case MENELAUS_INT_MASK2: + return (s->mask >> 8) & 0xff; + + case MENELAUS_INT_STATUS1: + return (s->status >> 0) & 0xff; + case MENELAUS_INT_STATUS2: + return (s->status >> 8) & 0xff; + + case MENELAUS_INT_ACK1: + case MENELAUS_INT_ACK2: + return 0; + + case MENELAUS_GPIO_CTRL: + return s->dir; + case MENELAUS_GPIO_IN: + return s->inputs | (~s->dir & s->outputs); + case MENELAUS_GPIO_OUT: + return s->outputs; + + case MENELAUS_BBSMS: + return s->bbsms; + + case MENELAUS_RTC_CTRL: + return s->rtc.ctrl; + case MENELAUS_RTC_UPDATE: + return 0x00; + case MENELAUS_RTC_SEC: + menelaus_rtc_update(s); + return to_bcd(s->rtc.tm.tm_sec); + case MENELAUS_RTC_MIN: + menelaus_rtc_update(s); + return to_bcd(s->rtc.tm.tm_min); + case MENELAUS_RTC_HR: + menelaus_rtc_update(s); + if ((s->rtc.ctrl >> 2) & 1) /* MODE12_n24 */ + return to_bcd((s->rtc.tm.tm_hour % 12) + 1) | + (!!(s->rtc.tm.tm_hour >= 12) << 7); /* PM_nAM */ + else + return to_bcd(s->rtc.tm.tm_hour); + case MENELAUS_RTC_DAY: + menelaus_rtc_update(s); + return to_bcd(s->rtc.tm.tm_mday); + case MENELAUS_RTC_MON: + menelaus_rtc_update(s); + return to_bcd(s->rtc.tm.tm_mon + 1); + case MENELAUS_RTC_YR: + menelaus_rtc_update(s); + return to_bcd(s->rtc.tm.tm_year - 2000); + case MENELAUS_RTC_WKDAY: + menelaus_rtc_update(s); + return to_bcd(s->rtc.tm.tm_wday); + case MENELAUS_RTC_AL_SEC: + return to_bcd(s->rtc.alm.tm_sec); + case MENELAUS_RTC_AL_MIN: + return to_bcd(s->rtc.alm.tm_min); + case MENELAUS_RTC_AL_HR: + if ((s->rtc.ctrl >> 2) & 1) /* MODE12_n24 */ + return to_bcd((s->rtc.alm.tm_hour % 12) + 1) | + (!!(s->rtc.alm.tm_hour >= 12) << 7);/* AL_PM_nAM */ + else + return to_bcd(s->rtc.alm.tm_hour); + case MENELAUS_RTC_AL_DAY: + return to_bcd(s->rtc.alm.tm_mday); + case MENELAUS_RTC_AL_MON: + return to_bcd(s->rtc.alm.tm_mon + 1); + case MENELAUS_RTC_AL_YR: + return to_bcd(s->rtc.alm.tm_year - 2000); + case MENELAUS_RTC_COMP_MSB: + return (s->rtc.comp >> 8) & 0xff; + case MENELAUS_RTC_COMP_LSB: + return (s->rtc.comp >> 0) & 0xff; + + case MENELAUS_S1_PULL_EN: + return s->pull[0]; + case MENELAUS_S1_PULL_DIR: + return s->pull[1]; + case MENELAUS_S2_PULL_EN: + return s->pull[2]; + case MENELAUS_S2_PULL_DIR: + return s->pull[3]; + + case MENELAUS_MCT_CTRL3: reg ++; + case MENELAUS_MCT_CTRL2: reg ++; + case MENELAUS_MCT_CTRL1: + return s->mmc_ctrl[reg]; + case MENELAUS_MCT_PIN_ST: + /* TODO: return the real Card Detect */ + return 0; + case MENELAUS_DEBOUNCE1: + return s->mmc_debounce; + + default: +#ifdef VERBOSE + printf("%s: unknown register %02x\n", __FUNCTION__, addr); +#endif + break; + } + return 0; +} + +static void menelaus_write(void *opaque, uint8_t addr, uint8_t value) +{ + MenelausState *s = (MenelausState *) opaque; + int line; + int reg = 0; + struct tm tm; + + switch (addr) { + case MENELAUS_VCORE_CTRL1: + s->vcore[0] = (value & 0xe) | MIN(value & 0x1f, 0x12); + break; + case MENELAUS_VCORE_CTRL2: + s->vcore[1] = value; + break; + case MENELAUS_VCORE_CTRL3: + s->vcore[2] = MIN(value & 0x1f, 0x12); + break; + case MENELAUS_VCORE_CTRL4: + s->vcore[3] = MIN(value & 0x1f, 0x12); + break; + case MENELAUS_VCORE_CTRL5: + s->vcore[4] = value & 3; + /* XXX + * auto set to 3 on M_Active, nRESWARM + * auto set to 0 on M_WaitOn, M_Backup + */ + break; + + case MENELAUS_DCDC_CTRL1: + s->dcdc[0] = value & 0x3f; + break; + case MENELAUS_DCDC_CTRL2: + s->dcdc[1] = value & 0x07; + /* XXX + * auto set to 3 on M_Active, nRESWARM + * auto set to 0 on M_WaitOn, M_Backup + */ + break; + case MENELAUS_DCDC_CTRL3: + s->dcdc[2] = value & 0x07; + break; + + case MENELAUS_LDO_CTRL1: + s->ldo[0] = value; + break; + case MENELAUS_LDO_CTRL2: + s->ldo[1] = value & 0x7f; + /* XXX + * auto set to 0x7e on M_WaitOn, M_Backup + */ + break; + case MENELAUS_LDO_CTRL3: + s->ldo[2] = value & 3; + /* XXX + * auto set to 3 on M_Active, nRESWARM + * auto set to 0 on M_WaitOn, M_Backup + */ + break; + case MENELAUS_LDO_CTRL4: + s->ldo[3] = value & 3; + /* XXX + * auto set to 3 on M_Active, nRESWARM + * auto set to 0 on M_WaitOn, M_Backup + */ + break; + case MENELAUS_LDO_CTRL5: + s->ldo[4] = value & 3; + /* XXX + * auto set to 3 on M_Active, nRESWARM + * auto set to 0 on M_WaitOn, M_Backup + */ + break; + case MENELAUS_LDO_CTRL6: + s->ldo[5] = value & 3; + break; + case MENELAUS_LDO_CTRL7: + s->ldo[6] = value & 3; + break; + case MENELAUS_LDO_CTRL8: + s->ldo[7] = value & 3; + break; + + case MENELAUS_SLEEP_CTRL2: reg ++; + case MENELAUS_SLEEP_CTRL1: + s->sleep[reg] = value; + break; + + case MENELAUS_DEVICE_OFF: + if (value & 1) + menelaus_reset(&s->i2c); + break; + + case MENELAUS_OSC_CTRL: + s->osc = value & 7; + break; + + case MENELAUS_DETECT_CTRL: + s->detect = value & 0x7f; + break; + + case MENELAUS_INT_MASK1: + s->mask &= 0xf00; + s->mask |= value << 0; + menelaus_update(s); + break; + case MENELAUS_INT_MASK2: + s->mask &= 0x0ff; + s->mask |= value << 8; + menelaus_update(s); + break; + + case MENELAUS_INT_ACK1: + s->status &= ~(((uint16_t) value) << 0); + menelaus_update(s); + break; + case MENELAUS_INT_ACK2: + s->status &= ~(((uint16_t) value) << 8); + menelaus_update(s); + break; + + case MENELAUS_GPIO_CTRL: + for (line = 0; line < 3; line ++) { + if (((s->dir ^ value) >> line) & 1) { + qemu_set_irq(s->out[line], + ((s->outputs & ~s->dir) >> line) & 1); + } + } + s->dir = value & 0x67; + break; + case MENELAUS_GPIO_OUT: + for (line = 0; line < 3; line ++) { + if ((((s->outputs ^ value) & ~s->dir) >> line) & 1) { + qemu_set_irq(s->out[line], (s->outputs >> line) & 1); + } + } + s->outputs = value & 0x07; + break; + + case MENELAUS_BBSMS: + s->bbsms = 0x0d; + break; + + case MENELAUS_RTC_CTRL: + if ((s->rtc.ctrl ^ value) & 1) { /* RTC_EN */ + if (value & 1) + menelaus_rtc_start(s); + else + menelaus_rtc_stop(s); + } + s->rtc.ctrl = value & 0x1f; + menelaus_alm_update(s); + break; + case MENELAUS_RTC_UPDATE: + menelaus_rtc_update(s); + memcpy(&tm, &s->rtc.tm, sizeof(tm)); + switch (value & 0xf) { + case 0: + break; + case 1: + tm.tm_sec = s->rtc.new.tm_sec; + break; + case 2: + tm.tm_min = s->rtc.new.tm_min; + break; + case 3: + if (s->rtc.new.tm_hour > 23) + goto rtc_badness; + tm.tm_hour = s->rtc.new.tm_hour; + break; + case 4: + if (s->rtc.new.tm_mday < 1) + goto rtc_badness; + /* TODO check range */ + tm.tm_mday = s->rtc.new.tm_mday; + break; + case 5: + if (s->rtc.new.tm_mon < 0 || s->rtc.new.tm_mon > 11) + goto rtc_badness; + tm.tm_mon = s->rtc.new.tm_mon; + break; + case 6: + tm.tm_year = s->rtc.new.tm_year; + break; + case 7: + /* TODO set .tm_mday instead */ + tm.tm_wday = s->rtc.new.tm_wday; + break; + case 8: + if (s->rtc.new.tm_hour > 23) + goto rtc_badness; + if (s->rtc.new.tm_mday < 1) + goto rtc_badness; + if (s->rtc.new.tm_mon < 0 || s->rtc.new.tm_mon > 11) + goto rtc_badness; + tm.tm_sec = s->rtc.new.tm_sec; + tm.tm_min = s->rtc.new.tm_min; + tm.tm_hour = s->rtc.new.tm_hour; + tm.tm_mday = s->rtc.new.tm_mday; + tm.tm_mon = s->rtc.new.tm_mon; + tm.tm_year = s->rtc.new.tm_year; + break; + rtc_badness: + default: + fprintf(stderr, "%s: bad RTC_UPDATE value %02x\n", + __FUNCTION__, value); + s->status |= 1 << 10; /* RTCERR */ + menelaus_update(s); + } + s->rtc.sec_offset = qemu_timedate_diff(&tm); + break; + case MENELAUS_RTC_SEC: + s->rtc.tm.tm_sec = from_bcd(value & 0x7f); + break; + case MENELAUS_RTC_MIN: + s->rtc.tm.tm_min = from_bcd(value & 0x7f); + break; + case MENELAUS_RTC_HR: + s->rtc.tm.tm_hour = (s->rtc.ctrl & (1 << 2)) ? /* MODE12_n24 */ + MIN(from_bcd(value & 0x3f), 12) + ((value >> 7) ? 11 : -1) : + from_bcd(value & 0x3f); + break; + case MENELAUS_RTC_DAY: + s->rtc.tm.tm_mday = from_bcd(value); + break; + case MENELAUS_RTC_MON: + s->rtc.tm.tm_mon = MAX(1, from_bcd(value)) - 1; + break; + case MENELAUS_RTC_YR: + s->rtc.tm.tm_year = 2000 + from_bcd(value); + break; + case MENELAUS_RTC_WKDAY: + s->rtc.tm.tm_mday = from_bcd(value); + break; + case MENELAUS_RTC_AL_SEC: + s->rtc.alm.tm_sec = from_bcd(value & 0x7f); + menelaus_alm_update(s); + break; + case MENELAUS_RTC_AL_MIN: + s->rtc.alm.tm_min = from_bcd(value & 0x7f); + menelaus_alm_update(s); + break; + case MENELAUS_RTC_AL_HR: + s->rtc.alm.tm_hour = (s->rtc.ctrl & (1 << 2)) ? /* MODE12_n24 */ + MIN(from_bcd(value & 0x3f), 12) + ((value >> 7) ? 11 : -1) : + from_bcd(value & 0x3f); + menelaus_alm_update(s); + break; + case MENELAUS_RTC_AL_DAY: + s->rtc.alm.tm_mday = from_bcd(value); + menelaus_alm_update(s); + break; + case MENELAUS_RTC_AL_MON: + s->rtc.alm.tm_mon = MAX(1, from_bcd(value)) - 1; + menelaus_alm_update(s); + break; + case MENELAUS_RTC_AL_YR: + s->rtc.alm.tm_year = 2000 + from_bcd(value); + menelaus_alm_update(s); + break; + case MENELAUS_RTC_COMP_MSB: + s->rtc.comp &= 0xff; + s->rtc.comp |= value << 8; + break; + case MENELAUS_RTC_COMP_LSB: + s->rtc.comp &= 0xff << 8; + s->rtc.comp |= value; + break; + + case MENELAUS_S1_PULL_EN: + s->pull[0] = value; + break; + case MENELAUS_S1_PULL_DIR: + s->pull[1] = value & 0x1f; + break; + case MENELAUS_S2_PULL_EN: + s->pull[2] = value; + break; + case MENELAUS_S2_PULL_DIR: + s->pull[3] = value & 0x1f; + break; + + case MENELAUS_MCT_CTRL1: + s->mmc_ctrl[0] = value & 0x7f; + break; + case MENELAUS_MCT_CTRL2: + s->mmc_ctrl[1] = value; + /* TODO update Card Detect interrupts */ + break; + case MENELAUS_MCT_CTRL3: + s->mmc_ctrl[2] = value & 0xf; + break; + case MENELAUS_DEBOUNCE1: + s->mmc_debounce = value & 0x3f; + break; + + default: +#ifdef VERBOSE + printf("%s: unknown register %02x\n", __FUNCTION__, addr); +#endif + } +} + +static void menelaus_event(I2CSlave *i2c, enum i2c_event event) +{ + MenelausState *s = (MenelausState *) i2c; + + if (event == I2C_START_SEND) + s->firstbyte = 1; +} + +static int menelaus_tx(I2CSlave *i2c, uint8_t data) +{ + MenelausState *s = (MenelausState *) i2c; + /* Interpret register address byte */ + if (s->firstbyte) { + s->reg = data; + s->firstbyte = 0; + } else + menelaus_write(s, s->reg ++, data); + + return 0; +} + +static int menelaus_rx(I2CSlave *i2c) +{ + MenelausState *s = (MenelausState *) i2c; + + return menelaus_read(s, s->reg ++); +} + +/* Save restore 32 bit int as uint16_t + This is a Big hack, but it is how the old state did it. + Or we broke compatibility in the state, or we can't use struct tm + */ + +static int get_int32_as_uint16(QEMUFile *f, void *pv, size_t size) +{ + int *v = pv; + *v = qemu_get_be16(f); + return 0; +} + +static void put_int32_as_uint16(QEMUFile *f, void *pv, size_t size) +{ + int *v = pv; + qemu_put_be16(f, *v); +} + +static const VMStateInfo vmstate_hack_int32_as_uint16 = { + .name = "int32_as_uint16", + .get = get_int32_as_uint16, + .put = put_int32_as_uint16, +}; + +#define VMSTATE_UINT16_HACK(_f, _s) \ + VMSTATE_SINGLE(_f, _s, 0, vmstate_hack_int32_as_uint16, int32_t) + + +static const VMStateDescription vmstate_menelaus_tm = { + .name = "menelaus_tm", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField []) { + VMSTATE_UINT16_HACK(tm_sec, struct tm), + VMSTATE_UINT16_HACK(tm_min, struct tm), + VMSTATE_UINT16_HACK(tm_hour, struct tm), + VMSTATE_UINT16_HACK(tm_mday, struct tm), + VMSTATE_UINT16_HACK(tm_min, struct tm), + VMSTATE_UINT16_HACK(tm_year, struct tm), + VMSTATE_END_OF_LIST() + } +}; + +static void menelaus_pre_save(void *opaque) +{ + MenelausState *s = opaque; + /* Should be <= 1000 */ + s->rtc_next_vmstate = s->rtc.next - qemu_get_clock_ms(rtc_clock); +} + +static int menelaus_post_load(void *opaque, int version_id) +{ + MenelausState *s = opaque; + + if (s->rtc.ctrl & 1) /* RTC_EN */ + menelaus_rtc_stop(s); + + s->rtc.next = s->rtc_next_vmstate; + + menelaus_alm_update(s); + menelaus_update(s); + if (s->rtc.ctrl & 1) /* RTC_EN */ + menelaus_rtc_start(s); + return 0; +} + +static const VMStateDescription vmstate_menelaus = { + .name = "menelaus", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .pre_save = menelaus_pre_save, + .post_load = menelaus_post_load, + .fields = (VMStateField []) { + VMSTATE_INT32(firstbyte, MenelausState), + VMSTATE_UINT8(reg, MenelausState), + VMSTATE_UINT8_ARRAY(vcore, MenelausState, 5), + VMSTATE_UINT8_ARRAY(dcdc, MenelausState, 3), + VMSTATE_UINT8_ARRAY(ldo, MenelausState, 8), + VMSTATE_UINT8_ARRAY(sleep, MenelausState, 2), + VMSTATE_UINT8(osc, MenelausState), + VMSTATE_UINT8(detect, MenelausState), + VMSTATE_UINT16(mask, MenelausState), + VMSTATE_UINT16(status, MenelausState), + VMSTATE_UINT8(dir, MenelausState), + VMSTATE_UINT8(inputs, MenelausState), + VMSTATE_UINT8(outputs, MenelausState), + VMSTATE_UINT8(bbsms, MenelausState), + VMSTATE_UINT8_ARRAY(pull, MenelausState, 4), + VMSTATE_UINT8_ARRAY(mmc_ctrl, MenelausState, 3), + VMSTATE_UINT8(mmc_debounce, MenelausState), + VMSTATE_UINT8(rtc.ctrl, MenelausState), + VMSTATE_UINT16(rtc.comp, MenelausState), + VMSTATE_UINT16(rtc_next_vmstate, MenelausState), + VMSTATE_STRUCT(rtc.new, MenelausState, 0, vmstate_menelaus_tm, + struct tm), + VMSTATE_STRUCT(rtc.alm, MenelausState, 0, vmstate_menelaus_tm, + struct tm), + VMSTATE_UINT8(pwrbtn_state, MenelausState), + VMSTATE_I2C_SLAVE(i2c, MenelausState), + VMSTATE_END_OF_LIST() + } +}; + +static int twl92230_init(I2CSlave *i2c) +{ + MenelausState *s = FROM_I2C_SLAVE(MenelausState, i2c); + + s->rtc.hz_tm = qemu_new_timer_ms(rtc_clock, menelaus_rtc_hz, s); + /* Three output pins plus one interrupt pin. */ + qdev_init_gpio_out(&i2c->qdev, s->out, 4); + + /* Three input pins plus one power-button pin. */ + qdev_init_gpio_in(&i2c->qdev, menelaus_gpio_set, 4); + + menelaus_reset(&s->i2c); + + return 0; +} + +static void twl92230_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass); + + sc->init = twl92230_init; + sc->event = menelaus_event; + sc->recv = menelaus_rx; + sc->send = menelaus_tx; + dc->vmsd = &vmstate_menelaus; +} + +static const TypeInfo twl92230_info = { + .name = "twl92230", + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(MenelausState), + .class_init = twl92230_class_init, +}; + +static void twl92230_register_types(void) +{ + type_register_static(&twl92230_info); +} + +type_init(twl92230_register_types) diff --git a/hw/timer/xilinx_timer.c b/hw/timer/xilinx_timer.c new file mode 100644 index 0000000000..0c39cff089 --- /dev/null +++ b/hw/timer/xilinx_timer.c @@ -0,0 +1,255 @@ +/* + * QEMU model of the Xilinx timer block. + * + * Copyright (c) 2009 Edgar E. Iglesias. + * + * 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 "hw/sysbus.h" +#include "hw/ptimer.h" +#include "qemu/log.h" + +#define D(x) + +#define R_TCSR 0 +#define R_TLR 1 +#define R_TCR 2 +#define R_MAX 4 + +#define TCSR_MDT (1<<0) +#define TCSR_UDT (1<<1) +#define TCSR_GENT (1<<2) +#define TCSR_CAPT (1<<3) +#define TCSR_ARHT (1<<4) +#define TCSR_LOAD (1<<5) +#define TCSR_ENIT (1<<6) +#define TCSR_ENT (1<<7) +#define TCSR_TINT (1<<8) +#define TCSR_PWMA (1<<9) +#define TCSR_ENALL (1<<10) + +struct xlx_timer +{ + QEMUBH *bh; + ptimer_state *ptimer; + void *parent; + int nr; /* for debug. */ + + unsigned long timer_div; + + uint32_t regs[R_MAX]; +}; + +struct timerblock +{ + SysBusDevice busdev; + MemoryRegion mmio; + qemu_irq irq; + uint8_t one_timer_only; + uint32_t freq_hz; + struct xlx_timer *timers; +}; + +static inline unsigned int num_timers(struct timerblock *t) +{ + return 2 - t->one_timer_only; +} + +static inline unsigned int timer_from_addr(hwaddr addr) +{ + /* Timers get a 4x32bit control reg area each. */ + return addr >> 2; +} + +static void timer_update_irq(struct timerblock *t) +{ + unsigned int i, irq = 0; + uint32_t csr; + + for (i = 0; i < num_timers(t); i++) { + csr = t->timers[i].regs[R_TCSR]; + irq |= (csr & TCSR_TINT) && (csr & TCSR_ENIT); + } + + /* All timers within the same slave share a single IRQ line. */ + qemu_set_irq(t->irq, !!irq); +} + +static uint64_t +timer_read(void *opaque, hwaddr addr, unsigned int size) +{ + struct timerblock *t = opaque; + struct xlx_timer *xt; + uint32_t r = 0; + unsigned int timer; + + addr >>= 2; + timer = timer_from_addr(addr); + xt = &t->timers[timer]; + /* Further decoding to address a specific timers reg. */ + addr &= 0x3; + switch (addr) + { + case R_TCR: + r = ptimer_get_count(xt->ptimer); + if (!(xt->regs[R_TCSR] & TCSR_UDT)) + r = ~r; + D(qemu_log("xlx_timer t=%d read counter=%x udt=%d\n", + timer, r, xt->regs[R_TCSR] & TCSR_UDT)); + break; + default: + if (addr < ARRAY_SIZE(xt->regs)) + r = xt->regs[addr]; + break; + + } + D(fprintf(stderr, "%s timer=%d %x=%x\n", __func__, timer, addr * 4, r)); + return r; +} + +static void timer_enable(struct xlx_timer *xt) +{ + uint64_t count; + + D(fprintf(stderr, "%s timer=%d down=%d\n", __func__, + xt->nr, xt->regs[R_TCSR] & TCSR_UDT)); + + ptimer_stop(xt->ptimer); + + if (xt->regs[R_TCSR] & TCSR_UDT) + count = xt->regs[R_TLR]; + else + count = ~0 - xt->regs[R_TLR]; + ptimer_set_limit(xt->ptimer, count, 1); + ptimer_run(xt->ptimer, 1); +} + +static void +timer_write(void *opaque, hwaddr addr, + uint64_t val64, unsigned int size) +{ + struct timerblock *t = opaque; + struct xlx_timer *xt; + unsigned int timer; + uint32_t value = val64; + + addr >>= 2; + timer = timer_from_addr(addr); + xt = &t->timers[timer]; + D(fprintf(stderr, "%s addr=%x val=%x (timer=%d off=%d)\n", + __func__, addr * 4, value, timer, addr & 3)); + /* Further decoding to address a specific timers reg. */ + addr &= 3; + switch (addr) + { + case R_TCSR: + if (value & TCSR_TINT) + value &= ~TCSR_TINT; + + xt->regs[addr] = value; + if (value & TCSR_ENT) + timer_enable(xt); + break; + + default: + if (addr < ARRAY_SIZE(xt->regs)) + xt->regs[addr] = value; + break; + } + timer_update_irq(t); +} + +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) +{ + struct xlx_timer *xt = opaque; + struct timerblock *t = xt->parent; + D(fprintf(stderr, "%s %d\n", __func__, xt->nr)); + xt->regs[R_TCSR] |= TCSR_TINT; + + if (xt->regs[R_TCSR] & TCSR_ARHT) + timer_enable(xt); + timer_update_irq(t); +} + +static int xilinx_timer_init(SysBusDevice *dev) +{ + struct timerblock *t = FROM_SYSBUS(typeof (*t), dev); + unsigned int i; + + /* All timers share a single irq line. */ + sysbus_init_irq(dev, &t->irq); + + /* Init all the ptimers. */ + t->timers = g_malloc0(sizeof t->timers[0] * num_timers(t)); + for (i = 0; i < num_timers(t); i++) { + struct xlx_timer *xt = &t->timers[i]; + + xt->parent = t; + xt->nr = i; + xt->bh = qemu_bh_new(timer_hit, xt); + xt->ptimer = ptimer_init(xt->bh); + ptimer_set_freq(xt->ptimer, t->freq_hz); + } + + memory_region_init_io(&t->mmio, &timer_ops, t, "xlnx.xps-timer", + R_MAX * 4 * num_timers(t)); + sysbus_init_mmio(dev, &t->mmio); + return 0; +} + +static Property xilinx_timer_properties[] = { + DEFINE_PROP_UINT32("clock-frequency", struct timerblock, freq_hz, + 62 * 1000000), + DEFINE_PROP_UINT8("one-timer-only", struct timerblock, one_timer_only, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void xilinx_timer_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = xilinx_timer_init; + dc->props = xilinx_timer_properties; +} + +static const TypeInfo xilinx_timer_info = { + .name = "xlnx.xps-timer", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(struct timerblock), + .class_init = xilinx_timer_class_init, +}; + +static void xilinx_timer_register_types(void) +{ + type_register_static(&xilinx_timer_info); +} + +type_init(xilinx_timer_register_types) |