]>
Commit | Line | Data |
---|---|---|
cd1a3f68 TS |
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 | ||
87ecb68b PB |
11 | #include "hw.h" |
12 | #include "sh.h" | |
13 | #include "qemu-timer.h" | |
cd1a3f68 TS |
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; | |
703243a0 | 36 | int old_level; |
cd1a3f68 TS |
37 | int feat; |
38 | int enabled; | |
96e2fc41 | 39 | qemu_irq irq; |
cd1a3f68 TS |
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 | { | |
703243a0 AZ |
46 | int new_level = s->int_level && (s->tcr & TIMER_TCR_UNIE); |
47 | ||
48 | if (new_level != s->old_level) | |
96e2fc41 | 49 | qemu_set_irq (s->irq, new_level); |
703243a0 AZ |
50 | |
51 | s->old_level = s->int_level; | |
52 | s->int_level = new_level; | |
cd1a3f68 TS |
53 | } |
54 | ||
9596ebb7 | 55 | static uint32_t sh_timer_read(void *opaque, target_phys_addr_t offset) |
cd1a3f68 TS |
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 | ||
96e2fc41 | 188 | static void *sh_timer_init(uint32_t freq, int feat, qemu_irq irq) |
cd1a3f68 TS |
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; | |
703243a0 | 201 | s->irq = irq; |
cd1a3f68 TS |
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; | |
cd1a3f68 TS |
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 | |
cd1a3f68 TS |
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 | |
cd1a3f68 TS |
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 | ||
703243a0 | 306 | void tmu012_init(target_phys_addr_t base, int feat, uint32_t freq, |
96e2fc41 AJ |
307 | qemu_irq ch0_irq, qemu_irq ch1_irq, |
308 | qemu_irq ch2_irq0, qemu_irq ch2_irq1) | |
cd1a3f68 TS |
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)); | |
cd1a3f68 | 315 | s->feat = feat; |
703243a0 AZ |
316 | s->timer[0] = sh_timer_init(freq, timer_feat, ch0_irq); |
317 | s->timer[1] = sh_timer_init(freq, timer_feat, ch1_irq); | |
cd1a3f68 | 318 | if (feat & TMU012_FEAT_3CHAN) |
703243a0 AZ |
319 | s->timer[2] = sh_timer_init(freq, timer_feat | TIMER_FEAT_CAPT, |
320 | ch2_irq0); /* ch2_irq1 not supported */ | |
cd1a3f68 TS |
321 | iomemtype = cpu_register_io_memory(0, tmu012_readfn, |
322 | tmu012_writefn, s); | |
323 | cpu_register_physical_memory(base, 0x00001000, iomemtype); | |
324 | /* ??? Save/restore. */ | |
325 | } |