root / hw / timer / sh_timer.c @ 3bd88451
History | View | Annotate | Download (8.8 kB)
1 | cd1a3f68 | ths | /*
|
---|---|---|---|
2 | cd1a3f68 | ths | * SuperH Timer modules.
|
3 | cd1a3f68 | ths | *
|
4 | cd1a3f68 | ths | * Copyright (c) 2007 Magnus Damm
|
5 | cd1a3f68 | ths | * Based on arm_timer.c by Paul Brook
|
6 | cd1a3f68 | ths | * Copyright (c) 2005-2006 CodeSourcery.
|
7 | cd1a3f68 | ths | *
|
8 | 8e31bf38 | Matthew Fernandez | * This code is licensed under the GPL.
|
9 | cd1a3f68 | ths | */
|
10 | cd1a3f68 | ths | |
11 | 83c9f4ca | Paolo Bonzini | #include "hw/hw.h" |
12 | 0d09e41a | Paolo Bonzini | #include "hw/sh4/sh.h" |
13 | 1de7afc9 | Paolo Bonzini | #include "qemu/timer.h" |
14 | 022c62cb | Paolo Bonzini | #include "exec/address-spaces.h" |
15 | 83c9f4ca | Paolo Bonzini | #include "hw/ptimer.h" |
16 | cd1a3f68 | ths | |
17 | cd1a3f68 | ths | //#define DEBUG_TIMER
|
18 | cd1a3f68 | ths | |
19 | cd1a3f68 | ths | #define TIMER_TCR_TPSC (7 << 0) |
20 | cd1a3f68 | ths | #define TIMER_TCR_CKEG (3 << 3) |
21 | cd1a3f68 | ths | #define TIMER_TCR_UNIE (1 << 5) |
22 | cd1a3f68 | ths | #define TIMER_TCR_ICPE (3 << 6) |
23 | cd1a3f68 | ths | #define TIMER_TCR_UNF (1 << 8) |
24 | cd1a3f68 | ths | #define TIMER_TCR_ICPF (1 << 9) |
25 | cd1a3f68 | ths | #define TIMER_TCR_RESERVED (0x3f << 10) |
26 | cd1a3f68 | ths | |
27 | cd1a3f68 | ths | #define TIMER_FEAT_CAPT (1 << 0) |
28 | cd1a3f68 | ths | #define TIMER_FEAT_EXTCLK (1 << 1) |
29 | cd1a3f68 | ths | |
30 | e7786f27 | aurel32 | #define OFFSET_TCOR 0 |
31 | e7786f27 | aurel32 | #define OFFSET_TCNT 1 |
32 | e7786f27 | aurel32 | #define OFFSET_TCR 2 |
33 | e7786f27 | aurel32 | #define OFFSET_TCPR 3 |
34 | e7786f27 | aurel32 | |
35 | cd1a3f68 | ths | typedef struct { |
36 | cd1a3f68 | ths | ptimer_state *timer; |
37 | cd1a3f68 | ths | uint32_t tcnt; |
38 | cd1a3f68 | ths | uint32_t tcor; |
39 | cd1a3f68 | ths | uint32_t tcr; |
40 | cd1a3f68 | ths | uint32_t tcpr; |
41 | cd1a3f68 | ths | int freq;
|
42 | cd1a3f68 | ths | int int_level;
|
43 | 703243a0 | balrog | int old_level;
|
44 | cd1a3f68 | ths | int feat;
|
45 | cd1a3f68 | ths | int enabled;
|
46 | 96e2fc41 | aurel32 | qemu_irq irq; |
47 | cd1a3f68 | ths | } sh_timer_state; |
48 | cd1a3f68 | ths | |
49 | cd1a3f68 | ths | /* Check all active timers, and schedule the next timer interrupt. */
|
50 | cd1a3f68 | ths | |
51 | cd1a3f68 | ths | static void sh_timer_update(sh_timer_state *s) |
52 | cd1a3f68 | ths | { |
53 | 703243a0 | balrog | int new_level = s->int_level && (s->tcr & TIMER_TCR_UNIE);
|
54 | 703243a0 | balrog | |
55 | 703243a0 | balrog | if (new_level != s->old_level)
|
56 | 96e2fc41 | aurel32 | qemu_set_irq (s->irq, new_level); |
57 | 703243a0 | balrog | |
58 | 703243a0 | balrog | s->old_level = s->int_level; |
59 | 703243a0 | balrog | s->int_level = new_level; |
60 | cd1a3f68 | ths | } |
61 | cd1a3f68 | ths | |
62 | a8170e5e | Avi Kivity | static uint32_t sh_timer_read(void *opaque, hwaddr offset) |
63 | cd1a3f68 | ths | { |
64 | cd1a3f68 | ths | sh_timer_state *s = (sh_timer_state *)opaque; |
65 | cd1a3f68 | ths | |
66 | cd1a3f68 | ths | switch (offset >> 2) { |
67 | e7786f27 | aurel32 | case OFFSET_TCOR:
|
68 | cd1a3f68 | ths | return s->tcor;
|
69 | e7786f27 | aurel32 | case OFFSET_TCNT:
|
70 | cd1a3f68 | ths | return ptimer_get_count(s->timer);
|
71 | e7786f27 | aurel32 | case OFFSET_TCR:
|
72 | cd1a3f68 | ths | return s->tcr | (s->int_level ? TIMER_TCR_UNF : 0); |
73 | e7786f27 | aurel32 | case OFFSET_TCPR:
|
74 | cd1a3f68 | ths | if (s->feat & TIMER_FEAT_CAPT)
|
75 | cd1a3f68 | ths | return s->tcpr;
|
76 | cd1a3f68 | ths | default:
|
77 | 2ac71179 | Paul Brook | hw_error("sh_timer_read: Bad offset %x\n", (int)offset); |
78 | cd1a3f68 | ths | return 0; |
79 | cd1a3f68 | ths | } |
80 | cd1a3f68 | ths | } |
81 | cd1a3f68 | ths | |
82 | a8170e5e | Avi Kivity | static void sh_timer_write(void *opaque, hwaddr offset, |
83 | cd1a3f68 | ths | uint32_t value) |
84 | cd1a3f68 | ths | { |
85 | cd1a3f68 | ths | sh_timer_state *s = (sh_timer_state *)opaque; |
86 | cd1a3f68 | ths | int freq;
|
87 | cd1a3f68 | ths | |
88 | cd1a3f68 | ths | switch (offset >> 2) { |
89 | e7786f27 | aurel32 | case OFFSET_TCOR:
|
90 | cd1a3f68 | ths | s->tcor = value; |
91 | cd1a3f68 | ths | ptimer_set_limit(s->timer, s->tcor, 0);
|
92 | cd1a3f68 | ths | break;
|
93 | e7786f27 | aurel32 | case OFFSET_TCNT:
|
94 | cd1a3f68 | ths | s->tcnt = value; |
95 | cd1a3f68 | ths | ptimer_set_count(s->timer, s->tcnt); |
96 | cd1a3f68 | ths | break;
|
97 | e7786f27 | aurel32 | case OFFSET_TCR:
|
98 | cd1a3f68 | ths | if (s->enabled) {
|
99 | cd1a3f68 | ths | /* Pause the timer if it is running. This may cause some
|
100 | cd1a3f68 | ths | inaccuracy dure to rounding, but avoids a whole lot of other
|
101 | cd1a3f68 | ths | messyness. */
|
102 | cd1a3f68 | ths | ptimer_stop(s->timer); |
103 | cd1a3f68 | ths | } |
104 | cd1a3f68 | ths | freq = s->freq; |
105 | cd1a3f68 | ths | /* ??? Need to recalculate expiry time after changing divisor. */
|
106 | cd1a3f68 | ths | switch (value & TIMER_TCR_TPSC) {
|
107 | cd1a3f68 | ths | case 0: freq >>= 2; break; |
108 | cd1a3f68 | ths | case 1: freq >>= 4; break; |
109 | cd1a3f68 | ths | case 2: freq >>= 6; break; |
110 | cd1a3f68 | ths | case 3: freq >>= 8; break; |
111 | cd1a3f68 | ths | case 4: freq >>= 10; break; |
112 | cd1a3f68 | ths | case 6: |
113 | cd1a3f68 | ths | case 7: if (s->feat & TIMER_FEAT_EXTCLK) break; |
114 | 2ac71179 | Paul Brook | default: hw_error("sh_timer_write: Reserved TPSC value\n"); break; |
115 | cd1a3f68 | ths | } |
116 | cd1a3f68 | ths | switch ((value & TIMER_TCR_CKEG) >> 3) { |
117 | cd1a3f68 | ths | case 0: break; |
118 | cd1a3f68 | ths | case 1: |
119 | cd1a3f68 | ths | case 2: |
120 | cd1a3f68 | ths | case 3: if (s->feat & TIMER_FEAT_EXTCLK) break; |
121 | 2ac71179 | Paul Brook | default: hw_error("sh_timer_write: Reserved CKEG value\n"); break; |
122 | cd1a3f68 | ths | } |
123 | cd1a3f68 | ths | switch ((value & TIMER_TCR_ICPE) >> 6) { |
124 | cd1a3f68 | ths | case 0: break; |
125 | cd1a3f68 | ths | case 2: |
126 | cd1a3f68 | ths | case 3: if (s->feat & TIMER_FEAT_CAPT) break; |
127 | 2ac71179 | Paul Brook | default: hw_error("sh_timer_write: Reserved ICPE value\n"); break; |
128 | cd1a3f68 | ths | } |
129 | cd1a3f68 | ths | if ((value & TIMER_TCR_UNF) == 0) |
130 | cd1a3f68 | ths | s->int_level = 0;
|
131 | cd1a3f68 | ths | |
132 | cd1a3f68 | ths | value &= ~TIMER_TCR_UNF; |
133 | cd1a3f68 | ths | |
134 | cd1a3f68 | ths | if ((value & TIMER_TCR_ICPF) && (!(s->feat & TIMER_FEAT_CAPT)))
|
135 | 2ac71179 | Paul Brook | hw_error("sh_timer_write: Reserved ICPF value\n");
|
136 | cd1a3f68 | ths | |
137 | cd1a3f68 | ths | value &= ~TIMER_TCR_ICPF; /* capture not supported */
|
138 | cd1a3f68 | ths | |
139 | cd1a3f68 | ths | if (value & TIMER_TCR_RESERVED)
|
140 | 2ac71179 | Paul Brook | hw_error("sh_timer_write: Reserved TCR bits set\n");
|
141 | cd1a3f68 | ths | s->tcr = value; |
142 | cd1a3f68 | ths | ptimer_set_limit(s->timer, s->tcor, 0);
|
143 | cd1a3f68 | ths | ptimer_set_freq(s->timer, freq); |
144 | cd1a3f68 | ths | if (s->enabled) {
|
145 | cd1a3f68 | ths | /* Restart the timer if still enabled. */
|
146 | cd1a3f68 | ths | ptimer_run(s->timer, 0);
|
147 | cd1a3f68 | ths | } |
148 | cd1a3f68 | ths | break;
|
149 | e7786f27 | aurel32 | case OFFSET_TCPR:
|
150 | cd1a3f68 | ths | if (s->feat & TIMER_FEAT_CAPT) {
|
151 | cd1a3f68 | ths | s->tcpr = value; |
152 | cd1a3f68 | ths | break;
|
153 | cd1a3f68 | ths | } |
154 | cd1a3f68 | ths | default:
|
155 | 2ac71179 | Paul Brook | hw_error("sh_timer_write: Bad offset %x\n", (int)offset); |
156 | cd1a3f68 | ths | } |
157 | cd1a3f68 | ths | sh_timer_update(s); |
158 | cd1a3f68 | ths | } |
159 | cd1a3f68 | ths | |
160 | cd1a3f68 | ths | static void sh_timer_start_stop(void *opaque, int enable) |
161 | cd1a3f68 | ths | { |
162 | cd1a3f68 | ths | sh_timer_state *s = (sh_timer_state *)opaque; |
163 | cd1a3f68 | ths | |
164 | cd1a3f68 | ths | #ifdef DEBUG_TIMER
|
165 | cd1a3f68 | ths | printf("sh_timer_start_stop %d (%d)\n", enable, s->enabled);
|
166 | cd1a3f68 | ths | #endif
|
167 | cd1a3f68 | ths | |
168 | cd1a3f68 | ths | if (s->enabled && !enable) {
|
169 | cd1a3f68 | ths | ptimer_stop(s->timer); |
170 | cd1a3f68 | ths | } |
171 | cd1a3f68 | ths | if (!s->enabled && enable) {
|
172 | cd1a3f68 | ths | ptimer_run(s->timer, 0);
|
173 | cd1a3f68 | ths | } |
174 | cd1a3f68 | ths | s->enabled = !!enable; |
175 | cd1a3f68 | ths | |
176 | cd1a3f68 | ths | #ifdef DEBUG_TIMER
|
177 | cd1a3f68 | ths | printf("sh_timer_start_stop done %d\n", s->enabled);
|
178 | cd1a3f68 | ths | #endif
|
179 | cd1a3f68 | ths | } |
180 | cd1a3f68 | ths | |
181 | cd1a3f68 | ths | static void sh_timer_tick(void *opaque) |
182 | cd1a3f68 | ths | { |
183 | cd1a3f68 | ths | sh_timer_state *s = (sh_timer_state *)opaque; |
184 | cd1a3f68 | ths | s->int_level = s->enabled; |
185 | cd1a3f68 | ths | sh_timer_update(s); |
186 | cd1a3f68 | ths | } |
187 | cd1a3f68 | ths | |
188 | 96e2fc41 | aurel32 | static void *sh_timer_init(uint32_t freq, int feat, qemu_irq irq) |
189 | cd1a3f68 | ths | { |
190 | cd1a3f68 | ths | sh_timer_state *s; |
191 | cd1a3f68 | ths | QEMUBH *bh; |
192 | cd1a3f68 | ths | |
193 | 7267c094 | Anthony Liguori | s = (sh_timer_state *)g_malloc0(sizeof(sh_timer_state));
|
194 | cd1a3f68 | ths | s->freq = freq; |
195 | cd1a3f68 | ths | s->feat = feat; |
196 | cd1a3f68 | ths | s->tcor = 0xffffffff;
|
197 | cd1a3f68 | ths | s->tcnt = 0xffffffff;
|
198 | cd1a3f68 | ths | s->tcpr = 0xdeadbeef;
|
199 | e7786f27 | aurel32 | s->tcr = 0;
|
200 | cd1a3f68 | ths | s->enabled = 0;
|
201 | 703243a0 | balrog | s->irq = irq; |
202 | cd1a3f68 | ths | |
203 | cd1a3f68 | ths | bh = qemu_bh_new(sh_timer_tick, s); |
204 | cd1a3f68 | ths | s->timer = ptimer_init(bh); |
205 | e7786f27 | aurel32 | |
206 | e7786f27 | aurel32 | sh_timer_write(s, OFFSET_TCOR >> 2, s->tcor);
|
207 | e7786f27 | aurel32 | sh_timer_write(s, OFFSET_TCNT >> 2, s->tcnt);
|
208 | e7786f27 | aurel32 | sh_timer_write(s, OFFSET_TCPR >> 2, s->tcpr);
|
209 | e7786f27 | aurel32 | sh_timer_write(s, OFFSET_TCR >> 2, s->tcpr);
|
210 | cd1a3f68 | ths | /* ??? Save/restore. */
|
211 | cd1a3f68 | ths | return s;
|
212 | cd1a3f68 | ths | } |
213 | cd1a3f68 | ths | |
214 | cd1a3f68 | ths | typedef struct { |
215 | 89e29451 | Benoît Canet | MemoryRegion iomem; |
216 | 89e29451 | Benoît Canet | MemoryRegion iomem_p4; |
217 | 89e29451 | Benoît Canet | MemoryRegion iomem_a7; |
218 | cd1a3f68 | ths | void *timer[3]; |
219 | cd1a3f68 | ths | int level[3]; |
220 | cd1a3f68 | ths | uint32_t tocr; |
221 | cd1a3f68 | ths | uint32_t tstr; |
222 | cd1a3f68 | ths | int feat;
|
223 | cd1a3f68 | ths | } tmu012_state; |
224 | cd1a3f68 | ths | |
225 | a8170e5e | Avi Kivity | static uint64_t tmu012_read(void *opaque, hwaddr offset, |
226 | 89e29451 | Benoît Canet | unsigned size)
|
227 | cd1a3f68 | ths | { |
228 | cd1a3f68 | ths | tmu012_state *s = (tmu012_state *)opaque; |
229 | cd1a3f68 | ths | |
230 | cd1a3f68 | ths | #ifdef DEBUG_TIMER
|
231 | cd1a3f68 | ths | printf("tmu012_read 0x%lx\n", (unsigned long) offset); |
232 | cd1a3f68 | ths | #endif
|
233 | cd1a3f68 | ths | |
234 | cd1a3f68 | ths | if (offset >= 0x20) { |
235 | cd1a3f68 | ths | if (!(s->feat & TMU012_FEAT_3CHAN))
|
236 | 2ac71179 | Paul Brook | hw_error("tmu012_write: Bad channel offset %x\n", (int)offset); |
237 | cd1a3f68 | ths | return sh_timer_read(s->timer[2], offset - 0x20); |
238 | cd1a3f68 | ths | } |
239 | cd1a3f68 | ths | |
240 | cd1a3f68 | ths | if (offset >= 0x14) |
241 | cd1a3f68 | ths | return sh_timer_read(s->timer[1], offset - 0x14); |
242 | cd1a3f68 | ths | |
243 | cd1a3f68 | ths | if (offset >= 0x08) |
244 | cd1a3f68 | ths | return sh_timer_read(s->timer[0], offset - 0x08); |
245 | cd1a3f68 | ths | |
246 | cd1a3f68 | ths | if (offset == 4) |
247 | cd1a3f68 | ths | return s->tstr;
|
248 | cd1a3f68 | ths | |
249 | cd1a3f68 | ths | if ((s->feat & TMU012_FEAT_TOCR) && offset == 0) |
250 | cd1a3f68 | ths | return s->tocr;
|
251 | cd1a3f68 | ths | |
252 | 2ac71179 | Paul Brook | hw_error("tmu012_write: Bad offset %x\n", (int)offset); |
253 | cd1a3f68 | ths | return 0; |
254 | cd1a3f68 | ths | } |
255 | cd1a3f68 | ths | |
256 | a8170e5e | Avi Kivity | static void tmu012_write(void *opaque, hwaddr offset, |
257 | 89e29451 | Benoît Canet | uint64_t value, unsigned size)
|
258 | cd1a3f68 | ths | { |
259 | cd1a3f68 | ths | tmu012_state *s = (tmu012_state *)opaque; |
260 | cd1a3f68 | ths | |
261 | cd1a3f68 | ths | #ifdef DEBUG_TIMER
|
262 | cd1a3f68 | ths | printf("tmu012_write 0x%lx 0x%08x\n", (unsigned long) offset, value); |
263 | cd1a3f68 | ths | #endif
|
264 | cd1a3f68 | ths | |
265 | cd1a3f68 | ths | if (offset >= 0x20) { |
266 | cd1a3f68 | ths | if (!(s->feat & TMU012_FEAT_3CHAN))
|
267 | 2ac71179 | Paul Brook | hw_error("tmu012_write: Bad channel offset %x\n", (int)offset); |
268 | cd1a3f68 | ths | sh_timer_write(s->timer[2], offset - 0x20, value); |
269 | cd1a3f68 | ths | return;
|
270 | cd1a3f68 | ths | } |
271 | cd1a3f68 | ths | |
272 | cd1a3f68 | ths | if (offset >= 0x14) { |
273 | cd1a3f68 | ths | sh_timer_write(s->timer[1], offset - 0x14, value); |
274 | cd1a3f68 | ths | return;
|
275 | cd1a3f68 | ths | } |
276 | cd1a3f68 | ths | |
277 | cd1a3f68 | ths | if (offset >= 0x08) { |
278 | cd1a3f68 | ths | sh_timer_write(s->timer[0], offset - 0x08, value); |
279 | cd1a3f68 | ths | return;
|
280 | cd1a3f68 | ths | } |
281 | cd1a3f68 | ths | |
282 | cd1a3f68 | ths | if (offset == 4) { |
283 | cd1a3f68 | ths | sh_timer_start_stop(s->timer[0], value & (1 << 0)); |
284 | cd1a3f68 | ths | sh_timer_start_stop(s->timer[1], value & (1 << 1)); |
285 | cd1a3f68 | ths | if (s->feat & TMU012_FEAT_3CHAN)
|
286 | cd1a3f68 | ths | sh_timer_start_stop(s->timer[2], value & (1 << 2)); |
287 | cd1a3f68 | ths | else
|
288 | cd1a3f68 | ths | if (value & (1 << 2)) |
289 | 2ac71179 | Paul Brook | hw_error("tmu012_write: Bad channel\n");
|
290 | cd1a3f68 | ths | |
291 | cd1a3f68 | ths | s->tstr = value; |
292 | cd1a3f68 | ths | return;
|
293 | cd1a3f68 | ths | } |
294 | cd1a3f68 | ths | |
295 | cd1a3f68 | ths | if ((s->feat & TMU012_FEAT_TOCR) && offset == 0) { |
296 | cd1a3f68 | ths | s->tocr = value & (1 << 0); |
297 | cd1a3f68 | ths | } |
298 | cd1a3f68 | ths | } |
299 | cd1a3f68 | ths | |
300 | 89e29451 | Benoît Canet | static const MemoryRegionOps tmu012_ops = { |
301 | 89e29451 | Benoît Canet | .read = tmu012_read, |
302 | 89e29451 | Benoît Canet | .write = tmu012_write, |
303 | 89e29451 | Benoît Canet | .endianness = DEVICE_NATIVE_ENDIAN, |
304 | cd1a3f68 | ths | }; |
305 | cd1a3f68 | ths | |
306 | a8170e5e | Avi Kivity | void tmu012_init(MemoryRegion *sysmem, hwaddr base,
|
307 | 89e29451 | Benoît Canet | int feat, uint32_t freq,
|
308 | 96e2fc41 | aurel32 | qemu_irq ch0_irq, qemu_irq ch1_irq, |
309 | 96e2fc41 | aurel32 | qemu_irq ch2_irq0, qemu_irq ch2_irq1) |
310 | cd1a3f68 | ths | { |
311 | cd1a3f68 | ths | tmu012_state *s; |
312 | cd1a3f68 | ths | int timer_feat = (feat & TMU012_FEAT_EXTCLK) ? TIMER_FEAT_EXTCLK : 0; |
313 | cd1a3f68 | ths | |
314 | 7267c094 | Anthony Liguori | s = (tmu012_state *)g_malloc0(sizeof(tmu012_state));
|
315 | cd1a3f68 | ths | s->feat = feat; |
316 | 703243a0 | balrog | s->timer[0] = sh_timer_init(freq, timer_feat, ch0_irq);
|
317 | 703243a0 | balrog | s->timer[1] = sh_timer_init(freq, timer_feat, ch1_irq);
|
318 | cd1a3f68 | ths | if (feat & TMU012_FEAT_3CHAN)
|
319 | 703243a0 | balrog | s->timer[2] = sh_timer_init(freq, timer_feat | TIMER_FEAT_CAPT,
|
320 | 703243a0 | balrog | ch2_irq0); /* ch2_irq1 not supported */
|
321 | 89e29451 | Benoît Canet | |
322 | 89e29451 | Benoît Canet | memory_region_init_io(&s->iomem, &tmu012_ops, s, |
323 | 89e29451 | Benoît Canet | "timer", 0x100000000ULL); |
324 | 89e29451 | Benoît Canet | |
325 | 89e29451 | Benoît Canet | memory_region_init_alias(&s->iomem_p4, "timer-p4",
|
326 | 89e29451 | Benoît Canet | &s->iomem, 0, 0x1000); |
327 | 89e29451 | Benoît Canet | memory_region_add_subregion(sysmem, P4ADDR(base), &s->iomem_p4); |
328 | 89e29451 | Benoît Canet | |
329 | 89e29451 | Benoît Canet | memory_region_init_alias(&s->iomem_a7, "timer-a7",
|
330 | 89e29451 | Benoît Canet | &s->iomem, 0, 0x1000); |
331 | 89e29451 | Benoît Canet | memory_region_add_subregion(sysmem, A7ADDR(base), &s->iomem_a7); |
332 | cd1a3f68 | ths | /* ??? Save/restore. */
|
333 | cd1a3f68 | ths | } |