diff mbox series

[05/20] hw/rx: Add RX62N Clock generator

Message ID 20200827123859.81793-6-ysato@users.sourceforge.jp
State New
Headers show
Series [01/20] loader.c: Add support Motrola S-record format. | expand

Commit Message

Yoshinori Sato Aug. 27, 2020, 12:38 p.m. UTC
This module generated core and peripheral clock.

Signed-off-by: Yoshinori Sato <ysato@users.sourceforge.jp>
---
 include/hw/rx/rx62n-cpg.h |  72 ++++++++
 include/hw/rx/rx62n.h     |   5 +-
 hw/rx/rx62n-cpg.c         | 344 ++++++++++++++++++++++++++++++++++++++
 hw/rx/rx62n.c             |  52 +++---
 hw/rx/meson.build         |   2 +-
 5 files changed, 447 insertions(+), 28 deletions(-)
 create mode 100644 include/hw/rx/rx62n-cpg.h
 create mode 100644 hw/rx/rx62n-cpg.c

Comments

Philippe Mathieu-Daudé Sept. 8, 2020, 9:11 p.m. UTC | #1
On 8/27/20 2:38 PM, Yoshinori Sato wrote:
> This module generated core and peripheral clock.
> 
> Signed-off-by: Yoshinori Sato <ysato@users.sourceforge.jp>
> ---
>  include/hw/rx/rx62n-cpg.h |  72 ++++++++
>  include/hw/rx/rx62n.h     |   5 +-
>  hw/rx/rx62n-cpg.c         | 344 ++++++++++++++++++++++++++++++++++++++
>  hw/rx/rx62n.c             |  52 +++---
>  hw/rx/meson.build         |   2 +-
>  5 files changed, 447 insertions(+), 28 deletions(-)
>  create mode 100644 include/hw/rx/rx62n-cpg.h
>  create mode 100644 hw/rx/rx62n-cpg.c
> 
> diff --git a/include/hw/rx/rx62n-cpg.h b/include/hw/rx/rx62n-cpg.h
> new file mode 100644
> index 0000000000..d90a067313
> --- /dev/null
> +++ b/include/hw/rx/rx62n-cpg.h
> @@ -0,0 +1,72 @@
> +/*
> + * RX62N Clock generator circuit
> + *
> + * Datasheet: RX62N Group, RX621 Group User's Manual: Hardware
> + * (Rev.1.40 R01UH0033EJ0140)
> + *
> + * Copyright (c) 2020 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/>.
> + */
> +
> +#ifndef HW_RX_RX62N_CPG_H
> +#define HW_RX_RX62N_CPG_H
> +
> +#include "hw/sysbus.h"
> +#include "hw/qdev-clock.h"
> +
> +#define TYPE_RX62N_CPG "rx62n-cpg"
> +#define RX62NCPG(obj) OBJECT_CHECK(RX62NCPGState, (obj), TYPE_RX62N_CPG)
> +
> +enum {
> +    CK_TMR8_1,
> +    CK_TMR8_0,
> +    CK_MTU_1,
> +    CK_MTU_0,
> +    CK_CMT_1,
> +    CK_CMT_0,
> +    CK_EDMAC,
> +    CK_SCI6,
> +    CK_SCI5,
> +    CK_SCI3,
> +    CK_SCI2,
> +    CK_SCI1,
> +    CK_SCI0,
> +    NUM_SUBCLOCK,
> +};
> +
> +typedef struct RX62NCPGState {
> +    SysBusDevice parent_obj;
> +    uint32_t mstpcr[3];
> +    uint32_t sckcr;
> +    uint8_t  bckcr;
> +    uint8_t  ostdcr;
> +
> +    int ick;
> +    Clock *clk_ick;
> +    int bck;
> +    Clock *clk_bck;
> +    int pck;
> +    Clock *clk_pck;
> +    Clock *dev_clocks[NUM_SUBCLOCK];
> +    uint32_t xtal_freq_hz;
> +    MemoryRegion memory;
> +} RX62NCPGState;
> +
> +typedef struct RX62NCPGClass {
> +    SysBusDeviceClass parent;
> +} RX62NCPGClass;
> +
> +#define OSTDCR_KEY 0xac

Please move the key to rx62n-cpg.c.

> +
> +#endif
> diff --git a/include/hw/rx/rx62n.h b/include/hw/rx/rx62n.h
> index 32e460bbad..e0ca1cfc33 100644
> --- a/include/hw/rx/rx62n.h
> +++ b/include/hw/rx/rx62n.h
> @@ -29,6 +29,7 @@
>  #include "hw/timer/renesas_tmr.h"
>  #include "hw/timer/renesas_cmt.h"
>  #include "hw/char/renesas_sci.h"
> +#include "hw/rx/rx62n-cpg.h"
>  #include "qemu/units.h"
>  
>  #define TYPE_RX62N_MCU "rx62n-mcu"
> @@ -70,9 +71,9 @@ typedef struct RX62NState {
>      RTMRState tmr[RX62N_NR_TMR];
>      RCMTState cmt[RX62N_NR_CMT];
>      RSCIState sci[RX62N_NR_SCI];
> +    RX62NCPGState cpg;
>  
>      MemoryRegion *sysmem;
> -    bool kernel;
>  
>      MemoryRegion iram;
>      MemoryRegion iomem1;
> @@ -84,8 +85,6 @@ typedef struct RX62NState {
>  
>      /* Input Clock (XTAL) frequency */
>      uint32_t xtal_freq_hz;
> -    /* Peripheral Module Clock frequency */
> -    uint32_t pclk_freq_hz;
>  } RX62NState;
>  
>  #endif
> diff --git a/hw/rx/rx62n-cpg.c b/hw/rx/rx62n-cpg.c
> new file mode 100644
> index 0000000000..9d70004302
> --- /dev/null
> +++ b/hw/rx/rx62n-cpg.c
> @@ -0,0 +1,344 @@
> +/*
> + * RX62N Clock Generation Circuit
> + *
> + * Datasheet: RX62N Group, RX621 Group User's Manual: Hardware
> + * (Rev.1.40 R01UH0033EJ0140)
> + *
> + * Copyright (c) 2020 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 "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/hw.h"
> +#include "hw/rx/rx62n-cpg.h"
> +#include "hw/sysbus.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/registerfields.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/clock.h"
> +#include "migration/vmstate.h"
> +
> +#define RX62N_XTAL_MIN_HZ  (8 * 1000 * 1000)
> +#define RX62N_XTAL_MAX_HZ (14 * 1000 * 1000)
> +
> +REG32(MSTPCRA, 0)
> +REG32(MSTPCRB, 4)
> +REG32(MSTPCRC, 8)
> +REG32(SCKCR, 16)
> +  FIELD(SCKCR, PCK,  8, 3)
> +  FIELD(SCKCR, BCK, 16, 3)
> +  FIELD(SCKCR, PSTOP, 22, 2)
> +  FIELD(SCKCR, ICK, 24, 3)
> +REG8(BCKCR, 32)
> +  FIELD(BCKCR, BCLKDIV, 0, 1)
> +REG16(OSTDCR, 48)
> +  FIELD(OSTDCR, OSTDF, 6, 1)
> +  FIELD(OSTDCR, OSTDE, 7, 1)
> +
> +static const int access_size[] = {4, 4, 1, 2};
> +
> +typedef struct {
> +    const char *name;
> +    int devnum;
> +    int reg;
> +    int offset;
> +    int parentck;
> +} dev_clock_t;
> +
> +enum {
> +    parent_ick, parent_bck, parent_pck,
> +};
> +
> +static const dev_clock_t dev_clock_list[] = {
> +    { .name = "pck_tmr8-1",
> +      .devnum = CK_TMR8_1, .reg = 0, .offset = 4, .parentck = parent_pck, },
> +    { .name = "pck_tmr8-0",
> +      .devnum = CK_TMR8_0, .reg = 0, .offset = 5, .parentck = parent_pck, },
> +    { .name = "pck_mtu-1",
> +      .devnum = CK_MTU_1, .reg = 0, .offset = 8, .parentck = parent_pck, },
> +    { .name = "pck_mtu-0",
> +      .devnum = CK_MTU_0, .reg = 0, .offset = 9, .parentck = parent_pck, },
> +    { .name = "pck_cmt-1",
> +      .devnum = CK_CMT_1, .reg = 0, .offset = 14, .parentck = parent_pck, },
> +    { .name = "pck_cmt-0",
> +      .devnum = CK_CMT_0, .reg = 0, .offset = 15, .parentck = parent_pck, },
> +    { .name = "ick_edmac",
> +      .devnum = CK_EDMAC, .reg = 1, .offset = 15, .parentck = parent_ick, },
> +    { .name = "pck_sci-6",
> +      .devnum = CK_SCI6, .reg = 1, .offset = 25, .parentck = parent_pck, },
> +    { .name = "pck_sci-5",
> +      .devnum = CK_SCI5, .reg = 1, .offset = 26, .parentck = parent_pck, },
> +    { .name = "pck_sci-3",
> +      .devnum = CK_SCI3, .reg = 1, .offset = 28, .parentck = parent_pck, },
> +    { .name = "pck_sci-2",
> +      .devnum = CK_SCI2, .reg = 1, .offset = 29, .parentck = parent_pck, },
> +    { .name = "pck_sci-1",
> +      .devnum = CK_SCI1, .reg = 1, .offset = 30, .parentck = parent_pck, },
> +    { .name = "pck_sci-0",
> +      .devnum = CK_SCI0, .reg = 1, .offset = 31, .parentck = parent_pck, },
> +    { },
> +};

Could simplify to remove offset field, and index by offset:

static const dev_clock_t dev_clock_list[32] = {
 [ 4] = { .name = "pck_tmr8-1", .devnum = CK_TMR8_1,
          .reg = 0, .offset = 4, .parentck = parent_pck, },
 ...

 [31] = { .name = "pck_sci-0", .devnum = CK_SCI0,
          .reg = 1, .parentck = parent_pck, }
};

And replace the while (p->name) by
for (... ARRAY_SIZE(dev_clock_list) ...).

> +
> +static void set_clock_in(RX62NCPGState *cpg, const dev_clock_t *ck)
> +{
> +    Clock *out;
> +    uint64_t period;
> +
> +    out = qdev_get_clock_out(DEVICE(cpg), ck->name);
> +    g_assert(out);
> +    period = 0;
> +    if (extract32(cpg->mstpcr[ck->reg], ck->offset, 1) == 0) {
> +        switch (ck->parentck) {
> +        case parent_ick:
> +            period = clock_get(cpg->clk_ick);
> +            break;
> +        case parent_pck:
> +            period = clock_get(cpg->clk_pck);
> +            break;
> +        }
> +    }
> +    if (clock_get(out) != period) {
> +        clock_update(out, period);
> +    }
> +}
> +
> +#define update_ck(ckname)                                             \
> +    if (cpg->ckname != ckname) {                                      \
> +        cpg->ckname = ckname;                                         \
> +        ckname =  8 / (1 << ckname);                                  \
> +        clock_update_hz(cpg->clk_ ## ckname,                          \
> +                        cpg->xtal_freq_hz * ckname);                  \
> +    }
> +
> +#define validate_setting(ckname)                                 \
> +    if (ick > ckname) {                                         \
> +        qemu_log_mask(LOG_GUEST_ERROR,                           \
> +                      "rx62n-cpg: Invalid " #ckname " setting."   \
> +                      " (ick=%d " #ckname "=%d)\n", ick, ckname); \
> +        cpg->ckname = ckname = ick;                              \
> +    }

Can you replace by a update_ck(clk, const char *name) function?
(validate_setting too).

> +
> +static void update_divrate(RX62NCPGState *cpg)
> +{
> +    int ick = FIELD_EX32(cpg->sckcr, SCKCR, ICK);
> +    int bck = FIELD_EX32(cpg->sckcr, SCKCR, BCK);
> +    int pck = FIELD_EX32(cpg->sckcr, SCKCR, PCK);
> +    const dev_clock_t *p = dev_clock_list;
> +    validate_setting(pck);
> +    validate_setting(bck);
> +    update_ck(ick);
> +    update_ck(bck);
> +    update_ck(pck);
> +    while (p->name) {
> +        set_clock_in(cpg, p);
> +        p++;
> +    }
> +}
> +
> +static const dev_clock_t *find_clock_list(int crno, int bit)
> +{
> +    const dev_clock_t *ret = dev_clock_list;
> +    while (ret->name) {
> +        if (ret->reg == crno && ret->offset == bit) {
> +            return ret;
> +        }
> +        ret++;
> +    }
> +    return NULL;
> +}
> +
> +static void update_mstpcr(RX62NCPGState *cpg, int crno, uint32_t diff)
> +{
> +    int bit = 0;
> +    const dev_clock_t *p;
> +
> +    while (diff) {
> +        if (diff & 1) {
> +            p = find_clock_list(crno, bit);
> +            if (p) {
> +                set_clock_in(cpg, p);
> +            } else {
> +                qemu_log_mask(LOG_UNIMP, "rx62n-cpg: MSTPCR%c "
> +                              " bit %d is not implement.\n", 'A' + crno, bit);
> +            }
> +        }
> +        bit++;
> +        diff >>= 1;
> +    }
> +}
> +
> +static uint64_t cpg_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    RX62NCPGState *cpg = RX62NCPG(opaque);
> +
> +    if (access_size[addr >> 4] != size) {
> +        qemu_log_mask(LOG_GUEST_ERROR, "rx62n-cpg: Register 0x%"
> +                      HWADDR_PRIX " Invalid access size.\n", addr);
> +        return UINT64_MAX;
> +    }
> +    switch (addr) {
> +    case A_MSTPCRA:
> +        return cpg->mstpcr[0] | 0x473530cf;
> +    case A_MSTPCRB:
> +        return cpg->mstpcr[1] | 0x09407ffe;
> +    case A_MSTPCRC:
> +        return (cpg->mstpcr[2] | 0xffff0000) & 0xffff0003;
> +    case A_SCKCR:
> +        return cpg->sckcr & 0x0fcf0f00;
> +    case A_BCKCR:
> +        return cpg->bckcr & 0x01;
> +    case A_OSTDCR:
> +        /* Main OSC always good */
> +        return cpg->ostdcr & 0x0080;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR, "rx62n-cpg: Register 0x%"
> +                      HWADDR_PRIX " Invalid address.\n", addr);
> +        return UINT64_MAX;
> +    }
> +}
> +
> +static void cpg_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
> +{
> +    RX62NCPGState *cpg = RX62NCPG(opaque);
> +    uint32_t old_mstpcr;
> +    int cr_no;
> +    if (access_size[addr >> 4] != size) {
> +        qemu_log_mask(LOG_GUEST_ERROR, "rx62n-cpg: Register 0x%"
> +                      HWADDR_PRIX " Invalid access size.\n", addr);
> +        return;
> +    }
> +    switch (addr) {
> +    case A_MSTPCRA:
> +    case A_MSTPCRB:
> +    case A_MSTPCRC:
> +        cr_no = (addr & 0x0f) >> 2;
> +        old_mstpcr = cpg->mstpcr[cr_no];
> +        old_mstpcr ^= val;
> +        cpg->mstpcr[cr_no] = val;
> +        update_mstpcr(cpg, cr_no, old_mstpcr);
> +        break;
> +    case A_SCKCR:
> +        cpg->sckcr = val;
> +        update_divrate(cpg);
> +        break;
> +    case A_BCKCR:
> +        cpg->bckcr = val;
> +        break;
> +    case A_OSTDCR:
> +        if (extract16(val, 8, 8) == OSTDCR_KEY) {
> +            cpg->ostdcr = val;
> +        } else {
> +            qemu_log_mask(LOG_GUEST_ERROR, "rx62n-cpg: Register 0x%"
> +                          HWADDR_PRIX " Invalid key value.\n", addr);
> +        }
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR, "rx62n-cpg: Register 0x%"
> +                      HWADDR_PRIX " Invalid address.\n", addr);
> +    }
> +}
> +
> +static const MemoryRegionOps cpg_ops = {
> +    .write = cpg_write,
> +    .read  = cpg_read,
> +    .endianness = DEVICE_NATIVE_ENDIAN,
> +    .impl = {
> +        .min_access_size = 1,
> +        .max_access_size = 4,
> +    },
> +};
> +
> +static const ClockPortInitArray rx62n_cpg_clocks = {
> +    QDEV_CLOCK_OUT(RX62NCPGState, clk_ick),
> +    QDEV_CLOCK_OUT(RX62NCPGState, clk_bck),
> +    QDEV_CLOCK_OUT(RX62NCPGState, clk_pck),
> +    QDEV_CLOCK_END
> +};
> +
> +static void cpg_realize(DeviceState *dev, Error **errp)
> +{
> +    RX62NCPGState *cpg = RX62NCPG(dev);
> +    const dev_clock_t *p = dev_clock_list;
> +
> +    if (cpg->xtal_freq_hz == 0) {
> +        error_setg(errp, "\"xtal-frequency-hz\" property must be provided.");
> +        return;
> +    }
> +    /* XTAL range: 8-14 MHz */
> +    if (cpg->xtal_freq_hz < RX62N_XTAL_MIN_HZ ||
> +        cpg->xtal_freq_hz > RX62N_XTAL_MAX_HZ) {
> +        error_setg(errp, "\"xtal-frequency-hz\" property in incorrect range.");
> +        return;
> +    }
> +
> +    cpg->sckcr = FIELD_DP32(cpg->sckcr, SCKCR, ICK, 2);
> +    cpg->sckcr = FIELD_DP32(cpg->sckcr, SCKCR, BCK, 2);
> +    cpg->sckcr = FIELD_DP32(cpg->sckcr, SCKCR, PCK, 2);
> +    cpg->ostdcr = FIELD_DP8(cpg->ostdcr, OSTDCR, OSTDE, 1);
> +    cpg->mstpcr[0] = 0x47ffffff;
> +    cpg->mstpcr[1] = 0xffffffff;
> +    cpg->mstpcr[2] = 0xffff0000;
> +
> +    /* set initial state */
> +    while (p->name) {
> +        set_clock_in(cpg, p);
> +        p++;
> +    }
> +    update_divrate(cpg);
> +}
> +
> +static void rx62n_cpg_init(Object *obj)
> +{
> +    RX62NCPGState *cpg = RX62NCPG(obj);
> +    const dev_clock_t *p = dev_clock_list;
> +    qdev_init_clocks(DEVICE(obj), rx62n_cpg_clocks);
> +    /* connect parent clock */
> +    while (p->name) {
> +        cpg->dev_clocks[p->devnum] = qdev_init_clock_out(DEVICE(obj),
> +                                                         p->name);
> +        p++;
> +    }
> +
> +    memory_region_init_io(&cpg->memory, OBJECT(cpg), &cpg_ops,
> +                          cpg, "rx62n-cpg", 0x40);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &cpg->memory);
> +}
> +
> +static Property rx62n_cpg_properties[] = {
> +    DEFINE_PROP_UINT32("xtal-frequency-hz", RX62NCPGState, xtal_freq_hz, 0),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void rx62n_cpg_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->realize = cpg_realize;
> +    device_class_set_props(dc, rx62n_cpg_properties);
> +}
> +
> +static const TypeInfo rx62n_cpg_info[] = {
> +    {
> +        .name       = TYPE_RX62N_CPG,
> +        .parent     = TYPE_SYS_BUS_DEVICE,
> +        .instance_size = sizeof(RX62NCPGState),
> +        .instance_init = rx62n_cpg_init,
> +        .class_init = rx62n_cpg_class_init,
> +        .class_size = sizeof(RX62NCPGClass),
> +    },
> +};
> +
> +DEFINE_TYPES(rx62n_cpg_info)
> diff --git a/hw/rx/rx62n.c b/hw/rx/rx62n.c
> index 4b5c3c1079..ec63fa5db1 100644
> --- a/hw/rx/rx62n.c
> +++ b/hw/rx/rx62n.c
> @@ -47,6 +47,7 @@
>  #define RX62N_TMR_BASE  0x00088200
>  #define RX62N_CMT_BASE  0x00088000
>  #define RX62N_SCI_BASE  0x00088240
> +#define RX62N_CPG_BASE  0x00080010
>  
>  /*
>   * RX62N Peripheral IRQ
> @@ -56,10 +57,6 @@
>  #define RX62N_CMT_IRQ   28
>  #define RX62N_SCI_IRQ   214
>  
> -#define RX62N_XTAL_MIN_HZ  (8 * 1000 * 1000)
> -#define RX62N_XTAL_MAX_HZ (14 * 1000 * 1000)
> -#define RX62N_PCLK_MAX_HZ (50 * 1000 * 1000)
> -
>  /*
>   * IRQ -> IPR mapping table
>   * 0x00 - 0x91: IPR no (IPR00 to IPR91)
> @@ -149,36 +146,45 @@ static void register_tmr(RX62NState *s, int unit)
>  {
>      SysBusDevice *tmr;
>      int i, irqbase;
> +    char ckname[16];
>  
>      object_initialize_child(OBJECT(s), "tmr[*]",
>                              &s->tmr[unit], TYPE_RENESAS_TMR);
>      tmr = SYS_BUS_DEVICE(&s->tmr[unit]);
> -    qdev_prop_set_uint64(DEVICE(tmr), "input-freq", s->pclk_freq_hz);
> -    sysbus_realize(tmr, &error_abort);

Please move here:

> +    qdev_prop_set_uint32(DEVICE(tmr), "unit", unit);
> +    snprintf(ckname, sizeof(ckname), "pck_tmr8-%d", unit);
> +    qdev_connect_clock_in(DEVICE(tmr), "pck",
> +                          qdev_get_clock_out(DEVICE(&s->cpg), ckname));
> +    sysbus_realize(tmr, &error_abort);

Note, you need to call sysbus_realize() *after* qdev_connect_clock_in().

>  
>      irqbase = RX62N_TMR_IRQ + TMR_NR_IRQ * unit;
>      for (i = 0; i < TMR_NR_IRQ; i++) {
>          sysbus_connect_irq(tmr, i, s->irq[irqbase + i]);
>      }
>      sysbus_mmio_map(tmr, 0, RX62N_TMR_BASE + unit * 0x10);
> +
> +    qdev_prop_set_uint32(DEVICE(tmr), "unit", unit);
> +    sysbus_realize(tmr, &error_abort);
> +    snprintf(ckname, sizeof(ckname), "pck_tmr8-%d", unit);
> +    qdev_connect_clock_in(DEVICE(tmr), "pck",
> +                          qdev_get_clock_out(DEVICE(&s->cpg), ckname));
>  }
>  
>  static void register_cmt(RX62NState *s, int unit)
>  {
>      SysBusDevice *cmt;
>      int i, irqbase;
> +    char ckname[16];
>  
>      object_initialize_child(OBJECT(s), "cmt[*]",
>                              &s->cmt[unit], TYPE_RENESAS_CMT);
>      cmt = SYS_BUS_DEVICE(&s->cmt[unit]);
> -    qdev_prop_set_uint64(DEVICE(cmt), "input-freq", s->pclk_freq_hz);
> -    sysbus_realize(cmt, &error_abort);
> +    qdev_prop_set_uint32(DEVICE(cmt), "unit", unit);
>  
>      irqbase = RX62N_CMT_IRQ + CMT_NR_IRQ * unit;
>      for (i = 0; i < CMT_NR_IRQ; i++) {
>          sysbus_connect_irq(cmt, i, s->irq[irqbase + i]);
>      }
>      sysbus_mmio_map(cmt, 0, RX62N_CMT_BASE + unit * 0x10);
> +    sysbus_realize(cmt, &error_abort);
> +    snprintf(ckname, sizeof(ckname), "pck_cmt-%d", unit);
> +    qdev_connect_clock_in(DEVICE(cmt), "pck",
> +                          qdev_get_clock_out(DEVICE(&s->cpg), ckname));

Ditto move.

>  }
>  
>  static void register_sci(RX62NState *s, int unit)
> @@ -190,7 +196,6 @@ static void register_sci(RX62NState *s, int unit)
>                              &s->sci[unit], TYPE_RENESAS_SCI);
>      sci = SYS_BUS_DEVICE(&s->sci[unit]);
>      qdev_prop_set_chr(DEVICE(sci), "chardev", serial_hd(unit));
> -    qdev_prop_set_uint64(DEVICE(sci), "input-freq", s->pclk_freq_hz);
>      sysbus_realize(sci, &error_abort);
>  
>      irqbase = RX62N_SCI_IRQ + SCI_NR_IRQ * unit;
> @@ -200,26 +205,23 @@ static void register_sci(RX62NState *s, int unit)
>      sysbus_mmio_map(sci, 0, RX62N_SCI_BASE + unit * 0x08);
>  }
>  
> +static void register_cpg(RX62NState *s)
> +{
> +    SysBusDevice *cpg;
> +
> +    object_initialize_child(OBJECT(s), "rx62n-cpg", &s->cpg,
> +                            TYPE_RX62N_CPG);
> +    cpg = SYS_BUS_DEVICE(&s->cpg);
> +    qdev_prop_set_uint64(DEVICE(cpg), "xtal-frequency-hz", s->xtal_freq_hz);

Eventually you can alias the property from the CPG on the SOC
using object_property_add_alias(), and remove RX62NState::xtal_freq_hz.

> +
> +    sysbus_mmio_map(cpg, 0, RX62N_CPG_BASE);
> +}
> +
>  static void rx62n_realize(DeviceState *dev, Error **errp)
>  {
>      RX62NState *s = RX62N_MCU(dev);
>      RX62NClass *rxc = RX62N_MCU_GET_CLASS(dev);
>  
> -    if (s->xtal_freq_hz == 0) {
> -        error_setg(errp, "\"xtal-frequency-hz\" property must be provided.");
> -        return;
> -    }
> -    /* XTAL range: 8-14 MHz */
> -    if (s->xtal_freq_hz < RX62N_XTAL_MIN_HZ
> -            || s->xtal_freq_hz > RX62N_XTAL_MAX_HZ) {
> -        error_setg(errp, "\"xtal-frequency-hz\" property in incorrect range.");
> -        return;
> -    }
> -    /* Use a 4x fixed multiplier */
> -    s->pclk_freq_hz = 4 * s->xtal_freq_hz;
> -    /* PCLK range: 8-50 MHz */
> -    assert(s->pclk_freq_hz <= RX62N_PCLK_MAX_HZ);
> -
>      memory_region_init_ram(&s->iram, OBJECT(dev), "iram",
>                             rxc->ram_size, &error_abort);
>      memory_region_add_subregion(s->sysmem, RX62N_IRAM_BASE, &s->iram);
> @@ -236,11 +238,13 @@ static void rx62n_realize(DeviceState *dev, Error **errp)
>  
>      register_icu(s);
>      s->cpu.env.ack = qdev_get_gpio_in_named(DEVICE(&s->icu), "ack", 0);
> +    register_cpg(s);
>      register_tmr(s, 0);
>      register_tmr(s, 1);
>      register_cmt(s, 0);
>      register_cmt(s, 1);
>      register_sci(s, 0);
> +    sysbus_realize(SYS_BUS_DEVICE(&s->cpg), &error_abort);
>  }
>  
>  static Property rx62n_properties[] = {
> diff --git a/hw/rx/meson.build b/hw/rx/meson.build
> index e73850f303..3a81d85a53 100644
> --- a/hw/rx/meson.build
> +++ b/hw/rx/meson.build
> @@ -1,6 +1,6 @@
>  rx_ss = ss.source_set()
>  rx_ss.add(files('loader.c'))
>  rx_ss.add(when: 'CONFIG_RX_GDBSIM', if_true: files('rx-gdbsim.c'))
> -rx_ss.add(when: 'CONFIG_RX62N_MCU', if_true: files('rx62n.c'))
> +rx_ss.add(when: 'CONFIG_RX62N_MCU', if_true: files('rx62n.c', 'rx62n-cpg.c'))
>  
>  hw_arch += {'rx': rx_ss}
> 

Very good patch :)
Philippe Mathieu-Daudé Oct. 24, 2020, 9:56 p.m. UTC | #2
On 8/27/20 2:38 PM, Yoshinori Sato wrote:
> This module generated core and peripheral clock.
> 
> Signed-off-by: Yoshinori Sato <ysato@users.sourceforge.jp>
> ---
>   include/hw/rx/rx62n-cpg.h |  72 ++++++++
>   include/hw/rx/rx62n.h     |   5 +-
>   hw/rx/rx62n-cpg.c         | 344 ++++++++++++++++++++++++++++++++++++++
>   hw/rx/rx62n.c             |  52 +++---
>   hw/rx/meson.build         |   2 +-
>   5 files changed, 447 insertions(+), 28 deletions(-)
>   create mode 100644 include/hw/rx/rx62n-cpg.h
>   create mode 100644 hw/rx/rx62n-cpg.c
...

> diff --git a/hw/rx/rx62n.c b/hw/rx/rx62n.c
> index 4b5c3c1079..ec63fa5db1 100644
> --- a/hw/rx/rx62n.c
> +++ b/hw/rx/rx62n.c
> @@ -47,6 +47,7 @@
>   #define RX62N_TMR_BASE  0x00088200
>   #define RX62N_CMT_BASE  0x00088000
>   #define RX62N_SCI_BASE  0x00088240
> +#define RX62N_CPG_BASE  0x00080010
>   
>   /*
>    * RX62N Peripheral IRQ
> @@ -56,10 +57,6 @@
>   #define RX62N_CMT_IRQ   28
>   #define RX62N_SCI_IRQ   214
>   
> -#define RX62N_XTAL_MIN_HZ  (8 * 1000 * 1000)
> -#define RX62N_XTAL_MAX_HZ (14 * 1000 * 1000)
> -#define RX62N_PCLK_MAX_HZ (50 * 1000 * 1000)
> -
>   /*
>    * IRQ -> IPR mapping table
>    * 0x00 - 0x91: IPR no (IPR00 to IPR91)
> @@ -149,36 +146,45 @@ static void register_tmr(RX62NState *s, int unit)
>   {
>       SysBusDevice *tmr;
>       int i, irqbase;
> +    char ckname[16];
>   
>       object_initialize_child(OBJECT(s), "tmr[*]",
>                               &s->tmr[unit], TYPE_RENESAS_TMR);
>       tmr = SYS_BUS_DEVICE(&s->tmr[unit]);
> -    qdev_prop_set_uint64(DEVICE(tmr), "input-freq", s->pclk_freq_hz);
> -    sysbus_realize(tmr, &error_abort);
>   
>       irqbase = RX62N_TMR_IRQ + TMR_NR_IRQ * unit;
>       for (i = 0; i < TMR_NR_IRQ; i++) {
>           sysbus_connect_irq(tmr, i, s->irq[irqbase + i]);
>       }
>       sysbus_mmio_map(tmr, 0, RX62N_TMR_BASE + unit * 0x10);
> +
> +    qdev_prop_set_uint32(DEVICE(tmr), "unit", unit);

Runtime failure:

qemu-system-rx: Property 'renesas-tmr.unit' not found

> +    sysbus_realize(tmr, &error_abort);
> +    snprintf(ckname, sizeof(ckname), "pck_tmr8-%d", unit);
> +    qdev_connect_clock_in(DEVICE(tmr), "pck",
> +                          qdev_get_clock_out(DEVICE(&s->cpg), ckname));
>   }
>   
>   static void register_cmt(RX62NState *s, int unit)
>   {
>       SysBusDevice *cmt;
>       int i, irqbase;
> +    char ckname[16];
>   
>       object_initialize_child(OBJECT(s), "cmt[*]",
>                               &s->cmt[unit], TYPE_RENESAS_CMT);
>       cmt = SYS_BUS_DEVICE(&s->cmt[unit]);
> -    qdev_prop_set_uint64(DEVICE(cmt), "input-freq", s->pclk_freq_hz);
> -    sysbus_realize(cmt, &error_abort);
> +    qdev_prop_set_uint32(DEVICE(cmt), "unit", unit);
>   
>       irqbase = RX62N_CMT_IRQ + CMT_NR_IRQ * unit;
>       for (i = 0; i < CMT_NR_IRQ; i++) {
>           sysbus_connect_irq(cmt, i, s->irq[irqbase + i]);
>       }
>       sysbus_mmio_map(cmt, 0, RX62N_CMT_BASE + unit * 0x10);
> +    sysbus_realize(cmt, &error_abort);
> +    snprintf(ckname, sizeof(ckname), "pck_cmt-%d", unit);
> +    qdev_connect_clock_in(DEVICE(cmt), "pck",
> +                          qdev_get_clock_out(DEVICE(&s->cpg), ckname));
>   }
Philippe Mathieu-Daudé Oct. 24, 2020, 9:58 p.m. UTC | #3
On 10/24/20 11:56 PM, Philippe Mathieu-Daudé wrote:
> On 8/27/20 2:38 PM, Yoshinori Sato wrote:

>> This module generated core and peripheral clock.

>>

>> Signed-off-by: Yoshinori Sato <ysato@users.sourceforge.jp>

>> ---

>>   include/hw/rx/rx62n-cpg.h |  72 ++++++++

>>   include/hw/rx/rx62n.h     |   5 +-

>>   hw/rx/rx62n-cpg.c         | 344 ++++++++++++++++++++++++++++++++++++++

>>   hw/rx/rx62n.c             |  52 +++---

>>   hw/rx/meson.build         |   2 +-

>>   5 files changed, 447 insertions(+), 28 deletions(-)

>>   create mode 100644 include/hw/rx/rx62n-cpg.h

>>   create mode 100644 hw/rx/rx62n-cpg.c

> ...

> 

>> diff --git a/hw/rx/rx62n.c b/hw/rx/rx62n.c

>> index 4b5c3c1079..ec63fa5db1 100644

>> --- a/hw/rx/rx62n.c

>> +++ b/hw/rx/rx62n.c

>> @@ -47,6 +47,7 @@

>>   #define RX62N_TMR_BASE  0x00088200

>>   #define RX62N_CMT_BASE  0x00088000

>>   #define RX62N_SCI_BASE  0x00088240

>> +#define RX62N_CPG_BASE  0x00080010

>>   /*

>>    * RX62N Peripheral IRQ

>> @@ -56,10 +57,6 @@

>>   #define RX62N_CMT_IRQ   28

>>   #define RX62N_SCI_IRQ   214

>> -#define RX62N_XTAL_MIN_HZ  (8 * 1000 * 1000)

>> -#define RX62N_XTAL_MAX_HZ (14 * 1000 * 1000)

>> -#define RX62N_PCLK_MAX_HZ (50 * 1000 * 1000)

>> -

>>   /*

>>    * IRQ -> IPR mapping table

>>    * 0x00 - 0x91: IPR no (IPR00 to IPR91)

>> @@ -149,36 +146,45 @@ static void register_tmr(RX62NState *s, int unit)

>>   {

>>       SysBusDevice *tmr;

>>       int i, irqbase;

>> +    char ckname[16];

>>       object_initialize_child(OBJECT(s), "tmr[*]",

>>                               &s->tmr[unit], TYPE_RENESAS_TMR);

>>       tmr = SYS_BUS_DEVICE(&s->tmr[unit]);

>> -    qdev_prop_set_uint64(DEVICE(tmr), "input-freq", s->pclk_freq_hz);

>> -    sysbus_realize(tmr, &error_abort);

>>       irqbase = RX62N_TMR_IRQ + TMR_NR_IRQ * unit;

>>       for (i = 0; i < TMR_NR_IRQ; i++) {

>>           sysbus_connect_irq(tmr, i, s->irq[irqbase + i]);

>>       }

>>       sysbus_mmio_map(tmr, 0, RX62N_TMR_BASE + unit * 0x10);

>> +

>> +    qdev_prop_set_uint32(DEVICE(tmr), "unit", unit);

> 

> Runtime failure:

> 

> qemu-system-rx: Property 'renesas-tmr.unit' not found

> 

>> +    sysbus_realize(tmr, &error_abort);

>> +    snprintf(ckname, sizeof(ckname), "pck_tmr8-%d", unit);

>> +    qdev_connect_clock_in(DEVICE(tmr), "pck",

>> +                          qdev_get_clock_out(DEVICE(&s->cpg), ckname));

>>   }

>>   static void register_cmt(RX62NState *s, int unit)

>>   {

>>       SysBusDevice *cmt;

>>       int i, irqbase;

>> +    char ckname[16];

>>       object_initialize_child(OBJECT(s), "cmt[*]",

>>                               &s->cmt[unit], TYPE_RENESAS_CMT);

>>       cmt = SYS_BUS_DEVICE(&s->cmt[unit]);

>> -    qdev_prop_set_uint64(DEVICE(cmt), "input-freq", s->pclk_freq_hz);

>> -    sysbus_realize(cmt, &error_abort);

>> +    qdev_prop_set_uint32(DEVICE(cmt), "unit", unit);

>>       irqbase = RX62N_CMT_IRQ + CMT_NR_IRQ * unit;

>>       for (i = 0; i < CMT_NR_IRQ; i++) {

>>           sysbus_connect_irq(cmt, i, s->irq[irqbase + i]);

>>       }

>>       sysbus_mmio_map(cmt, 0, RX62N_CMT_BASE + unit * 0x10);

>> +    sysbus_realize(cmt, &error_abort);

>> +    snprintf(ckname, sizeof(ckname), "pck_cmt-%d", unit);

>> +    qdev_connect_clock_in(DEVICE(cmt), "pck",

>> +                          qdev_get_clock_out(DEVICE(&s->cpg), ckname));


qemu-system-rx: Can not find clock-in 'pck' for device type 'renesas-tmr'

>>   }

>
diff mbox series

Patch

diff --git a/include/hw/rx/rx62n-cpg.h b/include/hw/rx/rx62n-cpg.h
new file mode 100644
index 0000000000..d90a067313
--- /dev/null
+++ b/include/hw/rx/rx62n-cpg.h
@@ -0,0 +1,72 @@ 
+/*
+ * RX62N Clock generator circuit
+ *
+ * Datasheet: RX62N Group, RX621 Group User's Manual: Hardware
+ * (Rev.1.40 R01UH0033EJ0140)
+ *
+ * Copyright (c) 2020 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/>.
+ */
+
+#ifndef HW_RX_RX62N_CPG_H
+#define HW_RX_RX62N_CPG_H
+
+#include "hw/sysbus.h"
+#include "hw/qdev-clock.h"
+
+#define TYPE_RX62N_CPG "rx62n-cpg"
+#define RX62NCPG(obj) OBJECT_CHECK(RX62NCPGState, (obj), TYPE_RX62N_CPG)
+
+enum {
+    CK_TMR8_1,
+    CK_TMR8_0,
+    CK_MTU_1,
+    CK_MTU_0,
+    CK_CMT_1,
+    CK_CMT_0,
+    CK_EDMAC,
+    CK_SCI6,
+    CK_SCI5,
+    CK_SCI3,
+    CK_SCI2,
+    CK_SCI1,
+    CK_SCI0,
+    NUM_SUBCLOCK,
+};
+
+typedef struct RX62NCPGState {
+    SysBusDevice parent_obj;
+    uint32_t mstpcr[3];
+    uint32_t sckcr;
+    uint8_t  bckcr;
+    uint8_t  ostdcr;
+
+    int ick;
+    Clock *clk_ick;
+    int bck;
+    Clock *clk_bck;
+    int pck;
+    Clock *clk_pck;
+    Clock *dev_clocks[NUM_SUBCLOCK];
+    uint32_t xtal_freq_hz;
+    MemoryRegion memory;
+} RX62NCPGState;
+
+typedef struct RX62NCPGClass {
+    SysBusDeviceClass parent;
+} RX62NCPGClass;
+
+#define OSTDCR_KEY 0xac
+
+#endif
diff --git a/include/hw/rx/rx62n.h b/include/hw/rx/rx62n.h
index 32e460bbad..e0ca1cfc33 100644
--- a/include/hw/rx/rx62n.h
+++ b/include/hw/rx/rx62n.h
@@ -29,6 +29,7 @@ 
 #include "hw/timer/renesas_tmr.h"
 #include "hw/timer/renesas_cmt.h"
 #include "hw/char/renesas_sci.h"
+#include "hw/rx/rx62n-cpg.h"
 #include "qemu/units.h"
 
 #define TYPE_RX62N_MCU "rx62n-mcu"
@@ -70,9 +71,9 @@  typedef struct RX62NState {
     RTMRState tmr[RX62N_NR_TMR];
     RCMTState cmt[RX62N_NR_CMT];
     RSCIState sci[RX62N_NR_SCI];
+    RX62NCPGState cpg;
 
     MemoryRegion *sysmem;
-    bool kernel;
 
     MemoryRegion iram;
     MemoryRegion iomem1;
@@ -84,8 +85,6 @@  typedef struct RX62NState {
 
     /* Input Clock (XTAL) frequency */
     uint32_t xtal_freq_hz;
-    /* Peripheral Module Clock frequency */
-    uint32_t pclk_freq_hz;
 } RX62NState;
 
 #endif
diff --git a/hw/rx/rx62n-cpg.c b/hw/rx/rx62n-cpg.c
new file mode 100644
index 0000000000..9d70004302
--- /dev/null
+++ b/hw/rx/rx62n-cpg.c
@@ -0,0 +1,344 @@ 
+/*
+ * RX62N Clock Generation Circuit
+ *
+ * Datasheet: RX62N Group, RX621 Group User's Manual: Hardware
+ * (Rev.1.40 R01UH0033EJ0140)
+ *
+ * Copyright (c) 2020 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 "qapi/error.h"
+#include "qemu/log.h"
+#include "hw/hw.h"
+#include "hw/rx/rx62n-cpg.h"
+#include "hw/sysbus.h"
+#include "hw/qdev-properties.h"
+#include "hw/registerfields.h"
+#include "hw/qdev-properties.h"
+#include "hw/clock.h"
+#include "migration/vmstate.h"
+
+#define RX62N_XTAL_MIN_HZ  (8 * 1000 * 1000)
+#define RX62N_XTAL_MAX_HZ (14 * 1000 * 1000)
+
+REG32(MSTPCRA, 0)
+REG32(MSTPCRB, 4)
+REG32(MSTPCRC, 8)
+REG32(SCKCR, 16)
+  FIELD(SCKCR, PCK,  8, 3)
+  FIELD(SCKCR, BCK, 16, 3)
+  FIELD(SCKCR, PSTOP, 22, 2)
+  FIELD(SCKCR, ICK, 24, 3)
+REG8(BCKCR, 32)
+  FIELD(BCKCR, BCLKDIV, 0, 1)
+REG16(OSTDCR, 48)
+  FIELD(OSTDCR, OSTDF, 6, 1)
+  FIELD(OSTDCR, OSTDE, 7, 1)
+
+static const int access_size[] = {4, 4, 1, 2};
+
+typedef struct {
+    const char *name;
+    int devnum;
+    int reg;
+    int offset;
+    int parentck;
+} dev_clock_t;
+
+enum {
+    parent_ick, parent_bck, parent_pck,
+};
+
+static const dev_clock_t dev_clock_list[] = {
+    { .name = "pck_tmr8-1",
+      .devnum = CK_TMR8_1, .reg = 0, .offset = 4, .parentck = parent_pck, },
+    { .name = "pck_tmr8-0",
+      .devnum = CK_TMR8_0, .reg = 0, .offset = 5, .parentck = parent_pck, },
+    { .name = "pck_mtu-1",
+      .devnum = CK_MTU_1, .reg = 0, .offset = 8, .parentck = parent_pck, },
+    { .name = "pck_mtu-0",
+      .devnum = CK_MTU_0, .reg = 0, .offset = 9, .parentck = parent_pck, },
+    { .name = "pck_cmt-1",
+      .devnum = CK_CMT_1, .reg = 0, .offset = 14, .parentck = parent_pck, },
+    { .name = "pck_cmt-0",
+      .devnum = CK_CMT_0, .reg = 0, .offset = 15, .parentck = parent_pck, },
+    { .name = "ick_edmac",
+      .devnum = CK_EDMAC, .reg = 1, .offset = 15, .parentck = parent_ick, },
+    { .name = "pck_sci-6",
+      .devnum = CK_SCI6, .reg = 1, .offset = 25, .parentck = parent_pck, },
+    { .name = "pck_sci-5",
+      .devnum = CK_SCI5, .reg = 1, .offset = 26, .parentck = parent_pck, },
+    { .name = "pck_sci-3",
+      .devnum = CK_SCI3, .reg = 1, .offset = 28, .parentck = parent_pck, },
+    { .name = "pck_sci-2",
+      .devnum = CK_SCI2, .reg = 1, .offset = 29, .parentck = parent_pck, },
+    { .name = "pck_sci-1",
+      .devnum = CK_SCI1, .reg = 1, .offset = 30, .parentck = parent_pck, },
+    { .name = "pck_sci-0",
+      .devnum = CK_SCI0, .reg = 1, .offset = 31, .parentck = parent_pck, },
+    { },
+};
+
+static void set_clock_in(RX62NCPGState *cpg, const dev_clock_t *ck)
+{
+    Clock *out;
+    uint64_t period;
+
+    out = qdev_get_clock_out(DEVICE(cpg), ck->name);
+    g_assert(out);
+    period = 0;
+    if (extract32(cpg->mstpcr[ck->reg], ck->offset, 1) == 0) {
+        switch (ck->parentck) {
+        case parent_ick:
+            period = clock_get(cpg->clk_ick);
+            break;
+        case parent_pck:
+            period = clock_get(cpg->clk_pck);
+            break;
+        }
+    }
+    if (clock_get(out) != period) {
+        clock_update(out, period);
+    }
+}
+
+#define update_ck(ckname)                                             \
+    if (cpg->ckname != ckname) {                                      \
+        cpg->ckname = ckname;                                         \
+        ckname =  8 / (1 << ckname);                                  \
+        clock_update_hz(cpg->clk_ ## ckname,                          \
+                        cpg->xtal_freq_hz * ckname);                  \
+    }
+
+#define validate_setting(ckname)                                 \
+    if (ick > ckname) {                                         \
+        qemu_log_mask(LOG_GUEST_ERROR,                           \
+                      "rx62n-cpg: Invalid " #ckname " setting."   \
+                      " (ick=%d " #ckname "=%d)\n", ick, ckname); \
+        cpg->ckname = ckname = ick;                              \
+    }
+
+static void update_divrate(RX62NCPGState *cpg)
+{
+    int ick = FIELD_EX32(cpg->sckcr, SCKCR, ICK);
+    int bck = FIELD_EX32(cpg->sckcr, SCKCR, BCK);
+    int pck = FIELD_EX32(cpg->sckcr, SCKCR, PCK);
+    const dev_clock_t *p = dev_clock_list;
+    validate_setting(pck);
+    validate_setting(bck);
+    update_ck(ick);
+    update_ck(bck);
+    update_ck(pck);
+    while (p->name) {
+        set_clock_in(cpg, p);
+        p++;
+    }
+}
+
+static const dev_clock_t *find_clock_list(int crno, int bit)
+{
+    const dev_clock_t *ret = dev_clock_list;
+    while (ret->name) {
+        if (ret->reg == crno && ret->offset == bit) {
+            return ret;
+        }
+        ret++;
+    }
+    return NULL;
+}
+
+static void update_mstpcr(RX62NCPGState *cpg, int crno, uint32_t diff)
+{
+    int bit = 0;
+    const dev_clock_t *p;
+
+    while (diff) {
+        if (diff & 1) {
+            p = find_clock_list(crno, bit);
+            if (p) {
+                set_clock_in(cpg, p);
+            } else {
+                qemu_log_mask(LOG_UNIMP, "rx62n-cpg: MSTPCR%c "
+                              " bit %d is not implement.\n", 'A' + crno, bit);
+            }
+        }
+        bit++;
+        diff >>= 1;
+    }
+}
+
+static uint64_t cpg_read(void *opaque, hwaddr addr, unsigned size)
+{
+    RX62NCPGState *cpg = RX62NCPG(opaque);
+
+    if (access_size[addr >> 4] != size) {
+        qemu_log_mask(LOG_GUEST_ERROR, "rx62n-cpg: Register 0x%"
+                      HWADDR_PRIX " Invalid access size.\n", addr);
+        return UINT64_MAX;
+    }
+    switch (addr) {
+    case A_MSTPCRA:
+        return cpg->mstpcr[0] | 0x473530cf;
+    case A_MSTPCRB:
+        return cpg->mstpcr[1] | 0x09407ffe;
+    case A_MSTPCRC:
+        return (cpg->mstpcr[2] | 0xffff0000) & 0xffff0003;
+    case A_SCKCR:
+        return cpg->sckcr & 0x0fcf0f00;
+    case A_BCKCR:
+        return cpg->bckcr & 0x01;
+    case A_OSTDCR:
+        /* Main OSC always good */
+        return cpg->ostdcr & 0x0080;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "rx62n-cpg: Register 0x%"
+                      HWADDR_PRIX " Invalid address.\n", addr);
+        return UINT64_MAX;
+    }
+}
+
+static void cpg_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+    RX62NCPGState *cpg = RX62NCPG(opaque);
+    uint32_t old_mstpcr;
+    int cr_no;
+    if (access_size[addr >> 4] != size) {
+        qemu_log_mask(LOG_GUEST_ERROR, "rx62n-cpg: Register 0x%"
+                      HWADDR_PRIX " Invalid access size.\n", addr);
+        return;
+    }
+    switch (addr) {
+    case A_MSTPCRA:
+    case A_MSTPCRB:
+    case A_MSTPCRC:
+        cr_no = (addr & 0x0f) >> 2;
+        old_mstpcr = cpg->mstpcr[cr_no];
+        old_mstpcr ^= val;
+        cpg->mstpcr[cr_no] = val;
+        update_mstpcr(cpg, cr_no, old_mstpcr);
+        break;
+    case A_SCKCR:
+        cpg->sckcr = val;
+        update_divrate(cpg);
+        break;
+    case A_BCKCR:
+        cpg->bckcr = val;
+        break;
+    case A_OSTDCR:
+        if (extract16(val, 8, 8) == OSTDCR_KEY) {
+            cpg->ostdcr = val;
+        } else {
+            qemu_log_mask(LOG_GUEST_ERROR, "rx62n-cpg: Register 0x%"
+                          HWADDR_PRIX " Invalid key value.\n", addr);
+        }
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "rx62n-cpg: Register 0x%"
+                      HWADDR_PRIX " Invalid address.\n", addr);
+    }
+}
+
+static const MemoryRegionOps cpg_ops = {
+    .write = cpg_write,
+    .read  = cpg_read,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .impl = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+    },
+};
+
+static const ClockPortInitArray rx62n_cpg_clocks = {
+    QDEV_CLOCK_OUT(RX62NCPGState, clk_ick),
+    QDEV_CLOCK_OUT(RX62NCPGState, clk_bck),
+    QDEV_CLOCK_OUT(RX62NCPGState, clk_pck),
+    QDEV_CLOCK_END
+};
+
+static void cpg_realize(DeviceState *dev, Error **errp)
+{
+    RX62NCPGState *cpg = RX62NCPG(dev);
+    const dev_clock_t *p = dev_clock_list;
+
+    if (cpg->xtal_freq_hz == 0) {
+        error_setg(errp, "\"xtal-frequency-hz\" property must be provided.");
+        return;
+    }
+    /* XTAL range: 8-14 MHz */
+    if (cpg->xtal_freq_hz < RX62N_XTAL_MIN_HZ ||
+        cpg->xtal_freq_hz > RX62N_XTAL_MAX_HZ) {
+        error_setg(errp, "\"xtal-frequency-hz\" property in incorrect range.");
+        return;
+    }
+
+    cpg->sckcr = FIELD_DP32(cpg->sckcr, SCKCR, ICK, 2);
+    cpg->sckcr = FIELD_DP32(cpg->sckcr, SCKCR, BCK, 2);
+    cpg->sckcr = FIELD_DP32(cpg->sckcr, SCKCR, PCK, 2);
+    cpg->ostdcr = FIELD_DP8(cpg->ostdcr, OSTDCR, OSTDE, 1);
+    cpg->mstpcr[0] = 0x47ffffff;
+    cpg->mstpcr[1] = 0xffffffff;
+    cpg->mstpcr[2] = 0xffff0000;
+
+    /* set initial state */
+    while (p->name) {
+        set_clock_in(cpg, p);
+        p++;
+    }
+    update_divrate(cpg);
+}
+
+static void rx62n_cpg_init(Object *obj)
+{
+    RX62NCPGState *cpg = RX62NCPG(obj);
+    const dev_clock_t *p = dev_clock_list;
+    qdev_init_clocks(DEVICE(obj), rx62n_cpg_clocks);
+    /* connect parent clock */
+    while (p->name) {
+        cpg->dev_clocks[p->devnum] = qdev_init_clock_out(DEVICE(obj),
+                                                         p->name);
+        p++;
+    }
+
+    memory_region_init_io(&cpg->memory, OBJECT(cpg), &cpg_ops,
+                          cpg, "rx62n-cpg", 0x40);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &cpg->memory);
+}
+
+static Property rx62n_cpg_properties[] = {
+    DEFINE_PROP_UINT32("xtal-frequency-hz", RX62NCPGState, xtal_freq_hz, 0),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void rx62n_cpg_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = cpg_realize;
+    device_class_set_props(dc, rx62n_cpg_properties);
+}
+
+static const TypeInfo rx62n_cpg_info[] = {
+    {
+        .name       = TYPE_RX62N_CPG,
+        .parent     = TYPE_SYS_BUS_DEVICE,
+        .instance_size = sizeof(RX62NCPGState),
+        .instance_init = rx62n_cpg_init,
+        .class_init = rx62n_cpg_class_init,
+        .class_size = sizeof(RX62NCPGClass),
+    },
+};
+
+DEFINE_TYPES(rx62n_cpg_info)
diff --git a/hw/rx/rx62n.c b/hw/rx/rx62n.c
index 4b5c3c1079..ec63fa5db1 100644
--- a/hw/rx/rx62n.c
+++ b/hw/rx/rx62n.c
@@ -47,6 +47,7 @@ 
 #define RX62N_TMR_BASE  0x00088200
 #define RX62N_CMT_BASE  0x00088000
 #define RX62N_SCI_BASE  0x00088240
+#define RX62N_CPG_BASE  0x00080010
 
 /*
  * RX62N Peripheral IRQ
@@ -56,10 +57,6 @@ 
 #define RX62N_CMT_IRQ   28
 #define RX62N_SCI_IRQ   214
 
-#define RX62N_XTAL_MIN_HZ  (8 * 1000 * 1000)
-#define RX62N_XTAL_MAX_HZ (14 * 1000 * 1000)
-#define RX62N_PCLK_MAX_HZ (50 * 1000 * 1000)
-
 /*
  * IRQ -> IPR mapping table
  * 0x00 - 0x91: IPR no (IPR00 to IPR91)
@@ -149,36 +146,45 @@  static void register_tmr(RX62NState *s, int unit)
 {
     SysBusDevice *tmr;
     int i, irqbase;
+    char ckname[16];
 
     object_initialize_child(OBJECT(s), "tmr[*]",
                             &s->tmr[unit], TYPE_RENESAS_TMR);
     tmr = SYS_BUS_DEVICE(&s->tmr[unit]);
-    qdev_prop_set_uint64(DEVICE(tmr), "input-freq", s->pclk_freq_hz);
-    sysbus_realize(tmr, &error_abort);
 
     irqbase = RX62N_TMR_IRQ + TMR_NR_IRQ * unit;
     for (i = 0; i < TMR_NR_IRQ; i++) {
         sysbus_connect_irq(tmr, i, s->irq[irqbase + i]);
     }
     sysbus_mmio_map(tmr, 0, RX62N_TMR_BASE + unit * 0x10);
+
+    qdev_prop_set_uint32(DEVICE(tmr), "unit", unit);
+    sysbus_realize(tmr, &error_abort);
+    snprintf(ckname, sizeof(ckname), "pck_tmr8-%d", unit);
+    qdev_connect_clock_in(DEVICE(tmr), "pck",
+                          qdev_get_clock_out(DEVICE(&s->cpg), ckname));
 }
 
 static void register_cmt(RX62NState *s, int unit)
 {
     SysBusDevice *cmt;
     int i, irqbase;
+    char ckname[16];
 
     object_initialize_child(OBJECT(s), "cmt[*]",
                             &s->cmt[unit], TYPE_RENESAS_CMT);
     cmt = SYS_BUS_DEVICE(&s->cmt[unit]);
-    qdev_prop_set_uint64(DEVICE(cmt), "input-freq", s->pclk_freq_hz);
-    sysbus_realize(cmt, &error_abort);
+    qdev_prop_set_uint32(DEVICE(cmt), "unit", unit);
 
     irqbase = RX62N_CMT_IRQ + CMT_NR_IRQ * unit;
     for (i = 0; i < CMT_NR_IRQ; i++) {
         sysbus_connect_irq(cmt, i, s->irq[irqbase + i]);
     }
     sysbus_mmio_map(cmt, 0, RX62N_CMT_BASE + unit * 0x10);
+    sysbus_realize(cmt, &error_abort);
+    snprintf(ckname, sizeof(ckname), "pck_cmt-%d", unit);
+    qdev_connect_clock_in(DEVICE(cmt), "pck",
+                          qdev_get_clock_out(DEVICE(&s->cpg), ckname));
 }
 
 static void register_sci(RX62NState *s, int unit)
@@ -190,7 +196,6 @@  static void register_sci(RX62NState *s, int unit)
                             &s->sci[unit], TYPE_RENESAS_SCI);
     sci = SYS_BUS_DEVICE(&s->sci[unit]);
     qdev_prop_set_chr(DEVICE(sci), "chardev", serial_hd(unit));
-    qdev_prop_set_uint64(DEVICE(sci), "input-freq", s->pclk_freq_hz);
     sysbus_realize(sci, &error_abort);
 
     irqbase = RX62N_SCI_IRQ + SCI_NR_IRQ * unit;
@@ -200,26 +205,23 @@  static void register_sci(RX62NState *s, int unit)
     sysbus_mmio_map(sci, 0, RX62N_SCI_BASE + unit * 0x08);
 }
 
+static void register_cpg(RX62NState *s)
+{
+    SysBusDevice *cpg;
+
+    object_initialize_child(OBJECT(s), "rx62n-cpg", &s->cpg,
+                            TYPE_RX62N_CPG);
+    cpg = SYS_BUS_DEVICE(&s->cpg);
+    qdev_prop_set_uint64(DEVICE(cpg), "xtal-frequency-hz", s->xtal_freq_hz);
+
+    sysbus_mmio_map(cpg, 0, RX62N_CPG_BASE);
+}
+
 static void rx62n_realize(DeviceState *dev, Error **errp)
 {
     RX62NState *s = RX62N_MCU(dev);
     RX62NClass *rxc = RX62N_MCU_GET_CLASS(dev);
 
-    if (s->xtal_freq_hz == 0) {
-        error_setg(errp, "\"xtal-frequency-hz\" property must be provided.");
-        return;
-    }
-    /* XTAL range: 8-14 MHz */
-    if (s->xtal_freq_hz < RX62N_XTAL_MIN_HZ
-            || s->xtal_freq_hz > RX62N_XTAL_MAX_HZ) {
-        error_setg(errp, "\"xtal-frequency-hz\" property in incorrect range.");
-        return;
-    }
-    /* Use a 4x fixed multiplier */
-    s->pclk_freq_hz = 4 * s->xtal_freq_hz;
-    /* PCLK range: 8-50 MHz */
-    assert(s->pclk_freq_hz <= RX62N_PCLK_MAX_HZ);
-
     memory_region_init_ram(&s->iram, OBJECT(dev), "iram",
                            rxc->ram_size, &error_abort);
     memory_region_add_subregion(s->sysmem, RX62N_IRAM_BASE, &s->iram);
@@ -236,11 +238,13 @@  static void rx62n_realize(DeviceState *dev, Error **errp)
 
     register_icu(s);
     s->cpu.env.ack = qdev_get_gpio_in_named(DEVICE(&s->icu), "ack", 0);
+    register_cpg(s);
     register_tmr(s, 0);
     register_tmr(s, 1);
     register_cmt(s, 0);
     register_cmt(s, 1);
     register_sci(s, 0);
+    sysbus_realize(SYS_BUS_DEVICE(&s->cpg), &error_abort);
 }
 
 static Property rx62n_properties[] = {
diff --git a/hw/rx/meson.build b/hw/rx/meson.build
index e73850f303..3a81d85a53 100644
--- a/hw/rx/meson.build
+++ b/hw/rx/meson.build
@@ -1,6 +1,6 @@ 
 rx_ss = ss.source_set()
 rx_ss.add(files('loader.c'))
 rx_ss.add(when: 'CONFIG_RX_GDBSIM', if_true: files('rx-gdbsim.c'))
-rx_ss.add(when: 'CONFIG_RX62N_MCU', if_true: files('rx62n.c'))
+rx_ss.add(when: 'CONFIG_RX62N_MCU', if_true: files('rx62n.c', 'rx62n-cpg.c'))
 
 hw_arch += {'rx': rx_ss}