Signed-off-by: Simon Law <[email protected]>
---
 MAINTAINERS                |   3 +
 hw/misc/Kconfig            |   3 +
 hw/misc/k230_cmu.c         | 903 +++++++++++++++++++++++++++++++++++++++++++++
 hw/misc/meson.build        |   1 +
 hw/misc/trace-events       |   9 +
 hw/riscv/Kconfig           |   1 +
 include/hw/misc/k230_cmu.h |  58 +++
 7 files changed, 978 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 6171cc7494..2666e4d56a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1825,9 +1825,12 @@ L: [email protected]
 S: Maintained
 F: docs/system/riscv/k230.rst
 F: hw/riscv/k230.c
+F: hw/misc/k230_cmu.c
 F: hw/watchdog/k230_wdt.c
+F: include/hw/misc/k230_cmu.h
 F: include/hw/riscv/k230.h
 F: include/hw/watchdog/k230_wdt.h
+F: tests/qtest/k230-cmu-test.c
 F: tests/qtest/k230-wdt-test.c
 
 RX Machines
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1543ee6653..58a0421e84 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -101,6 +101,9 @@ config FSL_IMX8MP_ANALOG
 config FSL_IMX8MP_CCM
     bool
 
+config K230_CMU
+    bool
+
 config STM32_RCC
     bool
 
diff --git a/hw/misc/k230_cmu.c b/hw/misc/k230_cmu.c
new file mode 100644
index 0000000000..bbf01dd925
--- /dev/null
+++ b/hw/misc/k230_cmu.c
@@ -0,0 +1,903 @@
+/*
+ * Kendryte K230 Clock Management Unit
+ *
+ * Copyright (c) 2026 Simon Law <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Emulates the K230 PLL and system-clock register protocol.  It models
+ * software-visible reset values, masks, write strobes, and PLL lock state,
+ * but does not provide cycle-accurate clocks or propagate QEMU Clock signals.
+ *
+ * K230 Technical Reference Manual V0.3.1 (2024-11-18):
+ * 
https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf
+ *
+ * For more information, see <https://www.kendryte.com/en/proDetail/230>
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/registerfields.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+#include "hw/misc/k230_cmu.h"
+#include "trace.h"
+
+REG32(CMU_CFG0, 0x00)
+    FIELD(CMU_CFG0, PLL_OUT_DIV, 24, 4)
+    FIELD(CMU_CFG0, PLL_REF_DIV, 16, 6)
+    FIELD(CMU_CFG0, PLL_FB_DIV, 0, 13)
+
+REG32(CMU_CFG1, 0x04)
+    FIELD(CMU_CFG1, PLL_BYPASS, 19, 1)
+    FIELD(CMU_CFG1, PLL_TEST, 18, 1)
+    FIELD(CMU_CFG1, PLL_ENSAT, 17, 1)
+    FIELD(CMU_CFG1, PLL_FASTEN, 16, 1)
+    FIELD(CMU_CFG1, PLL_BWADJ, 0, 12)
+
+REG32(CMU_CTL, 0x08)
+    FIELD(CMU_CTL, PLL_LOCK_TIM, 4, 8)
+    FIELD(CMU_CTL, PLL_CLK_OE, 2, 1)
+    FIELD(CMU_CTL, PLL_INIT, 1, 1)
+    FIELD(CMU_CTL, PLL_PWRDWN, 0, 1)
+
+REG32(CMU_STAT, 0x0c)
+    FIELD(CMU_STAT, PLL_FSM_STAT, 4, 2)
+    FIELD(CMU_STAT, PLL_RESERVED, 3, 1)
+    FIELD(CMU_STAT, PLL_FB_SLIP, 2, 1)
+    FIELD(CMU_STAT, PLL_REF_SLIP, 1, 1)
+    FIELD(CMU_STAT, PLL_LOCK, 0, 1)
+
+#define PLL_CFG0_RW_MASK       (R_CMU_CFG0_PLL_OUT_DIV_MASK | \
+                                R_CMU_CFG0_PLL_REF_DIV_MASK | \
+                                R_CMU_CFG0_PLL_FB_DIV_MASK)
+#define PLL_CFG1_RW_MASK       (R_CMU_CFG1_PLL_BYPASS_MASK | \
+                                R_CMU_CFG1_PLL_TEST_MASK | \
+                                R_CMU_CFG1_PLL_ENSAT_MASK | \
+                                R_CMU_CFG1_PLL_FASTEN_MASK | \
+                                R_CMU_CFG1_PLL_BWADJ_MASK)
+#define PLL_CTL_RW_MASK        (R_CMU_CTL_PLL_LOCK_TIM_MASK | \
+                                R_CMU_CTL_PLL_CLK_OE_MASK)
+#define PLL_CTL_W1T_MASK       (R_CMU_CTL_PLL_INIT_MASK | \
+                                R_CMU_CTL_PLL_PWRDWN_MASK)
+#define PLL_CTL_LOW_MASK       (PLL_CTL_RW_MASK | PLL_CTL_W1T_MASK)
+#define PLL_STAT_RO_MASK       (R_CMU_STAT_PLL_FSM_STAT_MASK | \
+                                R_CMU_STAT_PLL_RESERVED_MASK | \
+                                R_CMU_STAT_PLL_FB_SLIP_MASK | \
+                                R_CMU_STAT_PLL_REF_SLIP_MASK | \
+                                R_CMU_STAT_PLL_LOCK_MASK)
+
+REG32(SYSCLK_CPU0_CLK_CFG, 0x000)
+    FIELD(SYSCLK_CPU0_CLK_CFG, CORE_CLK_ROOT_GDIV_UPD, 31, 1)
+    FIELD(SYSCLK_CPU0_CLK_CFG, PCLK_DIV, 15, 3)
+    FIELD(SYSCLK_CPU0_CLK_CFG, TDI_PCLK_EN, 14, 1)
+    /* FIXME: SDK get_leaf_en() reads bit 7 for this gate; */
+    /* TRM says bit 13. */
+    FIELD(SYSCLK_CPU0_CLK_CFG, PCLK_ENABLE, 13, 1)
+    FIELD(SYSCLK_CPU0_CLK_CFG, PLICLK_DIV, 10, 3)
+    FIELD(SYSCLK_CPU0_CLK_CFG, PLICLK_ENABLE, 9, 1)
+    FIELD(SYSCLK_CPU0_CLK_CFG, ACLK_DIV, 6, 3)
+    FIELD(SYSCLK_CPU0_CLK_CFG, CORE_CLK_ROOT_DIV, 1, 4)
+    FIELD(SYSCLK_CPU0_CLK_CFG, CORE_CLK_ENABLE, 0, 1)
+REG32(SYSCLK_CPU1_CLK_CFG, 0x004)
+    FIELD(SYSCLK_CPU1_CLK_CFG, CORE_CLK_DIV_UPD, 31, 1)
+    FIELD(SYSCLK_CPU1_CLK_CFG, PCLK_ENABLE, 19, 1)
+    FIELD(SYSCLK_CPU1_CLK_CFG, PLICLK_DIV, 16, 3)
+    FIELD(SYSCLK_CPU1_CLK_CFG, PLICLK_ENABLE, 15, 1)
+    FIELD(SYSCLK_CPU1_CLK_CFG, ACLK_DIV, 12, 3)
+    FIELD(SYSCLK_CPU1_CLK_CFG, CORE_CLK_ROOT_DIV, 3, 3)
+    FIELD(SYSCLK_CPU1_CLK_CFG, CORE_CLK_SEL, 1, 2)
+    FIELD(SYSCLK_CPU1_CLK_CFG, CORE_CLK_ENABLE, 0, 1)
+REG32(SYSCLK_AI_CLK_CFG, 0x008)
+    FIELD(SYSCLK_AI_CLK_CFG, CLK_DIV_UPD, 31, 1)
+    FIELD(SYSCLK_AI_CLK_CFG, ACLK_ENABLE, 10, 1)
+    FIELD(SYSCLK_AI_CLK_CFG, CLK_CDIV, 3, 3)
+    FIELD(SYSCLK_AI_CLK_CFG, CLK_SEL, 2, 1)
+    FIELD(SYSCLK_AI_CLK_CFG, CLK_ENABLE, 0, 1)
+REG32(SYSCLK_VPU_CLK_CFG, 0x00c)
+    FIELD(SYSCLK_VPU_CLK_CFG, CLK_GDIV_UPD, 31, 1)
+    FIELD(SYSCLK_VPU_CLK_CFG, CFGCLK_ROOT_DIV, 11, 4)
+    FIELD(SYSCLK_VPU_CLK_CFG, CFGCLK_ENABLE, 10, 1)
+    FIELD(SYSCLK_VPU_CLK_CFG, ACLK_ROOT_DIV, 6, 4)
+    FIELD(SYSCLK_VPU_CLK_CFG, ACLK_ENABLE, 5, 1)
+    FIELD(SYSCLK_VPU_CLK_CFG, CLK_DIV, 1, 4)
+    FIELD(SYSCLK_VPU_CLK_CFG, CLK_EN, 0, 1)
+REG32(SYSCLK_PMU_CLK_CFG, 0x010)
+    FIELD(SYSCLK_PMU_CLK_CFG, CLK_EN, 0, 1)
+REG32(SYSCLK_HS_CLKEN_CFG, 0x018)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SSI2_ACLK_ENABLE, 30, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SSI1_ACLK_ENABLE, 29, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, QSPI_ACLK_ENABLE, 28, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SSI0_ACLK_ENABLE, 27, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SSI2_CLK_ENABLE, 26, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SSI1_CLK_ENABLE, 25, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SSI0_CLK_ENABLE, 24, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, USB_REFCLK_SEL, 23, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, USB1_REFCLK_ENABLE, 22, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, USB0_REFCLK_ENABLE, 21, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD1_TMCLK_ENABLE, 20, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD1_CCLK_ENABLE, 19, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD1_BCLK_ENABLE, 18, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD1_ACLK_ENABLE, 17, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD0_TMCLK_ENABLE, 16, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD0_CCLK_ENABLE, 15, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD0_BCLK_ENABLE, 14, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD0_ACLK_ENABLE, 13, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD_TMCLK_ENABLE, 12, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD_CCLK_ENABLE, 11, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD_BCLK_ENABLE, 10, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD_ACLK_ENABLE, 9, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SSI2_HCLK_ENABLE, 8, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SSI1_HCLK_ENABLE, 7, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SSI0_HCLK_ENABLE, 6, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, USB1_HCLK_ENABLE, 5, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, USB0_HCLK_ENABLE, 4, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD1_HCLK_ENABLE, 3, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, SD0_HCLK_ENABLE, 2, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, HCLK_HIGH_ENABLE, 1, 1)
+    FIELD(SYSCLK_HS_CLKEN_CFG, HCLK_ENABLE, 0, 1)
+REG32(SYSCLK_HS_SDCLK_CFG, 0x01c)
+    FIELD(SYSCLK_HS_SDCLK_CFG, ACLK_ROOT_CDIV_UPD, 31, 1)
+    FIELD(SYSCLK_HS_SDCLK_CFG, TMCLK_DIV, 15, 5)
+    /* FIXME: SDK get_leaf_rate() uses ACLK_ROOT_DIV for SD CCLK, */
+    /* not bits 14:12. */
+    FIELD(SYSCLK_HS_SDCLK_CFG, CCLK_DIV, 12, 3)
+    FIELD(SYSCLK_HS_SDCLK_CFG, BCLK_DIV, 9, 3)
+    FIELD(SYSCLK_HS_SDCLK_CFG, ACLK_ROOT_DIV, 6, 3)
+    FIELD(SYSCLK_HS_SDCLK_CFG, HCLK_DIV, 3, 3)
+    FIELD(SYSCLK_HS_SDCLK_CFG, HCLK_HIGH_ROOT_DIV, 0, 3)
+REG32(SYSCLK_HS_SPI_CFG, 0x020)
+    FIELD(SYSCLK_HS_SPI_CFG, SSI0_ACLK_ROOT_CDIV_UPD, 31, 1)
+    FIELD(SYSCLK_HS_SPI_CFG, SSI0_CLK_SEL, 18, 1)
+    FIELD(SYSCLK_HS_SPI_CFG, USB_REFCLK_50M_DIV, 15, 3)
+    FIELD(SYSCLK_HS_SPI_CFG, QSPI_ACLK_ROOT_DIV, 12, 3)
+    FIELD(SYSCLK_HS_SPI_CFG, SSI0_ACLK_DIV, 9, 3)
+    FIELD(SYSCLK_HS_SPI_CFG, SSI2_CLK_DIV, 6, 3)
+    FIELD(SYSCLK_HS_SPI_CFG, SSI1_CLK_DIV, 3, 3)
+REG32(SYSCLK_LS_CLKEN_CFG0, 0x024)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, PDM_CLK_ENABLE, 31, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, CODEC_MCLK_DAC_ENABLE, 30, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, CODEC_MCLK_ADC_ENABLE, 29, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, AUD_DEV_CLK_ENABLE, 28, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, GPIO_DBCLK_ENABLE, 27, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, ADC_CLK_ENABLE, 26, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, I2C4_CLK_ENABLE, 25, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, I2C3_CLK_ENABLE, 24, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, I2C2_CLK_ENABLE, 23, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, I2C1_CLK_ENABLE, 22, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, I2C0_CLK_ENABLE, 21, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, UART4_CLK_ENABLE, 20, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, UART3_CLK_ENABLE, 19, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, UART2_CLK_ENABLE, 18, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, UART1_CLK_ENABLE, 17, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, UART0_CLK_ENABLE, 16, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, ADC_PCLK_ENABLE, 15, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, CODEC_PCLK_ENABLE, 14, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, AUD_DEV_PCLK_ENABLE, 13, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, PWM_PCLK_ENABLE, 12, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, GPIO_PCLK_ENABLE, 11, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, I2C4_PCLK_ENABLE, 10, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, I2C3_PCLK_ENABLE, 9, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, I2C2_PCLK_ENABLE, 8, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, I2C1_PCLK_ENABLE, 7, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, I2C0_PCLK_ENABLE, 6, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, UART4_PCLK_ENABLE, 5, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, UART3_PCLK_ENABLE, 4, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, UART2_PCLK_ENABLE, 3, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, UART1_PCLK_ENABLE, 2, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, UART0_PCLK_ENABLE, 1, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG0, PCLK_ENABLE, 0, 1)
+REG32(SYSCLK_LS_CLKEN_CFG1, 0x028)
+    FIELD(SYSCLK_LS_CLKEN_CFG1, JAMLINK3_PCLK_ENABLE, 7, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG1, JAMLINK2_PCLK_ENABLE, 6, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG1, JAMLINK1_PCLK_ENABLE, 5, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG1, JAMLINK0_PCLK_ENABLE, 4, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG1, JAMLINK3_CO_ENABLE, 3, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG1, JAMLINK2_CO_ENABLE, 2, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG1, JAMLINK1_CO_ENABLE, 1, 1)
+    FIELD(SYSCLK_LS_CLKEN_CFG1, JAMLINK0_CO_ENABLE, 0, 1)
+REG32(SYSCLK_UART_I2C_CLKDIV_CFG, 0x02c)
+    FIELD(SYSCLK_UART_I2C_CLKDIV_CFG, I2C0_CLK_CDIV_UPD, 31, 1)
+    FIELD(SYSCLK_UART_I2C_CLKDIV_CFG, I2C4_CLK_DIV, 27, 3)
+    FIELD(SYSCLK_UART_I2C_CLKDIV_CFG, I2C3_CLK_DIV, 24, 3)
+    FIELD(SYSCLK_UART_I2C_CLKDIV_CFG, I2C2_CLK_DIV, 21, 3)
+    FIELD(SYSCLK_UART_I2C_CLKDIV_CFG, I2C1_CLK_DIV, 18, 3)
+    FIELD(SYSCLK_UART_I2C_CLKDIV_CFG, I2C0_CLK_DIV, 15, 3)
+    FIELD(SYSCLK_UART_I2C_CLKDIV_CFG, UART4_SCLK_DIV, 12, 3)
+    FIELD(SYSCLK_UART_I2C_CLKDIV_CFG, UART3_SCLK_DIV, 9, 3)
+    FIELD(SYSCLK_UART_I2C_CLKDIV_CFG, UART2_SCLK_DIV, 6, 3)
+    FIELD(SYSCLK_UART_I2C_CLKDIV_CFG, UART1_SCLK_DIV, 3, 3)
+    FIELD(SYSCLK_UART_I2C_CLKDIV_CFG, UART0_SCLK_DIV, 0, 3)
+REG32(SYSCLK_LS_CLKDIV_CFG, 0x030)
+    FIELD(SYSCLK_LS_CLKDIV_CFG, JAMLINK_CO_CDIV_UPD, 31, 1)
+    FIELD(SYSCLK_LS_CLKDIV_CFG, JAMLINK_CO_DIV, 23, 8)
+    FIELD(SYSCLK_LS_CLKDIV_CFG, GPIO_DBCLK_DIV, 13, 10)
+    FIELD(SYSCLK_LS_CLKDIV_CFG, ADC_CLK_DIV, 3, 10)
+    FIELD(SYSCLK_LS_CLKDIV_CFG, PCLK_DIV, 0, 3)
+/*
+ * SDK sysctl_clk_t marks offsets 0x034..0x044 reserved; the TRM defines
+ * these registers, so the TRM layout is authoritative for the v1 model.
+ */
+REG32(SYSCLK_AUDIO_CLKDIV_CFG, 0x034)
+    FIELD(SYSCLK_AUDIO_CLKDIV_CFG, AUD_DEV_CLK_FDIV_UPD, 31, 1)
+    FIELD(SYSCLK_AUDIO_CLKDIV_CFG, AUDIO_CLK_STEP, 16, 15)
+    FIELD(SYSCLK_AUDIO_CLKDIV_CFG, AUDIO_CLK_SUM, 0, 16)
+REG32(SYSCLK_CODEC_ADC_MCLKDIV_CFG, 0x038)
+    FIELD(SYSCLK_CODEC_ADC_MCLKDIV_CFG, MCLK_ADC_FDIV_UPD, 31, 1)
+    FIELD(SYSCLK_CODEC_ADC_MCLKDIV_CFG, MCLK_ADC_STEP, 14, 13)
+    FIELD(SYSCLK_CODEC_ADC_MCLKDIV_CFG, MCLK_ADC_SUM, 0, 14)
+REG32(SYSCLK_CODEC_DAC_MCLKDIV_CFG, 0x03c)
+    FIELD(SYSCLK_CODEC_DAC_MCLKDIV_CFG, MCLK_DAC_FDIV_UPD, 31, 1)
+    FIELD(SYSCLK_CODEC_DAC_MCLKDIV_CFG, MCLK_DAC_STEP, 14, 13)
+    FIELD(SYSCLK_CODEC_DAC_MCLKDIV_CFG, MCLK_DAC_SUM, 0, 14)
+REG32(SYSCLK_PDM_CLKDIV_CFG0, 0x040)
+    FIELD(SYSCLK_PDM_CLKDIV_CFG0, CLK_SUM, 0, 17)
+REG32(SYSCLK_PDM_CLKDIV_CFG1, 0x044)
+    FIELD(SYSCLK_PDM_CLKDIV_CFG1, CLK_FDIV_UPD, 31, 1)
+    FIELD(SYSCLK_PDM_CLKDIV_CFG1, CLK_STEP, 0, 16)
+REG32(SYSCLK_SYSCTL_CLKEN_CFG, 0x050)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, HDI_CLK_ENABLE, 21, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, IOMUX_PCLK_ENABLE, 20, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, STC_CLK_EN, 19, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER5_CLK_ENABLE, 18, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER4_CLK_ENABLE, 17, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER3_CLK_ENABLE, 16, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER2_CLK_ENABLE, 15, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER1_CLK_ENABLE, 14, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER0_CLK_ENABLE, 13, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER5_CLK_SEL, 12, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER4_CLK_SEL, 11, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER3_CLK_SEL, 10, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER2_CLK_SEL, 9, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER1_CLK_SEL, 8, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER0_CLK_SEL, 7, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, WDT1_CLK_ENABLE, 6, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, WDT0_CLK_ENABLE, 5, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, MAILBOX_PCLK_ENABLE, 4, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, TIMER_PCLK_ENABLE, 3, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, WDT1_PCLK_ENABLE, 2, 1)
+    FIELD(SYSCLK_SYSCTL_CLKEN_CFG, WDT0_PCLK_ENABLE, 1, 1)
+REG32(SYSCLK_TIMER_CLK_CFG, 0x054)
+    FIELD(SYSCLK_TIMER_CLK_CFG, CLK0_CDIV_UPD, 31, 1)
+    FIELD(SYSCLK_TIMER_CLK_CFG, CLK5_DIV, 15, 3)
+    FIELD(SYSCLK_TIMER_CLK_CFG, CLK4_DIV, 12, 3)
+    FIELD(SYSCLK_TIMER_CLK_CFG, CLK3_DIV, 9, 3)
+    FIELD(SYSCLK_TIMER_CLK_CFG, CLK2_DIV, 6, 3)
+    FIELD(SYSCLK_TIMER_CLK_CFG, CLK1_DIV, 3, 3)
+    FIELD(SYSCLK_TIMER_CLK_CFG, CLK0_DIV, 0, 3)
+REG32(SYSCLK_SYSCTL_CLK_DIV_CFG, 0x058)
+    FIELD(SYSCLK_SYSCTL_CLK_DIV_CFG, PCLK_CDIV_UPD, 31, 1)
+    FIELD(SYSCLK_SYSCTL_CLK_DIV_CFG, HDI_CLK_DIV, 28, 3)
+    FIELD(SYSCLK_SYSCTL_CLK_DIV_CFG, TS_CLK_DIV, 20, 8)
+    FIELD(SYSCLK_SYSCTL_CLK_DIV_CFG, STC_CLK_DIV, 15, 5)
+    FIELD(SYSCLK_SYSCTL_CLK_DIV_CFG, WDT1_CLK_DIV, 9, 6)
+    FIELD(SYSCLK_SYSCTL_CLK_DIV_CFG, WDT0_CLK_DIV, 3, 6)
+REG32(SYSCLK_SHRM_CLK_CFG, 0x05c)
+    FIELD(SYSCLK_SHRM_CLK_CFG, PCLK_ROOT_DIV_UPD, 31, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, PCLK_ROOT_DIV, 18, 3)
+    FIELD(SYSCLK_SHRM_CLK_CFG, SRAM_CLK_SEL, 14, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, AXIM_BRIDGE_CLK_EN, 13, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, AXIM_CLK_EN, 12, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, AXIS_CLK_EN, 11, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, SRAM_ACLK_ENABLE, 10, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, NONAI2D_ACLK_ENABLE, 9, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, NONAI2D_PCLK_ENABLE, 8, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, DECOMPRESS_ACLK_ENABLE, 7, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, DECOMPRESS_PCLK_ENABLE, 6, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, GSDMA_ACLK_ENABLE, 5, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, GSDMA_PCLK_ENABLE, 4, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, PERI_DMA_ACLK_ENABLE, 3, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, PERI_DMA_PCLK_ENABLE, 2, 1)
+    FIELD(SYSCLK_SHRM_CLK_CFG, PCLK_ENABLE, 0, 1)
+REG32(SYSCLK_DDR_CLK_CFG, 0x060)
+    FIELD(SYSCLK_DDR_CLK_CFG, CORE_CLK_CDIV_UPD, 31, 1)
+    FIELD(SYSCLK_DDR_CLK_CFG, PCLK_DIV, 14, 4)
+    FIELD(SYSCLK_DDR_CLK_CFG, CORE_CLK_DIV, 10, 4)
+    FIELD(SYSCLK_DDR_CLK_CFG, PCLK_ENABLE, 9, 1)
+    FIELD(SYSCLK_DDR_CLK_CFG, BYPASSPCLK_ENABLE, 8, 1)
+    FIELD(SYSCLK_DDR_CLK_CFG, AXI4CLK_ENABLE, 7, 1)
+    FIELD(SYSCLK_DDR_CLK_CFG, AXI3CLK_ENABLE, 6, 1)
+    FIELD(SYSCLK_DDR_CLK_CFG, AXI2CLK_ENABLE, 5, 1)
+    FIELD(SYSCLK_DDR_CLK_CFG, AXI1CLK_ENABLE, 4, 1)
+    FIELD(SYSCLK_DDR_CLK_CFG, AXI0CLK_ENABLE, 3, 1)
+    FIELD(SYSCLK_DDR_CLK_CFG, CLK_ENABLE, 2, 1)
+    FIELD(SYSCLK_DDR_CLK_CFG, CLK_SEL, 0, 2)
+/*
+ * SDK sysctl_clk_t marks offsets 0x064..0x078 reserved; the TRM defines
+ * these registers, so the TRM layout is authoritative for the v1 model.
+ */
+REG32(SYSCLK_ISP_CLKEN_CFG, 0x064)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, DW_ACLK_ENABLE, 19, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, DW_HCLK_ENABLE, 18, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, MCM_CLK_ENABLE, 17, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, VSE_CLK_ENABLE, 16, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, DWE_CLK_ENABLE, 15, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, ACLK_ENABLE, 14, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, HCLK_ENABLE, 13, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, CLK_ENABLE, 12, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, CSI2_PCLK_ENABLE, 11, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, CSI1_PCLK_ENABLE, 10, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, CSI0_PCLK_ENABLE, 9, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, CSI2_PIXELCLK_ENABLE, 8, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, CSI1_PIXELCLK_ENABLE, 7, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, CSI0_PIXELCLK_ENABLE, 6, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, DPHY_PCLK_ENABLE, 5, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, CFG2_CLK_ENABLE, 4, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, CFG1_CLK_ENABLE, 3, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, CFG0_CLK_ENABLE, 2, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, PCLK_ENABLE, 1, 1)
+    FIELD(SYSCLK_ISP_CLKEN_CFG, CSIPHY_CFGCLK_ENABLE, 0, 1)
+REG32(SYSCLK_ISP_CLKDIV_CFG, 0x068)
+    FIELD(SYSCLK_ISP_CLKDIV_CFG, CFG_CLK_CDIV_UPD, 31, 1)
+    FIELD(SYSCLK_ISP_CLKDIV_CFG, CLK_SEL, 29, 1)
+    FIELD(SYSCLK_ISP_CLKDIV_CFG, CLK_DIV, 26, 3)
+    FIELD(SYSCLK_ISP_CLKDIV_CFG, PCLK_DIV, 23, 3)
+    FIELD(SYSCLK_ISP_CLKDIV_CFG, CSI2_PIXEL_CLK_DIV, 17, 6)
+    FIELD(SYSCLK_ISP_CLKDIV_CFG, CSI1_PIXEL_CLK_DIV, 11, 6)
+    FIELD(SYSCLK_ISP_CLKDIV_CFG, CSI0_PIXEL_CLK_DIV, 5, 6)
+    FIELD(SYSCLK_ISP_CLKDIV_CFG, CFG_CLK_DIV, 0, 5)
+REG32(SYSCLK_MCLK_CFG, 0x06c)
+    FIELD(SYSCLK_MCLK_CFG, MCLK0_CDIV_UPD, 31, 1)
+    FIELD(SYSCLK_MCLK_CFG, MCLK2_DIV, 19, 5)
+    FIELD(SYSCLK_MCLK_CFG, MCLK2_SEL, 17, 2)
+    FIELD(SYSCLK_MCLK_CFG, MCLK1_DIV, 12, 5)
+    FIELD(SYSCLK_MCLK_CFG, MCLK1_SEL, 10, 2)
+    FIELD(SYSCLK_MCLK_CFG, MCLK0_DIV, 5, 5)
+    FIELD(SYSCLK_MCLK_CFG, MCLK0_SEL, 3, 2)
+    FIELD(SYSCLK_MCLK_CFG, MCLK2_ENABLE, 2, 1)
+    FIELD(SYSCLK_MCLK_CFG, MCLK1_ENABLE, 1, 1)
+    FIELD(SYSCLK_MCLK_CFG, MCLK0_ENABLE, 0, 1)
+REG32(SYSCLK_DPU_CLK_CFG, 0x070)
+    FIELD(SYSCLK_DPU_CLK_CFG, CLK_DIV_UPD, 31, 1)
+    FIELD(SYSCLK_DPU_CLK_CFG, PCLK_DIV, 7, 3)
+    FIELD(SYSCLK_DPU_CLK_CFG, CLK_DIV, 3, 4)
+    FIELD(SYSCLK_DPU_CLK_CFG, PCLK_ENABLE, 2, 1)
+    FIELD(SYSCLK_DPU_CLK_CFG, ACLK_ENABLE, 1, 1)
+    FIELD(SYSCLK_DPU_CLK_CFG, CLK_ENABLE, 0, 1)
+REG32(SYSCLK_VO_CLK_CFG, 0x074)
+    FIELD(SYSCLK_VO_CLK_CFG, ACLK_GPU_EN, 12, 1)
+    FIELD(SYSCLK_VO_CLK_CFG, ACLK_VO_EN, 11, 1)
+    FIELD(SYSCLK_VO_CLK_CFG, HCLK_GPU_EN, 10, 1)
+    FIELD(SYSCLK_VO_CLK_CFG, PCLK_DSI_PHY_EN, 9, 1)
+    FIELD(SYSCLK_VO_CLK_CFG, PCLK_DSI_ENABLE, 8, 1)
+    FIELD(SYSCLK_VO_CLK_CFG, PCLK_VO_ENABLE, 7, 1)
+    FIELD(SYSCLK_VO_CLK_CFG, CLK_GPU_ENABLE, 6, 1)
+    FIELD(SYSCLK_VO_CLK_CFG, CLKEXT_ENABLE, 5, 1)
+    FIELD(SYSCLK_VO_CLK_CFG, CFGCLK_ENABLE, 4, 1)
+    FIELD(SYSCLK_VO_CLK_CFG, REFCLK_ENABLE, 3, 1)
+    FIELD(SYSCLK_VO_CLK_CFG, DPIPCLK_ENABLE, 2, 1)
+    FIELD(SYSCLK_VO_CLK_CFG, ACLK_ENABLE, 1, 1)
+    FIELD(SYSCLK_VO_CLK_CFG, HCLK_ENABLE, 0, 1)
+REG32(SYSCLK_DISP_CLK_DIV, 0x078)
+    FIELD(SYSCLK_DISP_CLK_DIV, HCLK_ROOT_CDIV_UPD, 31, 1)
+    FIELD(SYSCLK_DISP_CLK_DIV, CLK_GPU_DIV, 20, 4)
+    FIELD(SYSCLK_DISP_CLK_DIV, CLKEXT_DIV, 16, 4)
+    FIELD(SYSCLK_DISP_CLK_DIV, CFGCLK_DIV, 11, 5)
+    FIELD(SYSCLK_DISP_CLK_DIV, DPIPCLK_DIV, 3, 8)
+    FIELD(SYSCLK_DISP_CLK_DIV, HCLK_ROOT_DIV, 0, 3)
+REG32(SYSCLK_SEC_CLK_DIV, 0x080)
+    FIELD(SYSCLK_SEC_CLK_DIV, PCLK_DIV_UPD, 31, 1)
+    FIELD(SYSCLK_SEC_CLK_DIV, ACLK_DIV, 11, 3)
+    FIELD(SYSCLK_SEC_CLK_DIV, FIXCLK_DIV, 6, 5)
+    FIELD(SYSCLK_SEC_CLK_DIV, FIXCLK_ENABLE, 5, 1)
+    FIELD(SYSCLK_SEC_CLK_DIV, ACLK_ENABLE, 4, 1)
+    FIELD(SYSCLK_SEC_CLK_DIV, PCLK_DIV, 1, 3)
+    FIELD(SYSCLK_SEC_CLK_DIV, PCLK_ENABLE, 0, 1)
+REG32(SYSCLK_USB_TEST_CLK_DIV, 0x100)
+    FIELD(SYSCLK_USB_TEST_CLK_DIV, CLK480_CDIV_UPD, 31, 1)
+    FIELD(SYSCLK_USB_TEST_CLK_DIV, CLK100_DIV, 4, 3)
+    FIELD(SYSCLK_USB_TEST_CLK_DIV, CLK480_DIV, 1, 3)
+    FIELD(SYSCLK_USB_TEST_CLK_DIV, TEST_CLK_ENABLE, 0, 1)
+REG32(SYSCLK_DPHY_TEST_CLK_DIV, 0x104)
+    FIELD(SYSCLK_DPHY_TEST_CLK_DIV, TEST_CLK_CDIV_UPD, 31, 1)
+    FIELD(SYSCLK_DPHY_TEST_CLK_DIV, TEST_CLK_DIV, 1, 4)
+    FIELD(SYSCLK_DPHY_TEST_CLK_DIV, TEST_CLK_ENABLE, 0, 1)
+/*
+ * The TRM v0.3.1 sysclk summary ends at 0x104. Linux and the SDK both
+ * access SPI2AXI_CLK_DIV at 0x108, so v1 preserves this software ABI
+ * exception.
+ */
+REG32(SYSCLK_SPI2AXI_CLK_DIV, 0x108)
+    FIELD(SYSCLK_SPI2AXI_CLK_DIV, CLK_DIV_UPD, 31, 1)
+    FIELD(SYSCLK_SPI2AXI_CLK_DIV, CLK_DIV, 1, 3)
+    FIELD(SYSCLK_SPI2AXI_CLK_DIV, CLK_ENABLE, 0, 1)
+
+typedef struct K230PllRegDesc {
+    hwaddr offset;
+    const char *name;
+    uint32_t reset[K230_PLL_COUNT];
+    uint32_t rw_mask;
+    uint32_t ro_mask;
+    uint32_t w1t_mask;
+    uint8_t write_enable_shift;
+    uint32_t write_enable_mask;
+} K230PllRegDesc;
+
+typedef struct K230SysclkRegDesc {
+    hwaddr offset;
+    const char *name;
+    uint32_t reset;
+    uint32_t rw_mask;
+    uint32_t w1t_mask;
+} K230SysclkRegDesc;
+
+static const K230PllRegDesc k230_pll_regs[] = {
+    {
+        .offset = A_CMU_CFG0,
+        .name = "PLLx_CFG0",
+        .reset = {
+            0x0102018f, 0x00000062, 0x00070378, 0x0102018f,
+        },
+        .rw_mask = PLL_CFG0_RW_MASK,
+    },
+    {
+        .offset = A_CMU_CFG1,
+        .name = "PLLx_CFG1",
+        .reset = {
+            0x000200c7, 0x00020031, 0x000201bb, 0x000200c7,
+        },
+        .rw_mask = PLL_CFG1_RW_MASK,
+    },
+    {
+        .offset = A_CMU_CTL,
+        .name = "PLLx_CTL",
+        .reset = {
+            0x00000034, 0x00000034, 0x00000034, 0x00000074,
+        },
+        .rw_mask = PLL_CTL_RW_MASK,
+        .w1t_mask = PLL_CTL_W1T_MASK,
+        .write_enable_shift = 16,
+        .write_enable_mask = PLL_CTL_LOW_MASK,
+    },
+    {
+        .offset = A_CMU_STAT,
+        .name = "PLLx_STAT",
+        .reset = {
+            0x00000021, 0x00000021, 0x00000021, 0x00000021,
+        },
+        .ro_mask = PLL_STAT_RO_MASK,
+    },
+};
+
+#define SYSCLK_REG(_reg, _name, _reset, _rw, _w1t) \
+    { .offset = A_ ## _reg, .name = _name, .reset = _reset, \
+      .rw_mask = (_rw), .w1t_mask = (_w1t) }
+
+static const K230SysclkRegDesc k230_sysclk_regs[] = {
+    SYSCLK_REG(SYSCLK_CPU0_CLK_CFG, "CPU0_CLK_CFG", 0x0000e65f,
+               0x0003ffdf, R_SYSCLK_CPU0_CLK_CFG_CORE_CLK_ROOT_GDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_CPU1_CLK_CFG, "CPU1_CLK_CFG", 0x0009900d,
+               0x000f803f, R_SYSCLK_CPU1_CLK_CFG_CORE_CLK_DIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_AI_CLK_CFG, "AI_CLK_CFG", 0x0000043d,
+               0x0000043d, R_SYSCLK_AI_CLK_CFG_CLK_DIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_VPU_CLK_CFG, "VPU_CLK_CFG", 0x00000c7f,
+               0x00007fff, R_SYSCLK_VPU_CLK_CFG_CLK_GDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_PMU_CLK_CFG, "PMU_CLK_CFG", 0x00000001,
+               0x00000001, 0),
+    SYSCLK_REG(SYSCLK_HS_CLKEN_CFG, "HS_CLKEN_CFG", 0x7f7fffff,
+               0x7fffffff, 0),
+    SYSCLK_REG(SYSCLK_HS_SDCLK_CFG, "HS_SDCLK_CFG", 0x000b9248,
+               0x000fffff, R_SYSCLK_HS_SDCLK_CFG_ACLK_ROOT_CDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_HS_SPI_CFG, "HS_SPI_CFG", 0x00039048,
+               0x0007fff8, R_SYSCLK_HS_SPI_CFG_SSI0_ACLK_ROOT_CDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_LS_CLKEN_CFG0, "LS_CLKEN_CFG0", 0xffffffff,
+               0xffffffff, 0),
+    SYSCLK_REG(SYSCLK_LS_CLKEN_CFG1, "LS_CLKEN_CFG1", 0x000000ff,
+               0x000000ff, 0),
+    SYSCLK_REG(SYSCLK_UART_I2C_CLKDIV_CFG, "UART_I2C_CLKDIV_CFG",
+               0x1b6d9249, 0x3fffffff,
+               R_SYSCLK_UART_I2C_CLKDIV_CFG_I2C0_CLK_CDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_LS_CLKDIV_CFG, "LS_CLKDIV_CFG", 0x01dda0c3,
+               0x7fffffff, R_SYSCLK_LS_CLKDIV_CFG_JAMLINK_CO_CDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_AUDIO_CLKDIV_CFG, "AUDIO_CLKDIV_CFG", 0x01b9f424,
+               0x7fffffff, 
R_SYSCLK_AUDIO_CLKDIV_CFG_AUD_DEV_CLK_FDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_CODEC_ADC_MCLKDIV_CFG, "CODEC_ADC_MCLKDIV_CFG",
+               0x006e7d09, 0x07ffffff,
+               R_SYSCLK_CODEC_ADC_MCLKDIV_CFG_MCLK_ADC_FDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_CODEC_DAC_MCLKDIV_CFG, "CODEC_DAC_MCLKDIV_CFG",
+               0x006e7d09, 0x07ffffff,
+               R_SYSCLK_CODEC_DAC_MCLKDIV_CFG_MCLK_DAC_FDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_PDM_CLKDIV_CFG0, "PDM_CLKDIV_CFG0", 0x0000f424,
+               0x0001ffff, 0),
+    SYSCLK_REG(SYSCLK_PDM_CLKDIV_CFG1, "PDM_CLKDIV_CFG1", 0x000001b9,
+               0x0000ffff, R_SYSCLK_PDM_CLKDIV_CFG1_CLK_FDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_SYSCTL_CLKEN_CFG, "SYSCTL_CLKEN_CFG", 0x003ffffe,
+               0x003ffffe, 0),
+    SYSCLK_REG(SYSCLK_TIMER_CLK_CFG, "TIMER_CLK_CFG", 0x0003ffff,
+               0x0003ffff, R_SYSCLK_TIMER_CLK_CFG_CLK0_CDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_SYSCTL_CLK_DIV_CFG, "SYSCTL_CLK_DIV_CFG", 0x1efabef9,
+               0x7ffffff8, R_SYSCLK_SYSCTL_CLK_DIV_CFG_PCLK_CDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_SHRM_CLK_CFG, "SHRM_CLK_CFG", 0x000c7ffd,
+               0x001c7ffd, R_SYSCLK_SHRM_CLK_CFG_PCLK_ROOT_DIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_DDR_CLK_CFG, "DDR_CLK_CFG", 0x000043fd,
+               0x0003ffff, R_SYSCLK_DDR_CLK_CFG_CORE_CLK_CDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_ISP_CLKEN_CFG, "ISP_CLKEN_CFG", 0x000fffff,
+               0x000fffff, 0),
+    SYSCLK_REG(SYSCLK_ISP_CLKDIV_CFG, "ISP_CLKDIV_CFG", 0x00820835,
+               0x3fffffff, R_SYSCLK_ISP_CLKDIV_CFG_CFG_CLK_CDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_MCLK_CFG, "MCLK_CFG", 0x003a74ef,
+               0x00ffffff, R_SYSCLK_MCLK_CFG_MCLK0_CDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_DPU_CLK_CFG, "DPU_CLK_CFG", 0x000001ff,
+               0x000003ff, R_SYSCLK_DPU_CLK_CFG_CLK_DIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_VO_CLK_CFG, "VO_CLK_CFG", 0x00001fff,
+               0x00001fff, 0),
+    SYSCLK_REG(SYSCLK_DISP_CLK_DIV, "DISP_CLK_DIV", 0x0000a83b,
+               0x00ffffff, R_SYSCLK_DISP_CLK_DIV_HCLK_ROOT_CDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_SEC_CLK_DIV, "SEC_CLK_DIV", 0x000002f3,
+               0x00003fff, R_SYSCLK_SEC_CLK_DIV_PCLK_DIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_USB_TEST_CLK_DIV, "USB_TEST_CLK_DIV", 0x00000039,
+               0x0000007f, R_SYSCLK_USB_TEST_CLK_DIV_CLK480_CDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_DPHY_TEST_CLK_DIV, "DPHY_TEST_CLK_DIV", 0x00000009,
+               0x0000001f,
+               R_SYSCLK_DPHY_TEST_CLK_DIV_TEST_CLK_CDIV_UPD_MASK),
+    SYSCLK_REG(SYSCLK_SPI2AXI_CLK_DIV, "SPI2AXI_CLK_DIV", 0x00000007,
+               0x0000000f,
+               R_SYSCLK_SPI2AXI_CLK_DIV_CLK_DIV_UPD_MASK),
+};
+
+#undef SYSCLK_REG
+
+static const K230PllRegDesc *k230_pll_desc(hwaddr addr,
+                                           unsigned *pll_index)
+{
+    unsigned index = addr / K230_PLL_STRIDE;
+    hwaddr offset = addr % K230_PLL_STRIDE;
+
+    if (index >= K230_PLL_COUNT) {
+        return NULL;
+    }
+
+    for (unsigned i = 0; i < ARRAY_SIZE(k230_pll_regs); i++) {
+        if (k230_pll_regs[i].offset == offset) {
+            *pll_index = index;
+            return &k230_pll_regs[i];
+        }
+    }
+
+    return NULL;
+}
+
+static uint32_t k230_pll_value(const K230PllState *pll, hwaddr offset)
+{
+    switch (offset) {
+    case A_CMU_CFG0:
+        return pll->cfg0;
+    case A_CMU_CFG1:
+        return pll->cfg1;
+    case A_CMU_CTL:
+        return pll->ctl & PLL_CTL_RW_MASK;
+    case A_CMU_STAT:
+        return pll->stat & PLL_STAT_RO_MASK;
+    default:
+        return 0;
+    }
+}
+
+static uint32_t *k230_pll_storage(K230PllState *pll, hwaddr offset)
+{
+    switch (offset) {
+    case A_CMU_CFG0:
+        return &pll->cfg0;
+    case A_CMU_CFG1:
+        return &pll->cfg1;
+    case A_CMU_CTL:
+        return &pll->ctl;
+    default:
+        return NULL;
+    }
+}
+
+static uint32_t k230_merge_rw(uint32_t old_value, uint32_t write_value,
+                              uint32_t rw_mask, uint32_t w1t_mask)
+{
+    return ((old_value & ~rw_mask) | (write_value & rw_mask)) & ~w1t_mask;
+}
+
+static void k230_cmu_log_undefined_access(const char *region, bool write,
+                                          hwaddr addr, uint32_t value)
+{
+    if (write) {
+        trace_k230_cmu_undefined_write(region, addr, value);
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: write to undefined %s offset 0x%" PRIx64
+                      " (value 0x%08" PRIx32 ")\n",
+                      TYPE_K230_CMU, region, addr, value);
+    } else {
+        trace_k230_cmu_undefined_read(region, addr);
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: read from undefined %s offset 0x%" PRIx64 "\n",
+                      TYPE_K230_CMU, region, addr);
+    }
+}
+
+static uint64_t k230_pll_read(void *opaque, hwaddr addr, unsigned size)
+{
+    K230CmuState *s = K230_CMU(opaque);
+    unsigned index;
+    const K230PllRegDesc *desc;
+    uint32_t value;
+
+    if (size != sizeof(uint32_t)) {
+        return 0;
+    }
+
+    desc = k230_pll_desc(addr, &index);
+    if (!desc) {
+        k230_cmu_log_undefined_access("PLL", false, addr, 0);
+        return 0;
+    }
+
+    value = k230_pll_value(&s->pll[index], desc->offset);
+    trace_k230_cmu_pll_read(index, desc->offset, value);
+    return value;
+}
+
+static void k230_pll_write(void *opaque, hwaddr addr, uint64_t value,
+                           unsigned size)
+{
+    K230CmuState *s = K230_CMU(opaque);
+    unsigned index;
+    const K230PllRegDesc *desc;
+    K230PllState *pll;
+    uint32_t *storage;
+    uint32_t write_value = value;
+    uint32_t old_value;
+    uint32_t new_value;
+
+    if (size != sizeof(uint32_t)) {
+        return;
+    }
+
+    desc = k230_pll_desc(addr, &index);
+    if (!desc) {
+        k230_cmu_log_undefined_access("PLL", true, addr, write_value);
+        return;
+    }
+
+    pll = &s->pll[index];
+    old_value = k230_pll_value(pll, desc->offset);
+
+    if (desc->offset == A_CMU_STAT) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: write to read-only PLL%u STAT\n",
+                      TYPE_K230_CMU, index);
+        return;
+    }
+
+    if (desc->offset == A_CMU_CTL) {
+        uint32_t low = write_value & 0xffff;
+        uint32_t enabled = (write_value >> desc->write_enable_shift) &
+                           desc->write_enable_mask;
+        uint32_t old_status = pll->stat;
+        uint32_t w1t = enabled & desc->w1t_mask & low;
+        bool powerdown = w1t & R_CMU_CTL_PLL_PWRDWN_MASK;
+        bool init = w1t & R_CMU_CTL_PLL_INIT_MASK;
+
+        pll->ctl = k230_merge_rw(pll->ctl, low,
+                                 enabled & desc->rw_mask,
+                                 desc->w1t_mask);
+
+        if (powerdown && init) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: PLL%u CTL powerdown/init conflict\n",
+                          TYPE_K230_CMU, index);
+        }
+
+        if (powerdown) {
+            pll->stat = 0;
+        }
+        if (init) {
+            pll->stat = 0x00000021;
+        }
+        if (old_status != pll->stat) {
+            trace_k230_cmu_pll_status(index, old_status, pll->stat);
+        }
+        new_value = k230_pll_value(pll, desc->offset);
+        trace_k230_cmu_pll_write(index, desc->offset, write_value,
+                                 old_value, new_value);
+        return;
+    }
+
+    storage = k230_pll_storage(pll, desc->offset);
+    if (!storage) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: PLL%u %s has no writable storage\n",
+                      TYPE_K230_CMU, index, desc->name);
+        return;
+    }
+    *storage = k230_merge_rw(*storage, write_value,
+                             desc->rw_mask, desc->w1t_mask);
+    new_value = k230_pll_value(pll, desc->offset);
+    trace_k230_cmu_pll_write(index, desc->offset, write_value,
+                             old_value, new_value);
+}
+
+static const K230SysclkRegDesc *k230_sysclk_desc(hwaddr addr,
+                                                 unsigned *index)
+{
+    for (unsigned i = 0; i < ARRAY_SIZE(k230_sysclk_regs); i++) {
+        if (k230_sysclk_regs[i].offset == addr) {
+            *index = i;
+            return &k230_sysclk_regs[i];
+        }
+    }
+
+    return NULL;
+}
+
+static uint64_t k230_sysclk_read(void *opaque, hwaddr addr, unsigned size)
+{
+    K230CmuState *s = K230_CMU(opaque);
+    unsigned index;
+    uint32_t value;
+
+    if (size != sizeof(uint32_t)) {
+        return 0;
+    }
+
+    if (!k230_sysclk_desc(addr, &index)) {
+        k230_cmu_log_undefined_access("sysclk", false, addr, 0);
+        return 0;
+    }
+
+    value = s->sysclk[index];
+    trace_k230_cmu_sysclk_read(addr, value);
+    return value;
+}
+
+static void k230_sysclk_write(void *opaque, hwaddr addr, uint64_t value,
+                              unsigned size)
+{
+    K230CmuState *s = K230_CMU(opaque);
+    const K230SysclkRegDesc *desc;
+    unsigned index;
+    uint32_t old_value;
+    uint32_t new_value;
+
+    if (size != sizeof(uint32_t)) {
+        return;
+    }
+
+    desc = k230_sysclk_desc(addr, &index);
+    if (!desc) {
+        k230_cmu_log_undefined_access("sysclk", true, addr, value);
+        return;
+    }
+
+    old_value = s->sysclk[index];
+    new_value = k230_merge_rw(old_value, value,
+                              desc->rw_mask, desc->w1t_mask);
+    s->sysclk[index] = new_value;
+    trace_k230_cmu_sysclk_write(addr, value, old_value, new_value);
+}
+
+static const MemoryRegionOps k230_pll_ops = {
+    .read = k230_pll_read,
+    .write = k230_pll_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static const MemoryRegionOps k230_sysclk_ops = {
+    .read = k230_sysclk_read,
+    .write = k230_sysclk_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static void k230_cmu_check_reg_descs(void)
+{
+    for (unsigned i = 0; i < ARRAY_SIZE(k230_pll_regs); i++) {
+        const K230PllRegDesc *desc = &k230_pll_regs[i];
+
+        g_assert(!(desc->rw_mask & desc->w1t_mask));
+        g_assert(!(desc->rw_mask & desc->ro_mask));
+        g_assert(!(desc->w1t_mask & desc->ro_mask));
+    }
+
+    for (unsigned i = 0; i < ARRAY_SIZE(k230_sysclk_regs); i++) {
+        const K230SysclkRegDesc *desc = &k230_sysclk_regs[i];
+
+        g_assert(!(desc->rw_mask & desc->w1t_mask));
+    }
+}
+
+static void k230_cmu_reset_hold(Object *obj, ResetType type)
+{
+    K230CmuState *s = K230_CMU(obj);
+
+    for (unsigned index = 0; index < K230_PLL_COUNT; index++) {
+        s->pll[index].cfg0 = k230_pll_regs[0].reset[index];
+        s->pll[index].cfg1 = k230_pll_regs[1].reset[index];
+        s->pll[index].ctl = k230_pll_regs[2].reset[index];
+        s->pll[index].stat = k230_pll_regs[3].reset[index];
+    }
+
+    for (unsigned index = 0; index < ARRAY_SIZE(k230_sysclk_regs); index++) {
+        s->sysclk[index] = k230_sysclk_regs[index].reset;
+    }
+}
+
+static void k230_cmu_init(Object *obj)
+{
+    K230CmuState *s = K230_CMU(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+    k230_cmu_check_reg_descs();
+    memory_region_init_io(&s->pll_mmio, obj, &k230_pll_ops, s,
+                          "k230-cmu-pll", K230_CMU_SIZE);
+    memory_region_init_io(&s->sysclk_mmio, obj, &k230_sysclk_ops, s,
+                          "k230-cmu-sysclk", K230_SYSCLK_SIZE);
+    sysbus_init_mmio(sbd, &s->pll_mmio);
+    sysbus_init_mmio(sbd, &s->sysclk_mmio);
+}
+
+static const VMStateDescription vmstate_k230_pll = {
+    .name = "riscv.k230.cmu/pll",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(cfg0, K230PllState),
+        VMSTATE_UINT32(cfg1, K230PllState),
+        VMSTATE_UINT32(ctl, K230PllState),
+        VMSTATE_UINT32(stat, K230PllState),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static const VMStateDescription vmstate_k230_cmu = {
+    .name = TYPE_K230_CMU,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_STRUCT_ARRAY(pll, K230CmuState, K230_PLL_COUNT, 1,
+                             vmstate_k230_pll, K230PllState),
+        VMSTATE_UINT32_ARRAY(sysclk, K230CmuState, K230_SYSCLK_REG_COUNT),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static void k230_cmu_class_init(ObjectClass *klass, const void *data)
+{
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    rc->phases.hold = k230_cmu_reset_hold;
+    dc->vmsd = &vmstate_k230_cmu;
+}
+
+static const TypeInfo k230_cmu_info = {
+    .name = TYPE_K230_CMU,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(K230CmuState),
+    .instance_init = k230_cmu_init,
+    .class_init = k230_cmu_class_init,
+};
+
+static void k230_cmu_register_types(void)
+{
+    type_register_static(&k230_cmu_info);
+}
+
+type_init(k230_cmu_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 23265f6035..5f1c9c439d 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -28,6 +28,7 @@ system_ss.add(when: 'CONFIG_IOSB', if_true: files('iosb.c'))
 system_ss.add(when: 'CONFIG_VIRT_CTRL', if_true: files('virt_ctrl.c'))
 
 # RISC-V devices
+system_ss.add(when: 'CONFIG_K230_CMU', if_true: files('k230_cmu.c'))
 system_ss.add(when: 'CONFIG_MCHP_PFSOC_DMC', if_true: 
files('mchp_pfsoc_dmc.c'))
 system_ss.add(when: 'CONFIG_MCHP_PFSOC_IOSCB', if_true: 
files('mchp_pfsoc_ioscb.c'))
 system_ss.add(when: 'CONFIG_MCHP_PFSOC_SYSREG', if_true: 
files('mchp_pfsoc_sysreg.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index c9a868b3ef..bcedea6954 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -66,6 +66,15 @@ ecc_mem_readl_ecr1(uint32_t ret) "Read event count 2 0x%08x"
 ecc_diag_mem_writeb(uint64_t addr, uint32_t val) "Write diagnostic %"PRId64" = 
0x%02x"
 ecc_diag_mem_readb(uint64_t addr, uint32_t ret) "Read diagnostic %"PRId64"= 
0x%02x"
 
+# k230_cmu.c
+k230_cmu_pll_read(unsigned int pll, uint64_t offset, uint32_t value) "PLL%u 
read offset=0x%"PRIx64" value=0x%08"PRIx32
+k230_cmu_pll_write(unsigned int pll, uint64_t offset, uint32_t value, uint32_t 
old_value, uint32_t new_value) "PLL%u write offset=0x%"PRIx64" 
value=0x%08"PRIx32" old=0x%08"PRIx32" new=0x%08"PRIx32
+k230_cmu_pll_status(unsigned int pll, uint32_t old_status, uint32_t 
new_status) "PLL%u status old=0x%08"PRIx32" new=0x%08"PRIx32
+k230_cmu_sysclk_read(uint64_t offset, uint32_t value) "sysclk read 
offset=0x%"PRIx64" value=0x%08"PRIx32
+k230_cmu_sysclk_write(uint64_t offset, uint32_t value, uint32_t old_value, 
uint32_t new_value) "sysclk write offset=0x%"PRIx64" value=0x%08"PRIx32" 
old=0x%08"PRIx32" new=0x%08"PRIx32
+k230_cmu_undefined_read(const char *region, uint64_t offset) "%s undefined 
read offset=0x%"PRIx64
+k230_cmu_undefined_write(const char *region, uint64_t offset, uint32_t value) 
"%s undefined write offset=0x%"PRIx64" value=0x%08"PRIx32
+
 # empty_slot.c
 empty_slot_write(uint64_t addr, unsigned width, uint64_t value, unsigned size, 
const char *name) "wr addr:0x%04"PRIx64" data:0x%0*"PRIx64" size %u [%s]"
 
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index de37c08cae..d2e365fd70 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -161,4 +161,5 @@ config K230
     select RISCV_IMSIC
     select SERIAL_MM
     select UNIMP
+    select K230_CMU
     select K230_WDT
diff --git a/include/hw/misc/k230_cmu.h b/include/hw/misc/k230_cmu.h
new file mode 100644
index 0000000000..e373ec0aa0
--- /dev/null
+++ b/include/hw/misc/k230_cmu.h
@@ -0,0 +1,58 @@
+/*
+ * Kendryte K230 Clock Management Unit
+ *
+ * Copyright (c) 2026 Simon Law <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Defines the K230 PLL and system-clock register protocol model.  It keeps
+ * software-visible register state but does not provide cycle-accurate clocks
+ * or propagate QEMU Clock signals.
+ *
+ * K230 Technical Reference Manual V0.3.1 (2024-11-18):
+ * 
https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf
+ *
+ * For more information, see <https://www.kendryte.com/en/proDetail/230>
+ *
+ */
+
+#ifndef HW_MISC_K230_CMU_H
+#define HW_MISC_K230_CMU_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_K230_CMU "riscv.k230.cmu"
+OBJECT_DECLARE_SIMPLE_TYPE(K230CmuState, K230_CMU)
+
+#define K230_PLL_COUNT        4
+#define K230_PLL_STRIDE       0x10
+#define K230_SYSCLK_REG_COUNT 32
+
+#define K230_CMU_BASE_ADDR  0x91102000
+#define K230_CMU_SIZE       0x40
+
+#define K230_SYSCLK_BASE_ADDR 0x91100000
+#define K230_SYSCLK_SIZE      0x1000
+
+typedef struct K230PllState {
+    uint32_t cfg0;
+    uint32_t cfg1;
+    uint32_t ctl;
+    uint32_t stat;
+} K230PllState;
+
+typedef struct K230CmuState {
+    /* <private> */
+    SysBusDevice parent_obj;
+
+    /* <public> */
+    MemoryRegion pll_mmio;
+    MemoryRegion sysclk_mmio;
+
+    /* Properties */
+    K230PllState pll[K230_PLL_COUNT];
+    uint32_t sysclk[K230_SYSCLK_REG_COUNT];
+} K230CmuState;
+
+#endif /* HW_MISC_K230_CMU_H */

-- 
2.43.0


Reply via email to