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