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