* [PATCH 0/2] Add Model for Tenstorrent Atlantis PRCM
@ 2026-07-29 15:21 Anirudh Srinivasan
2026-07-29 15:21 ` [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model Anirudh Srinivasan
2026-07-29 15:21 ` [PATCH 2/2] hw/riscv/tt-atlantis: Add PRCMs to tt-atlantis model Anirudh Srinivasan
0 siblings, 2 replies; 12+ messages in thread
From: Anirudh Srinivasan @ 2026-07-29 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Joel Stanley, Nicholas Piggin, Michael Ellerman, Portia Stephens,
qemu-riscv, Pierrick Bouvier, Palmer Dabbelt, Alistair Francis,
Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, Chao Liu,
Anirudh Srinivasan
This series adds a qemu model for the PRCM blocks present in Tenstorrent
Atlantis. This block controls clock, reset and other misc functionality
in the SoC. Linux support for the Clocks/Resets served by the RCPU PRCM
was added in [1]. This PRCM model has been tested with a linux DT that
enables the PRCM and sets the UART clk to the one provided by the PRCM.
This DT will be sent out shortly.
[1] https://lore.kernel.org/linux-riscv/20260306-atlantis-clocks-v8-0-6c9b14a4aa8e@oss.tenstorrent.com/
Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
---
Anirudh Srinivasan (2):
hw/misc: Add Tenstorrent Atlantis PRCM model
hw/riscv/tt-atlantis: Add PRCMs to tt-atlantis model
MAINTAINERS | 2 +
docs/system/riscv/tt_atlantis.rst | 1 +
hw/misc/meson.build | 1 +
hw/misc/trace-events | 4 +
hw/misc/tt_atlantis_prcm.c | 436 +++++++++++++++++++++++++++++++++++++
hw/riscv/tt_atlantis.c | 92 ++++++++
include/hw/misc/tt_atlantis_prcm.h | 51 +++++
include/hw/riscv/tt_atlantis.h | 19 ++
8 files changed, 606 insertions(+)
---
base-commit: 499039798cdad7d86b787fec0eaf1da4151c0f05
change-id: 20260727-atlantis_prcm-b642cfb37d57
Best regards,
--
Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model
2026-07-29 15:21 [PATCH 0/2] Add Model for Tenstorrent Atlantis PRCM Anirudh Srinivasan
@ 2026-07-29 15:21 ` Anirudh Srinivasan
2026-08-03 2:24 ` Chao Liu
` (2 more replies)
2026-07-29 15:21 ` [PATCH 2/2] hw/riscv/tt-atlantis: Add PRCMs to tt-atlantis model Anirudh Srinivasan
1 sibling, 3 replies; 12+ messages in thread
From: Anirudh Srinivasan @ 2026-07-29 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Joel Stanley, Nicholas Piggin, Michael Ellerman, Portia Stephens,
qemu-riscv, Pierrick Bouvier, Palmer Dabbelt, Alistair Francis,
Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, Chao Liu,
Anirudh Srinivasan
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 <portias@oss.tenstorrent.com>
Signed-off-by: Portia Stephens <portias@oss.tenstorrent.com>
Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
---
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 <portias@oss.tenstorrent.com>
L: qemu-riscv@nongnu.org
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) {
+ 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;
+ 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
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 2/2] hw/riscv/tt-atlantis: Add PRCMs to tt-atlantis model
2026-07-29 15:21 [PATCH 0/2] Add Model for Tenstorrent Atlantis PRCM Anirudh Srinivasan
2026-07-29 15:21 ` [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model Anirudh Srinivasan
@ 2026-07-29 15:21 ` Anirudh Srinivasan
2026-08-03 2:25 ` Chao Liu
2026-08-07 13:36 ` Daniel Henrique Barboza
1 sibling, 2 replies; 12+ messages in thread
From: Anirudh Srinivasan @ 2026-07-29 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Joel Stanley, Nicholas Piggin, Michael Ellerman, Portia Stephens,
qemu-riscv, Pierrick Bouvier, Palmer Dabbelt, Alistair Francis,
Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, Chao Liu,
Anirudh Srinivasan
Atlantis has 4 PRCMs that are visible from S mode by the Ascalon Core
(RCPU, HSIO, PCIe and MM). This commit adds these 4 PRCM blocks to the
tt-atlantis model, along with necessary device tree nodes for them.
Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
---
docs/system/riscv/tt_atlantis.rst | 1 +
hw/riscv/tt_atlantis.c | 92 +++++++++++++++++++++++++++++++++++++++
include/hw/riscv/tt_atlantis.h | 19 ++++++++
3 files changed, 112 insertions(+)
diff --git a/docs/system/riscv/tt_atlantis.rst b/docs/system/riscv/tt_atlantis.rst
index 1f2880d617..539c22354f 100644
--- a/docs/system/riscv/tt_atlantis.rst
+++ b/docs/system/riscv/tt_atlantis.rst
@@ -14,6 +14,7 @@ tt-atlantis QEMU model features
* 8-core Ascalon-X CPU Cluster
* RISC-V compliant Advanced Interrupt Architecture
* 16550A compatible UART
+* 4 PRCMs (Clock and Reset Controllers)
Known limitations
-----------------
diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
index d808bcc11c..17cc5318b3 100644
--- a/hw/riscv/tt_atlantis.c
+++ b/hw/riscv/tt_atlantis.c
@@ -26,6 +26,7 @@
#include "hw/char/serial-mm.h"
#include "hw/intc/riscv_aclint.h"
#include "hw/misc/unimp.h"
+#include "hw/misc/tt_atlantis_prcm.h"
#include "system/system.h"
#include "system/device_tree.h"
@@ -59,6 +60,10 @@ static const MemMapEntry tt_atlantis_memmap[] = {
[TT_ATL_I2C3] = { 0xd4070000, 0x10000 },
[TT_ATL_I2C4] = { 0xd4080000, 0x10000 },
[TT_ATL_UART1] = { 0xd4110000, 0x10000 },
+ [TT_ATL_PRCM_RCPU] = { 0xd0000000, 0x10000 },
+ [TT_ATL_PRCM_PCIE] = { 0xd8000000, 0x100 },
+ [TT_ATL_PRCM_MM] = { 0xdc000000, 0x1000 },
+ [TT_ATL_PRCM_HSIO] = { 0xe00c0000, 0x510 },
[TT_ATL_SAPLIC] = { 0xe8000000, 0x4000000 },
[TT_ATL_DDR_HI] = { 0x100000000, 0x1000000000 },
};
@@ -328,11 +333,38 @@ static void create_fdt_i2c_device(TTAtlantisState *s, int bus,
qemu_fdt_setprop_cell(fdt, name, "reg", addr);
}
+static char *create_fdt_prcm(void *fdt, const MemMapEntry *mem,
+ const char *prcm_name, uint32_t prcm_phandle)
+{
+ hwaddr base = mem->base;
+ hwaddr size = mem->size;
+ char *name = g_strdup_printf("/soc/prcm_%s@%" PRIx64,
+ prcm_name, mem->base);
+ g_autofree char *compatible =
+ g_strdup_printf("tenstorrent,atlantis-prcm-%s", prcm_name);
+
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", compatible);
+ qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, base, 2, size);
+ qemu_fdt_setprop_cell(fdt, name, "#address-cells", 1);
+ qemu_fdt_setprop_cell(fdt, name, "#size-cells", 0);
+ qemu_fdt_setprop_cell(fdt, name, "#clock-cells", 1);
+ qemu_fdt_setprop_cell(fdt, name, "#reset-cells", 1);
+ qemu_fdt_setprop_cell(fdt, name, "phandle", prcm_phandle);
+
+ return name;
+}
+
static void finalize_fdt(TTAtlantisState *s)
{
uint32_t aplic_s_phandle = next_phandle();
uint32_t imsic_s_phandle = next_phandle();
uint32_t periph_clk_phandle = next_phandle();
+ uint32_t osc_24m_phandle = next_phandle();
+ uint32_t prcm_rcpu_phandle = next_phandle();
+ uint32_t prcm_hsio_phandle = next_phandle();
+ uint32_t prcm_pcie_phandle = next_phandle();
+ uint32_t prcm_mm_phandle = next_phandle();
void *fdt = MACHINE(s)->fdt;
create_fdt_cpu(s, s->memmap, aplic_s_phandle, imsic_s_phandle);
@@ -348,6 +380,33 @@ static void finalize_fdt(TTAtlantisState *s)
aplic_s_phandle);
create_fdt_clk(fdt, "periph-clk", 100000000, periph_clk_phandle);
+ create_fdt_clk(fdt, "osc_24m", 24000000, osc_24m_phandle);
+
+ g_autofree char *rcpu_name = create_fdt_prcm(fdt,
+ &s->memmap[TT_ATL_PRCM_RCPU], "rcpu", prcm_rcpu_phandle);
+ qemu_fdt_setprop_cells(fdt, rcpu_name, "clocks", osc_24m_phandle);
+ qemu_fdt_setprop_cells(fdt, rcpu_name, "assigned-clocks",
+ prcm_rcpu_phandle, TT_ATL_CLK_RCPU_ROOT,
+ prcm_rcpu_phandle, TT_ATL_CLK_NOCC_CLK);
+ qemu_fdt_setprop_cells(fdt, rcpu_name, "assigned-clock-parents",
+ prcm_rcpu_phandle, TT_ATL_CLK_RCPU_PLL,
+ prcm_rcpu_phandle, TT_ATL_CLK_NOC_PLL);
+
+ g_autofree char *hsio_name = create_fdt_prcm(fdt,
+ &s->memmap[TT_ATL_PRCM_HSIO], "hsio", prcm_hsio_phandle);
+ qemu_fdt_setprop_cells(fdt, hsio_name, "clocks", osc_24m_phandle,
+ prcm_rcpu_phandle, TT_ATL_CLK_HSIO_PLL);
+
+ g_autofree char *pcie_name = create_fdt_prcm(fdt,
+ &s->memmap[TT_ATL_PRCM_PCIE], "pcie", prcm_pcie_phandle);
+ qemu_fdt_setprop_cells(fdt, pcie_name, "clocks", osc_24m_phandle,
+ prcm_rcpu_phandle, TT_ATL_CLK_PCIE_PLL);
+
+ g_autofree char *mm_name = create_fdt_prcm(fdt,
+ &s->memmap[TT_ATL_PRCM_MM], "mm", prcm_mm_phandle);
+ qemu_fdt_setprop_cells(fdt, mm_name, "clocks", osc_24m_phandle,
+ prcm_rcpu_phandle, TT_ATL_CLK_MM_PLL0,
+ prcm_rcpu_phandle, TT_ATL_CLK_MM_PLL1);
for (int i = 0; i < TT_ATL_NUM_I2C; i++) {
create_fdt_i2c(fdt,
@@ -545,6 +604,39 @@ static void tt_atlantis_machine_init(MachineState *machine)
serial_mm_init(system_memory, s->memmap[TT_ATL_UART1].base, 2,
qdev_get_gpio_in(s->irqchip, TT_ATL_UART1_IRQ),
115200, serial_hd(0), DEVICE_LITTLE_ENDIAN);
+
+ /* Add rcpu prcm block */
+ object_initialize_child(OBJECT(s), "prcm-rcpu", &s->prcm[0],
+ TYPE_TT_ATLANTIS_PRCM_RCPU);
+ sysbus_realize(SYS_BUS_DEVICE(&s->prcm[0]), &error_fatal);
+ memory_region_add_subregion(system_memory,
+ s->memmap[TT_ATL_PRCM_RCPU].base,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[0]), 0));
+
+ /* Add hsio prcm block */
+ object_initialize_child(OBJECT(s), "prcm-hsio", &s->prcm[1],
+ TYPE_TT_ATLANTIS_PRCM_HSIO);
+ sysbus_realize(SYS_BUS_DEVICE(&s->prcm[1]), &error_fatal);
+ memory_region_add_subregion(system_memory,
+ s->memmap[TT_ATL_PRCM_HSIO].base,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[1]), 0));
+
+ /* Add pcie prcm block */
+ object_initialize_child(OBJECT(s), "prcm-pcie", &s->prcm[2],
+ TYPE_TT_ATLANTIS_PRCM_PCIE);
+ sysbus_realize(SYS_BUS_DEVICE(&s->prcm[2]), &error_fatal);
+ memory_region_add_subregion(system_memory,
+ s->memmap[TT_ATL_PRCM_PCIE].base,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[2]), 0));
+
+ /* Add mm prcm block */
+ object_initialize_child(OBJECT(s), "prcm-mm", &s->prcm[3],
+ TYPE_TT_ATLANTIS_PRCM_MM);
+ sysbus_realize(SYS_BUS_DEVICE(&s->prcm[3]), &error_fatal);
+ memory_region_add_subregion(system_memory,
+ s->memmap[TT_ATL_PRCM_MM].base,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[3]), 0));
+
/*
* Atlantis contains a DesignWare uart while the QEMU machine
* uses the serial_mm model with the base ns16550 register set.
diff --git a/include/hw/riscv/tt_atlantis.h b/include/hw/riscv/tt_atlantis.h
index 7f7d4a5a59..20bcdfeb52 100644
--- a/include/hw/riscv/tt_atlantis.h
+++ b/include/hw/riscv/tt_atlantis.h
@@ -13,12 +13,14 @@
#include "hw/core/sysbus.h"
#include "hw/i2c/designware_i2c.h"
#include "hw/intc/riscv_imsic.h"
+#include "hw/misc/tt_atlantis_prcm.h"
#include "hw/riscv/riscv_hart.h"
#define TYPE_TT_ATLANTIS_MACHINE MACHINE_TYPE_NAME("tt-atlantis")
OBJECT_DECLARE_SIMPLE_TYPE(TTAtlantisState, TT_ATLANTIS_MACHINE)
#define TT_ATL_NUM_I2C 5
+#define TT_ATL_NUM_PRCM 4
struct TTAtlantisState {
/*< private >*/
@@ -31,6 +33,7 @@ struct TTAtlantisState {
RISCVHartArrayState soc;
DeviceState *irqchip;
DesignWareI2CState i2c[TT_ATL_NUM_I2C];
+ TTAtlantisPRCMState prcm[TT_ATL_NUM_PRCM];
int fdt_size;
};
@@ -59,6 +62,22 @@ enum {
TT_ATL_SAPLIC,
TT_ATL_SIMSIC,
TT_ATL_UART1,
+ TT_ATL_PRCM_RCPU,
+ TT_ATL_PRCM_HSIO,
+ TT_ATL_PRCM_PCIE,
+ TT_ATL_PRCM_MM,
+};
+
+/* RCPU PRCM Clock IDs */
+enum {
+ TT_ATL_CLK_RCPU_PLL = 0,
+ TT_ATL_CLK_RCPU_ROOT = 1,
+ TT_ATL_CLK_NOC_PLL = 25,
+ TT_ATL_CLK_NOCC_CLK = 26,
+ TT_ATL_CLK_HSIO_PLL = 54,
+ TT_ATL_CLK_PCIE_PLL = 55,
+ TT_ATL_CLK_MM_PLL0 = 56,
+ TT_ATL_CLK_MM_PLL1 = 57,
};
#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model
2026-07-29 15:21 ` [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model Anirudh Srinivasan
@ 2026-08-03 2:24 ` Chao Liu
2026-08-03 15:36 ` Anirudh Srinivasan
2026-08-07 13:01 ` Daniel Henrique Barboza
2026-08-07 13:38 ` Daniel Henrique Barboza
2 siblings, 1 reply; 12+ messages in thread
From: Chao Liu @ 2026-08-03 2:24 UTC (permalink / raw)
To: Anirudh Srinivasan
Cc: qemu-devel, Joel Stanley, Nicholas Piggin, Michael Ellerman,
Portia Stephens, qemu-riscv, Pierrick Bouvier, Palmer Dabbelt,
Alistair Francis, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei
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 <portias@oss.tenstorrent.com>
> Signed-off-by: Portia Stephens <portias@oss.tenstorrent.com>
> Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
> ---
> 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 <portias@oss.tenstorrent.com>
> L: qemu-riscv@nongnu.org
> 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
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/2] hw/riscv/tt-atlantis: Add PRCMs to tt-atlantis model
2026-07-29 15:21 ` [PATCH 2/2] hw/riscv/tt-atlantis: Add PRCMs to tt-atlantis model Anirudh Srinivasan
@ 2026-08-03 2:25 ` Chao Liu
2026-08-07 13:36 ` Daniel Henrique Barboza
1 sibling, 0 replies; 12+ messages in thread
From: Chao Liu @ 2026-08-03 2:25 UTC (permalink / raw)
To: Anirudh Srinivasan
Cc: qemu-devel, Joel Stanley, Nicholas Piggin, Michael Ellerman,
Portia Stephens, qemu-riscv, Pierrick Bouvier, Palmer Dabbelt,
Alistair Francis, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei
On Wed, Jul 29, 2026 at 10:21:35AM +0800, Anirudh Srinivasan wrote:
> Atlantis has 4 PRCMs that are visible from S mode by the Ascalon Core
> (RCPU, HSIO, PCIe and MM). This commit adds these 4 PRCM blocks to the
> tt-atlantis model, along with necessary device tree nodes for them.
>
> Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>
Thanks,
Chao
> ---
> docs/system/riscv/tt_atlantis.rst | 1 +
> hw/riscv/tt_atlantis.c | 92 +++++++++++++++++++++++++++++++++++++++
> include/hw/riscv/tt_atlantis.h | 19 ++++++++
> 3 files changed, 112 insertions(+)
>
> diff --git a/docs/system/riscv/tt_atlantis.rst b/docs/system/riscv/tt_atlantis.rst
> index 1f2880d617..539c22354f 100644
> --- a/docs/system/riscv/tt_atlantis.rst
> +++ b/docs/system/riscv/tt_atlantis.rst
> @@ -14,6 +14,7 @@ tt-atlantis QEMU model features
> * 8-core Ascalon-X CPU Cluster
> * RISC-V compliant Advanced Interrupt Architecture
> * 16550A compatible UART
> +* 4 PRCMs (Clock and Reset Controllers)
>
> Known limitations
> -----------------
> diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
> index d808bcc11c..17cc5318b3 100644
> --- a/hw/riscv/tt_atlantis.c
> +++ b/hw/riscv/tt_atlantis.c
> @@ -26,6 +26,7 @@
> #include "hw/char/serial-mm.h"
> #include "hw/intc/riscv_aclint.h"
> #include "hw/misc/unimp.h"
> +#include "hw/misc/tt_atlantis_prcm.h"
>
> #include "system/system.h"
> #include "system/device_tree.h"
> @@ -59,6 +60,10 @@ static const MemMapEntry tt_atlantis_memmap[] = {
> [TT_ATL_I2C3] = { 0xd4070000, 0x10000 },
> [TT_ATL_I2C4] = { 0xd4080000, 0x10000 },
> [TT_ATL_UART1] = { 0xd4110000, 0x10000 },
> + [TT_ATL_PRCM_RCPU] = { 0xd0000000, 0x10000 },
> + [TT_ATL_PRCM_PCIE] = { 0xd8000000, 0x100 },
> + [TT_ATL_PRCM_MM] = { 0xdc000000, 0x1000 },
> + [TT_ATL_PRCM_HSIO] = { 0xe00c0000, 0x510 },
> [TT_ATL_SAPLIC] = { 0xe8000000, 0x4000000 },
> [TT_ATL_DDR_HI] = { 0x100000000, 0x1000000000 },
> };
> @@ -328,11 +333,38 @@ static void create_fdt_i2c_device(TTAtlantisState *s, int bus,
> qemu_fdt_setprop_cell(fdt, name, "reg", addr);
> }
>
> +static char *create_fdt_prcm(void *fdt, const MemMapEntry *mem,
> + const char *prcm_name, uint32_t prcm_phandle)
> +{
> + hwaddr base = mem->base;
> + hwaddr size = mem->size;
> + char *name = g_strdup_printf("/soc/prcm_%s@%" PRIx64,
> + prcm_name, mem->base);
> + g_autofree char *compatible =
> + g_strdup_printf("tenstorrent,atlantis-prcm-%s", prcm_name);
> +
> + qemu_fdt_add_subnode(fdt, name);
> + qemu_fdt_setprop_string(fdt, name, "compatible", compatible);
> + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, base, 2, size);
> + qemu_fdt_setprop_cell(fdt, name, "#address-cells", 1);
> + qemu_fdt_setprop_cell(fdt, name, "#size-cells", 0);
> + qemu_fdt_setprop_cell(fdt, name, "#clock-cells", 1);
> + qemu_fdt_setprop_cell(fdt, name, "#reset-cells", 1);
> + qemu_fdt_setprop_cell(fdt, name, "phandle", prcm_phandle);
> +
> + return name;
> +}
> +
> static void finalize_fdt(TTAtlantisState *s)
> {
> uint32_t aplic_s_phandle = next_phandle();
> uint32_t imsic_s_phandle = next_phandle();
> uint32_t periph_clk_phandle = next_phandle();
> + uint32_t osc_24m_phandle = next_phandle();
> + uint32_t prcm_rcpu_phandle = next_phandle();
> + uint32_t prcm_hsio_phandle = next_phandle();
> + uint32_t prcm_pcie_phandle = next_phandle();
> + uint32_t prcm_mm_phandle = next_phandle();
> void *fdt = MACHINE(s)->fdt;
>
> create_fdt_cpu(s, s->memmap, aplic_s_phandle, imsic_s_phandle);
> @@ -348,6 +380,33 @@ static void finalize_fdt(TTAtlantisState *s)
> aplic_s_phandle);
>
> create_fdt_clk(fdt, "periph-clk", 100000000, periph_clk_phandle);
> + create_fdt_clk(fdt, "osc_24m", 24000000, osc_24m_phandle);
> +
> + g_autofree char *rcpu_name = create_fdt_prcm(fdt,
> + &s->memmap[TT_ATL_PRCM_RCPU], "rcpu", prcm_rcpu_phandle);
> + qemu_fdt_setprop_cells(fdt, rcpu_name, "clocks", osc_24m_phandle);
> + qemu_fdt_setprop_cells(fdt, rcpu_name, "assigned-clocks",
> + prcm_rcpu_phandle, TT_ATL_CLK_RCPU_ROOT,
> + prcm_rcpu_phandle, TT_ATL_CLK_NOCC_CLK);
> + qemu_fdt_setprop_cells(fdt, rcpu_name, "assigned-clock-parents",
> + prcm_rcpu_phandle, TT_ATL_CLK_RCPU_PLL,
> + prcm_rcpu_phandle, TT_ATL_CLK_NOC_PLL);
> +
> + g_autofree char *hsio_name = create_fdt_prcm(fdt,
> + &s->memmap[TT_ATL_PRCM_HSIO], "hsio", prcm_hsio_phandle);
> + qemu_fdt_setprop_cells(fdt, hsio_name, "clocks", osc_24m_phandle,
> + prcm_rcpu_phandle, TT_ATL_CLK_HSIO_PLL);
> +
> + g_autofree char *pcie_name = create_fdt_prcm(fdt,
> + &s->memmap[TT_ATL_PRCM_PCIE], "pcie", prcm_pcie_phandle);
> + qemu_fdt_setprop_cells(fdt, pcie_name, "clocks", osc_24m_phandle,
> + prcm_rcpu_phandle, TT_ATL_CLK_PCIE_PLL);
> +
> + g_autofree char *mm_name = create_fdt_prcm(fdt,
> + &s->memmap[TT_ATL_PRCM_MM], "mm", prcm_mm_phandle);
> + qemu_fdt_setprop_cells(fdt, mm_name, "clocks", osc_24m_phandle,
> + prcm_rcpu_phandle, TT_ATL_CLK_MM_PLL0,
> + prcm_rcpu_phandle, TT_ATL_CLK_MM_PLL1);
>
> for (int i = 0; i < TT_ATL_NUM_I2C; i++) {
> create_fdt_i2c(fdt,
> @@ -545,6 +604,39 @@ static void tt_atlantis_machine_init(MachineState *machine)
> serial_mm_init(system_memory, s->memmap[TT_ATL_UART1].base, 2,
> qdev_get_gpio_in(s->irqchip, TT_ATL_UART1_IRQ),
> 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN);
> +
> + /* Add rcpu prcm block */
> + object_initialize_child(OBJECT(s), "prcm-rcpu", &s->prcm[0],
> + TYPE_TT_ATLANTIS_PRCM_RCPU);
> + sysbus_realize(SYS_BUS_DEVICE(&s->prcm[0]), &error_fatal);
> + memory_region_add_subregion(system_memory,
> + s->memmap[TT_ATL_PRCM_RCPU].base,
> + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[0]), 0));
> +
> + /* Add hsio prcm block */
> + object_initialize_child(OBJECT(s), "prcm-hsio", &s->prcm[1],
> + TYPE_TT_ATLANTIS_PRCM_HSIO);
> + sysbus_realize(SYS_BUS_DEVICE(&s->prcm[1]), &error_fatal);
> + memory_region_add_subregion(system_memory,
> + s->memmap[TT_ATL_PRCM_HSIO].base,
> + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[1]), 0));
> +
> + /* Add pcie prcm block */
> + object_initialize_child(OBJECT(s), "prcm-pcie", &s->prcm[2],
> + TYPE_TT_ATLANTIS_PRCM_PCIE);
> + sysbus_realize(SYS_BUS_DEVICE(&s->prcm[2]), &error_fatal);
> + memory_region_add_subregion(system_memory,
> + s->memmap[TT_ATL_PRCM_PCIE].base,
> + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[2]), 0));
> +
> + /* Add mm prcm block */
> + object_initialize_child(OBJECT(s), "prcm-mm", &s->prcm[3],
> + TYPE_TT_ATLANTIS_PRCM_MM);
> + sysbus_realize(SYS_BUS_DEVICE(&s->prcm[3]), &error_fatal);
> + memory_region_add_subregion(system_memory,
> + s->memmap[TT_ATL_PRCM_MM].base,
> + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[3]), 0));
> +
> /*
> * Atlantis contains a DesignWare uart while the QEMU machine
> * uses the serial_mm model with the base ns16550 register set.
> diff --git a/include/hw/riscv/tt_atlantis.h b/include/hw/riscv/tt_atlantis.h
> index 7f7d4a5a59..20bcdfeb52 100644
> --- a/include/hw/riscv/tt_atlantis.h
> +++ b/include/hw/riscv/tt_atlantis.h
> @@ -13,12 +13,14 @@
> #include "hw/core/sysbus.h"
> #include "hw/i2c/designware_i2c.h"
> #include "hw/intc/riscv_imsic.h"
> +#include "hw/misc/tt_atlantis_prcm.h"
> #include "hw/riscv/riscv_hart.h"
>
> #define TYPE_TT_ATLANTIS_MACHINE MACHINE_TYPE_NAME("tt-atlantis")
> OBJECT_DECLARE_SIMPLE_TYPE(TTAtlantisState, TT_ATLANTIS_MACHINE)
>
> #define TT_ATL_NUM_I2C 5
> +#define TT_ATL_NUM_PRCM 4
>
> struct TTAtlantisState {
> /*< private >*/
> @@ -31,6 +33,7 @@ struct TTAtlantisState {
> RISCVHartArrayState soc;
> DeviceState *irqchip;
> DesignWareI2CState i2c[TT_ATL_NUM_I2C];
> + TTAtlantisPRCMState prcm[TT_ATL_NUM_PRCM];
>
> int fdt_size;
> };
> @@ -59,6 +62,22 @@ enum {
> TT_ATL_SAPLIC,
> TT_ATL_SIMSIC,
> TT_ATL_UART1,
> + TT_ATL_PRCM_RCPU,
> + TT_ATL_PRCM_HSIO,
> + TT_ATL_PRCM_PCIE,
> + TT_ATL_PRCM_MM,
> +};
> +
> +/* RCPU PRCM Clock IDs */
> +enum {
> + TT_ATL_CLK_RCPU_PLL = 0,
> + TT_ATL_CLK_RCPU_ROOT = 1,
> + TT_ATL_CLK_NOC_PLL = 25,
> + TT_ATL_CLK_NOCC_CLK = 26,
> + TT_ATL_CLK_HSIO_PLL = 54,
> + TT_ATL_CLK_PCIE_PLL = 55,
> + TT_ATL_CLK_MM_PLL0 = 56,
> + TT_ATL_CLK_MM_PLL1 = 57,
> };
>
> #endif
>
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model
2026-08-03 2:24 ` Chao Liu
@ 2026-08-03 15:36 ` Anirudh Srinivasan
0 siblings, 0 replies; 12+ messages in thread
From: Anirudh Srinivasan @ 2026-08-03 15:36 UTC (permalink / raw)
To: Chao Liu
Cc: qemu-devel, Joel Stanley, Nicholas Piggin, Michael Ellerman,
Portia Stephens, qemu-riscv, Pierrick Bouvier, Palmer Dabbelt,
Alistair Francis, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei
Hi Chao,
On Sun, Aug 2, 2026 at 9:25 PM Chao Liu <chao.liu@processmission.com> wrote:
>
> 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 <portias@oss.tenstorrent.com>
> > Signed-off-by: Portia Stephens <portias@oss.tenstorrent.com>
> > Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
> > ---
> > 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 <portias@oss.tenstorrent.com>
> > L: qemu-riscv@nongnu.org
> > 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, checkpatch didn't seem to point these out. Will fix them up in V2.
Regards
Anirudh Srinivasan
> Thanks,
> Chao
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model
2026-07-29 15:21 ` [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model Anirudh Srinivasan
2026-08-03 2:24 ` Chao Liu
@ 2026-08-07 13:01 ` Daniel Henrique Barboza
2026-08-07 13:38 ` Daniel Henrique Barboza
2 siblings, 0 replies; 12+ messages in thread
From: Daniel Henrique Barboza @ 2026-08-07 13:01 UTC (permalink / raw)
To: Anirudh Srinivasan, qemu-devel
Cc: Joel Stanley, Nicholas Piggin, Michael Ellerman, Portia Stephens,
qemu-riscv, Pierrick Bouvier, Palmer Dabbelt, Alistair Francis,
Weiwei Li, Liu Zhiwei, Chao Liu
Hello,
On 7/29/2026 12:21 PM, 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 <portias@oss.tenstorrent.com>
> Signed-off-by: Portia Stephens <portias@oss.tenstorrent.com>
> Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
> ---
> 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 <portias@oss.tenstorrent.com>
> L: qemu-riscv@nongnu.org
> 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) {
> + 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;
> + }
I'm a bit confused here: in tt_atlantis_prcm_read() we're doing a qemu_log_mask() and
return, and then do a "trace_tt_atlantis...". So in an error case you'll have just
the qemu_log_mask(). Here we're doing a trace at the start of prcm_write() and then
a qemu_log_mask() if something wrong happens.
There's no rule on how you use trace so there's nothing wrong with both approaches but
would be nice if we could stick with one single approach: either do trace_tt_... during
function start or in function success. From what I can tell the most common pattern is
the one used in prcm_read(), i.e. qemu_log_mask on errors and trace_ on success.
> +
> + switch (TO_REG(offset)) {
> + default:
> + s->regs[TO_REG(offset)] = data;
> + break;
> + }
This switch is doing nothing. Just do s->regs[TO_REG(offset)] = data directly.
> +}
> +
> +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;
> + 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);
> + }
> + }
Seems like you're using pll_en_written as a flag to detect if we have a match
in the 'switch' and nothing else, since pll_en_written is always being set to
'true'. And if there's no match in the switch() the function just returns.
I suggest removing pll_en_written and doing a return in a default label.
Something like this:
> + uint32_t pll_reg_offset;
> + switch TO_REG(offset) {
> + case RCPU_PLL_RCPU_EN:
> + pll_reg_offset = RCPU_PLL_RCPU_CFG;
> + break;
> + case RCPU_PLL_NOCC_EN:
> + pll_reg_offset = RCPU_PLL_NOCC_CFG;
> + break;
> + case RCPU_PLL_HSIO_EN:
> + pll_reg_offset = RCPU_PLL_HSIO_CFG;
> + break;
> + case RCPU_PLL_PCIE_EN:
> + pll_reg_offset = RCPU_PLL_PCIE_CFG;
> + break;
> + case RCPU_PLL_MM_EN:
> + pll_reg_offset = RCPU_PLL_MM_CFG;
> + break;
> + case RCPU_PLL1_MM_EN:
> + pll_reg_offset = RCPU_PLL1_MM_CFG;
> + break;
> + case RCPU_PLL_I2S_EN:
> + pll_reg_offset = RCPU_PLL_I2S_CFG;
> + break;
default:
return;
> + }
> + 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);
There's a lot of the so called 'magic numbers' around the code. Please put
them in macros and, preferrably, explain where they're coming from (a combination
of reg values, a hardcoded value that the manual/spec dictates, etc).
One more thing:
> + 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);
Let's not use the legacy reset interface for new devices - we should use the
Resetabble interface instead. Here's a straightforward example from
riscv-iommu-sys.c:
static void riscv_iommu_sys_reset_hold(Object *obj, ResetType type)
{
// reset procedure
}
static void riscv_iommu_sys_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
ResettableClass *rc = RESETTABLE_CLASS(klass);
rc->phases.hold = riscv_iommu_sys_reset_hold;
(...)
In this case you would do "rc->phases.hold = tt_atlantis_prcm_mm_reset". Similar
thing for all the other 4 devices that are using legacy_reset() up above too.
Note that if the device requires a more elaborated reset procedure you would need
to do more stuff, but from what I can tell this would be enough for all devices
being added here. I suggest taking a look at the docs in case you're in doubt
about which reset phases you should implement:
https://www.qemu.org/docs/master/devel/reset.html
Thanks,
Daniel
> + 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
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/2] hw/riscv/tt-atlantis: Add PRCMs to tt-atlantis model
2026-07-29 15:21 ` [PATCH 2/2] hw/riscv/tt-atlantis: Add PRCMs to tt-atlantis model Anirudh Srinivasan
2026-08-03 2:25 ` Chao Liu
@ 2026-08-07 13:36 ` Daniel Henrique Barboza
2026-08-10 18:16 ` Anirudh Srinivasan
1 sibling, 1 reply; 12+ messages in thread
From: Daniel Henrique Barboza @ 2026-08-07 13:36 UTC (permalink / raw)
To: Anirudh Srinivasan, qemu-devel
Cc: Joel Stanley, Nicholas Piggin, Michael Ellerman, Portia Stephens,
qemu-riscv, Pierrick Bouvier, Palmer Dabbelt, Alistair Francis,
Weiwei Li, Liu Zhiwei, Chao Liu
Hello,
On 7/29/2026 12:21 PM, Anirudh Srinivasan wrote:
> Atlantis has 4 PRCMs that are visible from S mode by the Ascalon Core
> (RCPU, HSIO, PCIe and MM). This commit adds these 4 PRCM blocks to the
> tt-atlantis model, along with necessary device tree nodes for them.
>
> Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
> ---
> docs/system/riscv/tt_atlantis.rst | 1 +
> hw/riscv/tt_atlantis.c | 92 +++++++++++++++++++++++++++++++++++++++
> include/hw/riscv/tt_atlantis.h | 19 ++++++++
> 3 files changed, 112 insertions(+)
>
> diff --git a/docs/system/riscv/tt_atlantis.rst b/docs/system/riscv/tt_atlantis.rst
> index 1f2880d617..539c22354f 100644
> --- a/docs/system/riscv/tt_atlantis.rst
> +++ b/docs/system/riscv/tt_atlantis.rst
> @@ -14,6 +14,7 @@ tt-atlantis QEMU model features
> * 8-core Ascalon-X CPU Cluster
> * RISC-V compliant Advanced Interrupt Architecture
> * 16550A compatible UART
> +* 4 PRCMs (Clock and Reset Controllers)
>
> Known limitations
> -----------------
> diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
> index d808bcc11c..17cc5318b3 100644
> --- a/hw/riscv/tt_atlantis.c
> +++ b/hw/riscv/tt_atlantis.c
> @@ -26,6 +26,7 @@
> #include "hw/char/serial-mm.h"
> #include "hw/intc/riscv_aclint.h"
> #include "hw/misc/unimp.h"
> +#include "hw/misc/tt_atlantis_prcm.h"
>
> #include "system/system.h"
> #include "system/device_tree.h"
> @@ -59,6 +60,10 @@ static const MemMapEntry tt_atlantis_memmap[] = {
> [TT_ATL_I2C3] = { 0xd4070000, 0x10000 },
> [TT_ATL_I2C4] = { 0xd4080000, 0x10000 },
> [TT_ATL_UART1] = { 0xd4110000, 0x10000 },
> + [TT_ATL_PRCM_RCPU] = { 0xd0000000, 0x10000 },
> + [TT_ATL_PRCM_PCIE] = { 0xd8000000, 0x100 },
> + [TT_ATL_PRCM_MM] = { 0xdc000000, 0x1000 },
> + [TT_ATL_PRCM_HSIO] = { 0xe00c0000, 0x510 },
> [TT_ATL_SAPLIC] = { 0xe8000000, 0x4000000 },
> [TT_ATL_DDR_HI] = { 0x100000000, 0x1000000000 },
> };
> @@ -328,11 +333,38 @@ static void create_fdt_i2c_device(TTAtlantisState *s, int bus,
> qemu_fdt_setprop_cell(fdt, name, "reg", addr);
> }
>
> +static char *create_fdt_prcm(void *fdt, const MemMapEntry *mem,
> + const char *prcm_name, uint32_t prcm_phandle)
> +{
> + hwaddr base = mem->base;
> + hwaddr size = mem->size;
> + char *name = g_strdup_printf("/soc/prcm_%s@%" PRIx64,
> + prcm_name, mem->base);
> + g_autofree char *compatible =
> + g_strdup_printf("tenstorrent,atlantis-prcm-%s", prcm_name);
> +
> + qemu_fdt_add_subnode(fdt, name);
> + qemu_fdt_setprop_string(fdt, name, "compatible", compatible);
> + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, base, 2, size);
> + qemu_fdt_setprop_cell(fdt, name, "#address-cells", 1);
> + qemu_fdt_setprop_cell(fdt, name, "#size-cells", 0);
> + qemu_fdt_setprop_cell(fdt, name, "#clock-cells", 1);
> + qemu_fdt_setprop_cell(fdt, name, "#reset-cells", 1);
> + qemu_fdt_setprop_cell(fdt, name, "phandle", prcm_phandle);
> +
> + return name;
> +}
> +
> static void finalize_fdt(TTAtlantisState *s)
> {
> uint32_t aplic_s_phandle = next_phandle();
> uint32_t imsic_s_phandle = next_phandle();
> uint32_t periph_clk_phandle = next_phandle();
> + uint32_t osc_24m_phandle = next_phandle();
> + uint32_t prcm_rcpu_phandle = next_phandle();
> + uint32_t prcm_hsio_phandle = next_phandle();
> + uint32_t prcm_pcie_phandle = next_phandle();
> + uint32_t prcm_mm_phandle = next_phandle();
Hmmm seems like you're not really using all of these ... 'osc_24m_phandle'
is indeed needed since you're giving it to create_fdt_clk() and using it
to set "clocks" for all create_fdt_prcm() FDTs. 'prcm_rcpu_phandle' is
also used in other FDTs so it's fine.
But the other 3 phandles aren't used by nothing else aside from supplying
a phandle to create_fdt_prcm().
I think we can make things a little easier by:
- remove the 'uint32_t prcm_phandle' argument from create_fdt_prcm(). When
creating the phandle do instead:
> + qemu_fdt_setprop_cell(fdt, name, "phandle", next_phandle());
- remove all phandles that you created just to comply with the old create_fdt_prcm().
Keep osc_24m_phandle as is and just declare prcm_rcpu_phandle:
> + uint32_t osc_24m_phandle = next_phandle();
> + uint32_t prcm_rcpu_phandle;
After creating the FDT of rcpu, retrieve the phandle used to create the
rcpu subnode:
> + g_autofree char *rcpu_name = create_fdt_prcm(fdt,
> + &s->memmap[TT_ATL_PRCM_RCPU], "rcpu");
prcm_rcpu_phandle = qemu_fdt_get_phandle(fdt, rcpu_name);
And now you have access to the phandles you need for everything, without
bothering storing phandles you don't care about.
Thanks,
Daniel
> void *fdt = MACHINE(s)->fdt;
>
> create_fdt_cpu(s, s->memmap, aplic_s_phandle, imsic_s_phandle);
> @@ -348,6 +380,33 @@ static void finalize_fdt(TTAtlantisState *s)
> aplic_s_phandle);
>
> create_fdt_clk(fdt, "periph-clk", 100000000, periph_clk_phandle);
> + create_fdt_clk(fdt, "osc_24m", 24000000, osc_24m_phandle);
> +
> + g_autofree char *rcpu_name = create_fdt_prcm(fdt,
> + &s->memmap[TT_ATL_PRCM_RCPU], "rcpu", prcm_rcpu_phandle);
> + qemu_fdt_setprop_cells(fdt, rcpu_name, "clocks", osc_24m_phandle);
> + qemu_fdt_setprop_cells(fdt, rcpu_name, "assigned-clocks",
> + prcm_rcpu_phandle, TT_ATL_CLK_RCPU_ROOT,
> + prcm_rcpu_phandle, TT_ATL_CLK_NOCC_CLK);
> + qemu_fdt_setprop_cells(fdt, rcpu_name, "assigned-clock-parents",
> + prcm_rcpu_phandle, TT_ATL_CLK_RCPU_PLL,
> + prcm_rcpu_phandle, TT_ATL_CLK_NOC_PLL);
> +
> + g_autofree char *hsio_name = create_fdt_prcm(fdt,
> + &s->memmap[TT_ATL_PRCM_HSIO], "hsio", prcm_hsio_phandle);
> + qemu_fdt_setprop_cells(fdt, hsio_name, "clocks", osc_24m_phandle,
> + prcm_rcpu_phandle, TT_ATL_CLK_HSIO_PLL);
> +
> + g_autofree char *pcie_name = create_fdt_prcm(fdt,
> + &s->memmap[TT_ATL_PRCM_PCIE], "pcie", prcm_pcie_phandle);
> + qemu_fdt_setprop_cells(fdt, pcie_name, "clocks", osc_24m_phandle,
> + prcm_rcpu_phandle, TT_ATL_CLK_PCIE_PLL);
> +
> + g_autofree char *mm_name = create_fdt_prcm(fdt,
> + &s->memmap[TT_ATL_PRCM_MM], "mm", prcm_mm_phandle);
> + qemu_fdt_setprop_cells(fdt, mm_name, "clocks", osc_24m_phandle,
> + prcm_rcpu_phandle, TT_ATL_CLK_MM_PLL0,
> + prcm_rcpu_phandle, TT_ATL_CLK_MM_PLL1);
>
> for (int i = 0; i < TT_ATL_NUM_I2C; i++) {
> create_fdt_i2c(fdt,
> @@ -545,6 +604,39 @@ static void tt_atlantis_machine_init(MachineState *machine)
> serial_mm_init(system_memory, s->memmap[TT_ATL_UART1].base, 2,
> qdev_get_gpio_in(s->irqchip, TT_ATL_UART1_IRQ),
> 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN);
> +
> + /* Add rcpu prcm block */
> + object_initialize_child(OBJECT(s), "prcm-rcpu", &s->prcm[0],
> + TYPE_TT_ATLANTIS_PRCM_RCPU);
> + sysbus_realize(SYS_BUS_DEVICE(&s->prcm[0]), &error_fatal);
> + memory_region_add_subregion(system_memory,
> + s->memmap[TT_ATL_PRCM_RCPU].base,
> + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[0]), 0));
> +
> + /* Add hsio prcm block */
> + object_initialize_child(OBJECT(s), "prcm-hsio", &s->prcm[1],
> + TYPE_TT_ATLANTIS_PRCM_HSIO);
> + sysbus_realize(SYS_BUS_DEVICE(&s->prcm[1]), &error_fatal);
> + memory_region_add_subregion(system_memory,
> + s->memmap[TT_ATL_PRCM_HSIO].base,
> + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[1]), 0));
> +
> + /* Add pcie prcm block */
> + object_initialize_child(OBJECT(s), "prcm-pcie", &s->prcm[2],
> + TYPE_TT_ATLANTIS_PRCM_PCIE);
> + sysbus_realize(SYS_BUS_DEVICE(&s->prcm[2]), &error_fatal);
> + memory_region_add_subregion(system_memory,
> + s->memmap[TT_ATL_PRCM_PCIE].base,
> + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[2]), 0));
> +
> + /* Add mm prcm block */
> + object_initialize_child(OBJECT(s), "prcm-mm", &s->prcm[3],
> + TYPE_TT_ATLANTIS_PRCM_MM);
> + sysbus_realize(SYS_BUS_DEVICE(&s->prcm[3]), &error_fatal);
> + memory_region_add_subregion(system_memory,
> + s->memmap[TT_ATL_PRCM_MM].base,
> + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[3]), 0));
> +
> /*
> * Atlantis contains a DesignWare uart while the QEMU machine
> * uses the serial_mm model with the base ns16550 register set.
> diff --git a/include/hw/riscv/tt_atlantis.h b/include/hw/riscv/tt_atlantis.h
> index 7f7d4a5a59..20bcdfeb52 100644
> --- a/include/hw/riscv/tt_atlantis.h
> +++ b/include/hw/riscv/tt_atlantis.h
> @@ -13,12 +13,14 @@
> #include "hw/core/sysbus.h"
> #include "hw/i2c/designware_i2c.h"
> #include "hw/intc/riscv_imsic.h"
> +#include "hw/misc/tt_atlantis_prcm.h"
> #include "hw/riscv/riscv_hart.h"
>
> #define TYPE_TT_ATLANTIS_MACHINE MACHINE_TYPE_NAME("tt-atlantis")
> OBJECT_DECLARE_SIMPLE_TYPE(TTAtlantisState, TT_ATLANTIS_MACHINE)
>
> #define TT_ATL_NUM_I2C 5
> +#define TT_ATL_NUM_PRCM 4
>
> struct TTAtlantisState {
> /*< private >*/
> @@ -31,6 +33,7 @@ struct TTAtlantisState {
> RISCVHartArrayState soc;
> DeviceState *irqchip;
> DesignWareI2CState i2c[TT_ATL_NUM_I2C];
> + TTAtlantisPRCMState prcm[TT_ATL_NUM_PRCM];
>
> int fdt_size;
> };
> @@ -59,6 +62,22 @@ enum {
> TT_ATL_SAPLIC,
> TT_ATL_SIMSIC,
> TT_ATL_UART1,
> + TT_ATL_PRCM_RCPU,
> + TT_ATL_PRCM_HSIO,
> + TT_ATL_PRCM_PCIE,
> + TT_ATL_PRCM_MM,
> +};
> +
> +/* RCPU PRCM Clock IDs */
> +enum {
> + TT_ATL_CLK_RCPU_PLL = 0,
> + TT_ATL_CLK_RCPU_ROOT = 1,
> + TT_ATL_CLK_NOC_PLL = 25,
> + TT_ATL_CLK_NOCC_CLK = 26,
> + TT_ATL_CLK_HSIO_PLL = 54,
> + TT_ATL_CLK_PCIE_PLL = 55,
> + TT_ATL_CLK_MM_PLL0 = 56,
> + TT_ATL_CLK_MM_PLL1 = 57,
> };
>
> #endif
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model
2026-07-29 15:21 ` [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model Anirudh Srinivasan
2026-08-03 2:24 ` Chao Liu
2026-08-07 13:01 ` Daniel Henrique Barboza
@ 2026-08-07 13:38 ` Daniel Henrique Barboza
2026-08-10 19:35 ` Anirudh Srinivasan
2 siblings, 1 reply; 12+ messages in thread
From: Daniel Henrique Barboza @ 2026-08-07 13:38 UTC (permalink / raw)
To: Anirudh Srinivasan, qemu-devel
Cc: Joel Stanley, Nicholas Piggin, Michael Ellerman, Portia Stephens,
qemu-riscv, Pierrick Bouvier, Palmer Dabbelt, Alistair Francis,
Weiwei Li, Liu Zhiwei, Chao Liu
Hello,
On 7/29/2026 12:21 PM, 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 <portias@oss.tenstorrent.com>
> Signed-off-by: Portia Stephens <portias@oss.tenstorrent.com>
> Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
> ---
> 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 <portias@oss.tenstorrent.com>
> L: qemu-riscv@nongnu.org
> 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) {
> + 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;
> + }
I'm a bit confused here: in tt_atlantis_prcm_read() we're doing a qemu_log_mask() and
return, and then do a "trace_tt_atlantis...". So in an error case you'll have just
the qemu_log_mask(). Here we're doing a trace at the start of prcm_write() and then
a qemu_log_mask() if something wrong happens.
There's no rule on how you use trace so there's nothing wrong with both approaches but
would be nice if we could stick with one single approach: either do trace_tt_... during
function start or in function success. From what I can tell the most common pattern is
the one used in prcm_read(), i.e. qemu_log_mask on errors and trace_ on success.
> +
> + switch (TO_REG(offset)) {
> + default:
> + s->regs[TO_REG(offset)] = data;
> + break;
> + }
This switch is doing nothing. Just do s->regs[TO_REG(offset)] = data directly.
> +}
> +
> +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;
> + 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);
> + }
> + }
Seems like you're using pll_en_written as a flag to detect if we have a match
in the 'switch' and nothing else, since pll_en_written is always being set to
'true'. And if there's no match in the switch() the function just returns.
I suggest removing pll_en_written and doing a return in a default label.
Something like this:
> + uint32_t pll_reg_offset;
> + switch TO_REG(offset) {
> + case RCPU_PLL_RCPU_EN:
> + pll_reg_offset = RCPU_PLL_RCPU_CFG;
> + break;
> + case RCPU_PLL_NOCC_EN:
> + pll_reg_offset = RCPU_PLL_NOCC_CFG;
> + break;
> + case RCPU_PLL_HSIO_EN:
> + pll_reg_offset = RCPU_PLL_HSIO_CFG;
> + break;
> + case RCPU_PLL_PCIE_EN:
> + pll_reg_offset = RCPU_PLL_PCIE_CFG;
> + break;
> + case RCPU_PLL_MM_EN:
> + pll_reg_offset = RCPU_PLL_MM_CFG;
> + break;
> + case RCPU_PLL1_MM_EN:
> + pll_reg_offset = RCPU_PLL1_MM_CFG;
> + break;
> + case RCPU_PLL_I2S_EN:
> + pll_reg_offset = RCPU_PLL_I2S_CFG;
> + break;
default:
return;
> + }
> + 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);
There's a lot of the so called 'magic numbers' around the code. Please put
them in macros and, preferrably, explain where they're coming from (a combination
of reg values, a hardcoded value that the manual/spec dictates, etc).
One more thing:
> + 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);
Let's not use the legacy reset interface for new devices - we should use the
Resetabble interface instead. Here's a straightforward example from
riscv-iommu-sys.c:
static void riscv_iommu_sys_reset_hold(Object *obj, ResetType type)
{
// reset procedure
}
static void riscv_iommu_sys_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
ResettableClass *rc = RESETTABLE_CLASS(klass);
rc->phases.hold = riscv_iommu_sys_reset_hold;
(...)
In this case you would do "rc->phases.hold = tt_atlantis_prcm_mm_reset". Same
Note that if the device requires a more elaborated reset procedure you would need
to do more stuff, but from what I can tell this would be enough for this PRCM model.
For more information on reset phases feel free to take a look here:
https://www.qemu.org/docs/master/devel/reset.html
Thanks,
Daniel
> + 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
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/2] hw/riscv/tt-atlantis: Add PRCMs to tt-atlantis model
2026-08-07 13:36 ` Daniel Henrique Barboza
@ 2026-08-10 18:16 ` Anirudh Srinivasan
0 siblings, 0 replies; 12+ messages in thread
From: Anirudh Srinivasan @ 2026-08-10 18:16 UTC (permalink / raw)
To: Daniel Henrique Barboza
Cc: qemu-devel, Joel Stanley, Nicholas Piggin, Michael Ellerman,
Portia Stephens, qemu-riscv, Pierrick Bouvier, Palmer Dabbelt,
Alistair Francis, Weiwei Li, Liu Zhiwei, Chao Liu
Hi Daniel,
On Fri, Aug 7, 2026 at 8:36 AM Daniel Henrique Barboza
<daniel.barboza@oss.qualcomm.com> wrote:
>
> Hello,
>
> On 7/29/2026 12:21 PM, Anirudh Srinivasan wrote:
> > Atlantis has 4 PRCMs that are visible from S mode by the Ascalon Core
> > (RCPU, HSIO, PCIe and MM). This commit adds these 4 PRCM blocks to the
> > tt-atlantis model, along with necessary device tree nodes for them.
> >
> > Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
> > ---
> > docs/system/riscv/tt_atlantis.rst | 1 +
> > hw/riscv/tt_atlantis.c | 92 +++++++++++++++++++++++++++++++++++++++
> > include/hw/riscv/tt_atlantis.h | 19 ++++++++
> > 3 files changed, 112 insertions(+)
> >
> > diff --git a/docs/system/riscv/tt_atlantis.rst b/docs/system/riscv/tt_atlantis.rst
> > index 1f2880d617..539c22354f 100644
> > --- a/docs/system/riscv/tt_atlantis.rst
> > +++ b/docs/system/riscv/tt_atlantis.rst
> > @@ -14,6 +14,7 @@ tt-atlantis QEMU model features
> > * 8-core Ascalon-X CPU Cluster
> > * RISC-V compliant Advanced Interrupt Architecture
> > * 16550A compatible UART
> > +* 4 PRCMs (Clock and Reset Controllers)
> >
> > Known limitations
> > -----------------
> > diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
> > index d808bcc11c..17cc5318b3 100644
> > --- a/hw/riscv/tt_atlantis.c
> > +++ b/hw/riscv/tt_atlantis.c
> > @@ -26,6 +26,7 @@
> > #include "hw/char/serial-mm.h"
> > #include "hw/intc/riscv_aclint.h"
> > #include "hw/misc/unimp.h"
> > +#include "hw/misc/tt_atlantis_prcm.h"
> >
> > #include "system/system.h"
> > #include "system/device_tree.h"
> > @@ -59,6 +60,10 @@ static const MemMapEntry tt_atlantis_memmap[] = {
> > [TT_ATL_I2C3] = { 0xd4070000, 0x10000 },
> > [TT_ATL_I2C4] = { 0xd4080000, 0x10000 },
> > [TT_ATL_UART1] = { 0xd4110000, 0x10000 },
> > + [TT_ATL_PRCM_RCPU] = { 0xd0000000, 0x10000 },
> > + [TT_ATL_PRCM_PCIE] = { 0xd8000000, 0x100 },
> > + [TT_ATL_PRCM_MM] = { 0xdc000000, 0x1000 },
> > + [TT_ATL_PRCM_HSIO] = { 0xe00c0000, 0x510 },
> > [TT_ATL_SAPLIC] = { 0xe8000000, 0x4000000 },
> > [TT_ATL_DDR_HI] = { 0x100000000, 0x1000000000 },
> > };
> > @@ -328,11 +333,38 @@ static void create_fdt_i2c_device(TTAtlantisState *s, int bus,
> > qemu_fdt_setprop_cell(fdt, name, "reg", addr);
> > }
> >
> > +static char *create_fdt_prcm(void *fdt, const MemMapEntry *mem,
> > + const char *prcm_name, uint32_t prcm_phandle)
> > +{
> > + hwaddr base = mem->base;
> > + hwaddr size = mem->size;
> > + char *name = g_strdup_printf("/soc/prcm_%s@%" PRIx64,
> > + prcm_name, mem->base);
> > + g_autofree char *compatible =
> > + g_strdup_printf("tenstorrent,atlantis-prcm-%s", prcm_name);
> > +
> > + qemu_fdt_add_subnode(fdt, name);
> > + qemu_fdt_setprop_string(fdt, name, "compatible", compatible);
> > + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, base, 2, size);
> > + qemu_fdt_setprop_cell(fdt, name, "#address-cells", 1);
> > + qemu_fdt_setprop_cell(fdt, name, "#size-cells", 0);
> > + qemu_fdt_setprop_cell(fdt, name, "#clock-cells", 1);
> > + qemu_fdt_setprop_cell(fdt, name, "#reset-cells", 1);
> > + qemu_fdt_setprop_cell(fdt, name, "phandle", prcm_phandle);
> > +
> > + return name;
> > +}
> > +
> > static void finalize_fdt(TTAtlantisState *s)
> > {
> > uint32_t aplic_s_phandle = next_phandle();
> > uint32_t imsic_s_phandle = next_phandle();
> > uint32_t periph_clk_phandle = next_phandle();
> > + uint32_t osc_24m_phandle = next_phandle();
> > + uint32_t prcm_rcpu_phandle = next_phandle();
> > + uint32_t prcm_hsio_phandle = next_phandle();
> > + uint32_t prcm_pcie_phandle = next_phandle();
> > + uint32_t prcm_mm_phandle = next_phandle();
>
> Hmmm seems like you're not really using all of these ... 'osc_24m_phandle'
> is indeed needed since you're giving it to create_fdt_clk() and using it
> to set "clocks" for all create_fdt_prcm() FDTs. 'prcm_rcpu_phandle' is
> also used in other FDTs so it's fine.
>
> But the other 3 phandles aren't used by nothing else aside from supplying
> a phandle to create_fdt_prcm().
>
> I think we can make things a little easier by:
>
> - remove the 'uint32_t prcm_phandle' argument from create_fdt_prcm(). When
> creating the phandle do instead:
>
> > + qemu_fdt_setprop_cell(fdt, name, "phandle", next_phandle());
>
>
> - remove all phandles that you created just to comply with the old create_fdt_prcm().
> Keep osc_24m_phandle as is and just declare prcm_rcpu_phandle:
>
> > + uint32_t osc_24m_phandle = next_phandle();
> > + uint32_t prcm_rcpu_phandle;
>
>
> After creating the FDT of rcpu, retrieve the phandle used to create the
> rcpu subnode:
>
> > + g_autofree char *rcpu_name = create_fdt_prcm(fdt,
> > + &s->memmap[TT_ATL_PRCM_RCPU], "rcpu");
>
> prcm_rcpu_phandle = qemu_fdt_get_phandle(fdt, rcpu_name);
>
>
> And now you have access to the phandles you need for everything, without
> bothering storing phandles you don't care about.
Thanks for the feedback, will incorporate this in V2.
>
>
>
> Thanks,
> Daniel
>
>
> > void *fdt = MACHINE(s)->fdt;
> >
> > create_fdt_cpu(s, s->memmap, aplic_s_phandle, imsic_s_phandle);
> > @@ -348,6 +380,33 @@ static void finalize_fdt(TTAtlantisState *s)
> > aplic_s_phandle);
> >
> > create_fdt_clk(fdt, "periph-clk", 100000000, periph_clk_phandle);
> > + create_fdt_clk(fdt, "osc_24m", 24000000, osc_24m_phandle);
> > +
> > + g_autofree char *rcpu_name = create_fdt_prcm(fdt,
> > + &s->memmap[TT_ATL_PRCM_RCPU], "rcpu", prcm_rcpu_phandle);
> > + qemu_fdt_setprop_cells(fdt, rcpu_name, "clocks", osc_24m_phandle);
> > + qemu_fdt_setprop_cells(fdt, rcpu_name, "assigned-clocks",
> > + prcm_rcpu_phandle, TT_ATL_CLK_RCPU_ROOT,
> > + prcm_rcpu_phandle, TT_ATL_CLK_NOCC_CLK);
> > + qemu_fdt_setprop_cells(fdt, rcpu_name, "assigned-clock-parents",
> > + prcm_rcpu_phandle, TT_ATL_CLK_RCPU_PLL,
> > + prcm_rcpu_phandle, TT_ATL_CLK_NOC_PLL);
> > +
> > + g_autofree char *hsio_name = create_fdt_prcm(fdt,
> > + &s->memmap[TT_ATL_PRCM_HSIO], "hsio", prcm_hsio_phandle);
> > + qemu_fdt_setprop_cells(fdt, hsio_name, "clocks", osc_24m_phandle,
> > + prcm_rcpu_phandle, TT_ATL_CLK_HSIO_PLL);
> > +
> > + g_autofree char *pcie_name = create_fdt_prcm(fdt,
> > + &s->memmap[TT_ATL_PRCM_PCIE], "pcie", prcm_pcie_phandle);
> > + qemu_fdt_setprop_cells(fdt, pcie_name, "clocks", osc_24m_phandle,
> > + prcm_rcpu_phandle, TT_ATL_CLK_PCIE_PLL);
> > +
> > + g_autofree char *mm_name = create_fdt_prcm(fdt,
> > + &s->memmap[TT_ATL_PRCM_MM], "mm", prcm_mm_phandle);
> > + qemu_fdt_setprop_cells(fdt, mm_name, "clocks", osc_24m_phandle,
> > + prcm_rcpu_phandle, TT_ATL_CLK_MM_PLL0,
> > + prcm_rcpu_phandle, TT_ATL_CLK_MM_PLL1);
> >
> > for (int i = 0; i < TT_ATL_NUM_I2C; i++) {
> > create_fdt_i2c(fdt,
> > @@ -545,6 +604,39 @@ static void tt_atlantis_machine_init(MachineState *machine)
> > serial_mm_init(system_memory, s->memmap[TT_ATL_UART1].base, 2,
> > qdev_get_gpio_in(s->irqchip, TT_ATL_UART1_IRQ),
> > 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN);
> > +
> > + /* Add rcpu prcm block */
> > + object_initialize_child(OBJECT(s), "prcm-rcpu", &s->prcm[0],
> > + TYPE_TT_ATLANTIS_PRCM_RCPU);
> > + sysbus_realize(SYS_BUS_DEVICE(&s->prcm[0]), &error_fatal);
> > + memory_region_add_subregion(system_memory,
> > + s->memmap[TT_ATL_PRCM_RCPU].base,
> > + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[0]), 0));
> > +
> > + /* Add hsio prcm block */
> > + object_initialize_child(OBJECT(s), "prcm-hsio", &s->prcm[1],
> > + TYPE_TT_ATLANTIS_PRCM_HSIO);
> > + sysbus_realize(SYS_BUS_DEVICE(&s->prcm[1]), &error_fatal);
> > + memory_region_add_subregion(system_memory,
> > + s->memmap[TT_ATL_PRCM_HSIO].base,
> > + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[1]), 0));
> > +
> > + /* Add pcie prcm block */
> > + object_initialize_child(OBJECT(s), "prcm-pcie", &s->prcm[2],
> > + TYPE_TT_ATLANTIS_PRCM_PCIE);
> > + sysbus_realize(SYS_BUS_DEVICE(&s->prcm[2]), &error_fatal);
> > + memory_region_add_subregion(system_memory,
> > + s->memmap[TT_ATL_PRCM_PCIE].base,
> > + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[2]), 0));
> > +
> > + /* Add mm prcm block */
> > + object_initialize_child(OBJECT(s), "prcm-mm", &s->prcm[3],
> > + TYPE_TT_ATLANTIS_PRCM_MM);
> > + sysbus_realize(SYS_BUS_DEVICE(&s->prcm[3]), &error_fatal);
> > + memory_region_add_subregion(system_memory,
> > + s->memmap[TT_ATL_PRCM_MM].base,
> > + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->prcm[3]), 0));
> > +
> > /*
> > * Atlantis contains a DesignWare uart while the QEMU machine
> > * uses the serial_mm model with the base ns16550 register set.
> > diff --git a/include/hw/riscv/tt_atlantis.h b/include/hw/riscv/tt_atlantis.h
> > index 7f7d4a5a59..20bcdfeb52 100644
> > --- a/include/hw/riscv/tt_atlantis.h
> > +++ b/include/hw/riscv/tt_atlantis.h
> > @@ -13,12 +13,14 @@
> > #include "hw/core/sysbus.h"
> > #include "hw/i2c/designware_i2c.h"
> > #include "hw/intc/riscv_imsic.h"
> > +#include "hw/misc/tt_atlantis_prcm.h"
> > #include "hw/riscv/riscv_hart.h"
> >
> > #define TYPE_TT_ATLANTIS_MACHINE MACHINE_TYPE_NAME("tt-atlantis")
> > OBJECT_DECLARE_SIMPLE_TYPE(TTAtlantisState, TT_ATLANTIS_MACHINE)
> >
> > #define TT_ATL_NUM_I2C 5
> > +#define TT_ATL_NUM_PRCM 4
> >
> > struct TTAtlantisState {
> > /*< private >*/
> > @@ -31,6 +33,7 @@ struct TTAtlantisState {
> > RISCVHartArrayState soc;
> > DeviceState *irqchip;
> > DesignWareI2CState i2c[TT_ATL_NUM_I2C];
> > + TTAtlantisPRCMState prcm[TT_ATL_NUM_PRCM];
> >
> > int fdt_size;
> > };
> > @@ -59,6 +62,22 @@ enum {
> > TT_ATL_SAPLIC,
> > TT_ATL_SIMSIC,
> > TT_ATL_UART1,
> > + TT_ATL_PRCM_RCPU,
> > + TT_ATL_PRCM_HSIO,
> > + TT_ATL_PRCM_PCIE,
> > + TT_ATL_PRCM_MM,
> > +};
> > +
> > +/* RCPU PRCM Clock IDs */
> > +enum {
> > + TT_ATL_CLK_RCPU_PLL = 0,
> > + TT_ATL_CLK_RCPU_ROOT = 1,
> > + TT_ATL_CLK_NOC_PLL = 25,
> > + TT_ATL_CLK_NOCC_CLK = 26,
> > + TT_ATL_CLK_HSIO_PLL = 54,
> > + TT_ATL_CLK_PCIE_PLL = 55,
> > + TT_ATL_CLK_MM_PLL0 = 56,
> > + TT_ATL_CLK_MM_PLL1 = 57,
> > };
> >
> > #endif
> >
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model
2026-08-07 13:38 ` Daniel Henrique Barboza
@ 2026-08-10 19:35 ` Anirudh Srinivasan
2026-08-10 19:58 ` Daniel Henrique Barboza
0 siblings, 1 reply; 12+ messages in thread
From: Anirudh Srinivasan @ 2026-08-10 19:35 UTC (permalink / raw)
To: Daniel Henrique Barboza
Cc: qemu-devel, Joel Stanley, Nicholas Piggin, Michael Ellerman,
Portia Stephens, qemu-riscv, Pierrick Bouvier, Palmer Dabbelt,
Alistair Francis, Weiwei Li, Liu Zhiwei, Chao Liu
Hi Daniel,
On Fri, Aug 7, 2026 at 8:38 AM Daniel Henrique Barboza
<daniel.barboza@oss.qualcomm.com> wrote:
>
> Hello,
>
> On 7/29/2026 12:21 PM, 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 <portias@oss.tenstorrent.com>
> > Signed-off-by: Portia Stephens <portias@oss.tenstorrent.com>
> > Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
> > ---
> > 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 <portias@oss.tenstorrent.com>
> > L: qemu-riscv@nongnu.org
> > 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
> >
> > +}
> > +
> > +static void tt_atlantis_prcm_write(void *opaque, hwaddr offset,
> > + uint64_t data, unsigned size) {
> > + 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;
> > + }
>
> I'm a bit confused here: in tt_atlantis_prcm_read() we're doing a qemu_log_mask() and
> return, and then do a "trace_tt_atlantis...". So in an error case you'll have just
> the qemu_log_mask(). Here we're doing a trace at the start of prcm_write() and then
> a qemu_log_mask() if something wrong happens.
>
> There's no rule on how you use trace so there's nothing wrong with both approaches but
> would be nice if we could stick with one single approach: either do trace_tt_... during
> function start or in function success. From what I can tell the most common pattern is
> the one used in prcm_read(), i.e. qemu_log_mask on errors and trace_ on success.
I think I tried placing the trace for the write at the top of the
function and missed this bit. Will do it this way instead.
>
> > +
> > + switch (TO_REG(offset)) {
> > + default:
> > + s->regs[TO_REG(offset)] = data;
> > + break;
> > + }
>
> This switch is doing nothing. Just do s->regs[TO_REG(offset)] = data directly.
Yes, I think I left this in case I was expecting to handle writes to
specific registers here, but that doesn't seem to be needed. WIll
remove this.
>
>
> > +}
> > +
> > +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;
> > + 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);
> > + }
> > + }
>
> Seems like you're using pll_en_written as a flag to detect if we have a match
> in the 'switch' and nothing else, since pll_en_written is always being set to
> 'true'. And if there's no match in the switch() the function just returns.
>
> I suggest removing pll_en_written and doing a return in a default label.
> Something like this:
>
> > + uint32_t pll_reg_offset;
> > + switch TO_REG(offset) {
> > + case RCPU_PLL_RCPU_EN:
> > + pll_reg_offset = RCPU_PLL_RCPU_CFG;
> > + break;
> > + case RCPU_PLL_NOCC_EN:
> > + pll_reg_offset = RCPU_PLL_NOCC_CFG;
> > + break;
> > + case RCPU_PLL_HSIO_EN:
> > + pll_reg_offset = RCPU_PLL_HSIO_CFG;
> > + break;
> > + case RCPU_PLL_PCIE_EN:
> > + pll_reg_offset = RCPU_PLL_PCIE_CFG;
> > + break;
> > + case RCPU_PLL_MM_EN:
> > + pll_reg_offset = RCPU_PLL_MM_CFG;
> > + break;
> > + case RCPU_PLL1_MM_EN:
> > + pll_reg_offset = RCPU_PLL1_MM_CFG;
> > + break;
> > + case RCPU_PLL_I2S_EN:
> > + pll_reg_offset = RCPU_PLL_I2S_CFG;
> > + break;
> default:
> return;
> > + }
>
> > + if (data & PLL_EN) {
> > + s->regs[pll_reg_offset] |= BIT(PLL_LOCK_BIT);
> > + } else {
> > + s->regs[pll_reg_offset] &= ~BIT(PLL_LOCK_BIT);
> > + }
Yes, this looks simpler. Will change it to this.
>
> > +}
> > +
> > +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);
>
> There's a lot of the so called 'magic numbers' around the code. Please put
> them in macros and, preferrably, explain where they're coming from (a combination
> of reg values, a hardcoded value that the manual/spec dictates, etc).
Most of the reset values are already covered by the PLL_RESET_VAL
macro, which is defined at the top of this file. For the remaining
few, some are all 0s or all Fs, and the remaining "magic" values come
from the datasheet, so I will add a comment to reflect that.
>
> One more thing:
>
> > + 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);
>
> Let's not use the legacy reset interface for new devices - we should use the
> Resetabble interface instead. Here's a straightforward example from
> riscv-iommu-sys.c:
Okay, will change over to this.
>
>
> static void riscv_iommu_sys_reset_hold(Object *obj, ResetType type)
> {
> // reset procedure
> }
>
> static void riscv_iommu_sys_class_init(ObjectClass *klass, const void *data)
> {
> DeviceClass *dc = DEVICE_CLASS(klass);
> ResettableClass *rc = RESETTABLE_CLASS(klass);
>
> rc->phases.hold = riscv_iommu_sys_reset_hold;
> (...)
>
>
> In this case you would do "rc->phases.hold = tt_atlantis_prcm_mm_reset". Same
>
> Note that if the device requires a more elaborated reset procedure you would need
> to do more stuff, but from what I can tell this would be enough for this PRCM model.
> For more information on reset phases feel free to take a look here:
>
> https://www.qemu.org/docs/master/devel/reset.html
>
>
> Thanks,
> Daniel
I ended up getting 2 replies from you to this mail. Not sure if this
was a bug or not. I have replied to the 2nd one.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model
2026-08-10 19:35 ` Anirudh Srinivasan
@ 2026-08-10 19:58 ` Daniel Henrique Barboza
0 siblings, 0 replies; 12+ messages in thread
From: Daniel Henrique Barboza @ 2026-08-10 19:58 UTC (permalink / raw)
To: Anirudh Srinivasan
Cc: qemu-devel, Joel Stanley, Nicholas Piggin, Michael Ellerman,
Portia Stephens, qemu-riscv, Pierrick Bouvier, Palmer Dabbelt,
Alistair Francis, Weiwei Li, Liu Zhiwei, Chao Liu
On 8/10/2026 4:35 PM, Anirudh Srinivasan wrote:
> Hi Daniel,
>
> On Fri, Aug 7, 2026 at 8:38 AM Daniel Henrique Barboza
> <daniel.barboza@oss.qualcomm.com> wrote:
[...]
>
> I ended up getting 2 replies from you to this mail. Not sure if this
> was a bug or not. I have replied to the 2nd one.
That's my fault. My Thunderbird had a tantrum and I re-send the same
message thinking the first one wasn't delivered.
Thanks,
Daniel
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-08-10 19:59 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-29 15:21 [PATCH 0/2] Add Model for Tenstorrent Atlantis PRCM Anirudh Srinivasan
2026-07-29 15:21 ` [PATCH 1/2] hw/misc: Add Tenstorrent Atlantis PRCM model Anirudh Srinivasan
2026-08-03 2:24 ` Chao Liu
2026-08-03 15:36 ` Anirudh Srinivasan
2026-08-07 13:01 ` Daniel Henrique Barboza
2026-08-07 13:38 ` Daniel Henrique Barboza
2026-08-10 19:35 ` Anirudh Srinivasan
2026-08-10 19:58 ` Daniel Henrique Barboza
2026-07-29 15:21 ` [PATCH 2/2] hw/riscv/tt-atlantis: Add PRCMs to tt-atlantis model Anirudh Srinivasan
2026-08-03 2:25 ` Chao Liu
2026-08-07 13:36 ` Daniel Henrique Barboza
2026-08-10 18:16 ` Anirudh Srinivasan
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.