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