blob: 5aa78a91fdfb62641fc336b2c4290469b9d1ef9b [file] [log] [blame]
liguang3589de82013-12-17 19:42:37 +00001/*
2 * Allwinner A10 timer device emulation
3 *
4 * Copyright (C) 2013 Li Guang
5 * Written by Li Guang <lig.fnst@cn.fujitsu.com>
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 */
17
18#include "hw/sysbus.h"
19#include "sysemu/sysemu.h"
20#include "hw/timer/allwinner-a10-pit.h"
21
Beniamino Galvania63f9f82014-03-25 19:22:07 +010022static void a10_pit_update_irq(AwA10PITState *s)
23{
24 int i;
25
26 for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
27 qemu_set_irq(s->irq[i], !!(s->irq_status & s->irq_enable & (1 << i)));
28 }
29}
30
liguang3589de82013-12-17 19:42:37 +000031static uint64_t a10_pit_read(void *opaque, hwaddr offset, unsigned size)
32{
33 AwA10PITState *s = AW_A10_PIT(opaque);
34 uint8_t index;
35
36 switch (offset) {
37 case AW_A10_PIT_TIMER_IRQ_EN:
38 return s->irq_enable;
39 case AW_A10_PIT_TIMER_IRQ_ST:
40 return s->irq_status;
41 case AW_A10_PIT_TIMER_BASE ... AW_A10_PIT_TIMER_BASE_END:
42 index = offset & 0xf0;
43 index >>= 4;
44 index -= 1;
45 switch (offset & 0x0f) {
46 case AW_A10_PIT_TIMER_CONTROL:
47 return s->control[index];
48 case AW_A10_PIT_TIMER_INTERVAL:
49 return s->interval[index];
50 case AW_A10_PIT_TIMER_COUNT:
51 s->count[index] = ptimer_get_count(s->timer[index]);
52 return s->count[index];
53 default:
54 qemu_log_mask(LOG_GUEST_ERROR,
55 "%s: Bad offset 0x%x\n", __func__, (int)offset);
56 break;
57 }
58 case AW_A10_PIT_WDOG_CONTROL:
59 break;
60 case AW_A10_PIT_WDOG_MODE:
61 break;
62 case AW_A10_PIT_COUNT_LO:
63 return s->count_lo;
64 case AW_A10_PIT_COUNT_HI:
65 return s->count_hi;
66 case AW_A10_PIT_COUNT_CTL:
67 return s->count_ctl;
68 default:
69 qemu_log_mask(LOG_GUEST_ERROR,
70 "%s: Bad offset 0x%x\n", __func__, (int)offset);
71 break;
72 }
73
74 return 0;
75}
76
77static void a10_pit_write(void *opaque, hwaddr offset, uint64_t value,
78 unsigned size)
79{
80 AwA10PITState *s = AW_A10_PIT(opaque);
81 uint8_t index;
82
83 switch (offset) {
84 case AW_A10_PIT_TIMER_IRQ_EN:
85 s->irq_enable = value;
Beniamino Galvania63f9f82014-03-25 19:22:07 +010086 a10_pit_update_irq(s);
liguang3589de82013-12-17 19:42:37 +000087 break;
88 case AW_A10_PIT_TIMER_IRQ_ST:
89 s->irq_status &= ~value;
Beniamino Galvania63f9f82014-03-25 19:22:07 +010090 a10_pit_update_irq(s);
liguang3589de82013-12-17 19:42:37 +000091 break;
92 case AW_A10_PIT_TIMER_BASE ... AW_A10_PIT_TIMER_BASE_END:
93 index = offset & 0xf0;
94 index >>= 4;
95 index -= 1;
96 switch (offset & 0x0f) {
97 case AW_A10_PIT_TIMER_CONTROL:
98 s->control[index] = value;
99 if (s->control[index] & AW_A10_PIT_TIMER_RELOAD) {
100 ptimer_set_count(s->timer[index], s->interval[index]);
101 }
102 if (s->control[index] & AW_A10_PIT_TIMER_EN) {
103 int oneshot = 0;
104 if (s->control[index] & AW_A10_PIT_TIMER_MODE) {
105 oneshot = 1;
106 }
107 ptimer_run(s->timer[index], oneshot);
108 } else {
109 ptimer_stop(s->timer[index]);
110 }
111 break;
112 case AW_A10_PIT_TIMER_INTERVAL:
113 s->interval[index] = value;
114 ptimer_set_limit(s->timer[index], s->interval[index], 1);
115 break;
116 case AW_A10_PIT_TIMER_COUNT:
117 s->count[index] = value;
118 break;
119 default:
120 qemu_log_mask(LOG_GUEST_ERROR,
121 "%s: Bad offset 0x%x\n", __func__, (int)offset);
122 }
123 break;
124 case AW_A10_PIT_WDOG_CONTROL:
125 s->watch_dog_control = value;
126 break;
127 case AW_A10_PIT_WDOG_MODE:
128 s->watch_dog_mode = value;
129 break;
130 case AW_A10_PIT_COUNT_LO:
131 s->count_lo = value;
132 break;
133 case AW_A10_PIT_COUNT_HI:
134 s->count_hi = value;
135 break;
136 case AW_A10_PIT_COUNT_CTL:
137 s->count_ctl = value;
138 if (s->count_ctl & AW_A10_PIT_COUNT_RL_EN) {
139 uint64_t tmp_count = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
140
141 s->count_lo = tmp_count;
142 s->count_hi = tmp_count >> 32;
143 s->count_ctl &= ~AW_A10_PIT_COUNT_RL_EN;
144 }
145 if (s->count_ctl & AW_A10_PIT_COUNT_CLR_EN) {
146 s->count_lo = 0;
147 s->count_hi = 0;
148 s->count_ctl &= ~AW_A10_PIT_COUNT_CLR_EN;
149 }
150 break;
151 default:
152 qemu_log_mask(LOG_GUEST_ERROR,
153 "%s: Bad offset 0x%x\n", __func__, (int)offset);
154 break;
155 }
156}
157
158static const MemoryRegionOps a10_pit_ops = {
159 .read = a10_pit_read,
160 .write = a10_pit_write,
161 .endianness = DEVICE_NATIVE_ENDIAN,
162};
163
164static const VMStateDescription vmstate_a10_pit = {
165 .name = "a10.pit",
166 .version_id = 1,
167 .minimum_version_id = 1,
168 .minimum_version_id_old = 1,
169 .fields = (VMStateField[]) {
170 VMSTATE_UINT32(irq_enable, AwA10PITState),
171 VMSTATE_UINT32(irq_status, AwA10PITState),
172 VMSTATE_UINT32_ARRAY(control, AwA10PITState, AW_A10_PIT_TIMER_NR),
173 VMSTATE_UINT32_ARRAY(interval, AwA10PITState, AW_A10_PIT_TIMER_NR),
174 VMSTATE_UINT32_ARRAY(count, AwA10PITState, AW_A10_PIT_TIMER_NR),
175 VMSTATE_UINT32(watch_dog_mode, AwA10PITState),
176 VMSTATE_UINT32(watch_dog_control, AwA10PITState),
177 VMSTATE_UINT32(count_lo, AwA10PITState),
178 VMSTATE_UINT32(count_hi, AwA10PITState),
179 VMSTATE_UINT32(count_ctl, AwA10PITState),
180 VMSTATE_PTIMER_ARRAY(timer, AwA10PITState, AW_A10_PIT_TIMER_NR),
181 VMSTATE_END_OF_LIST()
182 }
183};
184
185static void a10_pit_reset(DeviceState *dev)
186{
187 AwA10PITState *s = AW_A10_PIT(dev);
188 uint8_t i;
189
190 s->irq_enable = 0;
191 s->irq_status = 0;
Beniamino Galvania63f9f82014-03-25 19:22:07 +0100192 a10_pit_update_irq(s);
193
liguang3589de82013-12-17 19:42:37 +0000194 for (i = 0; i < 6; i++) {
195 s->control[i] = AW_A10_PIT_DEFAULT_CLOCK;
196 s->interval[i] = 0;
197 s->count[i] = 0;
198 ptimer_stop(s->timer[i]);
199 }
200 s->watch_dog_mode = 0;
201 s->watch_dog_control = 0;
202 s->count_lo = 0;
203 s->count_hi = 0;
204 s->count_ctl = 0;
205}
206
207static void a10_pit_timer_cb(void *opaque)
208{
Beniamino Galvani323a8772014-03-25 19:22:06 +0100209 AwA10TimerContext *tc = opaque;
210 AwA10PITState *s = tc->container;
211 uint8_t i = tc->index;
liguang3589de82013-12-17 19:42:37 +0000212
Beniamino Galvani323a8772014-03-25 19:22:06 +0100213 if (s->control[i] & AW_A10_PIT_TIMER_EN) {
214 s->irq_status |= 1 << i;
215 if (s->control[i] & AW_A10_PIT_TIMER_MODE) {
216 ptimer_stop(s->timer[i]);
217 s->control[i] &= ~AW_A10_PIT_TIMER_EN;
liguang3589de82013-12-17 19:42:37 +0000218 }
Beniamino Galvania63f9f82014-03-25 19:22:07 +0100219 a10_pit_update_irq(s);
liguang3589de82013-12-17 19:42:37 +0000220 }
221}
222
223static void a10_pit_init(Object *obj)
224{
225 AwA10PITState *s = AW_A10_PIT(obj);
226 SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
227 QEMUBH * bh[AW_A10_PIT_TIMER_NR];
228 uint8_t i;
229
230 for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
231 sysbus_init_irq(sbd, &s->irq[i]);
232 }
233 memory_region_init_io(&s->iomem, OBJECT(s), &a10_pit_ops, s,
234 TYPE_AW_A10_PIT, 0x400);
235 sysbus_init_mmio(sbd, &s->iomem);
236
237 for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) {
Beniamino Galvani323a8772014-03-25 19:22:06 +0100238 AwA10TimerContext *tc = &s->timer_context[i];
239
240 tc->container = s;
241 tc->index = i;
242 bh[i] = qemu_bh_new(a10_pit_timer_cb, tc);
liguang3589de82013-12-17 19:42:37 +0000243 s->timer[i] = ptimer_init(bh[i]);
244 ptimer_set_freq(s->timer[i], 240000);
245 }
246}
247
248static void a10_pit_class_init(ObjectClass *klass, void *data)
249{
250 DeviceClass *dc = DEVICE_CLASS(klass);
251
252 dc->reset = a10_pit_reset;
253 dc->desc = "allwinner a10 timer";
254 dc->vmsd = &vmstate_a10_pit;
255}
256
257static const TypeInfo a10_pit_info = {
258 .name = TYPE_AW_A10_PIT,
259 .parent = TYPE_SYS_BUS_DEVICE,
260 .instance_size = sizeof(AwA10PITState),
261 .instance_init = a10_pit_init,
262 .class_init = a10_pit_class_init,
263};
264
265static void a10_register_types(void)
266{
267 type_register_static(&a10_pit_info);
268}
269
270type_init(a10_register_types);