diff mbox series

[13/20] hw/timer: Add Renesas MTU2

Message ID 20200827123859.81793-14-ysato@users.sourceforge.jp
State New
Headers show
Series RX target update | expand

Commit Message

Yoshinori Sato Aug. 27, 2020, 12:38 p.m. UTC
Signed-off-by: Yoshinori Sato <ysato@users.sourceforge.jp>
---
 include/hw/timer/renesas_mtu.h |   90 +++
 hw/timer/renesas_mtu.c         | 1312 ++++++++++++++++++++++++++++++++
 hw/timer/Kconfig               |    2 +
 hw/timer/meson.build           |    1 +
 4 files changed, 1405 insertions(+)
 create mode 100644 include/hw/timer/renesas_mtu.h
 create mode 100644 hw/timer/renesas_mtu.c
diff mbox series

Patch

diff --git a/include/hw/timer/renesas_mtu.h b/include/hw/timer/renesas_mtu.h
new file mode 100644
index 0000000000..27df14b308
--- /dev/null
+++ b/include/hw/timer/renesas_mtu.h
@@ -0,0 +1,90 @@ 
+/*
+ * Renesas Multi-function Timer Uint Object
+ *
+ * Copyright (c) 2020 Yoshinori Sato
+ *
+ * This code is licensed under the GPL version 2 or later.
+ *
+ */
+
+#ifndef HW_RENESAS_MTU_H
+#define HW_RENESAS_MTU_H
+
+#include "hw/sysbus.h"
+#include "hw/qdev-clock.h"
+
+#define TYPE_RENESAS_MTU2 "renesas-mtu2"
+#define RenesasMTU2(obj) \
+    OBJECT_CHECK(RenesasMTU2State, (obj), TYPE_RENESAS_MTU2)
+
+#define MTU2Class(klass) \
+    OBJECT_CLASS_CHECK(RenesasMTU2Class, klass, TYPE_RENESAS_MTU2)
+
+enum {
+    NR_MAX_IRQ = 7,
+    MTU_NR_IRQ = 7 + 4 + 4 + 5 + 5 + 3,
+};
+
+struct RenesasMTU2State;
+
+typedef struct {
+    uint8_t tcr;
+    uint8_t tmdr;
+    uint8_t tsr;
+    uint16_t tior;
+    uint16_t tier;
+    uint32_t tcnt;
+    uint16_t tgr[6];
+
+    int num_gr;
+    int64_t base;
+    int64_t next;
+    int64_t clk;
+    bool start;
+    bool cntclr;
+    bool ier;
+    QEMUTimer *timer;
+    int ch;
+    qemu_irq irq[NR_MAX_IRQ];
+    int next_cnt;
+    struct RenesasMTU2State *mtu;
+} RenesasMTURegs;
+
+typedef struct RenesasMTU2State {
+    SysBusDevice parent_obj;
+    RenesasMTURegs r[5];
+    RenesasMTURegs r5[3];
+    uint8_t tbtm;
+    uint8_t ticcr;
+    uint16_t tadcr;
+    uint16_t tadcor[2];
+    uint16_t tadcobr[2];
+    /* CH A registers */
+    uint8_t toer;
+    uint8_t tgcr;
+    uint8_t tocr[2];
+    uint16_t tcdr;
+    uint16_t tddr;
+    uint16_t tcnts;
+    uint16_t tcbr;
+    uint8_t titcr;
+    uint8_t titcnt;
+    uint8_t tbter;
+    uint8_t tder;
+    uint8_t tolbr;
+    uint8_t twcr;
+    uint8_t trwer;
+    uint8_t tsyr;
+
+    Clock *pck;
+    int64_t input_freq;
+    MemoryRegion memory[3];
+    uint8_t trwer_r;
+    uint32_t unit;
+} RenesasMTU2State;
+
+typedef struct {
+    SysBusDeviceClass parent;
+} RenesasMTU2Class;
+
+#endif
diff --git a/hw/timer/renesas_mtu.c b/hw/timer/renesas_mtu.c
new file mode 100644
index 0000000000..fc204f10ce
--- /dev/null
+++ b/hw/timer/renesas_mtu.c
@@ -0,0 +1,1312 @@ 
+/*
+ * Renesas Multi-function Timer Uint
+ *
+ * Datasheet: RX62N Group, RX621 Group User's Manual: Hardware
+ * (Rev.1.40 R01UH0033EJ0140)
+ *
+ * Copyright (c) 2019 Yoshinori Sato
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "qemu/timer.h"
+#include "hw/hw.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+#include "hw/registerfields.h"
+#include "hw/qdev-properties.h"
+#include "hw/timer/renesas_mtu.h"
+#include "qemu/error-report.h"
+
+REG8(TCR_012, 0)
+REG8(TMDR_012, 1)
+REG8(TIORH_012, 2)
+REG8(TIORL_012, 3)
+REG8(TIER_012, 4)
+REG8(TSR_012, 5)
+REG16(TCNT_012, 6)
+REG16(TGRA_012, 8)
+REG16(TGRB_012, 10)
+REG16(TGRC_012, 12)
+REG16(TGRD_012, 14)
+REG8(TICCR_1, 16)
+REG16(TGRE_0, 32)
+REG16(TGRF_0, 34)
+REG8(TIER2_0, 36)
+REG8(TBTM_0, 38)
+
+REG8(TCR_3, 0)
+REG8(TCR_4, 1)
+REG8(TMDR_3, 2)
+REG8(TMDR_4, 3)
+REG8(TIORH_3, 4)
+REG8(TIORL_3, 5)
+REG8(TIORH_4, 6)
+REG8(TIORL_4, 7)
+REG8(TIER_3, 8)
+REG8(TIER_4, 9)
+REG8(TOER, 10)
+  FIELD(TOER, OE3B, 0, 1)
+  FIELD(TOER, OE4A, 1, 1)
+  FIELD(TOER, OE4B, 2, 1)
+  FIELD(TOER, OE3D, 3, 1)
+  FIELD(TOER, OE4C, 4, 1)
+  FIELD(TOER, OE4D, 5, 1)
+REG8(TGCR, 13)
+  FIELD(TGCR, BDC, 6, 1)
+  FIELD(TGCR, N, 5, 1)
+  FIELD(TGCR, P, 4, 1)
+  FIELD(TGCR, FB, 3, 1)
+  FIELD(TGCR, WF, 2, 1)
+  FIELD(TGCR, VF, 1, 1)
+  FIELD(TGCR, UF, 0, 1)
+REG8(TOCR1, 14)
+  FIELD(TOCR1, OLSP, 0, 1)
+  FIELD(TOCR1, OLSN, 1, 1)
+  FIELD(TOCR1, TOCS, 2, 1)
+  FIELD(TOCR1, TOCL, 3, 1)
+  FIELD(TOCR1, PSYE, 6, 1)
+REG8(TOCR2, 15)
+  FIELD(TOCR2, OLS1P, 0, 1)
+  FIELD(TOCR2, OLS1N, 1, 1)
+  FIELD(TOCR2, OLS2P, 2, 1)
+  FIELD(TOCR2, OLS2N, 3, 1)
+  FIELD(TOCR2, OLS3P, 4, 1)
+  FIELD(TOCR2, OLS3N, 5, 1)
+  FIELD(TOCR2, BF, 6, 2)
+REG16(TCNT_3, 16)
+REG16(TCNT_4, 18)
+REG16(TCDR, 20)
+REG16(TDDR, 22)
+REG16(TGRA_3, 24)
+REG16(TGRB_3, 26)
+REG16(TGRA_4, 28)
+REG16(TGRB_4, 30)
+REG16(TCNTS, 32)
+REG16(TCBR, 34)
+REG16(TGRC_3, 36)
+REG16(TGRD_3, 38)
+REG16(TGRC_4, 40)
+REG16(TGRD_4, 42)
+REG8(TSR_3, 44)
+REG8(TSR_4, 45)
+REG8(TITCR, 48)
+  FIELD(TITCR, T4VCOR, 0, 3)
+  FIELD(TITCR, T4VEN, 3, 1)
+  FIELD(TITCR, T3ACOR, 4, 3)
+  FIELD(TITCR, T3AEN, 7, 1)
+REG8(TITCNT, 49)
+  FIELD(TITCNT, T4VCNT, 0, 3)
+  FIELD(TITCNT, T3ACNT, 4, 3)
+REG8(TBTER, 50)
+  FIELD(TBTER, BTE, 0, 2)
+REG8(TDER, 52)
+  FIELD(TDER, TDER, 0, 1)
+REG8(TOLBR, 54)
+  FIELD(TOLBR, OLS1P, 0, 1)
+  FIELD(TOLBR, OLS1N, 1, 1)
+  FIELD(TOLBR, OLS2P, 2, 1)
+  FIELD(TOLBR, OLS2N, 3, 1)
+  FIELD(TOLBR, OLS3P, 4, 1)
+  FIELD(TOLBR, OLS3N, 5, 1)
+REG8(TBTM_3, 56)
+REG8(TBTM_4, 57)
+REG16(TADCR_4, 64)
+REG16(TADCORA_4, 68)
+REG16(TADCORB_4, 70)
+REG16(TADCOBRA_4, 72)
+REG16(TADCOBRB_4, 74)
+REG8(TWCR, 96)
+  FIELD(TWCR, WRE, 0, 1)
+  FIELD(TWCR, CCE, 7, 1)
+REG8(TSTR, 128)
+  FIELD(TSTR, CST0, 0, 1)
+  FIELD(TSTR, CST1, 1, 1)
+  FIELD(TSTR, CST2, 2, 1)
+  FIELD(TSTR, CSTL, 0, 3)
+  FIELD(TSTR, CST3, 6, 1)
+  FIELD(TSTR, CST4, 7, 1)
+  FIELD(TSTR, CSTH, 6, 2)
+REG8(TSYR, 129)
+  FIELD(TSYR, SYNC0, 0, 1)
+  FIELD(TSYR, SYNC1, 1, 1)
+  FIELD(TSYR, SYNC2, 2, 1)
+  FIELD(TSYR, SYNC3, 6, 1)
+  FIELD(TSYR, SYNC4, 7, 1)
+REG8(TRWER, 132)
+  FIELD(TRWER, RWE, 0, 1)
+
+REG16(TCNTU_5, 0)
+REG16(TGRU_5, 2)
+REG16(TCRU_5, 4)
+REG8(TIORU_5, 6)
+REG16(TCNTV_5, 16)
+REG16(TGRV_5, 18)
+REG8(TCRV_5, 20)
+REG8(TIORV_5, 22)
+REG16(TCNTW_5, 32)
+REG16(TGRW_5, 34)
+REG8(TCRW_5, 36)
+REG8(TIORW_5, 38)
+REG8(TIER_5, 50)
+  FIELD(TIER_5, TGIEW5, 0, 1)
+  FIELD(TIER_5, TGIEV5, 1, 1)
+  FIELD(TIER_5, TGIEU5, 2, 1)
+  FIELD(TIER_5, TGIE5,  0, 3)
+REG8(TSTR_5, 52)
+  FIELD(TSTR_5, CSTW5, 0, 1)
+  FIELD(TSTR_5, CSTV5, 1, 1)
+  FIELD(TSTR_5, CSTU5, 2, 1)
+  FIELD(TSTR_5, CST5,  0, 3)
+REG8(TCNTCMPCLR_5, 54)
+  FIELD(TCNTCMPCLR_5, CMPCLRW5, 0, 1)
+  FIELD(TCNTCMPCLR_5, CMPCLRV5, 1, 1)
+  FIELD(TCNTCMPCLR_5, CMPCLRU5, 2, 1)
+  FIELD(TCNTCMPCLR_5, CMPCLR5,  0, 3)
+
+REG8(TCR, 1)
+  FIELD(TCR, TPSC, 0, 3)
+  FIELD(TCR, CKEG, 3, 2)
+  FIELD(TCR, CCLR, 5, 3)
+REG8(TMDR, 2)
+  FIELD(TMDR, MD,  0, 4)
+  FIELD(TMDR, BFA, 4, 1)
+  FIELD(TMDR, BFB, 5, 1)
+  FIELD(TMDR, BFE, 6, 1)
+REG16(TIOR, 3)
+  FIELD(TIOR, IOA,  0, 3)
+  FIELD(TIOR, IOB,  4, 3)
+  FIELD(TIOR, IOC,  8, 3)
+  FIELD(TIOR, IOD, 12, 3)
+REG8(TIOR5, 4)
+  FIELD(TIOR5, IOC,  0, 4)
+REG8(TCNTCMPCLR, 5)
+  FIELD(TCNTCMPCLR, CMPCLR5W, 0, 1)
+  FIELD(TCNTCMPCLR, CMPCLR5V, 1, 1)
+  FIELD(TCNTCMPCLR, CMPCLR5U, 2, 1)
+  FIELD(TCNTCMPCLR, CMPCLR5,  0, 3)
+REG8(TIER, 6)
+  FIELD(TIER, TGIEA, 0, 1)
+  FIELD(TIER, TGIEB, 1, 1)
+  FIELD(TIER, TGIEC, 2, 1)
+  FIELD(TIER, TGIED, 3, 1)
+  FIELD(TIER, TGIE,  0, 4)
+  FIELD(TIER, TCIEV, 4, 1)
+  FIELD(TIER, TCIEU, 5, 1)
+  FIELD(TIER, TTGE2, 6, 1)
+  FIELD(TIER, TTGE,  7, 1)
+REG8(TIER2, 7)
+  FIELD(TIER2, TGIEE, 0, 1)
+  FIELD(TIER2, TGIEF, 1, 1)
+  FIELD(TIER2, TGIE,  0, 2)
+REG8(TSR, 8)
+  FIELD(TSR, TCFD, 7, 1)
+REG8(TBTM, 9)
+  FIELD(TBTM, TTSA, 0, 1)
+  FIELD(TBTM, TTSB, 1, 1)
+  FIELD(TBTM, TTSE, 2, 1)
+REG8(TICCR, 10)
+  FIELD(TICCR, I1AE, 0, 1)
+  FIELD(TICCR, I1BE, 1, 1)
+  FIELD(TICCR, I2AE, 2, 1)
+  FIELD(TICCR, I2BE, 3, 1)
+REG16(TADCR, 11)
+  FIELD(TADCR, ITB4VE, 0, 1)
+  FIELD(TADCR, ITB3AE, 1, 1)
+  FIELD(TADCR, ITA4VE, 2, 1)
+  FIELD(TADCR, ITA3AE, 3, 1)
+  FIELD(TADCR, DT4BE,  4, 1)
+  FIELD(TADCR, UT4BE,  5, 1)
+  FIELD(TADCR, DT4AE,  6, 1)
+  FIELD(TADCR, UT4AE,  7, 1)
+  FIELD(TADCR, BF,     0, 1)
+REG16(TCNT, 12)
+REG16(TGRA, 13)
+REG16(TGRB, 14)
+REG16(TGRC, 15)
+REG16(TGRD, 16)
+REG16(TGRE, 17)
+REG16(TGRF, 18)
+REG8(TIORH, 19)
+REG8(TIORL, 20)
+REG16(TADCOBRA, 21)
+REG16(TADCOBRB, 22)
+REG16(TADCORA, 23)
+REG16(TADCORB, 24)
+
+static const int div_rate[6][8] = {
+    [0] = {1, 4, 16, 64, 0, 0, 0, 0, },
+    [1] = {1, 4, 16, 64, 0, 0, 256, 0, },
+    [2] = {1, 4, 16, 64, 0, 0, 0, 1024, },
+    [3] = {1, 4, 16, 64, 256, 1024, 0, 0, },
+    [4] = {1, 4, 16, 64, 256, 1024, 0, 0, },
+    [5] = {1, 4, 16, 64, 0, 0, 0, 0, },
+};
+
+static bool is_cascade(RenesasMTU2State *mtu)
+{
+    if (mtu == NULL) {
+        return false;
+    }
+    if (FIELD_EX8(mtu->r[1].tcr, TCR, TPSC) != 7 ||
+        mtu->r[2].ier) {
+        return false;
+    }
+    return true;
+}
+
+static void mtu2_event(void *opaque);
+static void set_next_event(RenesasMTURegs *r)
+{
+    int gr;
+    int64_t next;
+    uint32_t wcnt;
+    int ch = r->ch;
+    RenesasMTU2State *mtu = r->mtu;
+
+    if (ch == 1 && is_cascade(mtu)) {
+        /* If cascade count mode, skip ch1 event */
+        return;
+    }
+    if (r->start) {
+        if (ch != 2 || !is_cascade(mtu)) {
+            /* normal counter */
+            r->next_cnt = 0x10000;
+            for (gr = 0; gr < r->num_gr; gr++) {
+                if (r->tcnt <= r->tgr[gr]) {
+                    r->next_cnt = MIN(r->next_cnt, r->tgr[gr] + 1);
+                }
+            }
+            next = (r->next_cnt - r->tcnt) * r->clk;
+            g_assert(next > 0);
+        } else {
+            /* 32bit freerun counter */
+            wcnt = mtu->r[2].tcnt;
+            wcnt = deposit32(wcnt, 16, 16, mtu->r[1].tcnt);
+            next = (0x100000000LL - wcnt) * r->clk;
+         }
+        g_assert(next > 0);
+        r->next = r->base + next;
+        if (r->timer == NULL) {
+            r->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+                                    mtu2_event, r);
+        }
+        timer_mod(r->timer, r->next);
+    } else {
+        if (r->timer) {
+            timer_del(r->timer);
+        }
+    }
+}
+
+static void mtu2_5_event(void *opaque);
+static void set_next_event5(RenesasMTURegs *r)
+{
+    int64_t next;
+
+    r->next_cnt = r->cntclr ? r->tgr[0] : 0x10000;
+    if (r->start) {
+        next = (r->next_cnt - r->tcnt) * r->clk;
+        g_assert(next > 0);
+        r->next = r->base + next;
+        if (r->timer == NULL) {
+            r->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+                                    mtu2_5_event, r);
+        }
+        timer_mod(r->timer, r->next);
+    } else {
+        if (r->timer) {
+            timer_del(r->timer);
+        }
+    }
+}
+
+static void tgr_match(RenesasMTURegs *r, int clr_gr)
+{
+    int gr;
+
+    for (gr = 0; gr < r->num_gr; gr++) {
+        if (r->next_cnt == r->tgr[gr]) {
+            /* TGR match */
+            if (clr_gr == gr) {
+                r->tcnt = 0;
+            } else {
+                r->tcnt = r->next_cnt;
+            }
+            if (extract16(r->tier, (gr < 4 ? gr : gr + 4), 1)) {
+                qemu_irq_pulse(r->irq[gr]);
+            }
+        }
+    }
+}
+
+static int clr_gr(uint8_t tcr, int ch)
+{
+    switch (FIELD_EX8(tcr, TCR, CCLR)) {
+    case 1:
+        return 0;
+    case 2:
+        return 1;
+    case 5:
+        return 2;
+    case 6:
+        return 3;
+    default:
+        return -1;
+    }
+}
+
+static void mtu2_event(void *opaque)
+{
+    RenesasMTURegs *r = opaque;
+    RenesasMTU2State *mtu = r->mtu;
+    uint32_t sync;
+    int ch;
+
+    if (r->ch != 2 || !is_cascade(mtu)) {
+        tgr_match(r, clr_gr(r->tcr, r->ch));
+        if (r->next_cnt == 0x10000) {
+            /* Count overflow */
+            r->tcnt = 0;
+            r->base = r->next;
+            if (FIELD_EX16(r->tier, TIER, TCIEV)) {
+                qemu_irq_pulse(r->irq[r->num_gr]);
+            }
+            if (r->ch == 2 && FIELD_EX8(mtu->r[1].tcr, TCR, TPSC) == 7) {
+                mtu->r[1].tcnt++;
+                tgr_match(&mtu->r[1], clr_gr(mtu->r[1].tcr, 1));
+                if (mtu->r[1].tcnt >= 0x10000) {
+                    mtu->r[1].tcnt = 0;
+                    if (FIELD_EX16(mtu->r[1].tier, TIER, TCIEV)) {
+                        qemu_irq_pulse(mtu->r[1].irq[mtu->r[1].num_gr]);
+                    }
+                }
+            }
+        }
+    } else {
+        r->tcnt = 0;
+        mtu->r[1].tcnt = 0;
+        r->base = r->next;
+        if (FIELD_EX16(mtu->r[1].tier, TIER, TCIEV)) {
+            qemu_irq_pulse(mtu->r[1].irq[mtu->r[1].num_gr]);
+        }
+    }
+    set_next_event(r);
+    if (r->tcnt == 0) {
+        sync = extract8(mtu->tsyr, 0, 3);
+        sync = deposit32(sync, 3, 2, extract8(mtu->tsyr, 6, 2));
+        if (extract32(sync, r->ch, 1)) {
+            /* Syncronus clear */
+            for (ch = 0; ch < 5; ch++) {
+                if (ch == r->ch || !extract8(sync, ch, 1)) {
+                    continue;
+                }
+                if ((FIELD_EX8(mtu->r[ch].tcr, TCR, CCLR) & 3) == 3) {
+                    mtu->r[ch].tcnt = 0;
+                    set_next_event(&mtu->r[ch]);
+                }
+            }
+        }
+    }
+}
+
+static void mtu2_5_event(void *opaque)
+{
+    RenesasMTURegs *r = opaque;
+
+    if (r->next_cnt < 0x10000) {
+        if (r->ier) {
+            qemu_irq_pulse(r->irq[0]);
+        }
+        if (r->cntclr) {
+            r->tcnt = 0;
+            r->base = r->next;
+        }
+    } else {
+        r->tcnt = 0;
+        r->base = r->next;
+    }
+    set_next_event5(r);
+}
+
+static uint16_t read_tcnt(RenesasMTURegs *r)
+{
+    int64_t now;
+    uint32_t wcnt;
+
+    if (r->start) {
+        now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        if (!is_cascade(r->mtu)) {
+            if (r->ch == 1 && FIELD_EX8(r->mtu->r[1].tcr, TCR, TPSC) == 7) {
+                return r->tcnt;
+            } else {
+                return (r->tcnt + (now - r->base) / r->clk) & 0xffff;
+            }
+        } else {
+            wcnt = r->mtu->r[2].tcnt;
+            wcnt = deposit32(wcnt, 16, 16, r->mtu->r[1].tcnt);
+            wcnt += (now - r->mtu->r[2].base) / r->mtu->r[2].clk;
+            switch (r->ch) {
+            case 1:
+                return extract32(wcnt, 16, 16);
+            case 2:
+                return extract32(wcnt, 0, 16);
+            default:
+                g_assert_not_reached();
+            }
+        }
+    } else {
+        return r->tcnt;
+    }
+}
+
+static void mtu_pck_update(void *opaque)
+{
+    RenesasMTU2State *mtu = RenesasMTU2(opaque);
+    int ch;
+    for (ch = 0; ch < 5; ch++) {
+        mtu->r[ch].tcnt = read_tcnt(&mtu->r[ch]);
+    }
+    for (ch = 0; ch < 3; ch++) {
+        mtu->r5[ch].tcnt = read_tcnt(&mtu->r5[ch]);
+    }
+    mtu->input_freq = clock_get_hz(mtu->pck);
+    if (clock_is_enabled(mtu->pck)) {
+        for (ch = 0; ch < 5; ch++) {
+            set_next_event(&mtu->r[ch]);
+        }
+        for (ch = 0; ch < 3; ch++) {
+            set_next_event5(&mtu->r5[ch]);
+        }
+    } else {
+        for (ch = 0; ch < 5; ch++) {
+            if (mtu->r[ch].timer) {
+                timer_del(mtu->r[ch].timer);
+            }
+        }
+        for (ch = 0; ch < 3; ch++) {
+            if (mtu->r5[ch].timer) {
+                timer_del(mtu->r5[ch].timer);
+            }
+        }
+    }
+}
+
+static bool mtu2_low_valid_size(hwaddr addr, unsigned size)
+{
+    if ((A_TCNT_012 <= addr && addr < (A_TGRD_012 + 2)) ||
+        (A_TGRE_0 <= addr && addr < (A_TGRF_0 + 2))) {
+        if (size == 2) {
+            return true;
+        }
+    } else {
+        if (size == 1) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static uint64_t mtu2_low_read(void *opaque, hwaddr addr, unsigned size)
+{
+    RenesasMTU2State *mtu = RenesasMTU2(opaque);
+    int gr;
+    int ch = (addr >> 7) & 3;
+    addr &= 0x7f;
+
+    if (!mtu2_low_valid_size(addr, size)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Invalid access size %d of 0x%"
+                      HWADDR_PRIX "\n", size, addr);
+        return UINT64_MAX;
+    }
+    if (!clock_is_enabled(mtu->pck)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Unit %d is stopped.\n", mtu->unit);
+        return UINT64_MAX;
+    }
+    switch (addr) {
+    case A_TCR_012:
+        return mtu->r[ch].tcr;
+    case A_TMDR_012:
+        return mtu->r[ch].tmdr;
+    case A_TIORL_012:
+        return extract16(mtu->r[ch].tior, 0, 8);
+    case A_TIORH_012:
+        return extract16(mtu->r[ch].tior, 8, 8);
+    case A_TIER_012:
+        return extract16(mtu->r[ch].tier, 0, 8);
+    case A_TIER2_0:
+        if (ch == 0) {
+            return extract16(mtu->r[ch].tier, 8, 8);
+        } else {
+            goto no_register;
+        }
+    case A_TSR_012:
+        return mtu->r[ch].tsr;
+    case A_TBTM_0:
+        if (ch == 0) {
+            return mtu->tbtm;
+        } else {
+            goto no_register;
+        }
+    case A_TICCR_1:
+        if (ch == 1) {
+            return mtu->ticcr;
+        } else {
+            goto no_register;
+        }
+    case A_TCNT_012:
+        return read_tcnt(&mtu->r[ch]);
+    case A_TGRA_012:
+    case A_TGRB_012:
+    case A_TGRC_012:
+    case A_TGRD_012:
+        gr = ((addr - A_TGRA_012) >> 1) & 3;
+        if (gr < mtu->r[ch].num_gr) {
+            return mtu->r[ch].tgr[gr];
+        } else {
+            goto no_register;
+        }
+    case A_TGRE_0:
+    case A_TGRF_0:
+        if (ch == 0) {
+            gr = (((addr - A_TGRE_0) >> 1) & 2) + 4;
+            return mtu->r[0].tgr[gr];
+        } else {
+            goto no_register;
+        }
+    no_register:
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Unknown register %08lx\n",
+                      addr);
+        return UINT64_MAX;
+    }
+}
+
+static bool mtu2_high_valid_size(hwaddr addr, unsigned size)
+{
+    if ((A_TCNT_3 <= addr && addr < (A_TGRD_4 + 2)) ||
+        (A_TADCR <= addr && addr < (A_TADCORB + 2)) ||
+        (A_TCDR <= addr && addr < (A_TCBR + 2))) {
+        if (size == 2) {
+            return true;
+        }
+    } else {
+        if (size == 1) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static uint64_t mtu2_high_read(void *opaque, hwaddr addr, unsigned size)
+{
+    RenesasMTU2State *mtu = RenesasMTU2(opaque);
+    int ch = 3 + (addr & 1);
+    int ch_w = 3 + ((addr >> 1) & 1);
+    uint32_t ret;
+
+    if (!mtu2_high_valid_size(addr, size)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Invalid access size %d\n",
+                      size);
+        return UINT64_MAX;
+    }
+    if (addr < 0x20 && ((mtu->trwer & 1) == 0)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: register read protected "
+                      "0x%" HWADDR_PRIX "\n", addr);
+        return UINT64_MAX;
+    }
+    if (!clock_is_enabled(mtu->pck)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Unit %d is stopped.\n", mtu->unit);
+        return UINT64_MAX;
+    }
+    switch (addr) {
+    case A_TCR_3:
+    case A_TCR_4:
+        return mtu->r[ch].tcr;
+    case A_TMDR_3:
+    case A_TMDR_4:
+        return mtu->r[ch].tmdr;
+    case A_TIORL_3:
+    case A_TIORL_4:
+        return extract32(mtu->r[ch_w].tior, 0, 8);
+    case A_TIORH_3:
+    case A_TIORH_4:
+        return extract32(mtu->r[ch_w].tior, 8, 8);
+    case A_TIER_3:
+    case A_TIER_4:
+        return mtu->r[ch].tier;
+    case A_TSR_3:
+    case A_TSR_4:
+        return mtu->r[ch].tsr;
+    case A_TCNT_3:
+    case A_TCNT_4:
+        return read_tcnt(&mtu->r[ch]);
+    case A_TGRA_3:
+    case A_TGRB_3:
+    case A_TGRA_4:
+    case A_TGRB_4:
+        return mtu->r[3 + ((addr >> 2) & 1)].tgr[(addr >> 1) & 1];
+    case A_TGRC_3:
+    case A_TGRD_3:
+    case A_TGRC_4:
+    case A_TGRD_4:
+        return mtu->r[3 + ((addr >> 2) & 1)].tgr[2 + ((addr >> 1) & 1)];
+    case A_TADCR_4:
+        return mtu->tadcr;
+    case A_TADCOBRA_4:
+    case A_TADCOBRB_4:
+        return mtu->tadcobr[(addr >> 1) & 1];
+    case A_TADCORA_4:
+    case A_TADCORB_4:
+        return mtu->tadcor[(addr >> 1) & 1];
+    case A_TOER:
+        return mtu->toer;
+    case A_TGCR:
+        return mtu->tgcr;
+    case A_TOCR1:
+    case A_TOCR2:
+        return mtu->tocr[addr & 1];
+    case A_TCDR:
+        return mtu->tcdr;
+    case A_TDDR:
+        return mtu->tddr;
+    case A_TCNTS:
+        return mtu->tcnts;
+    case A_TCBR:
+        return mtu->tcbr;
+    case A_TITCR:
+        return mtu->titcr;
+    case A_TITCNT:
+        return mtu->titcnt;
+    case A_TBTER:
+        return mtu->tbter;
+    case A_TDER:
+        return mtu->tder;
+    case A_TOLBR:
+        return mtu->tolbr;
+    case A_TWCR:
+        return mtu->twcr;
+    case A_TSTR:
+        ret = 0;
+        for (ch = 0; ch < 5; ch++) {
+            ret = deposit32(ret, (ch < 3 ? ch : ch + 3), 1, mtu->r[ch].start);
+        }
+        return ret;
+    case A_TSYR:
+        return mtu->tsyr;
+    case A_TRWER:
+        mtu->trwer_r = mtu->trwer;
+        return mtu->trwer;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Unknown register 0x%" HWADDR_PRIX "\n",
+                      addr);
+        return UINT64_MAX;
+    }
+}
+
+static bool mtu2_5_valid_size(hwaddr addr, unsigned size)
+{
+    if (addr < A_TIER_5) {
+        addr &= 0x0f;
+        if (addr < A_TCRU_5) {
+            if (size == 2) {
+                return true;
+            }
+        } else {
+            if (size == 1) {
+                return true;
+            }
+        }
+    } else {
+        if (size == 1) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static uint64_t mtu2_5_read(void *opaque, hwaddr addr, unsigned size)
+{
+    RenesasMTU2State *mtu = RenesasMTU2(opaque);
+    int ch;
+    uint32_t ret;
+    ch = addr >> 4;
+    if (!mtu2_5_valid_size(addr, size)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Invalid access size at "
+                      "0x%" HWADDR_PRIX "\n", addr);
+    }
+    if (!clock_is_enabled(mtu->pck)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Unit %d is stopped.\n", mtu->unit);
+        return UINT64_MAX;
+    }
+    if (ch < 3) {
+        switch (addr & 0x0f) {
+        case A_TCNTU_5:
+            return read_tcnt(&mtu->r5[ch]);
+        case A_TGRU_5:
+            return mtu->r5[ch].tgr[0];
+        case A_TCRU_5:
+            return mtu->r5[ch].tcr;
+        case A_TIORU_5:
+            return mtu->r5[ch].tior;
+        }
+    } else {
+        switch (addr) {
+        case A_TIER_5:
+            ret = 0;
+            for (ch = 0; ch < 3; ch++) {
+                ret = deposit32(ret, ch, 1, mtu->r5[ch].ier);
+            }
+            return ret;
+        case A_TSTR_5:
+            ret = 0;
+            for (ch = 0; ch < 3; ch++) {
+                ret = deposit32(ret, ch, 1, mtu->r5[ch].start);
+            }
+            return ret;
+        case A_TCNTCMPCLR_5:
+            ret = 0;
+            for (ch = 0; ch < 3; ch++) {
+                ret = deposit32(ret, ch, 1, mtu->r5[ch].cntclr);
+            }
+            return ret;
+        }
+    }
+    qemu_log_mask(LOG_GUEST_ERROR,
+                  "renesas_mtu: Unknown register "
+                  "0x%" HWADDR_PRIX "\n", addr);
+    return UINT64_MAX;
+}
+
+static bool is_ext_clock(int ch, int tcr)
+{
+    int tpsc = FIELD_EX8(tcr, TCR, TPSC);
+    if (ch == 1 && tcr == 7) {
+        return false;
+    } else {
+        return div_rate[ch][tpsc] == 0;
+    }
+}
+
+static void set_cnt_clock(int64_t input_freq, RenesasMTURegs *r)
+{
+    int tpsc = FIELD_EX8(r->tcr, TCR, TPSC);
+    int ckeg = FIELD_EX8(r->tcr, TCR, CKEG);
+    int div = div_rate[r->ch][tpsc];
+    int64_t clk;
+
+    if (div >= 4 && ckeg >= 2) {
+        div /= 2;
+    }
+    if (div > 0) {
+        clk = NANOSECONDS_PER_SECOND / input_freq;
+        r->clk = clk * div;
+    }
+}
+
+#define NOT_SUPPORT_REG_VAL(val, name)                                  \
+    if (val != 0) {                                                     \
+        qemu_log_mask(LOG_UNIMP,                                        \
+                      "renesas_mtu: " #name " %02x is not supported.\n", \
+                      (uint8_t)val);                                    \
+    }
+
+static void mtu2_low_write(void *opaque, hwaddr addr,
+                           uint64_t val, unsigned size)
+{
+    RenesasMTU2State *mtu = RenesasMTU2(opaque);
+    int ch = (addr >> 7) & 3;
+    addr &= 0x7f;
+    if (!mtu2_low_valid_size(addr, size)) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "renesas_mtu: Invalid access size %d of "
+                          "0x%" HWADDR_PRIX "\n", size, addr);
+            return;
+    }
+    if (!clock_is_enabled(mtu->pck)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Unit %d is stopped.\n", mtu->unit);
+        return;
+    }
+
+    switch (addr) {
+    case A_TCR_012:
+        if (mtu->r[ch].start) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "renesas_mtu: CH %d is already started.\n", ch);
+        }
+        if (is_ext_clock(ch, val)) {
+            qemu_log_mask(LOG_UNIMP,
+                          "renesas_mtu: External clock not supported.\n");
+        }
+        mtu->r[ch].tcr = val;
+        set_cnt_clock(mtu->input_freq, &mtu->r[ch]);
+        set_next_event(&mtu->r[ch]);
+        break;
+    case A_TMDR_012:
+        mtu->r[ch].tmdr = val;
+        break;
+    case A_TIORL_012:
+        mtu->r[ch].tior = deposit32(mtu->r[ch].tior, 0, 8, val);
+        NOT_SUPPORT_REG_VAL(val, TIORL);
+        break;
+    case A_TIORH_012:
+        mtu->r[ch].tior = deposit32(mtu->r[ch].tior, 8, 8, val);
+        NOT_SUPPORT_REG_VAL(val, TIORH);
+        break;
+    case A_TIER_012:
+        mtu->r[ch].tier = deposit32(mtu->r[ch].tier, 0, 8, val);
+        break;
+    case A_TIER2_0:
+        if (ch == 0) {
+            mtu->r[ch].tier = deposit32(mtu->r[ch].tior, 8, 8, val);
+        } else {
+            goto no_register;
+        }
+        break;
+    case A_TSR_012:
+        mtu->r[ch].tsr = deposit32(mtu->r[ch].tsr, 6, 1, extract32(val, 6, 1));
+        break;
+    case A_TBTM_0:
+        if (ch == 0) {
+            mtu->tbtm = val;
+            break;
+        } else {
+            goto no_register;
+        }
+    case A_TICCR_1:
+        if (ch == 1) {
+            mtu->ticcr = val;
+            break;
+        } else {
+            goto no_register;
+        }
+    case A_TCNT_012:
+        mtu->r[ch].tcnt = val;
+        if (mtu->r[ch].start) {
+            mtu->r[ch].base = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        }
+        set_next_event(&mtu->r[ch]);
+        break;
+    case A_TGRA_012:
+    case A_TGRB_012:
+    case A_TGRC_012:
+    case A_TGRD_012:
+        mtu->r[ch].tgr[((addr - A_TGRA_012) >> 1) & 3] = val;
+        set_next_event(&mtu->r[ch]);
+        break;
+    case A_TGRE_0:
+    case A_TGRF_0:
+        if (ch == 0) {
+            mtu->r[ch].tgr[(((addr - A_TGRE_0) >> 1) & 2) + 4] = val;
+            set_next_event(&mtu->r[ch]);
+            break;
+        } else {
+            goto no_register;
+        }
+    default:
+    no_register:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Unknown register 0x%" HWADDR_PRIX "\n",
+                      addr);
+        break;
+    }
+}
+
+static void mtu2_high_write(void *opaque, hwaddr addr,
+                            uint64_t val, unsigned size)
+{
+    RenesasMTU2State *mtu = RenesasMTU2(opaque);
+    int ch = 3 + (addr & 1);
+    int ch_w = 3 + ((addr >> 1) & 1);
+    int64_t now;
+
+    if (!mtu2_high_valid_size(addr, size)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Invalid access size %d\n",
+                      size);
+        return;
+    }
+    if (addr < 0x20 && ((mtu->trwer & 1) == 0)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: register write protected "
+                      "0x%" HWADDR_PRIX "\n", addr);
+        return;
+    }
+    if (!clock_is_enabled(mtu->pck)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Unit %d is stopped.\n", mtu->unit);
+        return;
+    }
+
+    switch (addr) {
+    case A_TCR_3:
+    case A_TCR_4:
+        if (mtu->r[ch].start) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "renesas_mtu: CH %d is already started.\n", ch);
+        }
+        if (is_ext_clock(ch, val)) {
+            qemu_log_mask(LOG_UNIMP,
+                          "renesas_mtu: External clock not supported.\n");
+        }
+        mtu->r[ch].tcr = val;
+        set_cnt_clock(mtu->input_freq, &mtu->r[ch]);
+        set_next_event(&mtu->r[ch]);
+        break;
+    case A_TMDR_3:
+    case A_TMDR_4:
+        mtu->r[ch].tmdr = val;
+        NOT_SUPPORT_REG_VAL(val, TMDR);
+        break;
+    case A_TIORL_3:
+    case A_TIORL_4:
+        mtu->r[ch_w].tior = deposit32(mtu->r[ch_w].tior, 0, 8, val);
+        NOT_SUPPORT_REG_VAL(val, TIORL);
+        break;
+    case A_TIORH_3:
+    case A_TIORH_4:
+        mtu->r[ch_w].tior = deposit32(mtu->r[ch_w].tior, 8, 8, val);
+        NOT_SUPPORT_REG_VAL(val, TIORH);
+        break;
+    case A_TIER_3:
+    case A_TIER_4:
+        mtu->r[ch].tier = val;
+        set_next_event(&mtu->r[ch]);
+        break;
+    case A_TSR_3:
+    case A_TSR_4:
+        mtu->r[ch].tsr = val;
+        break;
+    case A_TCNT_3:
+    case A_TCNT_4:
+        mtu->r[ch_w].tcnt = val;
+        if (mtu->r[ch].start) {
+            mtu->r[ch].base = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        }
+        set_next_event(&mtu->r[ch]);
+        break;
+    case A_TGRA_3:
+    case A_TGRA_4:
+    case A_TGRB_3:
+    case A_TGRB_4:
+        mtu->r[3 + ((addr >> 2) & 1)].tgr[(addr >> 1) & 1] = val;
+        set_next_event(&mtu->r[3 + ((addr >> 2) & 1)]);
+        break;
+    case A_TGRC_3:
+    case A_TGRD_3:
+    case A_TGRC_4:
+    case A_TGRD_4:
+        mtu->r[3 + ((addr >> 2) & 1)].tgr[2 + ((addr >> 1) & 1)] = val;
+        set_next_event(&mtu->r[3 + ((addr >> 2) & 1)]);
+        break;
+    case A_TADCR_4:
+        mtu->tadcr = val;
+        NOT_SUPPORT_REG_VAL(val, TADCR);
+        break;
+    case A_TADCOBRA_4:
+    case A_TADCOBRB_4:
+        mtu->tadcobr[(addr >> 1) & 1] = val;
+    case A_TADCORA_4:
+    case A_TADCORB_4:
+        mtu->tadcor[(addr >> 1) & 1] = val;
+    case A_TOER:
+        mtu->toer = val;
+        break;
+    case A_TGCR:
+        mtu->tgcr = val;
+        break;
+    case A_TOCR1:
+    case A_TOCR2:
+        mtu->tocr[addr & 1] = val;
+        break;
+    case A_TCDR:
+        mtu->tcdr = val;
+        break;
+    case A_TDDR:
+        mtu->tddr = val;
+        break;
+    case A_TCNTS:
+        mtu->tcnts = val;
+        break;
+    case A_TCBR:
+        mtu->tcbr = val;
+        break;
+    case A_TITCR:
+        mtu->titcr = val;
+        break;
+    case A_TITCNT:
+        mtu->titcnt = val;
+        break;
+    case A_TBTER:
+        mtu->tbter = val;
+        break;
+    case A_TDER:
+        mtu->tder = val;
+        break;
+    case A_TOLBR:
+        mtu->tolbr = val;
+        break;
+    case A_TWCR:
+        mtu->twcr = val;
+        break;
+    case A_TSTR:
+        val = deposit64(val, 3, 2, extract64(val, 6, 2));
+        now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        for (ch = 0; ch < 5; ch++) {
+            if (mtu->r[ch].start != extract32(val, ch, 1)) {
+                mtu->r[ch].start = extract32(val, ch, 1);
+                if (mtu->r[ch].start) {
+                    mtu->r[ch].base = now;
+                }
+                set_next_event(&mtu->r[ch]);
+            }
+        }
+        break;
+    case A_TSYR:
+        mtu->tsyr = val;
+        break;
+    case A_TRWER:
+        if (mtu->trwer_r) {
+            mtu->trwer = FIELD_DP8(mtu->trwer, TRWER, RWE,
+                                   FIELD_EX8(val, TRWER, RWE));
+            mtu->trwer_r = 0;
+        } else {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "renesas_mtu: TRWER protected.\n");
+        }
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Unknown register "
+                      "0x%" HWADDR_PRIX "\n", addr);
+        break;
+    }
+}
+
+static void mtu2_5_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+    RenesasMTU2State *mtu = RenesasMTU2(opaque);
+    int ch;
+    int64_t now;
+
+    ch = addr >> 4;
+    if (!mtu2_5_valid_size(addr, size)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Invalid access size at "
+                      "0x%" HWADDR_PRIX "\n", addr);
+        return;
+    }
+    if (!clock_is_enabled(mtu->pck)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "renesas_mtu: Unit %d is stopped.\n", mtu->unit);
+        return;
+    }
+    if (ch < 3) {
+        switch (addr & 0x0f) {
+        case A_TCNTU_5:
+            mtu->r5[ch].tcnt = val;
+            set_next_event5(&mtu->r5[ch]);
+            break;
+        case A_TGRU_5:
+            mtu->r5[ch].tgr[0] = val;
+            set_next_event5(&mtu->r5[ch]);
+            break;
+        case A_TCRU_5:
+            mtu->r5[ch].tcr = val;
+            set_next_event5(&mtu->r5[ch]);
+            break;
+        case A_TIORU_5:
+            mtu->r5[ch].tior = val;
+            break;
+        default:
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "renesas_mtu: Unknown register 0x%"
+                          HWADDR_PRIX "\n", addr);
+            break;
+        }
+    } else {
+        switch (addr & 0xff) {
+        case A_TIER_5:
+            for (ch = 0; ch < 3; ch++) {
+                mtu->r5[ch].ier = extract64(val, ch, 1);
+            }
+            break;
+        case A_TSTR_5:
+            now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+            for (ch = 0; ch < 3; ch++) {
+                if (mtu->r5[ch].start != extract64(val, ch, 1)) {
+                    mtu->r5[ch].start = extract64(val, ch, 1);
+                    if (mtu->r5[ch].start) {
+                        mtu->r5[ch].base = now;
+                    }
+                    set_next_event5(&mtu->r5[ch]);
+                }
+            }
+            break;
+        case A_TCNTCMPCLR_5:
+            for (ch = 0; ch < 3; ch++) {
+                if (mtu->r5[ch].cntclr != extract64(val, ch, 1)) {
+                    mtu->r5[ch].cntclr = extract64(val, ch, 1);
+                    set_next_event5(&mtu->r5[ch]);
+                }
+            }
+            break;
+        default:
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "renesas_mtu: Unknown register %08lx\n",
+                          addr);
+            break;
+        }
+    }
+}
+
+static const MemoryRegionOps mtu2_low_ops = {
+    .write = mtu2_low_write,
+    .read  = mtu2_low_read,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .impl = {
+        .min_access_size = 1,
+        .max_access_size = 2,
+    },
+};
+
+static const MemoryRegionOps mtu2_high_ops = {
+    .write = mtu2_high_write,
+    .read  = mtu2_high_read,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .impl = {
+        .min_access_size = 1,
+        .max_access_size = 2,
+    },
+};
+
+static const MemoryRegionOps mtu2_5_ops = {
+    .write = mtu2_5_write,
+    .read  = mtu2_5_read,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .impl = {
+        .min_access_size = 1,
+        .max_access_size = 2,
+    },
+};
+
+static void mtu_reg_init(int channel, RenesasMTU2State *mtu, RenesasMTURegs *r)
+{
+    int grn;
+    static const int gr[] = {6, 2, 2, 4, 4};
+    r->ch = channel;
+    r->mtu = mtu;
+    r->tsr = 0xc0;
+    r->num_gr = gr[channel];
+    for (grn = 0; grn < r->num_gr; grn++) {
+        r->tgr[grn] = 0xffff;
+    }
+}
+
+static void mtu2_realize(DeviceState *dev, Error **errp)
+{
+    int ch;
+    RenesasMTU2State *mtu = RenesasMTU2(dev);
+
+    for (ch = 0; ch < 5; ch++) {
+        mtu_reg_init(ch, mtu, &mtu->r[ch]);
+        if (clock_is_enabled(mtu->pck)) {
+            set_cnt_clock(mtu->input_freq, &mtu->r[ch]);
+        }
+    }
+    for (ch = 0; ch < 3; ch++) {
+        mtu->r5[ch].mtu = NULL;
+        mtu->r5[ch].tgr[0] = 0xffff;
+        if (clock_is_enabled(mtu->pck)) {
+            set_cnt_clock(mtu->input_freq, &mtu->r5[ch]);
+        }
+    }
+    mtu->ticcr = 0x00;
+    mtu->toer = 0xc0;
+    mtu->tgcr = 0x80;
+    mtu->tcdr = mtu->tddr = 0xffff;
+    mtu->tcbr = 0xffff;
+    mtu->tder = 0x01;
+    mtu->trwer = 0x01;
+}
+
+static void mtu2_init(Object *obj)
+{
+    int ch, irq;
+    static int nr_irq[] = {7, 4, 4, 5, 5};
+    SysBusDevice *d = SYS_BUS_DEVICE(obj);
+    RenesasMTU2State *mtu = RenesasMTU2(obj);
+
+    memory_region_init_io(&mtu->memory[0], OBJECT(mtu), &mtu2_low_ops,
+                          mtu, "renesas-mtu2-low", 0x180);
+    sysbus_init_mmio(d, &mtu->memory[0]);
+    memory_region_init_io(&mtu->memory[1], OBJECT(mtu), &mtu2_high_ops,
+                          mtu, "renesas-mtu2-high", 0x90);
+    sysbus_init_mmio(d, &mtu->memory[1]);
+    memory_region_init_io(&mtu->memory[2], OBJECT(mtu), &mtu2_5_ops,
+                          mtu, "renesas-mtu2-5", 0x40);
+    sysbus_init_mmio(d, &mtu->memory[2]);
+    for (ch = 0; ch < 5; ch++) {
+        for (irq = 0; irq < nr_irq[ch]; irq++) {
+            sysbus_init_irq(d, &mtu->r[ch].irq[irq]);
+        }
+    }
+    for (ch = 0; ch < 3; ch++) {
+        sysbus_init_irq(d, &mtu->r5[ch].irq[0]);
+    }
+    mtu->pck = qdev_init_clock_in(DEVICE(d), "pck",
+                                  mtu_pck_update, mtu);
+}
+
+static Property mtu_properties[] = {
+    DEFINE_PROP_UINT32("unit", RenesasMTU2State, unit, 0),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mtu2_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = mtu2_realize;
+    device_class_set_props(dc, mtu_properties);
+}
+
+static const TypeInfo renesas_mtu_info = {
+    .name       = TYPE_RENESAS_MTU2,
+    .parent     = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(RenesasMTU2State),
+    .instance_init = mtu2_init,
+    .class_init = mtu2_class_init,
+    .class_size = sizeof(RenesasMTU2Class),
+};
+
+static void mtu_register_types(void)
+{
+    type_register_static(&renesas_mtu_info);
+}
+
+type_init(mtu_register_types)
diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
index 4d21b50ab0..b4553d7847 100644
--- a/hw/timer/Kconfig
+++ b/hw/timer/Kconfig
@@ -45,3 +45,5 @@  config AVR_TIMER16
 config RENESAS_TIMER
     bool
 
+config RENESAS_MTU
+    bool
diff --git a/hw/timer/meson.build b/hw/timer/meson.build
index 6aed6d1e5f..4d16a59c02 100644
--- a/hw/timer/meson.build
+++ b/hw/timer/meson.build
@@ -10,6 +10,7 @@  softmmu_ss.add(when: 'CONFIG_CMSDK_APB_DUALTIMER', if_true: files('cmsdk-apb-dua
 softmmu_ss.add(when: 'CONFIG_CMSDK_APB_TIMER', if_true: files('cmsdk-apb-timer.c'))
 softmmu_ss.add(when: 'CONFIG_RENESAS_TMR8', if_true: files('renesas_tmr8.c'))
 softmmu_ss.add(when: 'CONFIG_RENESAS_TIMER', if_true: files('renesas_timer.c'))
+softmmu_ss.add(when: 'CONFIG_RENESAS_MTU', if_true: files('renesas_mtu.c'))
 softmmu_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-timer.c'))
 softmmu_ss.add(when: 'CONFIG_ETRAXFS', if_true: files('etraxfs_timer.c'))
 softmmu_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_mct.c'))