1 | /* |
2 | * QEMU ETRAX Timers |
3 | * |
4 | * Copyright (c) 2007 Edgar E. Iglesias, Axis Communications AB. |
5 | * |
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
7 | * of this software and associated documentation files (the "Software"), to deal |
8 | * in the Software without restriction, including without limitation the rights |
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
10 | * copies of the Software, and to permit persons to whom the Software is |
11 | * furnished to do so, subject to the following conditions: |
12 | * |
13 | * The above copyright notice and this permission notice shall be included in |
14 | * all copies or substantial portions of the Software. |
15 | * |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
22 | * THE SOFTWARE. |
23 | */ |
24 | |
25 | #include "qemu/osdep.h" |
26 | #include "hw/sysbus.h" |
27 | #include "sysemu/reset.h" |
28 | #include "sysemu/runstate.h" |
29 | #include "qemu/main-loop.h" |
30 | #include "qemu/module.h" |
31 | #include "qemu/timer.h" |
32 | #include "hw/irq.h" |
33 | #include "hw/ptimer.h" |
34 | |
35 | #define D(x) |
36 | |
37 | #define RW_TMR0_DIV 0x00 |
38 | #define R_TMR0_DATA 0x04 |
39 | #define RW_TMR0_CTRL 0x08 |
40 | #define RW_TMR1_DIV 0x10 |
41 | #define R_TMR1_DATA 0x14 |
42 | #define RW_TMR1_CTRL 0x18 |
43 | #define R_TIME 0x38 |
44 | #define RW_WD_CTRL 0x40 |
45 | #define R_WD_STAT 0x44 |
46 | #define RW_INTR_MASK 0x48 |
47 | #define RW_ACK_INTR 0x4c |
48 | #define R_INTR 0x50 |
49 | #define R_MASKED_INTR 0x54 |
50 | |
51 | #define TYPE_ETRAX_FS_TIMER "etraxfs,timer" |
52 | #define ETRAX_TIMER(obj) \ |
53 | OBJECT_CHECK(ETRAXTimerState, (obj), TYPE_ETRAX_FS_TIMER) |
54 | |
55 | typedef struct ETRAXTimerState { |
56 | SysBusDevice parent_obj; |
57 | |
58 | MemoryRegion mmio; |
59 | qemu_irq irq; |
60 | qemu_irq nmi; |
61 | |
62 | QEMUBH *bh_t0; |
63 | QEMUBH *bh_t1; |
64 | QEMUBH *bh_wd; |
65 | ptimer_state *ptimer_t0; |
66 | ptimer_state *ptimer_t1; |
67 | ptimer_state *ptimer_wd; |
68 | |
69 | int wd_hits; |
70 | |
71 | /* Control registers. */ |
72 | uint32_t rw_tmr0_div; |
73 | uint32_t r_tmr0_data; |
74 | uint32_t rw_tmr0_ctrl; |
75 | |
76 | uint32_t rw_tmr1_div; |
77 | uint32_t r_tmr1_data; |
78 | uint32_t rw_tmr1_ctrl; |
79 | |
80 | uint32_t rw_wd_ctrl; |
81 | |
82 | uint32_t rw_intr_mask; |
83 | uint32_t rw_ack_intr; |
84 | uint32_t r_intr; |
85 | uint32_t r_masked_intr; |
86 | } ETRAXTimerState; |
87 | |
88 | static uint64_t |
89 | timer_read(void *opaque, hwaddr addr, unsigned int size) |
90 | { |
91 | ETRAXTimerState *t = opaque; |
92 | uint32_t r = 0; |
93 | |
94 | switch (addr) { |
95 | case R_TMR0_DATA: |
96 | r = ptimer_get_count(t->ptimer_t0); |
97 | break; |
98 | case R_TMR1_DATA: |
99 | r = ptimer_get_count(t->ptimer_t1); |
100 | break; |
101 | case R_TIME: |
102 | r = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 10; |
103 | break; |
104 | case RW_INTR_MASK: |
105 | r = t->rw_intr_mask; |
106 | break; |
107 | case R_MASKED_INTR: |
108 | r = t->r_intr & t->rw_intr_mask; |
109 | break; |
110 | default: |
111 | D(printf ("%s %x\n" , __func__, addr)); |
112 | break; |
113 | } |
114 | return r; |
115 | } |
116 | |
117 | static void update_ctrl(ETRAXTimerState *t, int tnum) |
118 | { |
119 | unsigned int op; |
120 | unsigned int freq; |
121 | unsigned int freq_hz; |
122 | unsigned int div; |
123 | uint32_t ctrl; |
124 | |
125 | ptimer_state *timer; |
126 | |
127 | if (tnum == 0) { |
128 | ctrl = t->rw_tmr0_ctrl; |
129 | div = t->rw_tmr0_div; |
130 | timer = t->ptimer_t0; |
131 | } else { |
132 | ctrl = t->rw_tmr1_ctrl; |
133 | div = t->rw_tmr1_div; |
134 | timer = t->ptimer_t1; |
135 | } |
136 | |
137 | |
138 | op = ctrl & 3; |
139 | freq = ctrl >> 2; |
140 | freq_hz = 32000000; |
141 | |
142 | switch (freq) |
143 | { |
144 | case 0: |
145 | case 1: |
146 | D(printf ("extern or disabled timer clock?\n" )); |
147 | break; |
148 | case 4: freq_hz = 29493000; break; |
149 | case 5: freq_hz = 32000000; break; |
150 | case 6: freq_hz = 32768000; break; |
151 | case 7: freq_hz = 100000000; break; |
152 | default: |
153 | abort(); |
154 | break; |
155 | } |
156 | |
157 | D(printf ("freq_hz=%d div=%d\n" , freq_hz, div)); |
158 | ptimer_set_freq(timer, freq_hz); |
159 | ptimer_set_limit(timer, div, 0); |
160 | |
161 | switch (op) |
162 | { |
163 | case 0: |
164 | /* Load. */ |
165 | ptimer_set_limit(timer, div, 1); |
166 | break; |
167 | case 1: |
168 | /* Hold. */ |
169 | ptimer_stop(timer); |
170 | break; |
171 | case 2: |
172 | /* Run. */ |
173 | ptimer_run(timer, 0); |
174 | break; |
175 | default: |
176 | abort(); |
177 | break; |
178 | } |
179 | } |
180 | |
181 | static void timer_update_irq(ETRAXTimerState *t) |
182 | { |
183 | t->r_intr &= ~(t->rw_ack_intr); |
184 | t->r_masked_intr = t->r_intr & t->rw_intr_mask; |
185 | |
186 | D(printf("%s: masked_intr=%x\n" , __func__, t->r_masked_intr)); |
187 | qemu_set_irq(t->irq, !!t->r_masked_intr); |
188 | } |
189 | |
190 | static void timer0_hit(void *opaque) |
191 | { |
192 | ETRAXTimerState *t = opaque; |
193 | t->r_intr |= 1; |
194 | timer_update_irq(t); |
195 | } |
196 | |
197 | static void timer1_hit(void *opaque) |
198 | { |
199 | ETRAXTimerState *t = opaque; |
200 | t->r_intr |= 2; |
201 | timer_update_irq(t); |
202 | } |
203 | |
204 | static void watchdog_hit(void *opaque) |
205 | { |
206 | ETRAXTimerState *t = opaque; |
207 | if (t->wd_hits == 0) { |
208 | /* real hw gives a single tick before reseting but we are |
209 | a bit friendlier to compensate for our slower execution. */ |
210 | ptimer_set_count(t->ptimer_wd, 10); |
211 | ptimer_run(t->ptimer_wd, 1); |
212 | qemu_irq_raise(t->nmi); |
213 | } |
214 | else |
215 | qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); |
216 | |
217 | t->wd_hits++; |
218 | } |
219 | |
220 | static inline void timer_watchdog_update(ETRAXTimerState *t, uint32_t value) |
221 | { |
222 | unsigned int wd_en = t->rw_wd_ctrl & (1 << 8); |
223 | unsigned int wd_key = t->rw_wd_ctrl >> 9; |
224 | unsigned int wd_cnt = t->rw_wd_ctrl & 511; |
225 | unsigned int new_key = value >> 9 & ((1 << 7) - 1); |
226 | unsigned int new_cmd = (value >> 8) & 1; |
227 | |
228 | /* If the watchdog is enabled, they written key must match the |
229 | complement of the previous. */ |
230 | wd_key = ~wd_key & ((1 << 7) - 1); |
231 | |
232 | if (wd_en && wd_key != new_key) |
233 | return; |
234 | |
235 | D(printf("en=%d new_key=%x oldkey=%x cmd=%d cnt=%d\n" , |
236 | wd_en, new_key, wd_key, new_cmd, wd_cnt)); |
237 | |
238 | if (t->wd_hits) |
239 | qemu_irq_lower(t->nmi); |
240 | |
241 | t->wd_hits = 0; |
242 | |
243 | ptimer_set_freq(t->ptimer_wd, 760); |
244 | if (wd_cnt == 0) |
245 | wd_cnt = 256; |
246 | ptimer_set_count(t->ptimer_wd, wd_cnt); |
247 | if (new_cmd) |
248 | ptimer_run(t->ptimer_wd, 1); |
249 | else |
250 | ptimer_stop(t->ptimer_wd); |
251 | |
252 | t->rw_wd_ctrl = value; |
253 | } |
254 | |
255 | static void |
256 | timer_write(void *opaque, hwaddr addr, |
257 | uint64_t val64, unsigned int size) |
258 | { |
259 | ETRAXTimerState *t = opaque; |
260 | uint32_t value = val64; |
261 | |
262 | switch (addr) |
263 | { |
264 | case RW_TMR0_DIV: |
265 | t->rw_tmr0_div = value; |
266 | break; |
267 | case RW_TMR0_CTRL: |
268 | D(printf ("RW_TMR0_CTRL=%x\n" , value)); |
269 | t->rw_tmr0_ctrl = value; |
270 | update_ctrl(t, 0); |
271 | break; |
272 | case RW_TMR1_DIV: |
273 | t->rw_tmr1_div = value; |
274 | break; |
275 | case RW_TMR1_CTRL: |
276 | D(printf ("RW_TMR1_CTRL=%x\n" , value)); |
277 | t->rw_tmr1_ctrl = value; |
278 | update_ctrl(t, 1); |
279 | break; |
280 | case RW_INTR_MASK: |
281 | D(printf ("RW_INTR_MASK=%x\n" , value)); |
282 | t->rw_intr_mask = value; |
283 | timer_update_irq(t); |
284 | break; |
285 | case RW_WD_CTRL: |
286 | timer_watchdog_update(t, value); |
287 | break; |
288 | case RW_ACK_INTR: |
289 | t->rw_ack_intr = value; |
290 | timer_update_irq(t); |
291 | t->rw_ack_intr = 0; |
292 | break; |
293 | default: |
294 | printf ("%s " TARGET_FMT_plx " %x\n" , |
295 | __func__, addr, value); |
296 | break; |
297 | } |
298 | } |
299 | |
300 | static const MemoryRegionOps timer_ops = { |
301 | .read = timer_read, |
302 | .write = timer_write, |
303 | .endianness = DEVICE_LITTLE_ENDIAN, |
304 | .valid = { |
305 | .min_access_size = 4, |
306 | .max_access_size = 4 |
307 | } |
308 | }; |
309 | |
310 | static void etraxfs_timer_reset(void *opaque) |
311 | { |
312 | ETRAXTimerState *t = opaque; |
313 | |
314 | ptimer_stop(t->ptimer_t0); |
315 | ptimer_stop(t->ptimer_t1); |
316 | ptimer_stop(t->ptimer_wd); |
317 | t->rw_wd_ctrl = 0; |
318 | t->r_intr = 0; |
319 | t->rw_intr_mask = 0; |
320 | qemu_irq_lower(t->irq); |
321 | } |
322 | |
323 | static void etraxfs_timer_realize(DeviceState *dev, Error **errp) |
324 | { |
325 | ETRAXTimerState *t = ETRAX_TIMER(dev); |
326 | SysBusDevice *sbd = SYS_BUS_DEVICE(dev); |
327 | |
328 | t->bh_t0 = qemu_bh_new(timer0_hit, t); |
329 | t->bh_t1 = qemu_bh_new(timer1_hit, t); |
330 | t->bh_wd = qemu_bh_new(watchdog_hit, t); |
331 | t->ptimer_t0 = ptimer_init(t->bh_t0, PTIMER_POLICY_DEFAULT); |
332 | t->ptimer_t1 = ptimer_init(t->bh_t1, PTIMER_POLICY_DEFAULT); |
333 | t->ptimer_wd = ptimer_init(t->bh_wd, PTIMER_POLICY_DEFAULT); |
334 | |
335 | sysbus_init_irq(sbd, &t->irq); |
336 | sysbus_init_irq(sbd, &t->nmi); |
337 | |
338 | memory_region_init_io(&t->mmio, OBJECT(t), &timer_ops, t, |
339 | "etraxfs-timer" , 0x5c); |
340 | sysbus_init_mmio(sbd, &t->mmio); |
341 | qemu_register_reset(etraxfs_timer_reset, t); |
342 | } |
343 | |
344 | static void etraxfs_timer_class_init(ObjectClass *klass, void *data) |
345 | { |
346 | DeviceClass *dc = DEVICE_CLASS(klass); |
347 | |
348 | dc->realize = etraxfs_timer_realize; |
349 | } |
350 | |
351 | static const TypeInfo etraxfs_timer_info = { |
352 | .name = TYPE_ETRAX_FS_TIMER, |
353 | .parent = TYPE_SYS_BUS_DEVICE, |
354 | .instance_size = sizeof(ETRAXTimerState), |
355 | .class_init = etraxfs_timer_class_init, |
356 | }; |
357 | |
358 | static void etraxfs_timer_register_types(void) |
359 | { |
360 | type_register_static(&etraxfs_timer_info); |
361 | } |
362 | |
363 | type_init(etraxfs_timer_register_types) |
364 | |