Hi Alistair,
On Thu, Aug 27, 2026 at 8:58 PM Alistair Francis
<[email protected]> wrote:
>
> On Mon, 2026-08-17 at 09:42 -0500, 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 | 442
> > +++++++++++++++++++++++++++++++++++++
> > include/hw/misc/tt_atlantis_prcm.h | 51 +++++
> > 5 files changed, 500 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..571f2a08fe
> > --- /dev/null
> > +++ b/hw/misc/tt_atlantis_prcm.c
> > @@ -0,0 +1,442 @@
> > +/*
> > + * 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)
>
> You don't have to change this, but in future please try to use the
> REG32/FIELD API. It's a lot nicer :)
Ha, I came across that way of doing things after writing this model.
Will keep it in mind for the future.
>
> > +
> > +#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);
> > +
> > + 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;
> > + }
> > +
> > + trace_tt_atlantis_prcm_write(c->domain, offset, data);
> > +
> > + s->regs[TO_REG(offset)] = data;
> > +}
> > +
> > +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);
>
> You probably don't need to log the size
>
> > +
> > + uint32_t pll_reg_offset;
>
> Don't mix declarations and code please. This should be at the top of
> the function (checkpatch should catch this).
Yes, will fix.
>
> > + 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_hold(Object *obj, ResetType type)
> > +{
> > + TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(obj);
> > + TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_GET_CLASS(s);
> > +
> > + memset(s->regs, 0, c->regs_size);
> > +}
> > +
> > +static void tt_atlantis_prcm_rcpu_reset_hold(Object *obj, ResetType
> > type)
> > +{
> > + TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(obj);
> > + tt_atlantis_prcm_reset_hold(obj, type);
> > +
> > + 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); /* Value from Datasheet */
>
> I hope the value comes from the datasheet. Where else would it come
> from!
Another reviewer (Daniel) asked to mention where these come from, I'll
add a single comment at the top instead.
>
> > + s->regs[RCPU_RCPU_DIV_CFG] = (0xb8c2 << 1); /* Value from
> > Datasheet */
> > + 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;
>
> You don't need to set the 0's, you have already memset(0)
I was experimenting with different reset values here to test if the
linux driver handles weird/unrealistic edge cases. So I've left them
in.
>
> > + 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; /* Value from Datasheet
> > */
> > + s->regs[RCPU_BUS_CFG] = NOCC_PLL_BUS_CG_EN | HSIO_PLL_BUS_CG_EN;
> > +}
> > +
> > +static void tt_atlantis_prcm_ddrc_reset_hold(Object *obj, ResetType
> > type)
> > +{
> > + TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(obj);
> > + tt_atlantis_prcm_reset_hold(obj, type);
> > +
> > + s->regs[DDRC_STAT] = 0x1;
> > + s->regs[DDRC_DFISTAT] = 0x1;
> > + s->regs[DDRC_SWSTAT] = 0x1;
> > +}
> > +
> > +static void tt_atlantis_prcm_hsio_reset_hold(Object *obj, ResetType
> > type)
> > +{
> > + TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(obj);
> > + tt_atlantis_prcm_reset_hold(obj, type);
> > +
> > + s->regs[HSIO_HSIO_DIV_CFG] = 0x20821731; /* Value from Datasheet
> > */
> > + s->regs[HSIO_HSIO_DIV_CFG1] = 0x8; /* Value from Datasheet */
> > + 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_hold(Object *obj, ResetType
> > type)
> > +{
> > + TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(obj);
> > + tt_atlantis_prcm_reset_hold(obj, type);
> > +
> > + s->regs[PCIE_SUBS_RST_CTL] = 0;
> > + s->regs[PCIE_SUBS_CG_CTL] = 0;
> > + s->regs[PCIE_SUBS_CK_CTL] = 0x44908; /* Value from Datasheet */
> > +}
> > +
> > +static void tt_atlantis_prcm_mm_reset_hold(Object *obj, ResetType
> > type)
> > +{
> > + TTAtlantisPRCMState *s = TT_ATLANTIS_PRCM(obj);
> > + tt_atlantis_prcm_reset_hold(obj, type);
> > +
> > + s->regs[MM_MM_CLK_GATE_CFG] = 0;
> > + s->regs[MM_MM_CLK_DIV_CFG] = 0xddc8312; /* Value from Datasheet
> > */
> > + s->regs[MM_MM_CLK_SEL_CFG] = 0;
> > + s->regs[MM_MM_RSTN] = 0;
> > + s->regs[MM_MM_CLK_DIV_CFG1] = 0x333; /* Value from Datasheet */
> > +}
> > +
> > +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);
> > + ResettableClass *rc = RESETTABLE_CLASS(klass);
> > + TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_CLASS(klass);
> > +
> > + dc->realize = tt_atlantis_prcm_realize;
> > + dc->unrealize = tt_atlantis_prcm_unrealize;
> > + dc->desc = "Tenstorrent Atlantis PRCM Controller";
> + rc->phases.hold = tt_atlantis_prcm_reset_hold;
> > + c->ops = &tt_atlantis_prcm_ops;
> > +}
> > +
> > +static void tt_atlantis_prcm_rcpu_class_init(ObjectClass *klass,
> > + const void *data)
> > +{
> > + DeviceClass *dc = DEVICE_CLASS(klass);
> > + ResettableClass *rc = RESETTABLE_CLASS(klass);
> > + TTAtlantisPRCMClass *c = TT_ATLANTIS_PRCM_CLASS(klass);
> > +
> > + dc->realize = tt_atlantis_prcm_realize;
> > + dc->desc = "Tenstorrent Atlantis RCPU PRCM Controller";
> > + rc->phases.hold = tt_atlantis_prcm_rcpu_reset_hold;
>
> You could use a rc->phases.enter to call tt_atlantis_prcm_reset_hold()
> here instead. Not sure if that's better though
I was using legacy_reset previously. Another reviewer (Daniel)
suggested using reset_hold instead.
>
> Alistair
Regards
Anirudh Srinivasan