]>
Commit | Line | Data |
---|---|---|
a50c0d6f JCD |
1 | /* |
2 | * IMX EPIT Timer | |
3 | * | |
4 | * Copyright (c) 2008 OK Labs | |
5 | * Copyright (c) 2011 NICTA Pty Ltd | |
6 | * Originally written by Hans Jiang | |
7 | * Updated by Peter Chubb | |
95669e69 | 8 | * Updated by Jean-Christophe Dubois |
a50c0d6f JCD |
9 | * |
10 | * This code is licensed under GPL version 2 or later. See | |
11 | * the COPYING file in the top-level directory. | |
12 | * | |
13 | */ | |
14 | ||
15 | #include "hw/hw.h" | |
16 | #include "qemu/bitops.h" | |
17 | #include "qemu/timer.h" | |
18 | #include "hw/ptimer.h" | |
19 | #include "hw/sysbus.h" | |
20 | #include "hw/arm/imx.h" | |
21 | ||
95669e69 JCD |
22 | #define TYPE_IMX_EPIT "imx.epit" |
23 | ||
24 | #define DEBUG_TIMER 0 | |
25 | #if DEBUG_TIMER | |
26 | ||
27 | static char const *imx_epit_reg_name(uint32_t reg) | |
28 | { | |
29 | switch (reg) { | |
30 | case 0: | |
31 | return "CR"; | |
32 | case 1: | |
33 | return "SR"; | |
34 | case 2: | |
35 | return "LR"; | |
36 | case 3: | |
37 | return "CMP"; | |
38 | case 4: | |
39 | return "CNT"; | |
40 | default: | |
41 | return "[?]"; | |
42 | } | |
43 | } | |
44 | ||
a50c0d6f | 45 | # define DPRINTF(fmt, args...) \ |
95669e69 | 46 | do { printf("%s: " fmt , __func__, ##args); } while (0) |
a50c0d6f JCD |
47 | #else |
48 | # define DPRINTF(fmt, args...) do {} while (0) | |
49 | #endif | |
50 | ||
51 | /* | |
52 | * Define to 1 for messages about attempts to | |
53 | * access unimplemented registers or similar. | |
54 | */ | |
55 | #define DEBUG_IMPLEMENTATION 1 | |
56 | #if DEBUG_IMPLEMENTATION | |
95669e69 JCD |
57 | # define IPRINTF(fmt, args...) \ |
58 | do { fprintf(stderr, "%s: " fmt, __func__, ##args); } while (0) | |
a50c0d6f JCD |
59 | #else |
60 | # define IPRINTF(fmt, args...) do {} while (0) | |
61 | #endif | |
62 | ||
95669e69 JCD |
63 | #define IMX_EPIT(obj) \ |
64 | OBJECT_CHECK(IMXEPITState, (obj), TYPE_IMX_EPIT) | |
65 | ||
a50c0d6f JCD |
66 | /* |
67 | * EPIT: Enhanced periodic interrupt timer | |
68 | */ | |
69 | ||
70 | #define CR_EN (1 << 0) | |
71 | #define CR_ENMOD (1 << 1) | |
72 | #define CR_OCIEN (1 << 2) | |
73 | #define CR_RLD (1 << 3) | |
74 | #define CR_PRESCALE_SHIFT (4) | |
75 | #define CR_PRESCALE_MASK (0xfff) | |
76 | #define CR_SWR (1 << 16) | |
77 | #define CR_IOVW (1 << 17) | |
78 | #define CR_DBGEN (1 << 18) | |
79 | #define CR_WAITEN (1 << 19) | |
80 | #define CR_DOZEN (1 << 20) | |
81 | #define CR_STOPEN (1 << 21) | |
82 | #define CR_CLKSRC_SHIFT (24) | |
83 | #define CR_CLKSRC_MASK (0x3 << CR_CLKSRC_SHIFT) | |
84 | ||
85 | #define TIMER_MAX 0XFFFFFFFFUL | |
86 | ||
87 | /* | |
88 | * Exact clock frequencies vary from board to board. | |
89 | * These are typical. | |
90 | */ | |
95669e69 | 91 | static const IMXClk imx_epit_clocks[] = { |
a50c0d6f JCD |
92 | 0, /* 00 disabled */ |
93 | IPG, /* 01 ipg_clk, ~532MHz */ | |
94 | IPG, /* 10 ipg_clk_highfreq */ | |
95 | CLK_32k, /* 11 ipg_clk_32k -- ~32kHz */ | |
96 | }; | |
97 | ||
98 | typedef struct { | |
99 | SysBusDevice busdev; | |
100 | ptimer_state *timer_reload; | |
101 | ptimer_state *timer_cmp; | |
102 | MemoryRegion iomem; | |
103 | DeviceState *ccm; | |
104 | ||
105 | uint32_t cr; | |
106 | uint32_t sr; | |
107 | uint32_t lr; | |
108 | uint32_t cmp; | |
109 | uint32_t cnt; | |
110 | ||
111 | uint32_t freq; | |
112 | qemu_irq irq; | |
95669e69 | 113 | } IMXEPITState; |
a50c0d6f JCD |
114 | |
115 | /* | |
116 | * Update interrupt status | |
117 | */ | |
95669e69 | 118 | static void imx_epit_update_int(IMXEPITState *s) |
a50c0d6f | 119 | { |
95669e69 | 120 | if (s->sr && (s->cr & CR_OCIEN) && (s->cr & CR_EN)) { |
a50c0d6f JCD |
121 | qemu_irq_raise(s->irq); |
122 | } else { | |
123 | qemu_irq_lower(s->irq); | |
124 | } | |
125 | } | |
126 | ||
95669e69 | 127 | static void imx_epit_set_freq(IMXEPITState *s) |
a50c0d6f | 128 | { |
95669e69 JCD |
129 | uint32_t clksrc; |
130 | uint32_t prescaler; | |
a50c0d6f JCD |
131 | uint32_t freq; |
132 | ||
133 | clksrc = extract32(s->cr, CR_CLKSRC_SHIFT, 2); | |
134 | prescaler = 1 + extract32(s->cr, CR_PRESCALE_SHIFT, 12); | |
135 | ||
95669e69 | 136 | freq = imx_clock_frequency(s->ccm, imx_epit_clocks[clksrc]) / prescaler; |
a50c0d6f JCD |
137 | |
138 | s->freq = freq; | |
95669e69 | 139 | |
a50c0d6f JCD |
140 | DPRINTF("Setting ptimer frequency to %u\n", freq); |
141 | ||
142 | if (freq) { | |
143 | ptimer_set_freq(s->timer_reload, freq); | |
144 | ptimer_set_freq(s->timer_cmp, freq); | |
145 | } | |
146 | } | |
147 | ||
95669e69 | 148 | static void imx_epit_reset(DeviceState *dev) |
a50c0d6f | 149 | { |
95669e69 | 150 | IMXEPITState *s = IMX_EPIT(dev); |
a50c0d6f JCD |
151 | |
152 | /* | |
153 | * Soft reset doesn't touch some bits; hard reset clears them | |
154 | */ | |
155 | s->cr &= ~(CR_EN|CR_ENMOD|CR_STOPEN|CR_DOZEN|CR_WAITEN|CR_DBGEN); | |
156 | s->sr = 0; | |
157 | s->lr = TIMER_MAX; | |
158 | s->cmp = 0; | |
159 | s->cnt = 0; | |
160 | /* stop both timers */ | |
161 | ptimer_stop(s->timer_cmp); | |
162 | ptimer_stop(s->timer_reload); | |
163 | /* compute new frequency */ | |
95669e69 | 164 | imx_epit_set_freq(s); |
a50c0d6f JCD |
165 | /* init both timers to TIMER_MAX */ |
166 | ptimer_set_limit(s->timer_cmp, TIMER_MAX, 1); | |
167 | ptimer_set_limit(s->timer_reload, TIMER_MAX, 1); | |
168 | if (s->freq && (s->cr & CR_EN)) { | |
169 | /* if the timer is still enabled, restart it */ | |
170 | ptimer_run(s->timer_reload, 1); | |
171 | } | |
172 | } | |
173 | ||
95669e69 | 174 | static uint32_t imx_epit_update_count(IMXEPITState *s) |
a50c0d6f JCD |
175 | { |
176 | s->cnt = ptimer_get_count(s->timer_reload); | |
177 | ||
178 | return s->cnt; | |
179 | } | |
180 | ||
95669e69 | 181 | static uint64_t imx_epit_read(void *opaque, hwaddr offset, unsigned size) |
a50c0d6f | 182 | { |
95669e69 JCD |
183 | IMXEPITState *s = IMX_EPIT(opaque); |
184 | uint32_t reg_value = 0; | |
185 | uint32_t reg = offset >> 2; | |
a50c0d6f | 186 | |
95669e69 | 187 | switch (reg) { |
a50c0d6f | 188 | case 0: /* Control Register */ |
95669e69 JCD |
189 | reg_value = s->cr; |
190 | break; | |
a50c0d6f JCD |
191 | |
192 | case 1: /* Status Register */ | |
95669e69 JCD |
193 | reg_value = s->sr; |
194 | break; | |
a50c0d6f JCD |
195 | |
196 | case 2: /* LR - ticks*/ | |
95669e69 JCD |
197 | reg_value = s->lr; |
198 | break; | |
a50c0d6f JCD |
199 | |
200 | case 3: /* CMP */ | |
95669e69 JCD |
201 | reg_value = s->cmp; |
202 | break; | |
a50c0d6f JCD |
203 | |
204 | case 4: /* CNT */ | |
95669e69 JCD |
205 | imx_epit_update_count(s); |
206 | reg_value = s->cnt; | |
207 | break; | |
208 | ||
209 | default: | |
210 | IPRINTF("Bad offset %x\n", reg); | |
211 | break; | |
a50c0d6f JCD |
212 | } |
213 | ||
95669e69 JCD |
214 | DPRINTF("(%s) = 0x%08x\n", imx_epit_reg_name(reg), reg_value); |
215 | ||
216 | return reg_value; | |
a50c0d6f JCD |
217 | } |
218 | ||
95669e69 | 219 | static void imx_epit_reload_compare_timer(IMXEPITState *s) |
a50c0d6f JCD |
220 | { |
221 | if ((s->cr & CR_OCIEN) && s->cmp) { | |
222 | /* if the compare feature is on */ | |
95669e69 | 223 | uint32_t tmp = imx_epit_update_count(s); |
a50c0d6f JCD |
224 | if (tmp > s->cmp) { |
225 | /* reinit the cmp timer if required */ | |
226 | ptimer_set_count(s->timer_cmp, tmp - s->cmp); | |
227 | if ((s->cr & CR_EN)) { | |
228 | /* Restart the cmp timer if required */ | |
229 | ptimer_run(s->timer_cmp, 0); | |
230 | } | |
231 | } | |
232 | } | |
233 | } | |
234 | ||
95669e69 JCD |
235 | static void imx_epit_write(void *opaque, hwaddr offset, uint64_t value, |
236 | unsigned size) | |
a50c0d6f | 237 | { |
95669e69 JCD |
238 | IMXEPITState *s = IMX_EPIT(opaque); |
239 | uint32_t reg = offset >> 2; | |
240 | ||
241 | DPRINTF("(%s, value = 0x%08x)\n", imx_epit_reg_name(reg), (uint32_t)value); | |
a50c0d6f | 242 | |
95669e69 | 243 | switch (reg) { |
a50c0d6f JCD |
244 | case 0: /* CR */ |
245 | s->cr = value & 0x03ffffff; | |
246 | if (s->cr & CR_SWR) { | |
247 | /* handle the reset */ | |
95669e69 | 248 | imx_epit_reset(DEVICE(s)); |
a50c0d6f | 249 | } else { |
95669e69 | 250 | imx_epit_set_freq(s); |
a50c0d6f JCD |
251 | } |
252 | ||
253 | if (s->freq && (s->cr & CR_EN)) { | |
254 | if (s->cr & CR_ENMOD) { | |
255 | if (s->cr & CR_RLD) { | |
256 | ptimer_set_limit(s->timer_reload, s->lr, 1); | |
257 | } else { | |
258 | ptimer_set_limit(s->timer_reload, TIMER_MAX, 1); | |
259 | } | |
260 | } | |
261 | ||
95669e69 | 262 | imx_epit_reload_compare_timer(s); |
a50c0d6f JCD |
263 | |
264 | ptimer_run(s->timer_reload, 1); | |
265 | } else { | |
266 | /* stop both timers */ | |
267 | ptimer_stop(s->timer_reload); | |
268 | ptimer_stop(s->timer_cmp); | |
269 | } | |
270 | break; | |
271 | ||
272 | case 1: /* SR - ACK*/ | |
273 | /* writing 1 to OCIF clear the OCIF bit */ | |
274 | if (value & 0x01) { | |
275 | s->sr = 0; | |
95669e69 | 276 | imx_epit_update_int(s); |
a50c0d6f JCD |
277 | } |
278 | break; | |
279 | ||
280 | case 2: /* LR - set ticks */ | |
281 | s->lr = value; | |
282 | ||
283 | if (s->cr & CR_RLD) { | |
284 | /* Also set the limit if the LRD bit is set */ | |
285 | /* If IOVW bit is set then set the timer value */ | |
286 | ptimer_set_limit(s->timer_reload, s->lr, s->cr & CR_IOVW); | |
287 | } else if (s->cr & CR_IOVW) { | |
288 | /* If IOVW bit is set then set the timer value */ | |
289 | ptimer_set_count(s->timer_reload, s->lr); | |
290 | } | |
291 | ||
95669e69 | 292 | imx_epit_reload_compare_timer(s); |
a50c0d6f JCD |
293 | |
294 | break; | |
295 | ||
296 | case 3: /* CMP */ | |
297 | s->cmp = value; | |
298 | ||
95669e69 | 299 | imx_epit_reload_compare_timer(s); |
a50c0d6f JCD |
300 | |
301 | break; | |
302 | ||
303 | default: | |
95669e69 JCD |
304 | IPRINTF("Bad offset %x\n", reg); |
305 | ||
306 | break; | |
a50c0d6f JCD |
307 | } |
308 | } | |
309 | ||
95669e69 | 310 | static void imx_epit_timeout(void *opaque) |
a50c0d6f | 311 | { |
95669e69 | 312 | IMXEPITState *s = IMX_EPIT(opaque); |
a50c0d6f | 313 | |
95669e69 | 314 | DPRINTF("\n"); |
a50c0d6f JCD |
315 | |
316 | if (!(s->cr & CR_EN)) { | |
317 | return; | |
318 | } | |
319 | ||
320 | if (s->cr & CR_RLD) { | |
321 | ptimer_set_limit(s->timer_reload, s->lr, 1); | |
322 | } else { | |
323 | ptimer_set_limit(s->timer_reload, TIMER_MAX, 1); | |
324 | } | |
325 | ||
326 | if (s->cr & CR_OCIEN) { | |
327 | /* if compare register is 0 then we handle the interrupt here */ | |
328 | if (s->cmp == 0) { | |
329 | s->sr = 1; | |
95669e69 | 330 | imx_epit_update_int(s); |
a50c0d6f JCD |
331 | } else if (s->cmp <= s->lr) { |
332 | /* We should launch the compare register */ | |
333 | ptimer_set_count(s->timer_cmp, s->lr - s->cmp); | |
334 | ptimer_run(s->timer_cmp, 0); | |
335 | } else { | |
95669e69 | 336 | IPRINTF("s->lr < s->cmp\n"); |
a50c0d6f JCD |
337 | } |
338 | } | |
339 | } | |
340 | ||
95669e69 | 341 | static void imx_epit_cmp(void *opaque) |
a50c0d6f | 342 | { |
95669e69 | 343 | IMXEPITState *s = IMX_EPIT(opaque); |
a50c0d6f | 344 | |
95669e69 | 345 | DPRINTF("\n"); |
a50c0d6f JCD |
346 | |
347 | ptimer_stop(s->timer_cmp); | |
348 | ||
349 | /* compare register is not 0 */ | |
350 | if (s->cmp) { | |
351 | s->sr = 1; | |
95669e69 | 352 | imx_epit_update_int(s); |
a50c0d6f JCD |
353 | } |
354 | } | |
355 | ||
95669e69 | 356 | void imx_timerp_create(const hwaddr addr, qemu_irq irq, DeviceState *ccm) |
a50c0d6f | 357 | { |
95669e69 | 358 | IMXEPITState *pp; |
a50c0d6f JCD |
359 | DeviceState *dev; |
360 | ||
95669e69 JCD |
361 | dev = sysbus_create_simple(TYPE_IMX_EPIT, addr, irq); |
362 | pp = IMX_EPIT(dev); | |
a50c0d6f JCD |
363 | pp->ccm = ccm; |
364 | } | |
365 | ||
95669e69 JCD |
366 | static const MemoryRegionOps imx_epit_ops = { |
367 | .read = imx_epit_read, | |
368 | .write = imx_epit_write, | |
a50c0d6f JCD |
369 | .endianness = DEVICE_NATIVE_ENDIAN, |
370 | }; | |
371 | ||
95669e69 JCD |
372 | static const VMStateDescription vmstate_imx_timer_epit = { |
373 | .name = TYPE_IMX_EPIT, | |
a50c0d6f JCD |
374 | .version_id = 2, |
375 | .minimum_version_id = 2, | |
376 | .minimum_version_id_old = 2, | |
377 | .fields = (VMStateField[]) { | |
95669e69 JCD |
378 | VMSTATE_UINT32(cr, IMXEPITState), |
379 | VMSTATE_UINT32(sr, IMXEPITState), | |
380 | VMSTATE_UINT32(lr, IMXEPITState), | |
381 | VMSTATE_UINT32(cmp, IMXEPITState), | |
382 | VMSTATE_UINT32(cnt, IMXEPITState), | |
383 | VMSTATE_UINT32(freq, IMXEPITState), | |
384 | VMSTATE_PTIMER(timer_reload, IMXEPITState), | |
385 | VMSTATE_PTIMER(timer_cmp, IMXEPITState), | |
a50c0d6f JCD |
386 | VMSTATE_END_OF_LIST() |
387 | } | |
388 | }; | |
389 | ||
95669e69 | 390 | static void imx_epit_realize(DeviceState *dev, Error **errp) |
a50c0d6f | 391 | { |
95669e69 JCD |
392 | IMXEPITState *s = IMX_EPIT(dev); |
393 | SysBusDevice *sbd = SYS_BUS_DEVICE(dev); | |
a50c0d6f JCD |
394 | QEMUBH *bh; |
395 | ||
95669e69 JCD |
396 | DPRINTF("\n"); |
397 | ||
398 | sysbus_init_irq(sbd, &s->irq); | |
853dca12 | 399 | memory_region_init_io(&s->iomem, OBJECT(s), &imx_epit_ops, s, TYPE_IMX_EPIT, |
a50c0d6f | 400 | 0x00001000); |
95669e69 | 401 | sysbus_init_mmio(sbd, &s->iomem); |
a50c0d6f | 402 | |
95669e69 | 403 | bh = qemu_bh_new(imx_epit_timeout, s); |
a50c0d6f JCD |
404 | s->timer_reload = ptimer_init(bh); |
405 | ||
95669e69 | 406 | bh = qemu_bh_new(imx_epit_cmp, s); |
a50c0d6f | 407 | s->timer_cmp = ptimer_init(bh); |
a50c0d6f JCD |
408 | } |
409 | ||
95669e69 | 410 | static void imx_epit_class_init(ObjectClass *klass, void *data) |
a50c0d6f JCD |
411 | { |
412 | DeviceClass *dc = DEVICE_CLASS(klass); | |
95669e69 JCD |
413 | |
414 | dc->realize = imx_epit_realize; | |
415 | dc->reset = imx_epit_reset; | |
416 | dc->vmsd = &vmstate_imx_timer_epit; | |
a50c0d6f JCD |
417 | dc->desc = "i.MX periodic timer"; |
418 | } | |
419 | ||
95669e69 JCD |
420 | static const TypeInfo imx_epit_info = { |
421 | .name = TYPE_IMX_EPIT, | |
a50c0d6f | 422 | .parent = TYPE_SYS_BUS_DEVICE, |
95669e69 JCD |
423 | .instance_size = sizeof(IMXEPITState), |
424 | .class_init = imx_epit_class_init, | |
a50c0d6f JCD |
425 | }; |
426 | ||
95669e69 | 427 | static void imx_epit_register_types(void) |
a50c0d6f | 428 | { |
95669e69 | 429 | type_register_static(&imx_epit_info); |
a50c0d6f JCD |
430 | } |
431 | ||
95669e69 | 432 | type_init(imx_epit_register_types) |