]>
Commit | Line | Data |
---|---|---|
0f3a4a01 FC |
1 | /* |
2 | * QEMU GRLIB GPTimer Emulator | |
3 | * | |
4 | * Copyright (c) 2010-2011 AdaCore | |
5 | * | |
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
7 | * of this software and associated documentation files (the "Software"), to deal | |
8 | * in the Software without restriction, including without limitation the rights | |
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | * copies of the Software, and to permit persons to whom the Software is | |
11 | * furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in | |
14 | * all copies or substantial portions of the Software. | |
15 | * | |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
22 | * THE SOFTWARE. | |
23 | */ | |
24 | ||
25 | #include "sysbus.h" | |
26 | #include "qemu-timer.h" | |
27 | ||
28 | #include "trace.h" | |
29 | ||
30 | #define UNIT_REG_SIZE 16 /* Size of memory mapped regs for the unit */ | |
31 | #define GPTIMER_REG_SIZE 16 /* Size of memory mapped regs for a GPTimer */ | |
32 | ||
33 | #define GPTIMER_MAX_TIMERS 8 | |
34 | ||
35 | /* GPTimer Config register fields */ | |
36 | #define GPTIMER_ENABLE (1 << 0) | |
37 | #define GPTIMER_RESTART (1 << 1) | |
38 | #define GPTIMER_LOAD (1 << 2) | |
39 | #define GPTIMER_INT_ENABLE (1 << 3) | |
40 | #define GPTIMER_INT_PENDING (1 << 4) | |
41 | #define GPTIMER_CHAIN (1 << 5) /* Not supported */ | |
42 | #define GPTIMER_DEBUG_HALT (1 << 6) /* Not supported */ | |
43 | ||
44 | /* Memory mapped register offsets */ | |
45 | #define SCALER_OFFSET 0x00 | |
46 | #define SCALER_RELOAD_OFFSET 0x04 | |
47 | #define CONFIG_OFFSET 0x08 | |
48 | #define COUNTER_OFFSET 0x00 | |
49 | #define COUNTER_RELOAD_OFFSET 0x04 | |
50 | #define TIMER_BASE 0x10 | |
51 | ||
52 | typedef struct GPTimer GPTimer; | |
53 | typedef struct GPTimerUnit GPTimerUnit; | |
54 | ||
55 | struct GPTimer { | |
56 | QEMUBH *bh; | |
57 | struct ptimer_state *ptimer; | |
58 | ||
59 | qemu_irq irq; | |
60 | int id; | |
61 | GPTimerUnit *unit; | |
62 | ||
63 | /* registers */ | |
64 | uint32_t counter; | |
65 | uint32_t reload; | |
66 | uint32_t config; | |
67 | }; | |
68 | ||
69 | struct GPTimerUnit { | |
70 | SysBusDevice busdev; | |
cde844fa | 71 | MemoryRegion iomem; |
0f3a4a01 FC |
72 | |
73 | uint32_t nr_timers; /* Number of timers available */ | |
74 | uint32_t freq_hz; /* System frequency */ | |
75 | uint32_t irq_line; /* Base irq line */ | |
76 | ||
77 | GPTimer *timers; | |
78 | ||
79 | /* registers */ | |
80 | uint32_t scaler; | |
81 | uint32_t reload; | |
82 | uint32_t config; | |
83 | }; | |
84 | ||
85 | static void grlib_gptimer_enable(GPTimer *timer) | |
86 | { | |
87 | assert(timer != NULL); | |
88 | ||
89 | ||
90 | ptimer_stop(timer->ptimer); | |
91 | ||
92 | if (!(timer->config & GPTIMER_ENABLE)) { | |
93 | /* Timer disabled */ | |
94 | trace_grlib_gptimer_disabled(timer->id, timer->config); | |
95 | return; | |
96 | } | |
97 | ||
98 | /* ptimer is triggered when the counter reach 0 but GPTimer is triggered at | |
99 | underflow. Set count + 1 to simulate the GPTimer behavior. */ | |
100 | ||
101 | trace_grlib_gptimer_enable(timer->id, timer->counter + 1); | |
102 | ||
103 | ptimer_set_count(timer->ptimer, timer->counter + 1); | |
104 | ptimer_run(timer->ptimer, 1); | |
105 | } | |
106 | ||
107 | static void grlib_gptimer_restart(GPTimer *timer) | |
108 | { | |
109 | assert(timer != NULL); | |
110 | ||
111 | trace_grlib_gptimer_restart(timer->id, timer->reload); | |
112 | ||
113 | timer->counter = timer->reload; | |
114 | grlib_gptimer_enable(timer); | |
115 | } | |
116 | ||
117 | static void grlib_gptimer_set_scaler(GPTimerUnit *unit, uint32_t scaler) | |
118 | { | |
119 | int i = 0; | |
120 | uint32_t value = 0; | |
121 | ||
122 | assert(unit != NULL); | |
123 | ||
124 | if (scaler > 0) { | |
125 | value = unit->freq_hz / (scaler + 1); | |
126 | } else { | |
127 | value = unit->freq_hz; | |
128 | } | |
129 | ||
130 | trace_grlib_gptimer_set_scaler(scaler, value); | |
131 | ||
132 | for (i = 0; i < unit->nr_timers; i++) { | |
133 | ptimer_set_freq(unit->timers[i].ptimer, value); | |
134 | } | |
135 | } | |
136 | ||
137 | static void grlib_gptimer_hit(void *opaque) | |
138 | { | |
139 | GPTimer *timer = opaque; | |
140 | assert(timer != NULL); | |
141 | ||
142 | trace_grlib_gptimer_hit(timer->id); | |
143 | ||
144 | /* Timer expired */ | |
145 | ||
146 | if (timer->config & GPTIMER_INT_ENABLE) { | |
147 | /* Set the pending bit (only unset by write in the config register) */ | |
148 | timer->config |= GPTIMER_INT_PENDING; | |
149 | qemu_irq_pulse(timer->irq); | |
150 | } | |
151 | ||
152 | if (timer->config & GPTIMER_RESTART) { | |
153 | grlib_gptimer_restart(timer); | |
154 | } | |
155 | } | |
156 | ||
cde844fa AK |
157 | static uint64_t grlib_gptimer_read(void *opaque, target_phys_addr_t addr, |
158 | unsigned size) | |
0f3a4a01 FC |
159 | { |
160 | GPTimerUnit *unit = opaque; | |
161 | target_phys_addr_t timer_addr; | |
162 | int id; | |
163 | uint32_t value = 0; | |
164 | ||
165 | addr &= 0xff; | |
166 | ||
167 | /* Unit registers */ | |
168 | switch (addr) { | |
169 | case SCALER_OFFSET: | |
b4548fcc | 170 | trace_grlib_gptimer_readl(-1, addr, unit->scaler); |
0f3a4a01 FC |
171 | return unit->scaler; |
172 | ||
173 | case SCALER_RELOAD_OFFSET: | |
b4548fcc | 174 | trace_grlib_gptimer_readl(-1, addr, unit->reload); |
0f3a4a01 FC |
175 | return unit->reload; |
176 | ||
177 | case CONFIG_OFFSET: | |
b4548fcc | 178 | trace_grlib_gptimer_readl(-1, addr, unit->config); |
0f3a4a01 FC |
179 | return unit->config; |
180 | ||
181 | default: | |
182 | break; | |
183 | } | |
184 | ||
185 | timer_addr = (addr % TIMER_BASE); | |
186 | id = (addr - TIMER_BASE) / TIMER_BASE; | |
187 | ||
188 | if (id >= 0 && id < unit->nr_timers) { | |
189 | ||
190 | /* GPTimer registers */ | |
191 | switch (timer_addr) { | |
192 | case COUNTER_OFFSET: | |
193 | value = ptimer_get_count(unit->timers[id].ptimer); | |
b4548fcc | 194 | trace_grlib_gptimer_readl(id, addr, value); |
0f3a4a01 FC |
195 | return value; |
196 | ||
197 | case COUNTER_RELOAD_OFFSET: | |
198 | value = unit->timers[id].reload; | |
b4548fcc | 199 | trace_grlib_gptimer_readl(id, addr, value); |
0f3a4a01 FC |
200 | return value; |
201 | ||
202 | case CONFIG_OFFSET: | |
b4548fcc | 203 | trace_grlib_gptimer_readl(id, addr, unit->timers[id].config); |
0f3a4a01 FC |
204 | return unit->timers[id].config; |
205 | ||
206 | default: | |
207 | break; | |
208 | } | |
209 | ||
210 | } | |
211 | ||
b4548fcc | 212 | trace_grlib_gptimer_readl(-1, addr, 0); |
0f3a4a01 FC |
213 | return 0; |
214 | } | |
215 | ||
cde844fa AK |
216 | static void grlib_gptimer_write(void *opaque, target_phys_addr_t addr, |
217 | uint64_t value, unsigned size) | |
0f3a4a01 FC |
218 | { |
219 | GPTimerUnit *unit = opaque; | |
220 | target_phys_addr_t timer_addr; | |
221 | int id; | |
222 | ||
223 | addr &= 0xff; | |
224 | ||
225 | /* Unit registers */ | |
226 | switch (addr) { | |
227 | case SCALER_OFFSET: | |
228 | value &= 0xFFFF; /* clean up the value */ | |
229 | unit->scaler = value; | |
b4548fcc | 230 | trace_grlib_gptimer_writel(-1, addr, unit->scaler); |
0f3a4a01 FC |
231 | return; |
232 | ||
233 | case SCALER_RELOAD_OFFSET: | |
234 | value &= 0xFFFF; /* clean up the value */ | |
235 | unit->reload = value; | |
b4548fcc | 236 | trace_grlib_gptimer_writel(-1, addr, unit->reload); |
0f3a4a01 FC |
237 | grlib_gptimer_set_scaler(unit, value); |
238 | return; | |
239 | ||
240 | case CONFIG_OFFSET: | |
241 | /* Read Only (disable timer freeze not supported) */ | |
b4548fcc | 242 | trace_grlib_gptimer_writel(-1, addr, 0); |
0f3a4a01 FC |
243 | return; |
244 | ||
245 | default: | |
246 | break; | |
247 | } | |
248 | ||
249 | timer_addr = (addr % TIMER_BASE); | |
250 | id = (addr - TIMER_BASE) / TIMER_BASE; | |
251 | ||
252 | if (id >= 0 && id < unit->nr_timers) { | |
253 | ||
254 | /* GPTimer registers */ | |
255 | switch (timer_addr) { | |
256 | case COUNTER_OFFSET: | |
b4548fcc | 257 | trace_grlib_gptimer_writel(id, addr, value); |
0f3a4a01 FC |
258 | unit->timers[id].counter = value; |
259 | grlib_gptimer_enable(&unit->timers[id]); | |
260 | return; | |
261 | ||
262 | case COUNTER_RELOAD_OFFSET: | |
b4548fcc | 263 | trace_grlib_gptimer_writel(id, addr, value); |
0f3a4a01 FC |
264 | unit->timers[id].reload = value; |
265 | return; | |
266 | ||
267 | case CONFIG_OFFSET: | |
b4548fcc | 268 | trace_grlib_gptimer_writel(id, addr, value); |
0f3a4a01 FC |
269 | |
270 | if (value & GPTIMER_INT_PENDING) { | |
271 | /* clear pending bit */ | |
272 | value &= ~GPTIMER_INT_PENDING; | |
273 | } else { | |
274 | /* keep pending bit */ | |
275 | value |= unit->timers[id].config & GPTIMER_INT_PENDING; | |
276 | } | |
277 | ||
278 | unit->timers[id].config = value; | |
279 | ||
280 | /* gptimer_restart calls gptimer_enable, so if "enable" and "load" | |
281 | bits are present, we just have to call restart. */ | |
282 | ||
283 | if (value & GPTIMER_LOAD) { | |
284 | grlib_gptimer_restart(&unit->timers[id]); | |
285 | } else if (value & GPTIMER_ENABLE) { | |
286 | grlib_gptimer_enable(&unit->timers[id]); | |
287 | } | |
288 | ||
289 | /* These fields must always be read as 0 */ | |
290 | value &= ~(GPTIMER_LOAD & GPTIMER_DEBUG_HALT); | |
291 | ||
292 | unit->timers[id].config = value; | |
293 | return; | |
294 | ||
295 | default: | |
296 | break; | |
297 | } | |
298 | ||
299 | } | |
300 | ||
b4548fcc | 301 | trace_grlib_gptimer_writel(-1, addr, value); |
0f3a4a01 FC |
302 | } |
303 | ||
cde844fa AK |
304 | static const MemoryRegionOps grlib_gptimer_ops = { |
305 | .read = grlib_gptimer_read, | |
306 | .write = grlib_gptimer_write, | |
307 | .endianness = DEVICE_NATIVE_ENDIAN, | |
308 | .valid = { | |
309 | .min_access_size = 4, | |
310 | .max_access_size = 4, | |
311 | }, | |
0f3a4a01 FC |
312 | }; |
313 | ||
314 | static void grlib_gptimer_reset(DeviceState *d) | |
315 | { | |
316 | GPTimerUnit *unit = container_of(d, GPTimerUnit, busdev.qdev); | |
317 | int i = 0; | |
318 | ||
319 | assert(unit != NULL); | |
320 | ||
321 | unit->scaler = 0; | |
322 | unit->reload = 0; | |
323 | unit->config = 0; | |
324 | ||
325 | unit->config = unit->nr_timers; | |
326 | unit->config |= unit->irq_line << 3; | |
327 | unit->config |= 1 << 8; /* separate interrupt */ | |
328 | unit->config |= 1 << 9; /* Disable timer freeze */ | |
329 | ||
330 | ||
331 | for (i = 0; i < unit->nr_timers; i++) { | |
332 | GPTimer *timer = &unit->timers[i]; | |
333 | ||
334 | timer->counter = 0; | |
335 | timer->reload = 0; | |
336 | timer->config = 0; | |
337 | ptimer_stop(timer->ptimer); | |
338 | ptimer_set_count(timer->ptimer, 0); | |
339 | ptimer_set_freq(timer->ptimer, unit->freq_hz); | |
340 | } | |
341 | } | |
342 | ||
343 | static int grlib_gptimer_init(SysBusDevice *dev) | |
344 | { | |
345 | GPTimerUnit *unit = FROM_SYSBUS(typeof(*unit), dev); | |
346 | unsigned int i; | |
0f3a4a01 FC |
347 | |
348 | assert(unit->nr_timers > 0); | |
349 | assert(unit->nr_timers <= GPTIMER_MAX_TIMERS); | |
350 | ||
7267c094 | 351 | unit->timers = g_malloc0(sizeof unit->timers[0] * unit->nr_timers); |
0f3a4a01 FC |
352 | |
353 | for (i = 0; i < unit->nr_timers; i++) { | |
354 | GPTimer *timer = &unit->timers[i]; | |
355 | ||
356 | timer->unit = unit; | |
357 | timer->bh = qemu_bh_new(grlib_gptimer_hit, timer); | |
358 | timer->ptimer = ptimer_init(timer->bh); | |
359 | timer->id = i; | |
360 | ||
361 | /* One IRQ line for each timer */ | |
362 | sysbus_init_irq(dev, &timer->irq); | |
363 | ||
364 | ptimer_set_freq(timer->ptimer, unit->freq_hz); | |
365 | } | |
366 | ||
cde844fa AK |
367 | memory_region_init_io(&unit->iomem, &grlib_gptimer_ops, unit, "gptimer", |
368 | UNIT_REG_SIZE + GPTIMER_REG_SIZE * unit->nr_timers); | |
0f3a4a01 | 369 | |
cde844fa | 370 | sysbus_init_mmio_region(dev, &unit->iomem); |
0f3a4a01 FC |
371 | return 0; |
372 | } | |
373 | ||
374 | static SysBusDeviceInfo grlib_gptimer_info = { | |
375 | .init = grlib_gptimer_init, | |
376 | .qdev.name = "grlib,gptimer", | |
377 | .qdev.reset = grlib_gptimer_reset, | |
378 | .qdev.size = sizeof(GPTimerUnit), | |
379 | .qdev.props = (Property[]) { | |
380 | DEFINE_PROP_UINT32("frequency", GPTimerUnit, freq_hz, 40000000), | |
381 | DEFINE_PROP_UINT32("irq-line", GPTimerUnit, irq_line, 8), | |
382 | DEFINE_PROP_UINT32("nr-timers", GPTimerUnit, nr_timers, 2), | |
383 | DEFINE_PROP_END_OF_LIST() | |
384 | } | |
385 | }; | |
386 | ||
387 | static void grlib_gptimer_register(void) | |
388 | { | |
389 | sysbus_register_withprop(&grlib_gptimer_info); | |
390 | } | |
391 | ||
392 | device_init(grlib_gptimer_register) |