Revisión | 75a502e22cf20b852f107fe521c2600e22cb3a08 (tree) |
---|---|
Tiempo | 2020-06-01 00:49:33 |
Autor | Yoshinori Sato <ysato@user...> |
Commiter | Yoshinori Sato |
hw/timer: Renesas TMU/CMT module.
TMU - SH4 Timer module.
CMT - Compare and match timer used by some Renesas MCUs.
The two modules have similar interfaces and have been merged.
Signed-off-by: Yoshinori Sato <ysato@users.sourceforge.jp>
@@ -38,3 +38,6 @@ config CMSDK_APB_DUALTIMER | ||
38 | 38 | |
39 | 39 | config RENESAS_8TMR |
40 | 40 | bool |
41 | + | |
42 | +config RENESAS_TIMER | |
43 | + bool |
@@ -37,3 +37,4 @@ common-obj-$(CONFIG_MSF2) += mss-timer.o | ||
37 | 37 | common-obj-$(CONFIG_RASPI) += bcm2835_systmr.o |
38 | 38 | |
39 | 39 | common-obj-$(CONFIG_RENESAS_8TMR) += renesas_8timer.o |
40 | +common-obj-$(CONFIG_RENESAS_TIMER) += renesas_timer.o |
@@ -0,0 +1,421 @@ | ||
1 | +/* | |
2 | + * Renesas 16bit Compare-match timer | |
3 | + * | |
4 | + * Datasheet: RX62N Group, RX621 Group User's Manual: Hardware | |
5 | + * (Rev.1.40 R01UH0033EJ0140) | |
6 | + * | |
7 | + * Copyright (c) 2019 Yoshinori Sato | |
8 | + * | |
9 | + * This program is free software; you can redistribute it and/or modify it | |
10 | + * under the terms and conditions of the GNU General Public License, | |
11 | + * version 2 or later, as published by the Free Software Foundation. | |
12 | + * | |
13 | + * This program is distributed in the hope it will be useful, but WITHOUT | |
14 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
15 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
16 | + * more details. | |
17 | + * | |
18 | + * You should have received a copy of the GNU General Public License along with | |
19 | + * this program. If not, see <http://www.gnu.org/licenses/>. | |
20 | + */ | |
21 | + | |
22 | +#include "qemu/osdep.h" | |
23 | +#include "qemu-common.h" | |
24 | +#include "qemu/log.h" | |
25 | +#include "qapi/error.h" | |
26 | +#include "qemu/timer.h" | |
27 | +#include "hw/hw.h" | |
28 | +#include "hw/irq.h" | |
29 | +#include "hw/sysbus.h" | |
30 | +#include "hw/registerfields.h" | |
31 | +#include "hw/qdev-properties.h" | |
32 | +#include "hw/timer/renesas_timer.h" | |
33 | +#include "migration/vmstate.h" | |
34 | +#include "qemu/error-report.h" | |
35 | + | |
36 | +REG32(TOCR, 0) | |
37 | + FIELD(TOCR, TCOE, 0, 1) | |
38 | +REG32(TSTR, 4) | |
39 | +REG32(TCOR, 8) | |
40 | +REG32(TCNT, 12) | |
41 | +REG32(TCR, 16) | |
42 | + FIELD(TCR, TPSC, 0, 3) | |
43 | + FIELD(TCR, CKEG, 3, 2) | |
44 | + FIELD(TCR, UNIE, 5, 1) | |
45 | + FIELD(TCR, ICPE, 6, 2) | |
46 | + FIELD(TCR, UNF, 8, 1) | |
47 | + FIELD(TCR, ICPF, 9, 1) | |
48 | +REG32(CMCR, 16) | |
49 | + FIELD(CMCR, CKS, 0, 2) | |
50 | + FIELD(CMCR, CMIE, 6, 1) | |
51 | +REG32(TCPR, 20) | |
52 | + | |
53 | +#define IS_CMT(t) (t->feature == RTIMER_FEAT_CMT) | |
54 | + | |
55 | +static int clkdiv(RTIMERState *tmr, int ch) | |
56 | +{ | |
57 | + if (IS_CMT(tmr)) { | |
58 | + return 8 << (2 * FIELD_EX16(tmr->ch[ch].ctrl, CMCR, CKS)); | |
59 | + } else { | |
60 | + if (FIELD_EX16(tmr->ch[ch].ctrl, TCR, TPSC) <= 5) { | |
61 | + return 4 << (2 * FIELD_EX16(tmr->ch[ch].ctrl, TCR, TPSC)); | |
62 | + } else { | |
63 | + return 0; | |
64 | + } | |
65 | + } | |
66 | +} | |
67 | + | |
68 | +static void set_next_event(struct channel_rtimer *ch, int64_t now) | |
69 | +{ | |
70 | + int64_t next; | |
71 | + RTIMERState *tmr = ch->tmrp; | |
72 | + if (IS_CMT(tmr)) { | |
73 | + next = ch->cor - ch->cnt; | |
74 | + } else { | |
75 | + next = ch->cnt; | |
76 | + } | |
77 | + next *= ch->clk; | |
78 | + ch->base = now; | |
79 | + ch->next = now + next; | |
80 | + timer_mod(ch->timer, ch->next); | |
81 | +} | |
82 | + | |
83 | +static void timer_event(void *opaque) | |
84 | +{ | |
85 | + struct channel_rtimer *ch = opaque; | |
86 | + RTIMERState *tmr = ch->tmrp; | |
87 | + | |
88 | + if (IS_CMT(tmr)) { | |
89 | + ch->cnt = 0; | |
90 | + if (FIELD_EX16(ch->ctrl, CMCR, CMIE)) { | |
91 | + qemu_irq_pulse(ch->irq); | |
92 | + } | |
93 | + } else { | |
94 | + ch->cnt = ch->cor; | |
95 | + if (!FIELD_EX16(ch->ctrl, TCR, UNF)) { | |
96 | + ch->ctrl = FIELD_DP16(ch->ctrl, TCR, UNF, 1); | |
97 | + qemu_set_irq(ch->irq, FIELD_EX16(ch->ctrl, TCR, UNIE)); | |
98 | + } | |
99 | + } | |
100 | + set_next_event(ch, ch->next); | |
101 | +} | |
102 | + | |
103 | +static int64_t read_tcnt(RTIMERState *tmr, int ch) | |
104 | +{ | |
105 | + int64_t delta, now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | |
106 | + | |
107 | + if (tmr->ch[ch].clk > 0) { | |
108 | + delta = (now - tmr->ch[ch].base); | |
109 | + delta /= tmr->ch[ch].clk; | |
110 | + if (IS_CMT(tmr)) { | |
111 | + return delta; | |
112 | + } else { | |
113 | + return tmr->ch[ch].cnt - delta; | |
114 | + } | |
115 | + } else { | |
116 | + return tmr->ch[ch].cnt; | |
117 | + } | |
118 | +} | |
119 | + | |
120 | +static void tmr_start_stop(RTIMERState *tmr, int ch, int start) | |
121 | +{ | |
122 | + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | |
123 | + tmr->ch[ch].start = start; | |
124 | + if (start) { | |
125 | + if (!tmr->ch[ch].timer) { | |
126 | + tmr->ch[ch].timer = | |
127 | + timer_new_ns(QEMU_CLOCK_VIRTUAL, timer_event, &tmr->ch[ch]); | |
128 | + } | |
129 | + set_next_event(&tmr->ch[ch], now); | |
130 | + } else { | |
131 | + tmr->ch[ch].cnt = read_tcnt(tmr, ch); | |
132 | + tmr->ch[ch].next = 0; | |
133 | + if (tmr->ch[ch].timer) { | |
134 | + timer_del(tmr->ch[ch].timer); | |
135 | + } | |
136 | + } | |
137 | +} | |
138 | + | |
139 | +static void timer_register(RTIMERState *tmr, hwaddr addr, int *ch, int *reg) | |
140 | +{ | |
141 | + if (IS_CMT(tmr)) { | |
142 | + /* +0 - CMSTR (TSTR) */ | |
143 | + /* +2 - CMCR0 (TCR) */ | |
144 | + /* +4 - CMCNT0 (TCNT) */ | |
145 | + /* +6 - CMCOR0 (TCOR) */ | |
146 | + /* +8 - CMCR1 (TCR) */ | |
147 | + /* +10 - CMCNT1 (TCNT) */ | |
148 | + /* +12 - CMCOR1 (TCOR) */ | |
149 | + addr /= 2; | |
150 | + if (addr > 6) { | |
151 | + /* Out of register area */ | |
152 | + *reg = -1; | |
153 | + return; | |
154 | + } | |
155 | + if (addr == 0) { | |
156 | + *ch = -1; | |
157 | + *reg = R_TSTR; | |
158 | + } else { | |
159 | + *ch = addr / 4; | |
160 | + if (addr < 4) { | |
161 | + /* skip CMSTR */ | |
162 | + addr--; | |
163 | + } | |
164 | + *reg = 2 - (addr % 4); | |
165 | + } | |
166 | + } else { | |
167 | + /* +0 - TCOR */ | |
168 | + /* +4 - TSTR */ | |
169 | + /* +8 - TCOR0 */ | |
170 | + /* +12 - TCNT0 */ | |
171 | + /* +16 - TCR0 */ | |
172 | + /* +20 - TCOR1 */ | |
173 | + /* +24 - TCNT1 */ | |
174 | + /* +28 - TCR1 */ | |
175 | + /* +32 - TCOR2 */ | |
176 | + /* +36 - TCNT2 */ | |
177 | + /* +40 - TCR2 */ | |
178 | + /* +44 - TCPR2 */ | |
179 | + if (tmr->feature == RTIMER_FEAT_TMU_HIGH && addr >= 8) { | |
180 | + *reg = -1; | |
181 | + return; | |
182 | + } | |
183 | + addr /= 4; | |
184 | + if (addr < 2) { | |
185 | + *ch = -1; | |
186 | + *reg = addr; | |
187 | + } else if (addr < 11) { | |
188 | + *ch = (addr - 2) / 3; | |
189 | + *reg = (addr - 2) % 3 + 2; | |
190 | + } else { | |
191 | + *ch = 2; | |
192 | + *reg = R_TCPR; | |
193 | + } | |
194 | + } | |
195 | +} | |
196 | + | |
197 | +static uint64_t read_tstr(RTIMERState *tmr) | |
198 | +{ | |
199 | + uint64_t ret = 0; | |
200 | + int ch; | |
201 | + for (ch = 0; ch < tmr->num_ch; ch++) { | |
202 | + ret = deposit64(ret, ch, 1, tmr->ch[ch].start); | |
203 | + } | |
204 | + return ret; | |
205 | +} | |
206 | + | |
207 | +static void update_clk(RTIMERState *tmr, int ch) | |
208 | +{ | |
209 | + int tpsc; | |
210 | + int t; | |
211 | + if (!IS_CMT(tmr)) { | |
212 | + /* Clock setting validation */ | |
213 | + tpsc = FIELD_EX16(tmr->ch[ch].ctrl, TCR, TPSC); | |
214 | + switch (tpsc) { | |
215 | + case 5: | |
216 | + qemu_log_mask(LOG_GUEST_ERROR, | |
217 | + "renesas_timer: Invalid TPSC valule %d.", tpsc); | |
218 | + break; | |
219 | + case 6: | |
220 | + case 7: | |
221 | + qemu_log_mask(LOG_UNIMP, | |
222 | + "renesas_timer: External clock not implemented."); | |
223 | + break; | |
224 | + } | |
225 | + /* Interrupt clear */ | |
226 | + if (FIELD_EX16(tmr->ch[ch].ctrl, TCR, UNF) == 0) { | |
227 | + qemu_set_irq(tmr->ch[ch].irq, 0); | |
228 | + } | |
229 | + } | |
230 | + t = clkdiv(tmr, ch); | |
231 | + if (t > 0) { | |
232 | + t = tmr->input_freq / t; | |
233 | + tmr->ch[ch].clk = NANOSECONDS_PER_SECOND / t; | |
234 | + } else { | |
235 | + tmr->ch[ch].clk = 0; | |
236 | + } | |
237 | +} | |
238 | + | |
239 | +static uint64_t tmr_read(void *opaque, hwaddr addr, unsigned size) | |
240 | +{ | |
241 | + RTIMERState *tmr = opaque; | |
242 | + int ch = -1, reg = -1; | |
243 | + | |
244 | + timer_register(tmr, addr, &ch, ®); | |
245 | + switch (reg) { | |
246 | + case R_TOCR: | |
247 | + return tmr->tocr; | |
248 | + case R_TSTR: | |
249 | + return read_tstr(tmr); | |
250 | + case R_TCR: | |
251 | + return tmr->ch[ch].ctrl; | |
252 | + case R_TCNT: | |
253 | + if (tmr->ch[ch].start) { | |
254 | + return read_tcnt(tmr, ch); | |
255 | + } else { | |
256 | + return tmr->ch[ch].cnt; | |
257 | + } | |
258 | + case R_TCOR: | |
259 | + return tmr->ch[ch].cor; | |
260 | + case R_TCPR: | |
261 | + qemu_log_mask(LOG_UNIMP, | |
262 | + "renesas_timer: Input capture not implemented\n"); | |
263 | + return 0; | |
264 | + default: | |
265 | + qemu_log_mask(LOG_UNIMP, "renesas_timer: Register 0x%" | |
266 | + HWADDR_PRIX " not implemented\n", addr); | |
267 | + } | |
268 | + return UINT64_MAX; | |
269 | +} | |
270 | + | |
271 | +static void tmr_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) | |
272 | +{ | |
273 | + RTIMERState *tmr = opaque; | |
274 | + int ch = -1, reg = -1; | |
275 | + uint16_t tcr_mask; | |
276 | + | |
277 | + timer_register(tmr, addr, &ch, ®); | |
278 | + switch (reg) { | |
279 | + case R_TOCR: | |
280 | + tmr->tocr = FIELD_DP8(tmr->tocr, TOCR, TCOE, | |
281 | + FIELD_EX8(val, TOCR, TCOE)); | |
282 | + break; | |
283 | + case R_TSTR: | |
284 | + for (ch = 0; ch < tmr->num_ch; ch++) { | |
285 | + tmr_start_stop(tmr, ch, extract32(val, ch, 1)); | |
286 | + } | |
287 | + break; | |
288 | + case R_TCR: | |
289 | + switch (tmr->feature) { | |
290 | + case RTIMER_FEAT_CMT: | |
291 | + tcr_mask = 0x00a3; | |
292 | + /* bit7 always 1 */ | |
293 | + val |= 0x0080; | |
294 | + break; | |
295 | + case RTIMER_FEAT_TMU_LOW: | |
296 | + tcr_mask = (ch < 2) ? 0x013f : 0x03ff; | |
297 | + break; | |
298 | + case RTIMER_FEAT_TMU_HIGH: | |
299 | + tcr_mask = 0x0127; | |
300 | + break; | |
301 | + default: | |
302 | + tcr_mask = 0x00ff; | |
303 | + break; | |
304 | + } | |
305 | + /* Upper byte write only 0 */ | |
306 | + tmr->ch[ch].ctrl |= (tcr_mask & 0x00ff); | |
307 | + tmr->ch[ch].ctrl &= val & tcr_mask; | |
308 | + update_clk(tmr, ch); | |
309 | + break; | |
310 | + case R_TCNT: | |
311 | + tmr->ch[ch].cnt = val; | |
312 | + break; | |
313 | + case R_TCOR: | |
314 | + tmr->ch[ch].cor = val; | |
315 | + break; | |
316 | + case R_TCPR: | |
317 | + qemu_log_mask(LOG_GUEST_ERROR, | |
318 | + "renesas_timer: TCPR is read only."); | |
319 | + break; | |
320 | + default: | |
321 | + qemu_log_mask(LOG_UNIMP, "renesas_timer: Register 0x%" | |
322 | + HWADDR_PRIX " not implemented\n", addr); | |
323 | + } | |
324 | +} | |
325 | + | |
326 | +static const MemoryRegionOps tmr_ops = { | |
327 | + .write = tmr_write, | |
328 | + .read = tmr_read, | |
329 | + .endianness = DEVICE_NATIVE_ENDIAN, | |
330 | + .impl = { | |
331 | + .min_access_size = 2, | |
332 | + .max_access_size = 4, | |
333 | + }, | |
334 | +}; | |
335 | + | |
336 | +static void rtimer_realize(DeviceState *dev, Error **errp) | |
337 | +{ | |
338 | + SysBusDevice *d = SYS_BUS_DEVICE(dev); | |
339 | + RTIMERState *tmr = RTIMER(dev); | |
340 | + int i; | |
341 | + int ch; | |
342 | + | |
343 | + if (tmr->input_freq == 0) { | |
344 | + qemu_log_mask(LOG_GUEST_ERROR, | |
345 | + "renesas_timer: input-freq property must be set."); | |
346 | + return; | |
347 | + } | |
348 | + if (IS_CMT(tmr)) { | |
349 | + memory_region_init_io(&tmr->memory, OBJECT(tmr), &tmr_ops, | |
350 | + tmr, "renesas-cmt", 0x10); | |
351 | + sysbus_init_mmio(d, &tmr->memory); | |
352 | + | |
353 | + for (i = 0; i < TIMER_CH_CMT; i++) { | |
354 | + sysbus_init_irq(d, &tmr->ch[i].irq); | |
355 | + } | |
356 | + tmr->num_ch = 2; | |
357 | + } else { | |
358 | + memory_region_init_io(&tmr->memory, OBJECT(tmr), &tmr_ops, | |
359 | + tmr, "renesas-tmu", 0x30); | |
360 | + sysbus_init_mmio(d, &tmr->memory); | |
361 | + memory_region_init_alias(&tmr->memory_p4, NULL, "renesas-tmu-p4", | |
362 | + &tmr->memory, 0, 0x30); | |
363 | + sysbus_init_mmio(d, &tmr->memory_p4); | |
364 | + memory_region_init_alias(&tmr->memory_a7, NULL, "renesas-tmu-a7", | |
365 | + &tmr->memory, 0, 0x30); | |
366 | + sysbus_init_mmio(d, &tmr->memory_a7); | |
367 | + ch = (tmr->feature == RTIMER_FEAT_TMU_LOW) ? | |
368 | + TIMER_CH_TMU : TIMER_CH_TMU - 1; | |
369 | + for (i = 0; i < ch; i++) { | |
370 | + sysbus_init_irq(d, &tmr->ch[i].irq); | |
371 | + } | |
372 | + tmr->num_ch = (tmr->feature == RTIMER_FEAT_TMU_LOW) ? 3 : 2; | |
373 | + } | |
374 | + for (ch = 0; ch < tmr->num_ch; ch++) { | |
375 | + tmr->ch[ch].tmrp = tmr; | |
376 | + update_clk(tmr, ch); | |
377 | + if (IS_CMT(tmr)) { | |
378 | + tmr->ch[ch].cor = 0xffff; | |
379 | + } else { | |
380 | + tmr->ch[ch].cor = tmr->ch[ch].cnt = 0xffffffff; | |
381 | + } | |
382 | + } | |
383 | +} | |
384 | + | |
385 | +static const VMStateDescription vmstate_rtimer = { | |
386 | + .name = "rx-cmt", | |
387 | + .version_id = 1, | |
388 | + .minimum_version_id = 1, | |
389 | + .fields = (VMStateField[]) { | |
390 | + VMSTATE_END_OF_LIST() | |
391 | + } | |
392 | +}; | |
393 | + | |
394 | +static Property rtimer_properties[] = { | |
395 | + DEFINE_PROP_UINT32("feature", RTIMERState, feature, 0), | |
396 | + DEFINE_PROP_UINT64("input-freq", RTIMERState, input_freq, 0), | |
397 | + DEFINE_PROP_END_OF_LIST(), | |
398 | +}; | |
399 | + | |
400 | +static void rtimer_class_init(ObjectClass *klass, void *data) | |
401 | +{ | |
402 | + DeviceClass *dc = DEVICE_CLASS(klass); | |
403 | + | |
404 | + dc->vmsd = &vmstate_rtimer; | |
405 | + dc->realize = rtimer_realize; | |
406 | + device_class_set_props(dc, rtimer_properties); | |
407 | +} | |
408 | + | |
409 | +static const TypeInfo rtimer_info = { | |
410 | + .name = TYPE_RENESAS_TIMER, | |
411 | + .parent = TYPE_SYS_BUS_DEVICE, | |
412 | + .instance_size = sizeof(RTIMERState), | |
413 | + .class_init = rtimer_class_init, | |
414 | +}; | |
415 | + | |
416 | +static void rtimer_register_types(void) | |
417 | +{ | |
418 | + type_register_static(&rtimer_info); | |
419 | +} | |
420 | + | |
421 | +type_init(rtimer_register_types) |
@@ -0,0 +1,59 @@ | ||
1 | +/* | |
2 | + * Renesas Timer unit Object | |
3 | + * | |
4 | + * Copyright (c) 2020 Yoshinori Sato | |
5 | + * | |
6 | + * This code is licensed under the GPL version 2 or later. | |
7 | + * | |
8 | + */ | |
9 | + | |
10 | +#ifndef HW_RENESAS_TIMER_H | |
11 | +#define HW_RENESAS_TIMER_H | |
12 | + | |
13 | +#include "hw/sysbus.h" | |
14 | + | |
15 | +#define TYPE_RENESAS_TIMER "renesas-timer" | |
16 | +#define RTIMER(obj) OBJECT_CHECK(RTIMERState, (obj), TYPE_RENESAS_TIMER) | |
17 | + | |
18 | +enum { | |
19 | + TIMER_CH_CMT = 2, | |
20 | + /* TMU have 5channels. It separated 0-2 and 3-4. */ | |
21 | + TIMER_CH_TMU = 3, | |
22 | +}; | |
23 | + | |
24 | +enum { | |
25 | + RTIMER_FEAT_CMT, | |
26 | + RTIMER_FEAT_TMU_LOW, | |
27 | + RTIMER_FEAT_TMU_HIGH, | |
28 | +}; | |
29 | + | |
30 | +struct RTIMERState; | |
31 | + | |
32 | +struct channel_rtimer { | |
33 | + uint32_t cnt; | |
34 | + uint32_t cor; | |
35 | + uint16_t ctrl; | |
36 | + qemu_irq irq; | |
37 | + int64_t base; | |
38 | + int64_t next; | |
39 | + uint64_t clk; | |
40 | + bool start; | |
41 | + QEMUTimer *timer; | |
42 | + struct RTIMERState *tmrp; | |
43 | +}; | |
44 | + | |
45 | +typedef struct RTIMERState { | |
46 | + SysBusDevice parent_obj; | |
47 | + | |
48 | + uint64_t input_freq; | |
49 | + MemoryRegion memory; | |
50 | + MemoryRegion memory_p4; | |
51 | + MemoryRegion memory_a7; | |
52 | + | |
53 | + uint8_t tocr; | |
54 | + struct channel_rtimer ch[TIMER_CH_TMU]; | |
55 | + uint32_t feature; | |
56 | + int num_ch; | |
57 | +} RTIMERState; | |
58 | + | |
59 | +#endif |