hw: move target-independent files to subdirectories
This patch tackles all files that are compiled once, moving them to subdirectories of hw/. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
1 parent
ce3b494cb5
commit
49ab747f66
227 files changed
+205
-208
No files matched your search
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
+760
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user