/* * Renesas 8bit timer * * Datasheet: RX62N Group, RX621 Group User's Manual: Hardware * (Rev.1.40 R01UH0033EJ0140) * * Copyright (c) 2019 Yoshinori Sato * * SPDX-License-Identifier: GPL-2.0-or-later * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2 or later, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "qemu/osdep.h" #include "qemu/log.h" #include "hw/irq.h" #include "hw/registerfields.h" #include "hw/qdev-properties.h" #include "hw/timer/renesas_tmr.h" #include "migration/vmstate.h" REG8(TCR, 0) FIELD(TCR, CCLR, 3, 2) FIELD(TCR, OVIE, 5, 1) FIELD(TCR, CMIEA, 6, 1) FIELD(TCR, CMIEB, 7, 1) REG8(TCSR, 2) FIELD(TCSR, OSA, 0, 2) FIELD(TCSR, OSB, 2, 2) FIELD(TCSR, ADTE, 4, 2) REG8(TCORA, 4) REG8(TCORB, 6) REG8(TCNT, 8) REG8(TCCR, 10) FIELD(TCCR, CKS, 0, 3) FIELD(TCCR, CSS, 3, 2) FIELD(TCCR, TMRIS, 7, 1) #define CSS_EXTERNAL 0x00 #define CSS_INTERNAL 0x01 #define CSS_INVALID 0x02 #define CSS_CASCADING 0x03 #define CCLR_A 0x01 #define CCLR_B 0x02 static const int clkdiv[] = {0, 1, 2, 8, 32, 64, 1024, 8192}; static uint8_t concat_reg(uint8_t *reg) { return (reg[0] << 8) | reg[1]; } static void update_events(RTMRState *tmr, int ch) { uint16_t diff[TMR_NR_EVENTS], min; int64_t next_time; int i, event; if (tmr->tccr[ch] == 0) { return ; } if (FIELD_EX8(tmr->tccr[ch], TCCR, CSS) == 0) { /* external clock mode */ /* event not happened */ return ; } if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) == CSS_CASCADING) { /* cascading mode */ if (ch == 1) { tmr->next[ch] = none; return ; } diff[cmia] = concat_reg(tmr->tcora) - concat_reg(tmr->tcnt); diff[cmib] = concat_reg(tmr->tcorb) - concat_reg(tmr->tcnt); diff[ovi] = 0x10000 - concat_reg(tmr->tcnt); } else { /* separate mode */ diff[cmia] = tmr->tcora[ch] - tmr->tcnt[ch]; diff[cmib] = tmr->tcorb[ch] - tmr->tcnt[ch]; diff[ovi] = 0x100 - tmr->tcnt[ch]; } /* Search for the most recently occurring event. */ for (event = 0, min = diff[0], i = 1; i < none; i++) { if (min > diff[i]) { event = i; min = diff[i]; } } tmr->next[ch] = event; next_time = diff[event]; next_time *= clkdiv[FIELD_EX8(tmr->tccr[ch], TCCR, CKS)]; next_time *= NANOSECONDS_PER_SECOND; next_time /= tmr->input_freq; next_time += qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); timer_mod(&tmr->timer[ch], next_time); } static int elapsed_time(RTMRState *tmr, int ch, int64_t delta) { int divrate = clkdiv[FIELD_EX8(tmr->tccr[ch], TCCR, CKS)]; int et; tmr->div_round[ch] += delta; if (divrate > 0) { et = tmr->div_round[ch] / divrate; tmr->div_round[ch] %= divrate; } else { /* disble clock. so no update */ et = 0; } return et; } static uint16_t read_tcnt(RTMRState *tmr, unsigned size, int ch) { int64_t delta, now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); int elapsed, ovf = 0; uint16_t tcnt[2]; uint32_t ret; delta = (now - tmr->tick) * NANOSECONDS_PER_SECOND / tmr->input_freq; if (delta > 0) { tmr->tick = now; switch (FIELD_EX8(tmr->tccr[1], TCCR, CSS)) { case CSS_INTERNAL: /* timer1 count update */ elapsed = elapsed_time(tmr, 1, delta); if (elapsed >= 0x100) { ovf = elapsed >> 8; } tcnt[1] = tmr->tcnt[1] + (elapsed & 0xff); break; case CSS_INVALID: /* guest error to have set this */ case CSS_EXTERNAL: /* QEMU doesn't implement these */ case CSS_CASCADING: tcnt[1] = tmr->tcnt[1]; break; default: g_assert_not_reached(); } switch (FIELD_EX8(tmr->tccr[0], TCCR, CSS)) { case CSS_INTERNAL: elapsed = elapsed_time(tmr, 0, delta); tcnt[0] = tmr->tcnt[0] + elapsed; break; case CSS_CASCADING: tcnt[0] = tmr->tcnt[0] + ovf; break; case CSS_INVALID: /* guest error to have set this */ case CSS_EXTERNAL: /* QEMU doesn't implement this */ tcnt[0] = tmr->tcnt[0]; break; default: g_assert_not_reached(); } } else { tcnt[0] = tmr->tcnt[0]; tcnt[1] = tmr->tcnt[1]; } if (size == 1) { return tcnt[ch]; } else { ret = 0; ret = deposit32(ret, 0, 8, tcnt[1]); ret = deposit32(ret, 8, 8, tcnt[0]); return ret; } } static uint8_t read_tccr(uint8_t r) { uint8_t tccr = 0; tccr = FIELD_DP8(tccr, TCCR, TMRIS, FIELD_EX8(r, TCCR, TMRIS)); tccr = FIELD_DP8(tccr, TCCR, CSS, FIELD_EX8(r, TCCR, CSS)); tccr = FIELD_DP8(tccr, TCCR, CKS, FIELD_EX8(r, TCCR, CKS)); return tccr; } static uint64_t tmr_read(void *opaque, hwaddr addr, unsigned size) { RTMRState *tmr = opaque; int ch = addr & 1; uint64_t ret; if (size == 2 && (ch != 0 || addr == A_TCR || addr == A_TCSR)) { qemu_log_mask(LOG_GUEST_ERROR, "renesas_tmr: Invalid read size 0x%" HWADDR_PRIX "\n", addr); return UINT64_MAX; } switch (addr & 0x0e) { case A_TCR: ret = 0; ret = FIELD_DP8(ret, TCR, CCLR, FIELD_EX8(tmr->tcr[ch], TCR, CCLR)); ret = FIELD_DP8(ret, TCR, OVIE, FIELD_EX8(tmr->tcr[ch], TCR, OVIE)); ret = FIELD_DP8(ret, TCR, CMIEA, FIELD_EX8(tmr->tcr[ch], TCR, CMIEA)); ret = FIELD_DP8(ret, TCR, CMIEB, FIELD_EX8(tmr->tcr[ch], TCR, CMIEB)); return ret; case A_TCSR: ret = 0; ret = FIELD_DP8(ret, TCSR, OSA, FIELD_EX8(tmr->tcsr[ch], TCSR, OSA)); ret = FIELD_DP8(ret, TCSR, OSB, FIELD_EX8(tmr->tcsr[ch], TCSR, OSB)); switch (ch) { case 0: ret = FIELD_DP8(ret, TCSR, ADTE, FIELD_EX8(tmr->tcsr[ch], TCSR, ADTE)); break; case 1: /* CH1 ADTE unimplement always 1 */ ret = FIELD_DP8(ret, TCSR, ADTE, 1); break; } return ret; case A_TCORA: if (size == 1) { return tmr->tcora[ch]; } else if (ch == 0) { return concat_reg(tmr->tcora); } /* fall through */ case A_TCORB: if (size == 1) { return tmr->tcorb[ch]; } else { return concat_reg(tmr->tcorb); } case A_TCNT: return read_tcnt(tmr, size, ch); case A_TCCR: if (size == 1) { return read_tccr(tmr->tccr[ch]); } else { return read_tccr(tmr->tccr[0]) << 8 | read_tccr(tmr->tccr[1]); } default: qemu_log_mask(LOG_UNIMP, "renesas_tmr: Register 0x%" HWADDR_PRIX " not implemented\n", addr); break; } return UINT64_MAX; } static void tmr_write_count(RTMRState *tmr, int ch, unsigned size, uint8_t *reg, uint64_t val) { if (size == 1) { reg[ch] = val; update_events(tmr, ch); } else { reg[0] = extract32(val, 8, 8); reg[1] = extract32(val, 0, 8); update_events(tmr, 0); update_events(tmr, 1); } } static void tmr_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) { RTMRState *tmr = opaque; int ch = addr & 1; if (size == 2 && (ch != 0 || addr == A_TCR || addr == A_TCSR)) { qemu_log_mask(LOG_GUEST_ERROR, "renesas_tmr: Invalid write size 0x%" HWADDR_PRIX "\n", addr); return; } switch (addr & 0x0e) { case A_TCR: tmr->tcr[ch] = val; break; case A_TCSR: tmr->tcsr[ch] = val; break; case A_TCORA: tmr_write_count(tmr, ch, size, tmr->tcora, val); break; case A_TCORB: tmr_write_count(tmr, ch, size, tmr->tcorb, val); break; case A_TCNT: tmr_write_count(tmr, ch, size, tmr->tcnt, val); break; case A_TCCR: tmr_write_count(tmr, ch, size, tmr->tccr, val); break; default: qemu_log_mask(LOG_UNIMP, "renesas_tmr: Register 0x%" HWADDR_PRIX " not implemented\n", addr); break; } } static const MemoryRegionOps tmr_ops = { .write = tmr_write, .read = tmr_read, .endianness = DEVICE_LITTLE_ENDIAN, .impl = { .min_access_size = 1, .max_access_size = 2, }, .valid = { .min_access_size = 1, .max_access_size = 2, }, }; static void timer_events(RTMRState *tmr, int ch); static uint16_t issue_event(RTMRState *tmr, int ch, int sz, uint16_t tcnt, uint16_t tcora, uint16_t tcorb) { uint16_t ret = tcnt; switch (tmr->next[ch]) { case none: break; case cmia: if (tcnt >= tcora) { if (FIELD_EX8(tmr->tcr[ch], TCR, CCLR) == CCLR_A) { ret = tcnt - tcora; } if (FIELD_EX8(tmr->tcr[ch], TCR, CMIEA)) { qemu_irq_pulse(tmr->cmia[ch]); } if (sz == 8 && ch == 0 && FIELD_EX8(tmr->tccr[1], TCCR, CSS) == CSS_CASCADING) { tmr->tcnt[1]++; timer_events(tmr, 1); } } break; case cmib: if (tcnt >= tcorb) { if (FIELD_EX8(tmr->tcr[ch], TCR, CCLR) == CCLR_B) { ret = tcnt - tcorb; } if (FIELD_EX8(tmr->tcr[ch], TCR, CMIEB)) { qemu_irq_pulse(tmr->cmib[ch]); } } break; case ovi: if ((tcnt >= (1 << sz)) && FIELD_EX8(tmr->tcr[ch], TCR, OVIE)) { qemu_irq_pulse(tmr->ovi[ch]); } break; default: g_assert_not_reached(); } return ret; } static void timer_events(RTMRState *tmr, int ch) { uint16_t tcnt; tmr->tcnt[ch] = read_tcnt(tmr, 1, ch); if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) != CSS_CASCADING) { tmr->tcnt[ch] = issue_event(tmr, ch, 8, tmr->tcnt[ch], tmr->tcora[ch], tmr->tcorb[ch]) & 0xff; } else { if (ch == 1) { return ; } tcnt = issue_event(tmr, ch, 16, concat_reg(tmr->tcnt), concat_reg(tmr->tcora), concat_reg(tmr->tcorb)); tmr->tcnt[0] = (tcnt >> 8) & 0xff; tmr->tcnt[1] = tcnt & 0xff; } update_events(tmr, ch); } static void timer_event0(void *opaque) { RTMRState *tmr = opaque; timer_events(tmr, 0); } static void timer_event1(void *opaque) { RTMRState *tmr = opaque; timer_events(tmr, 1); } static void rtmr_reset(DeviceState *dev) { RTMRState *tmr = RTMR(dev); tmr->tcr[0] = tmr->tcr[1] = 0x00; tmr->tcsr[0] = 0x00; tmr->tcsr[1] = 0x10; tmr->tcnt[0] = tmr->tcnt[1] = 0x00; tmr->tcora[0] = tmr->tcora[1] = 0xff; tmr->tcorb[0] = tmr->tcorb[1] = 0xff; tmr->tccr[0] = tmr->tccr[1] = 0x00; tmr->next[0] = tmr->next[1] = none; tmr->tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); } static void rtmr_init(Object *obj) { SysBusDevice *d = SYS_BUS_DEVICE(obj); RTMRState *tmr = RTMR(obj); int i; memory_region_init_io(&tmr->memory, OBJECT(tmr), &tmr_ops, tmr, "renesas-tmr", 0x10); sysbus_init_mmio(d, &tmr->memory); for (i = 0; i < ARRAY_SIZE(tmr->ovi); i++) { sysbus_init_irq(d, &tmr->cmia[i]); sysbus_init_irq(d, &tmr->cmib[i]); sysbus_init_irq(d, &tmr->ovi[i]); } timer_init_ns(&tmr->timer[0], QEMU_CLOCK_VIRTUAL, timer_event0, tmr); timer_init_ns(&tmr->timer[1], QEMU_CLOCK_VIRTUAL, timer_event1, tmr); } static const VMStateDescription vmstate_rtmr = { .name = "rx-tmr", .version_id = 1, .minimum_version_id = 1, .fields = (VMStateField[]) { VMSTATE_INT64(tick, RTMRState), VMSTATE_UINT8_ARRAY(tcnt, RTMRState, TMR_CH), VMSTATE_UINT8_ARRAY(tcora, RTMRState, TMR_CH), VMSTATE_UINT8_ARRAY(tcorb, RTMRState, TMR_CH), VMSTATE_UINT8_ARRAY(tcr, RTMRState, TMR_CH), VMSTATE_UINT8_ARRAY(tccr, RTMRState, TMR_CH), VMSTATE_UINT8_ARRAY(tcor, RTMRState, TMR_CH), VMSTATE_UINT8_ARRAY(tcsr, RTMRState, TMR_CH), VMSTATE_INT64_ARRAY(div_round, RTMRState, TMR_CH), VMSTATE_UINT8_ARRAY(next, RTMRState, TMR_CH), VMSTATE_TIMER_ARRAY(timer, RTMRState, TMR_CH), VMSTATE_END_OF_LIST() } }; static Property rtmr_properties[] = { DEFINE_PROP_UINT64("input-freq", RTMRState, input_freq, 0), DEFINE_PROP_END_OF_LIST(), }; static void rtmr_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->vmsd = &vmstate_rtmr; dc->reset = rtmr_reset; device_class_set_props(dc, rtmr_properties); } static const TypeInfo rtmr_info = { .name = TYPE_RENESAS_TMR, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(RTMRState), .instance_init = rtmr_init, .class_init = rtmr_class_init, }; static void rtmr_register_types(void) { type_register_static(&rtmr_info); } type_init(rtmr_register_types)