Re: [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model

Chao Liu <[email protected]> Mon, 3 Aug 2026 10:24:52 +0800
Newsgroups org.nongnu.qemu-riscv,org.nongnu.qemu-devel
Message-ID <[email protected]>
On Wed, Jul 29, 2026 at 10:21:34AM +0800, Anirudh Srinivasan wrote:
> This device present in Tenstorrent Atlantis has registers that control
> clocks, reset and other misc functionality in the SoC. This commit adds
> models for the RCPU, HSIO, PCIE, MM and 2 DDR PRCMs.
> 
> Co-developed-by: Portia Stephens <[email protected]>
> Signed-off-by: Portia Stephens <[email protected]>
> Signed-off-by: Anirudh Srinivasan <[email protected]>
> ---
>  MAINTAINERS                        |   2 +
>  hw/misc/meson.build                |   1 +
>  hw/misc/trace-events               |   4 +
>  hw/misc/tt_atlantis_prcm.c         | 436 +++++++++++++++++++++++++++++++++++++
>  include/hw/misc/tt_atlantis_prcm.h |  51 +++++
>  5 files changed, 494 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e25df9493c..49c1a5ad76 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1802,7 +1802,9 @@ R: Portia Stephens <[email protected]>
>  L: [email protected]
>  S: Supported
>  F: docs/system/riscv/tt_*.rst
> +F: hw/misc/tt_*.c
>  F: hw/riscv/tt_*.c
> +F: include/hw/misc/tt_*.h
>  F: include/hw/riscv/tt_*.h
>  F: tests/functional/riscv64/test_tt_*.py
>  
> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> index 23265f6035..26627013f3 100644
> --- a/hw/misc/meson.build
> +++ b/hw/misc/meson.build
> @@ -36,6 +36,7 @@ system_ss.add(when: 'CONFIG_SIFIVE_E_PRCI', if_true: files('sifive_e_prci.c'))
>  system_ss.add(when: 'CONFIG_SIFIVE_E_AON', if_true: files('sifive_e_aon.c'))
>  system_ss.add(when: 'CONFIG_SIFIVE_U_OTP', if_true: files('sifive_u_otp.c'))
>  system_ss.add(when: 'CONFIG_SIFIVE_U_PRCI', if_true: files('sifive_u_prci.c'))
> +system_ss.add(when: 'CONFIG_TENSTORRENT', if_true: files('tt_atlantis_prcm.c'))
>  
>  subdir('macio')
>  
> diff --git a/hw/misc/trace-events b/hw/misc/trace-events
> index c9a868b3ef..9359a4e66d 100644
> --- a/hw/misc/trace-events
> +++ b/hw/misc/trace-events
> @@ -442,3 +442,7 @@ iommu_testdev_dma_read(uint64_t gva, uint32_t len) "gva=0x%" PRIx64 " len=%u"
>  iommu_testdev_dma_verify(uint32_t expected, uint32_t actual) "expected=0x%x actual=0x%x"
>  iommu_testdev_dma_result(uint32_t result) "DMA completed result=0x%x"
>  iommu_testdev_dma_armed(bool armed) "armed=%d"
> +
> +# tt_atlantis_prcm.c
> +tt_atlantis_prcm_read(uint32_t domain, uint64_t offset, uint64_t data) "domain 0x%02x offset 0x%" PRIx64 " data 0x%" PRIx64
> +tt_atlantis_prcm_write(uint32_t domain, uint64_t offset, uint64_t data) "domain 0x%02x offset 0x%" PRIx64 " data 0x%" PRIx64
> diff --git a/hw/misc/tt_atlantis_prcm.c b/hw/misc/tt_atlantis_prcm.c
> new file mode 100644
> index 0000000000..d341e1a8c3
> --- /dev/null
> +++ b/hw/misc/tt_atlantis_prcm.c
> @@ -0,0 +1,436 @@
> +/*
> + * PRCM (Clock and Reset Controller) in Tenstorrent Atlantis SoC
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright 2026 Tenstorrent
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/bitops.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/misc/tt_atlantis_prcm.h"
> +#include "hw/core/qdev-properties.h"
> +#include "hw/core/registerfields.h"
> +#include "hw/core/irq.h"
> +#include "trace.h"
> +
> +#define TO_REG(offset) ((offset) >> 2)
> +
> +#define PLL_LOCK_BIT        31
> +#define PLL_FBDIV_LSB       14
> +#define PLL_POSTDIV2_LSB    11
> +#define PLL_POSTDIV1_LSB    8
> +#define PLL_REFDIV_LSB      2
> +#define PLL_BYPASS_BIT      1
> +
> +#define PLL_RESET_VAL(BYPASS, REFDIV, POSTDIV1, POSTDIV2, FBDIV, LOCK)         \
> +  ((LOCK << PLL_LOCK_BIT) | (FBDIV << PLL_FBDIV_LSB) |                         \
> +   (POSTDIV2 << PLL_POSTDIV2_LSB) | (POSTDIV1 << PLL_POSTDIV1_LSB) |           \
> +   (REFDIV << PLL_REFDIV_LSB) | (BYPASS << PLL_BYPASS_BIT))
> +
> +#define RCPU_PLL_RCPU_CFG   TO_REG(0)
> +#define RCPU_PLL_NOCC_CFG   TO_REG(0x4)
> +#define RCPU_NOCC_CLK_CFG   TO_REG(0x8)
> +#define RCPU_RCPU_DIV_CFG   TO_REG(0xc)
> +#define RCPU_RCPU_BLK_CG    TO_REG(0x14)
> +#define RCPU_LSIO_BLK_CG    TO_REG(0x18)
> +#define RCPU_RCPU_BLK_RST   TO_REG(0x1c)
> +#define RCPU_LSIO_BLK_RST   TO_REG(0x20)
> +#define RCPU_PLL_RCPU_EN    TO_REG(0x11c)
> +#define RCPU_PLL_NOCC_EN    TO_REG(0x120)
> +#define RCPU_PLL_MM_EN      TO_REG(0x0124)
> +#define RCPU_PLL1_MM_EN     TO_REG(0x0128)
> +#define RCPU_PLL_HSIO_EN    TO_REG(0x12c)
> +#define RCPU_PLL_PCIE_EN    TO_REG(0x138)
> +#define RCPU_PLL_I2S_EN     TO_REG(0x13C)
> +#define RCPU_PLL_MM_CFG     TO_REG(0x19c)
> +#define RCPU_PLL_HSIO_CFG   TO_REG(0x1a0)
> +#define RCPU_PLL_PCIE_CFG   TO_REG(0x1ac)
> +#define RCPU_BOOT_MODE_CFG  TO_REG(0x1b0)
> +#define RCPU_PLL_I2S_CFG    TO_REG(0x1f4)
> +#define RCPU_I2S_DIV_CFG    TO_REG(0x1f8)
> +#define RCPU_BUS_CFG        TO_REG(0x1fc)
> +#define RCPU_PLL1_MM_CFG    TO_REG(0x2a4)
> +
> +#define HSIO_HSIO_DIV_CFG       TO_REG(0x0)
> +#define HSIO_HSIO_DIV_CFG1      TO_REG(0x4)
> +#define HSIO_HSIO_BLK_CG        TO_REG(0x8)
> +#define HSIO_HSIO_BLK_RST       TO_REG(0xc)
> +#define HSIO_HSIO_GMAC_DIV_CFG  TO_REG(0x10)
> +
> +#define PCIE_SUBS_RST_CTL   TO_REG(0x0)
> +#define PCIE_SUBS_CG_CTL    TO_REG(0x4)
> +#define PCIE_SUBS_CK_CTL    TO_REG(0x8)
> +
> +#define MM_MM_CLK_GATE_CFG  TO_REG(0x8)
> +#define MM_MM_CLK_DIV_CFG   TO_REG(0xC)
> +#define MM_MM_CLK_SEL_CFG   TO_REG(0x10)
> +#define MM_MM_RSTN          TO_REG(0x14)
> +#define MM_MM_CLK_DIV_CFG1  TO_REG(0x130)
> +
> +#define DDRC_STAT           TO_REG(0x10014)
> +#define DDRC_DFISTAT        TO_REG(0x10514)
> +#define DDRC_SWSTAT         TO_REG(0x10c84)
> +
> +#define PLL_EN              BIT(0)
> +#define RCPU_PLL_BUS_CG_EN  BIT(7)
> +#define HSIO_PLL_BUS_CG_EN  BIT(3)
> +#define PCIE_PLL_BUS_CG_EN  BIT(1)
> +#define NOCC_PLL_BUS_CG_EN  BIT(0)
> +
> +#define RCPU_BOOT_MODE_UART      (0x0 << 4)
> +#define RCPU_BOOT_MODE_NOR       (0x1 << 4)
> +#define RCPU_BOOT_MODE_SD        (0x2 << 4)
> +
> +#define RCPU_DOMAIN_SIZE   0x10000
> +#define HSIO_DOMAIN_SIZE   0x510
> +#define PCIE_DOMAIN_SIZE   0x100
> +#define MM_DOMAIN_SIZE     0x1000
> +#define DDRC0_DOMAIN_SIZE  0x101000
> +#define DDRC1_DOMAIN_SIZE  0x101000
> +
> +static uint64_t tt_atlantis_prcm_read(void *opaque, hwaddr offset,
> +                                               unsigned size) {
> +    TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(opaque);
> +    TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_GET_CLASS(s);
> +    uint32_t data;
> +
> +    if (offset >= c->regs_size) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
> +                      __func__, offset);
> +        return 0;
> +    }
> +    data = s->regs[TO_REG(offset)];
> +
> +    trace_tt_atlantis_prcm_read(c->domain, offset, data);
> +    return data;
> +}
> +
> +static void tt_atlantis_prcm_write(void *opaque, hwaddr offset,
> +                                            uint64_t data, unsigned size) {
Consider aligning the wrapped lines with the first parameter.

> +    TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(opaque);
> +    TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_GET_CLASS(s);
> +
> +    trace_tt_atlantis_prcm_write(c->domain, offset, data);
> +
> +    if (offset >= c->regs_size) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
> +                      __func__, offset);
> +        return;
> +    }
> +
> +    switch (TO_REG(offset)) {
> +    default:
> +        s->regs[TO_REG(offset)] = data;
> +        break;
> +    }
> +}
> +
> +static void tt_atlantis_prcm_rcpu_write(void *opaque, hwaddr offset,
> +                                            uint64_t data, unsigned size) {
> +    TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(opaque);
> +
> +    tt_atlantis_prcm_write(opaque, offset, data, size);
> +
> +    bool pll_en_written = false;
> +    uint32_t pll_reg_offset;
> +    switch TO_REG(offset)
> +    {
> +        case RCPU_PLL_RCPU_EN:
> +            pll_en_written = true;
> +            pll_reg_offset = RCPU_PLL_RCPU_CFG;
> +            break;
This should follow the QEMU coding style:

    switch (TO_REG(offset)) {
    case RCPU_PLL_RCPU_EN:
        pll_en_written = true;
        pll_reg_offset = RCPU_PLL_RCPU_CFG;
        break;
    ...
    case RCPU_PLL_I2S_EN:
        pll_en_written = true;
        pll_reg_offset = RCPU_PLL_I2S_CFG;
        break;
    default:
      g_assert_not_reached();
    }

Thanks,
Chao
> +        case RCPU_PLL_NOCC_EN:
> +            pll_en_written = true;
> +            pll_reg_offset = RCPU_PLL_NOCC_CFG;
> +            break;
> +        case RCPU_PLL_HSIO_EN:
> +            pll_en_written = true;
> +            pll_reg_offset = RCPU_PLL_HSIO_CFG;
> +            break;
> +        case RCPU_PLL_PCIE_EN:
> +            pll_en_written = true;
> +            pll_reg_offset = RCPU_PLL_PCIE_CFG;
> +            break;
> +        case RCPU_PLL_MM_EN:
> +            pll_en_written = true;
> +            pll_reg_offset = RCPU_PLL_MM_CFG;
> +            break;
> +        case RCPU_PLL1_MM_EN:
> +            pll_en_written = true;
> +            pll_reg_offset = RCPU_PLL1_MM_CFG;
> +            break;
> +        case RCPU_PLL_I2S_EN:
> +            pll_en_written = true;
> +            pll_reg_offset = RCPU_PLL_I2S_CFG;
> +            break;
> +    }
> +    if (pll_en_written) {
> +        if (data & PLL_EN) {
> +            s->regs[pll_reg_offset] |= BIT(PLL_LOCK_BIT);
> +        } else {
> +            s->regs[pll_reg_offset] &= ~BIT(PLL_LOCK_BIT);
> +        }
> +    }
> +}
> +
> +static const MemoryRegionOps tt_atlantis_prcm_ops = {
> +    .read = tt_atlantis_prcm_read,
> +    .write = tt_atlantis_prcm_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid.min_access_size = 4,
> +    .valid.max_access_size = 4,
> +};
> +
> +static const MemoryRegionOps tt_atlantis_prcm_rcpu_ops = {
> +    .read = tt_atlantis_prcm_read,
> +    .write = tt_atlantis_prcm_rcpu_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid.min_access_size = 4,
> +    .valid.max_access_size = 4,
> +};
> +
> +static void tt_atlantis_prcm_realize(DeviceState *dev, Error **errp)
> +{
> +    TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(dev);
> +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> +    TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_GET_CLASS(s);
> +
> +    if (c->domain >= PRCM_DOMAIN_COUNT) {
> +        error_setg(errp,
> +                   "domain %d is larger than max number of possible domains %d",
> +                   c->domain, PRCM_DOMAIN_COUNT);
> +        return;
> +    }
> +    s->regs = g_new0(uint32_t, TO_REG(c->regs_size));
> +
> +    memory_region_init_io(&s->mmio, OBJECT(s), c->ops, s,
> +                          TYPE_TT_ATLANTIS_PRCM, c->regs_size);
> +    sysbus_init_mmio(sbd, &s->mmio);
> +}
> +
> +static void tt_atlantis_prcm_reset(DeviceState *dev)
> +{
> +    TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(dev);
> +    TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_GET_CLASS(s);
> +
> +    memset(s->regs, 0, c->regs_size);
> +}
> +
> +static void tt_atlantis_prcm_rcpu_reset(DeviceState *dev)
> +{
> +    TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(dev);
> +    tt_atlantis_prcm_reset(dev);
> +
> +    s->regs[RCPU_PLL_RCPU_CFG] = PLL_RESET_VAL(1, 0, 4, 0, 100, 1);
> +    s->regs[RCPU_PLL_NOCC_CFG] = PLL_RESET_VAL(0, 2, 2, 2, 267, 1);
> +    s->regs[RCPU_NOCC_CLK_CFG] = (0x25f31 << 1) | BIT(0);
> +    s->regs[RCPU_RCPU_DIV_CFG] = (0xb8c2 << 1);
> +    s->regs[RCPU_RCPU_BLK_CG] = 0;
> +    s->regs[RCPU_LSIO_BLK_CG] = 0;
> +    s->regs[RCPU_RCPU_BLK_RST] = 0xffffffff;
> +    s->regs[RCPU_LSIO_BLK_RST] = 0xffffffff;
> +    s->regs[RCPU_PLL_RCPU_EN] = PLL_EN;
> +    s->regs[RCPU_PLL_NOCC_EN] = PLL_EN;
> +    s->regs[RCPU_PLL_HSIO_EN] = 0;
> +    s->regs[RCPU_PLL_MM_EN] = 0;
> +    s->regs[RCPU_PLL1_MM_EN] = 0;
> +    s->regs[RCPU_PLL_I2S_EN] = 0;
> +    s->regs[RCPU_PLL_PCIE_EN] = 0;
> +    s->regs[RCPU_PLL_HSIO_CFG] = PLL_RESET_VAL(0, 1, 2, 1, 50, 0);
> +    s->regs[RCPU_PLL_PCIE_CFG] = PLL_RESET_VAL(0, 2, 3, 1, 250, 0);
> +    s->regs[RCPU_BOOT_MODE_CFG] = RCPU_BOOT_MODE_SD;
> +    s->regs[RCPU_PLL_MM_CFG] = PLL_RESET_VAL(0, 1, 2, 1, 50, 1);
> +    s->regs[RCPU_PLL1_MM_CFG] = PLL_RESET_VAL(0, 1, 2, 1, 50, 1);
> +    s->regs[RCPU_PLL_I2S_CFG] = PLL_RESET_VAL(0, 1, 6, 6, 33, 0);
> +    s->regs[RCPU_I2S_DIV_CFG] = 0xde69ad31;
> +    s->regs[RCPU_BUS_CFG] = NOCC_PLL_BUS_CG_EN | HSIO_PLL_BUS_CG_EN;
> +}
> +
> +static void tt_atlantis_prcm_ddrc_reset(DeviceState *dev)
> +{
> +    TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(dev);
> +    tt_atlantis_prcm_reset(dev);
> +
> +    s->regs[DDRC_STAT] = 0x1;
> +    s->regs[DDRC_DFISTAT] = 0x1;
> +    s->regs[DDRC_SWSTAT] = 0x1;
> +}
> +
> +static void tt_atlantis_prcm_hsio_reset(DeviceState *dev)
> +{
> +    TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(dev);
> +    tt_atlantis_prcm_reset(dev);
> +
> +    s->regs[HSIO_HSIO_DIV_CFG] = 0x20821731;
> +    s->regs[HSIO_HSIO_DIV_CFG1] = 0x8;
> +    s->regs[HSIO_HSIO_BLK_CG] = 0;
> +    s->regs[HSIO_HSIO_BLK_RST] = 0;
> +    s->regs[HSIO_HSIO_GMAC_DIV_CFG] = 0;
> +}
> +
> +static void tt_atlantis_prcm_pcie_reset(DeviceState *dev)
> +{
> +    TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(dev);
> +    tt_atlantis_prcm_reset(dev);
> +
> +    s->regs[PCIE_SUBS_RST_CTL] = 0;
> +    s->regs[PCIE_SUBS_CG_CTL] = 0;
> +    s->regs[PCIE_SUBS_CK_CTL] = 0x44908;
> +}
> +
> +static void tt_atlantis_prcm_mm_reset(DeviceState *dev)
> +{
> +    TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(dev);
> +    tt_atlantis_prcm_reset(dev);
> +
> +    s->regs[MM_MM_CLK_GATE_CFG] = 0;
> +    s->regs[MM_MM_CLK_DIV_CFG] = 0xddc8312;
> +    s->regs[MM_MM_CLK_SEL_CFG] = 0;
> +    s->regs[MM_MM_RSTN] = 0;
> +    s->regs[MM_MM_CLK_DIV_CFG1] = 0x333;
> +}
> +
> +static void tt_atlantis_prcm_unrealize(DeviceState *dev)
> +{
> +    TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(dev);
> +
> +    g_free(s->regs);
> +}
> +
> +static void tt_atlantis_prcm_class_init(ObjectClass *klass,
> +                                                 const void *data) {
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_CLASS(klass);
> +
> +    dc->realize = tt_atlantis_prcm_realize;
> +    dc->unrealize = tt_atlantis_prcm_unrealize;
> +    device_class_set_legacy_reset(dc, tt_atlantis_prcm_reset);
> +    dc->desc = "Tenstorrent Atlantis PRCM Controller";
> +    c->ops = &tt_atlantis_prcm_ops;
> +}
> +
> +static void tt_atlantis_prcm_rcpu_class_init(ObjectClass *klass,
> +                                                 const void *data) {
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_CLASS(klass);
> +
> +    dc->realize = tt_atlantis_prcm_realize;
> +    device_class_set_legacy_reset(dc, tt_atlantis_prcm_rcpu_reset);
> +    dc->desc = "Tenstorrent Atlantis RCPU PRCM Controller";
> +    c->domain = PRCM_DOMAIN_RCPU;
> +    c->regs_size = RCPU_DOMAIN_SIZE;
> +    c->ops = &tt_atlantis_prcm_rcpu_ops;
> +}
> +
> +static void tt_atlantis_prcm_hsio_class_init(ObjectClass *klass,
> +                                                 const void *data) {
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_CLASS(klass);
> +
> +    dc->realize = tt_atlantis_prcm_realize;
> +    device_class_set_legacy_reset(dc, tt_atlantis_prcm_hsio_reset);
> +    dc->desc = "Tenstorrent Atlantis HSIO PRCM Controller";
> +    c->domain = PRCM_DOMAIN_HSIO;
> +    c->regs_size = HSIO_DOMAIN_SIZE;
> +    c->ops = &tt_atlantis_prcm_ops;
> +}
> +
> +static void tt_atlantis_prcm_pcie_class_init(ObjectClass *klass,
> +                                                 const void *data) {
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_CLASS(klass);
> +
> +    dc->realize = tt_atlantis_prcm_realize;
> +    device_class_set_legacy_reset(dc, tt_atlantis_prcm_pcie_reset);
> +    dc->desc = "Tenstorrent Atlantis PCIE PRCM Controller";
> +    c->domain = PRCM_DOMAIN_PCIE;
> +    c->regs_size = PCIE_DOMAIN_SIZE;
> +    c->ops = &tt_atlantis_prcm_ops;
> +}
> +
> +static void tt_atlantis_prcm_mm_class_init(ObjectClass *klass,
> +                                                 const void *data) {
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_CLASS(klass);
> +
> +    dc->realize = tt_atlantis_prcm_realize;
> +    device_class_set_legacy_reset(dc, tt_atlantis_prcm_mm_reset);
> +    dc->desc = "Tenstorrent Atlantis MM PRCM Controller";
> +    c->domain = PRCM_DOMAIN_MM;
> +    c->regs_size = MM_DOMAIN_SIZE;
> +    c->ops = &tt_atlantis_prcm_ops;
> +}
> +
> +static void tt_atlantis_prcm_ddrc0_class_init(ObjectClass *klass,
> +                                                 const void *data) {
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_CLASS(klass);
> +
> +    dc->realize = tt_atlantis_prcm_realize;
> +    device_class_set_legacy_reset(dc, tt_atlantis_prcm_ddrc_reset);
> +    dc->desc = "Tenstorrent Atlantis DDR Memory Controller 0 PRCM";
> +    c->domain = PRCM_DOMAIN_DDRC0;
> +    c->regs_size = DDRC0_DOMAIN_SIZE;
> +    c->ops = &tt_atlantis_prcm_ops;
> +}
> +
> +static void tt_atlantis_prcm_ddrc1_class_init(ObjectClass *klass,
> +                                                 const void *data) {
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_CLASS(klass);
> +
> +    dc->realize = tt_atlantis_prcm_realize;
> +    device_class_set_legacy_reset(dc, tt_atlantis_prcm_ddrc_reset);
> +    dc->desc = "Tenstorrent Atlantis DDR Memory Controller 1 PRCM";
> +    c->domain = PRCM_DOMAIN_DDRC1;
> +    c->regs_size = DDRC1_DOMAIN_SIZE;
> +    c->ops = &tt_atlantis_prcm_ops;
> +}
> +
> +static const TypeInfo tt_atlantis_prcm_types[] = {
> +    {
> +        .name = TYPE_TT_ATLANTIS_PRCM,
> +        .parent = TYPE_SYS_BUS_DEVICE,
> +        .instance_size = sizeof(TTAtlantisPRCMState),
> +        .class_init = tt_atlantis_prcm_class_init,
> +        .class_size = sizeof(TTAtlantisPRCMClass),
> +        .abstract = true,
> +    },
> +    {
> +        .name = TYPE_TT_ATLANTIS_PRCM_RCPU,
> +        .parent = TYPE_TT_ATLANTIS_PRCM,
> +        .class_init = tt_atlantis_prcm_rcpu_class_init,
> +    },
> +    {
> +        .name = TYPE_TT_ATLANTIS_PRCM_HSIO,
> +        .parent = TYPE_TT_ATLANTIS_PRCM,
> +        .class_init = tt_atlantis_prcm_hsio_class_init,
> +    },
> +    {
> +        .name = TYPE_TT_ATLANTIS_PRCM_PCIE,
> +        .parent = TYPE_TT_ATLANTIS_PRCM,
> +        .class_init = tt_atlantis_prcm_pcie_class_init,
> +    },
> +    {
> +        .name = TYPE_TT_ATLANTIS_PRCM_MM,
> +        .parent = TYPE_TT_ATLANTIS_PRCM,
> +        .class_init = tt_atlantis_prcm_mm_class_init,
> +    },
> +    {
> +        .name = TYPE_TT_ATLANTIS_PRCM_DDRC0,
> +        .parent = TYPE_TT_ATLANTIS_PRCM,
> +        .class_init = tt_atlantis_prcm_ddrc0_class_init,
> +    },
> +    {
> +        .name = TYPE_TT_ATLANTIS_PRCM_DDRC1,
> +        .parent = TYPE_TT_ATLANTIS_PRCM,
> +        .class_init = tt_atlantis_prcm_ddrc1_class_init,
> +    },
> +};
> +
> +DEFINE_TYPES(tt_atlantis_prcm_types);
> diff --git a/include/hw/misc/tt_atlantis_prcm.h b/include/hw/misc/tt_atlantis_prcm.h
> new file mode 100644
> index 0000000000..acb8a9b38b
> --- /dev/null
> +++ b/include/hw/misc/tt_atlantis_prcm.h
> @@ -0,0 +1,51 @@
> +/*
> + * Clock, Reset, Pinctrl PRCM in Tenstorrent Atlantis SoC
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright 2026 Tenstorrent
> + */
> +
> +#ifndef TT_ATLANTIS_PRCM_H
> +#define TT_ATLANTIS_PRCM_H
> +
> +#include "hw/core/sysbus.h"
> +
> +#define TYPE_TT_ATLANTIS_PRCM "tt.atlantis.prcm"
> +#define TYPE_TT_ATLANTIS_PRCM_RCPU "tt.atlantis.prcm.rcpu"
> +#define TYPE_TT_ATLANTIS_PRCM_HSIO "tt.atlantis.prcm.hsio"
> +#define TYPE_TT_ATLANTIS_PRCM_PCIE "tt.atlantis.prcm.pcie"
> +#define TYPE_TT_ATLANTIS_PRCM_MM "tt.atlantis.prcm.mm"
> +#define TYPE_TT_ATLANTIS_PRCM_DDRC0 "tt.atlantis.prcm.ddrc0"
> +#define TYPE_TT_ATLANTIS_PRCM_DDRC1 "tt.atlantis.prcm.ddrc1"
> +OBJECT_DECLARE_TYPE(TTAtlantisPRCMState, TTAtlantisPRCMClass,
> +                           TT_ATLANTIS_PRCM);
> +
> +
> +enum {
> +    PRCM_DOMAIN_RCPU = 0,
> +    PRCM_DOMAIN_HSIO = 1,
> +    PRCM_DOMAIN_PCIE = 2,
> +    PRCM_DOMAIN_MM = 3,
> +    PRCM_DOMAIN_DDRC0 = 4,
> +    PRCM_DOMAIN_DDRC1 = 5,
> +    PRCM_DOMAIN_COUNT = 6
> +};
> +
> +struct TTAtlantisPRCMState {
> +    SysBusDevice parent;
> +
> +    MemoryRegion mmio;
> +
> +    uint32_t *regs;
> +};
> +
> +struct TTAtlantisPRCMClass {
> +    SysBusDeviceClass parent_class;
> +
> +    uint32_t domain;
> +    uint32_t regs_size;
> +    const MemoryRegionOps *ops;
> +};
> +
> +#endif
> 
> -- 
> 2.43.0
>