This is an automated email from the ASF dual-hosted git repository.
simbit18 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git
The following commit(s) were added to refs/heads/master by this push:
new a48936788aa arch/risc-v/k210: Add sysctl driver for clock and reset
control
a48936788aa is described below
commit a48936788aa3d78a83f960fb85924cb6af3a5eef
Author: Huang Qi <[email protected]>
AuthorDate: Sat Mar 14 11:59:35 2026 +0800
arch/risc-v/k210: Add sysctl driver for clock and reset control
Add sysctl driver for K210 SoC providing clock and reset control:
- Clock enable/disable with two-stage APB/peripheral enable pattern
- Peripheral reset control with proper timing
- Clock frequency query for PLL, CPU, APB buses, and peripherals
- PLL frequency calculation and CPU frequency configuration
The sysctl driver is built unconditionally for K210 boards as it
provides essential clock and reset control functionality.
Add Kconfig option:
- K210_CPU_FREQ: Target CPU frequency in Hz (default 400MHz)
Signed-off-by: Huang Qi <[email protected]>
---
Documentation/platforms/risc-v/k210/index.rst | 20 +
arch/risc-v/src/k210/CMakeLists.txt | 2 +
arch/risc-v/src/k210/Kconfig | 10 +
arch/risc-v/src/k210/Make.defs | 2 +-
arch/risc-v/src/k210/hardware/k210_sysctl.h | 294 +++++-
arch/risc-v/src/k210/k210_clockconfig.c | 44 +-
arch/risc-v/src/k210/k210_sysctl.c | 1276 +++++++++++++++++++++++++
arch/risc-v/src/k210/k210_sysctl.h | 239 +++++
8 files changed, 1873 insertions(+), 14 deletions(-)
diff --git a/Documentation/platforms/risc-v/k210/index.rst
b/Documentation/platforms/risc-v/k210/index.rst
index f769049b810..7dfc61b80cc 100644
--- a/Documentation/platforms/risc-v/k210/index.rst
+++ b/Documentation/platforms/risc-v/k210/index.rst
@@ -2,6 +2,26 @@
Kendryte K210
=============
+System Controller (sysctl)
+==========================
+
+The K210 System Controller (sysctl) driver provides essential clock and reset
+control functionality for the K210 SoC. It is built unconditionally for all
+K210 boards.
+
+Clock Frequency Configuration
+-----------------------------
+
+The driver supports querying clock frequencies for:
+
+* PLL frequencies (PLL0, PLL1, PLL2)
+* CPU clock frequency
+* APB bus frequencies (APB0, APB1, APB2)
+* Individual peripheral clock frequencies
+
+CPU frequency can be configured at build time using the ``K210_CPU_FREQ``
+Kconfig option (default: 400 MHz, range: 40-600 MHz).
+
Supported Boards
================
diff --git a/arch/risc-v/src/k210/CMakeLists.txt
b/arch/risc-v/src/k210/CMakeLists.txt
index f81ad8fc8e6..4df61aab560 100644
--- a/arch/risc-v/src/k210/CMakeLists.txt
+++ b/arch/risc-v/src/k210/CMakeLists.txt
@@ -26,6 +26,8 @@ list(APPEND SRCS k210_timerisr.c k210_allocateheap.c
k210_clockconfig.c)
list(APPEND SRCS k210_lowputc.c k210_serial.c k210_fpioa.c k210_gpiohs.c)
+list(APPEND SRCS k210_sysctl.c)
+
if(CONFIG_BUILD_PROTECTED)
list(APPEND SRCS k210_userspace.c)
endif()
diff --git a/arch/risc-v/src/k210/Kconfig b/arch/risc-v/src/k210/Kconfig
index 9fb905a9305..4416e294c20 100644
--- a/arch/risc-v/src/k210/Kconfig
+++ b/arch/risc-v/src/k210/Kconfig
@@ -27,6 +27,16 @@ config K210_UART0
endmenu
+config K210_CPU_FREQ
+ int "K210 CPU target frequency (Hz)"
+ default 400000000
+ range 40000000 600000000
+ depends on ARCH_CHIP_K210
+ ---help---
+ Set the K210 CPU target frequency in Hz.
+ Default is 400000000 (400 MHz).
+ Valid range: 40000000-600000000 Hz (40-600 MHz).
+
menu "K210 Others"
config K210_WITH_QEMU
diff --git a/arch/risc-v/src/k210/Make.defs b/arch/risc-v/src/k210/Make.defs
index cb302ecc904..42d2d705140 100644
--- a/arch/risc-v/src/k210/Make.defs
+++ b/arch/risc-v/src/k210/Make.defs
@@ -30,7 +30,7 @@ HEAD_ASRC = k210_head.S
CHIP_CSRCS = k210_allocateheap.c k210_clockconfig.c
CHIP_CSRCS += k210_irq.c k210_irq_dispatch.c
CHIP_CSRCS += k210_lowputc.c k210_serial.c k210_fpioa.c
-CHIP_CSRCS += k210_start.c k210_timerisr.c k210_gpiohs.c
+CHIP_CSRCS += k210_start.c k210_timerisr.c k210_gpiohs.c k210_sysctl.c
ifeq ($(CONFIG_BUILD_PROTECTED),y)
CHIP_CSRCS += k210_userspace.c
diff --git a/arch/risc-v/src/k210/hardware/k210_sysctl.h
b/arch/risc-v/src/k210/hardware/k210_sysctl.h
index b26e10bfe9a..ac91bf96ef4 100644
--- a/arch/risc-v/src/k210/hardware/k210_sysctl.h
+++ b/arch/risc-v/src/k210/hardware/k210_sysctl.h
@@ -33,13 +33,303 @@
* Pre-processor Definitions
****************************************************************************/
-#define K210_SYSCTL_PLL0 (K210_SYSCTL_BASE + 0x08)
+/* Sysctl Register Offsets */
+
+#define K210_SYSCTL_GIT_ID (K210_SYSCTL_BASE + 0x00) /* Git short commit
id */
+#define K210_SYSCTL_CLK_FREQ (K210_SYSCTL_BASE + 0x04) /* System clock
base frequency */
+#define K210_SYSCTL_PLL0 (K210_SYSCTL_BASE + 0x08) /* PLL0 controller
*/
+#define K210_SYSCTL_PLL1 (K210_SYSCTL_BASE + 0x0c) /* PLL1 controller
*/
+#define K210_SYSCTL_PLL2 (K210_SYSCTL_BASE + 0x10) /* PLL2 controller
*/
+
+/* 0x14: Reserved */
+
+#define K210_SYSCTL_PLL_LOCK (K210_SYSCTL_BASE + 0x18) /* PLL lock tester
*/
+#define K210_SYSCTL_ROM_ERROR (K210_SYSCTL_BASE + 0x1c) /* AXI ROM detector
*/
+#define K210_SYSCTL_CLKSEL0 (K210_SYSCTL_BASE + 0x20) /* Clock select
controller 0 */
+#define K210_SYSCTL_CLKSEL1 (K210_SYSCTL_BASE + 0x24) /* Clock select
controller 1 */
+#define K210_SYSCTL_CLK_EN_CENT (K210_SYSCTL_BASE + 0x28) /* Central clock
enable */
+#define K210_SYSCTL_CLK_EN_PERI (K210_SYSCTL_BASE + 0x2c) /* Peripheral clock
enable */
+#define K210_SYSCTL_SOFT_RESET (K210_SYSCTL_BASE + 0x30) /* Soft reset
control */
+#define K210_SYSCTL_PERI_RESET (K210_SYSCTL_BASE + 0x34) /* Peripheral reset
controller */
+#define K210_SYSCTL_CLK_TH0 (K210_SYSCTL_BASE + 0x38) /* Clock threshold
controller 0 */
+#define K210_SYSCTL_CLK_TH1 (K210_SYSCTL_BASE + 0x3c) /* Clock threshold
controller 1 */
+#define K210_SYSCTL_CLK_TH2 (K210_SYSCTL_BASE + 0x40) /* Clock threshold
controller 2 */
+#define K210_SYSCTL_CLK_TH3 (K210_SYSCTL_BASE + 0x44) /* Clock threshold
controller 3 */
+#define K210_SYSCTL_CLK_TH4 (K210_SYSCTL_BASE + 0x48) /* Clock threshold
controller 4 */
+#define K210_SYSCTL_CLK_TH5 (K210_SYSCTL_BASE + 0x4c) /* Clock threshold
controller 5 */
+#define K210_SYSCTL_CLK_TH6 (K210_SYSCTL_BASE + 0x50) /* Clock threshold
controller 6 */
+#define K210_SYSCTL_MISC (K210_SYSCTL_BASE + 0x54) /* Miscellaneous
controller */
+#define K210_SYSCTL_PERI (K210_SYSCTL_BASE + 0x58) /* Peripheral
controller */
+#define K210_SYSCTL_SPI_SLEEP (K210_SYSCTL_BASE + 0x5c) /* SPI sleep
controller */
+#define K210_SYSCTL_RESET_STATUS (K210_SYSCTL_BASE + 0x60) /* Reset source
status */
+#define K210_SYSCTL_DMA_SEL0 (K210_SYSCTL_BASE + 0x64) /* DMA handshake
selector 0 */
+#define K210_SYSCTL_DMA_SEL1 (K210_SYSCTL_BASE + 0x68) /* DMA handshake
selector 1 */
+#define K210_SYSCTL_POWER_SEL (K210_SYSCTL_BASE + 0x6c) /* IO Power Mode
Select */
+
+/* PLL bit field extraction macros (legacy, kept for compatibility) */
#define PLL_CLK_R(n) (n & 0x00000f)
#define PLL_CLK_F(n) ((n & 0x0003f0) >> 4)
#define PLL_CLK_OD(n) ((n & 0x003c00) >> 10)
-#define K210_SYSCTL_CLKSEL0 (K210_SYSCTL_BASE + 0x20)
+/* PLL register bit field definitions */
+
+#define PLL_CLKR_SHIFT (0) /* Reference clock
divider */
+#define PLL_CLKR_MASK (0xf << PLL_CLKR_SHIFT)
+#define PLL_CLKF_SHIFT (4) /* Feedback divider */
+#define PLL_CLKF_MASK (0x3f << PLL_CLKF_SHIFT)
+#define PLL_CLKOD_SHIFT (10) /* Output divider */
+#define PLL_CLKOD_MASK (0xf << PLL_CLKOD_SHIFT)
+#define PLL_BWADJ_SHIFT (14) /* Bandwidth adjust */
+#define PLL_BWADJ_MASK (0x3f << PLL_BWADJ_SHIFT)
+#define PLL_RESET_SHIFT (20) /* PLL reset */
+#define PLL_RESET_MASK (1 << PLL_RESET_SHIFT)
+#define PLL_PWRD_SHIFT (21) /* Power down */
+#define PLL_PWRD_MASK (1 << PLL_PWRD_SHIFT)
+#define PLL_BYPASS_SHIFT (23) /* Bypass */
+#define PLL_BYPASS_MASK (1 << PLL_BYPASS_SHIFT)
+#define PLL_OUT_EN_SHIFT (25) /* Output enable */
+#define PLL_OUT_EN_MASK (1 << PLL_OUT_EN_SHIFT)
+
+/* CLK_EN_CENT register bit definitions (Central clock enable) */
+
+#define CLK_EN_CENT_CPU_SHIFT (0) /* CPU clock enable */
+#define CLK_EN_CENT_CPU_MASK (1 << CLK_EN_CENT_CPU_SHIFT)
+#define CLK_EN_CENT_SRAM0_SHIFT (1) /* SRAM0 clock enable */
+#define CLK_EN_CENT_SRAM0_MASK (1 << CLK_EN_CENT_SRAM0_SHIFT)
+#define CLK_EN_CENT_SRAM1_SHIFT (2) /* SRAM1 clock enable */
+#define CLK_EN_CENT_SRAM1_MASK (1 << CLK_EN_CENT_SRAM1_SHIFT)
+#define CLK_EN_CENT_APB0_SHIFT (3) /* APB0 bus clock enable */
+#define CLK_EN_CENT_APB0_MASK (1 << CLK_EN_CENT_APB0_SHIFT)
+#define CLK_EN_CENT_APB1_SHIFT (4) /* APB1 bus clock enable */
+#define CLK_EN_CENT_APB1_MASK (1 << CLK_EN_CENT_APB1_SHIFT)
+#define CLK_EN_CENT_APB2_SHIFT (5) /* APB2 bus clock enable */
+#define CLK_EN_CENT_APB2_MASK (1 << CLK_EN_CENT_APB2_SHIFT)
+
+/* CLK_EN_PERI register bit definitions (Peripheral clock enable) */
+
+#define CLK_EN_PERI_ROM_SHIFT (0) /* ROM clock enable */
+#define CLK_EN_PERI_ROM_MASK (1 << CLK_EN_PERI_ROM_SHIFT)
+#define CLK_EN_PERI_DMA_SHIFT (1) /* DMA clock enable */
+#define CLK_EN_PERI_DMA_MASK (1 << CLK_EN_PERI_DMA_SHIFT)
+#define CLK_EN_PERI_AI_SHIFT (2) /* AI accelerator clock enable */
+#define CLK_EN_PERI_AI_MASK (1 << CLK_EN_PERI_AI_SHIFT)
+#define CLK_EN_PERI_DVP_SHIFT (3) /* DVP camera interface clock enable */
+#define CLK_EN_PERI_DVP_MASK (1 << CLK_EN_PERI_DVP_SHIFT)
+#define CLK_EN_PERI_FFT_SHIFT (4) /* FFT accelerator clock enable */
+#define CLK_EN_PERI_FFT_MASK (1 << CLK_EN_PERI_FFT_SHIFT)
+#define CLK_EN_PERI_GPIO_SHIFT (5) /* GPIO clock enable */
+#define CLK_EN_PERI_GPIO_MASK (1 << CLK_EN_PERI_GPIO_SHIFT)
+#define CLK_EN_PERI_SPI0_SHIFT (6) /* SPI0 clock enable */
+#define CLK_EN_PERI_SPI0_MASK (1 << CLK_EN_PERI_SPI0_SHIFT)
+#define CLK_EN_PERI_SPI1_SHIFT (7) /* SPI1 clock enable */
+#define CLK_EN_PERI_SPI1_MASK (1 << CLK_EN_PERI_SPI1_SHIFT)
+#define CLK_EN_PERI_SPI2_SHIFT (8) /* SPI2 clock enable */
+#define CLK_EN_PERI_SPI2_MASK (1 << CLK_EN_PERI_SPI2_SHIFT)
+#define CLK_EN_PERI_SPI3_SHIFT (9) /* SPI3 clock enable */
+#define CLK_EN_PERI_SPI3_MASK (1 << CLK_EN_PERI_SPI3_SHIFT)
+#define CLK_EN_PERI_I2S0_SHIFT (10) /* I2S0 clock enable */
+#define CLK_EN_PERI_I2S0_MASK (1 << CLK_EN_PERI_I2S0_SHIFT)
+#define CLK_EN_PERI_I2S1_SHIFT (11) /* I2S1 clock enable */
+#define CLK_EN_PERI_I2S1_MASK (1 << CLK_EN_PERI_I2S1_SHIFT)
+#define CLK_EN_PERI_I2S2_SHIFT (12) /* I2S2 clock enable */
+#define CLK_EN_PERI_I2S2_MASK (1 << CLK_EN_PERI_I2S2_SHIFT)
+#define CLK_EN_PERI_I2C0_SHIFT (13) /* I2C0 clock enable */
+#define CLK_EN_PERI_I2C0_MASK (1 << CLK_EN_PERI_I2C0_SHIFT)
+#define CLK_EN_PERI_I2C1_SHIFT (14) /* I2C1 clock enable */
+#define CLK_EN_PERI_I2C1_MASK (1 << CLK_EN_PERI_I2C1_SHIFT)
+#define CLK_EN_PERI_I2C2_SHIFT (15) /* I2C2 clock enable */
+#define CLK_EN_PERI_I2C2_MASK (1 << CLK_EN_PERI_I2C2_SHIFT)
+#define CLK_EN_PERI_UART1_SHIFT (16) /* UART1 clock enable */
+#define CLK_EN_PERI_UART1_MASK (1 << CLK_EN_PERI_UART1_SHIFT)
+#define CLK_EN_PERI_UART2_SHIFT (17) /* UART2 clock enable */
+#define CLK_EN_PERI_UART2_MASK (1 << CLK_EN_PERI_UART2_SHIFT)
+#define CLK_EN_PERI_UART3_SHIFT (18) /* UART3 clock enable */
+#define CLK_EN_PERI_UART3_MASK (1 << CLK_EN_PERI_UART3_SHIFT)
+#define CLK_EN_PERI_AES_SHIFT (19) /* AES accelerator clock enable */
+#define CLK_EN_PERI_AES_MASK (1 << CLK_EN_PERI_AES_SHIFT)
+#define CLK_EN_PERI_FPIOA_SHIFT (20) /* FPIOA (GPIO multiplexer) clock
enable */
+#define CLK_EN_PERI_FPIOA_MASK (1 << CLK_EN_PERI_FPIOA_SHIFT)
+#define CLK_EN_PERI_TIMER0_SHIFT (21) /* TIMER0 clock enable */
+#define CLK_EN_PERI_TIMER0_MASK (1 << CLK_EN_PERI_TIMER0_SHIFT)
+#define CLK_EN_PERI_TIMER1_SHIFT (22) /* TIMER1 clock enable */
+#define CLK_EN_PERI_TIMER1_MASK (1 << CLK_EN_PERI_TIMER1_SHIFT)
+#define CLK_EN_PERI_TIMER2_SHIFT (23) /* TIMER2 clock enable */
+#define CLK_EN_PERI_TIMER2_MASK (1 << CLK_EN_PERI_TIMER2_SHIFT)
+#define CLK_EN_PERI_WDT0_SHIFT (24) /* Watchdog timer 0 clock enable */
+#define CLK_EN_PERI_WDT0_MASK (1 << CLK_EN_PERI_WDT0_SHIFT)
+#define CLK_EN_PERI_WDT1_SHIFT (25) /* Watchdog timer 1 clock enable */
+#define CLK_EN_PERI_WDT1_MASK (1 << CLK_EN_PERI_WDT1_SHIFT)
+#define CLK_EN_PERI_SHA_SHIFT (26) /* SHA accelerator clock enable */
+#define CLK_EN_PERI_SHA_MASK (1 << CLK_EN_PERI_SHA_SHIFT)
+#define CLK_EN_PERI_OTP_SHIFT (27) /* OTP (One-Time Programmable) clock
enable */
+#define CLK_EN_PERI_OTP_MASK (1 << CLK_EN_PERI_OTP_SHIFT)
+#define CLK_EN_PERI_RTC_SHIFT (29) /* RTC clock enable */
+#define CLK_EN_PERI_RTC_MASK (1 << CLK_EN_PERI_RTC_SHIFT)
+
+/* PERI_RESET register bit definitions (Peripheral reset control) */
+
+#define PERI_RESET_ROM_SHIFT (0) /* ROM reset */
+#define PERI_RESET_ROM_MASK (1 << PERI_RESET_ROM_SHIFT)
+#define PERI_RESET_DMA_SHIFT (1) /* DMA reset */
+#define PERI_RESET_DMA_MASK (1 << PERI_RESET_DMA_SHIFT)
+#define PERI_RESET_AI_SHIFT (2) /* AI accelerator reset */
+#define PERI_RESET_AI_MASK (1 << PERI_RESET_AI_SHIFT)
+#define PERI_RESET_DVP_SHIFT (3) /* DVP camera interface reset */
+#define PERI_RESET_DVP_MASK (1 << PERI_RESET_DVP_SHIFT)
+#define PERI_RESET_FFT_SHIFT (4) /* FFT accelerator reset */
+#define PERI_RESET_FFT_MASK (1 << PERI_RESET_FFT_SHIFT)
+#define PERI_RESET_GPIO_SHIFT (5) /* GPIO reset */
+#define PERI_RESET_GPIO_MASK (1 << PERI_RESET_GPIO_SHIFT)
+#define PERI_RESET_SPI0_SHIFT (6) /* SPI0 reset */
+#define PERI_RESET_SPI0_MASK (1 << PERI_RESET_SPI0_SHIFT)
+#define PERI_RESET_SPI1_SHIFT (7) /* SPI1 reset */
+#define PERI_RESET_SPI1_MASK (1 << PERI_RESET_SPI1_SHIFT)
+#define PERI_RESET_SPI2_SHIFT (8) /* SPI2 reset */
+#define PERI_RESET_SPI2_MASK (1 << PERI_RESET_SPI2_SHIFT)
+#define PERI_RESET_SPI3_SHIFT (9) /* SPI3 reset */
+#define PERI_RESET_SPI3_MASK (1 << PERI_RESET_SPI3_SHIFT)
+#define PERI_RESET_I2S0_SHIFT (10) /* I2S0 reset */
+#define PERI_RESET_I2S0_MASK (1 << PERI_RESET_I2S0_SHIFT)
+#define PERI_RESET_I2S1_SHIFT (11) /* I2S1 reset */
+#define PERI_RESET_I2S1_MASK (1 << PERI_RESET_I2S1_SHIFT)
+#define PERI_RESET_I2S2_SHIFT (12) /* I2S2 reset */
+#define PERI_RESET_I2S2_MASK (1 << PERI_RESET_I2S2_SHIFT)
+#define PERI_RESET_I2C0_SHIFT (13) /* I2C0 reset */
+#define PERI_RESET_I2C0_MASK (1 << PERI_RESET_I2C0_SHIFT)
+#define PERI_RESET_I2C1_SHIFT (14) /* I2C1 reset */
+#define PERI_RESET_I2C1_MASK (1 << PERI_RESET_I2C1_SHIFT)
+#define PERI_RESET_I2C2_SHIFT (15) /* I2C2 reset */
+#define PERI_RESET_I2C2_MASK (1 << PERI_RESET_I2C2_SHIFT)
+#define PERI_RESET_UART1_SHIFT (16) /* UART1 reset */
+#define PERI_RESET_UART1_MASK (1 << PERI_RESET_UART1_SHIFT)
+#define PERI_RESET_UART2_SHIFT (17) /* UART2 reset */
+#define PERI_RESET_UART2_MASK (1 << PERI_RESET_UART2_SHIFT)
+#define PERI_RESET_UART3_SHIFT (18) /* UART3 reset */
+#define PERI_RESET_UART3_MASK (1 << PERI_RESET_UART3_SHIFT)
+#define PERI_RESET_AES_SHIFT (19) /* AES accelerator reset */
+#define PERI_RESET_AES_MASK (1 << PERI_RESET_AES_SHIFT)
+#define PERI_RESET_FPIOA_SHIFT (20) /* FPIOA (GPIO multiplexer) reset */
+#define PERI_RESET_FPIOA_MASK (1 << PERI_RESET_FPIOA_SHIFT)
+#define PERI_RESET_TIMER0_SHIFT (21) /* TIMER0 reset */
+#define PERI_RESET_TIMER0_MASK (1 << PERI_RESET_TIMER0_SHIFT)
+#define PERI_RESET_TIMER1_SHIFT (22) /* TIMER1 reset */
+#define PERI_RESET_TIMER1_MASK (1 << PERI_RESET_TIMER1_SHIFT)
+#define PERI_RESET_TIMER2_SHIFT (23) /* TIMER2 reset */
+#define PERI_RESET_TIMER2_MASK (1 << PERI_RESET_TIMER2_SHIFT)
+#define PERI_RESET_WDT0_SHIFT (24) /* Watchdog timer 0 reset */
+#define PERI_RESET_WDT0_MASK (1 << PERI_RESET_WDT0_SHIFT)
+#define PERI_RESET_WDT1_SHIFT (25) /* Watchdog timer 1 reset */
+#define PERI_RESET_WDT1_MASK (1 << PERI_RESET_WDT1_SHIFT)
+#define PERI_RESET_SHA_SHIFT (26) /* SHA accelerator reset */
+#define PERI_RESET_SHA_MASK (1 << PERI_RESET_SHA_SHIFT)
+#define PERI_RESET_RTC_SHIFT (29) /* RTC reset */
+#define PERI_RESET_RTC_MASK (1 << PERI_RESET_RTC_SHIFT)
+
+/* RESET_STATUS register bit definitions (Reset source status) */
+
+#define RESET_STATUS_CLR_SHIFT (0) /* Reset status clear */
+#define RESET_STATUS_CLR_MASK (1 << RESET_STATUS_CLR_SHIFT)
+#define RESET_STATUS_PIN_SHIFT (1) /* Pin reset status */
+#define RESET_STATUS_PIN_MASK (1 << RESET_STATUS_PIN_SHIFT)
+#define RESET_STATUS_WDT0_SHIFT (2) /* Watchdog timer 0 reset status */
+#define RESET_STATUS_WDT0_MASK (1 << RESET_STATUS_WDT0_SHIFT)
+#define RESET_STATUS_WDT1_SHIFT (3) /* Watchdog timer 1 reset status */
+#define RESET_STATUS_WDT1_MASK (1 << RESET_STATUS_WDT1_SHIFT)
+#define RESET_STATUS_SOFT_SHIFT (4) /* Soft reset status */
+#define RESET_STATUS_SOFT_MASK (1 << RESET_STATUS_SOFT_SHIFT)
+
+/* CLKSEL0 register bit definitions (Clock select controller 0) */
+
+#define CLKSEL0_ACLK_SEL_SHIFT (0)
+#define CLKSEL0_ACLK_SEL_MASK (1 << CLKSEL0_ACLK_SEL_SHIFT)
+#define CLKSEL0_ACLK_DIV_SHIFT (1)
+#define CLKSEL0_ACLK_DIV_MASK (3 << CLKSEL0_ACLK_DIV_SHIFT)
+#define CLKSEL0_APB0_DIV_SHIFT (3)
+#define CLKSEL0_APB0_DIV_MASK (7 << CLKSEL0_APB0_DIV_SHIFT)
+#define CLKSEL0_APB1_DIV_SHIFT (6)
+#define CLKSEL0_APB1_DIV_MASK (7 << CLKSEL0_APB1_DIV_SHIFT)
+#define CLKSEL0_APB2_DIV_SHIFT (9)
+#define CLKSEL0_APB2_DIV_MASK (7 << CLKSEL0_APB2_DIV_SHIFT)
+
+/* CLK_TH0 register bit definitions (Clock threshold controller 0) */
+
+#define CLK_TH0_SRAM0_SHIFT (0)
+#define CLK_TH0_SRAM0_MASK (0xf << CLK_TH0_SRAM0_SHIFT)
+#define CLK_TH0_SRAM1_SHIFT (4)
+#define CLK_TH0_SRAM1_MASK (0xf << CLK_TH0_SRAM1_SHIFT)
+#define CLK_TH0_AI_SHIFT (8)
+#define CLK_TH0_AI_MASK (0xf << CLK_TH0_AI_SHIFT)
+#define CLK_TH0_DVP_SHIFT (12)
+#define CLK_TH0_DVP_MASK (0xf << CLK_TH0_DVP_SHIFT)
+#define CLK_TH0_ROM_SHIFT (16)
+#define CLK_TH0_ROM_MASK (0xf << CLK_TH0_ROM_SHIFT)
+
+/* CLK_TH1 register bit definitions (Clock threshold controller 1) */
+
+#define CLK_TH1_SPI0_SHIFT (0)
+#define CLK_TH1_SPI0_MASK (0xff << CLK_TH1_SPI0_SHIFT)
+#define CLK_TH1_SPI1_SHIFT (8)
+#define CLK_TH1_SPI1_MASK (0xff << CLK_TH1_SPI1_SHIFT)
+#define CLK_TH1_SPI2_SHIFT (16)
+#define CLK_TH1_SPI2_MASK (0xff << CLK_TH1_SPI2_SHIFT)
+#define CLK_TH1_SPI3_SHIFT (24)
+#define CLK_TH1_SPI3_MASK (0xff << CLK_TH1_SPI3_SHIFT)
+
+/* CLK_TH2 register bit definitions (Clock threshold controller 2) */
+
+#define CLK_TH2_TIMER0_SHIFT (0)
+#define CLK_TH2_TIMER0_MASK (0xff << CLK_TH2_TIMER0_SHIFT)
+#define CLK_TH2_TIMER1_SHIFT (8)
+#define CLK_TH2_TIMER1_MASK (0xff << CLK_TH2_TIMER1_SHIFT)
+#define CLK_TH2_TIMER2_SHIFT (16)
+#define CLK_TH2_TIMER2_MASK (0xff << CLK_TH2_TIMER2_SHIFT)
+
+/* CLK_TH3 register bit definitions (Clock threshold controller 3) */
+
+#define CLK_TH3_I2S0_SHIFT (0)
+#define CLK_TH3_I2S0_MASK (0xffff << CLK_TH3_I2S0_SHIFT)
+#define CLK_TH3_I2S1_SHIFT (16)
+#define CLK_TH3_I2S1_MASK (0xffff << CLK_TH3_I2S1_SHIFT)
+
+/* CLK_TH4 register bit definitions (Clock threshold controller 4) */
+
+#define CLK_TH4_I2S2_SHIFT (0)
+#define CLK_TH4_I2S2_MASK (0xffff << CLK_TH4_I2S2_SHIFT)
+
+/* CLK_TH5 register bit definitions (Clock threshold controller 5) */
+
+#define CLK_TH5_I2C0_SHIFT (8)
+#define CLK_TH5_I2C0_MASK (0xff << CLK_TH5_I2C0_SHIFT)
+#define CLK_TH5_I2C1_SHIFT (16)
+#define CLK_TH5_I2C1_MASK (0xff << CLK_TH5_I2C1_SHIFT)
+#define CLK_TH5_I2C2_SHIFT (24)
+#define CLK_TH5_I2C2_MASK (0xff << CLK_TH5_I2C2_SHIFT)
+
+/* CLK_TH6 register bit definitions (Clock threshold controller 6) */
+
+#define CLK_TH6_WDT0_SHIFT (0)
+#define CLK_TH6_WDT0_MASK (0xff << CLK_TH6_WDT0_SHIFT)
+#define CLK_TH6_WDT1_SHIFT (8)
+#define CLK_TH6_WDT1_MASK (0xff << CLK_TH6_WDT1_SHIFT)
+
+/* PLL_LOCK register bit definitions (PLL lock tester) */
+
+#define PLL_LOCK_PLL0_SHIFT (0)
+#define PLL_LOCK_PLL0_MASK (0x3 << PLL_LOCK_PLL0_SHIFT)
+#define PLL_LOCK_PLL0_SLIP_CLR (1 << 2)
+#define PLL_LOCK_PLL1_SHIFT (8)
+#define PLL_LOCK_PLL1_MASK (0x1 << PLL_LOCK_PLL1_SHIFT)
+#define PLL_LOCK_PLL1_SLIP_CLR (1 << 10)
+#define PLL_LOCK_PLL2_SHIFT (16)
+#define PLL_LOCK_PLL2_MASK (0x1 << PLL_LOCK_PLL2_SHIFT)
+#define PLL_LOCK_PLL2_SLIP_CLR (1 << 18)
+
+#define PLL_LOCK_PLL0_LOCKED (0x3 << PLL_LOCK_PLL0_SHIFT)
+#define PLL_LOCK_PLL1_LOCKED (0x1 << PLL_LOCK_PLL1_SHIFT)
+#define PLL_LOCK_PLL2_LOCKED (0x1 << PLL_LOCK_PLL2_SHIFT)
+
+/* Clock select macros */
#define CLKSEL0_ACLK_SEL(n) (n & 0x00000001)
diff --git a/arch/risc-v/src/k210/k210_clockconfig.c
b/arch/risc-v/src/k210/k210_clockconfig.c
index 10819ead985..b911cd3d4b6 100644
--- a/arch/risc-v/src/k210/k210_clockconfig.c
+++ b/arch/risc-v/src/k210/k210_clockconfig.c
@@ -35,6 +35,7 @@
#include "riscv_internal.h"
#include "k210_clockconfig.h"
+#include "k210_sysctl.h"
/****************************************************************************
* Pre-processor Definitions
@@ -46,7 +47,7 @@
* Private Data
****************************************************************************/
-static uint32_t g_cpu_clock = 416000000;
+static uint32_t g_cpu_clock = CONFIG_K210_CPU_FREQ;
/****************************************************************************
* Public Functions
@@ -90,22 +91,43 @@ void k210_clockconfig(void)
{
#ifndef CONFIG_K210_WITH_QEMU
uint32_t clksel0;
+ uint32_t div;
- /* Obtain clock selector for ACLK */
+ /* Initialize sysctl driver */
- clksel0 = getreg32(K210_SYSCTL_CLKSEL0);
+ k210_sysctl_init();
- if (1 == CLKSEL0_ACLK_SEL(clksel0))
- {
- /* PLL0 selected */
+ /* Wait for PLL0 to lock before configuring clocks */
- g_cpu_clock = k210_get_pll0clk() / 2;
- }
- else
+ while (!k210_sysctl_pll_is_locked(K210_SYSCTL_PLL0))
{
- /* OSC selected */
+ up_mdelay(1);
+ }
+
+ /* Enable essential system clocks */
- g_cpu_clock = OSC_FREQ;
+ k210_sysctl_clock_enable(K210_CLOCK_CPU);
+ k210_sysctl_clock_enable(K210_CLOCK_SRAM0);
+ k210_sysctl_clock_enable(K210_CLOCK_SRAM1);
+
+ /* Use new frequency API to update g_cpu_clock */
+
+ g_cpu_clock = k210_sysctl_clock_get_freq(K210_CLOCK_CPU);
+ if (g_cpu_clock == 0)
+ {
+ /* Fallback to PLL frequency calculation if new API fails */
+
+ clksel0 = getreg32(K210_SYSCTL_CLKSEL0);
+
+ if (1 == CLKSEL0_ACLK_SEL(clksel0))
+ {
+ div = (clksel0 & CLKSEL0_ACLK_DIV_MASK) >> CLKSEL0_ACLK_DIV_SHIFT;
+ g_cpu_clock = k210_get_pll0clk() / (2u << div);
+ }
+ else
+ {
+ g_cpu_clock = OSC_FREQ;
+ }
}
/* Workaround for stabilization */
diff --git a/arch/risc-v/src/k210/k210_sysctl.c
b/arch/risc-v/src/k210/k210_sysctl.c
new file mode 100644
index 00000000000..4c06ead3bf3
--- /dev/null
+++ b/arch/risc-v/src/k210/k210_sysctl.c
@@ -0,0 +1,1276 @@
+/****************************************************************************
+ * arch/risc-v/src/k210/k210_sysctl.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <errno.h>
+#include <debug.h>
+#include <nuttx/arch.h>
+
+#include "riscv_internal.h"
+#include "hardware/k210_sysctl.h"
+#include "hardware/k210_memorymap.h"
+#include "k210_sysctl.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define K210_PLL_INPUT_FREQ 26000000UL
+
+#define K210_PLL_REF_MIN 13671900UL
+#define K210_PLL_REF_MAX 1750000000ULL
+#define K210_PLL_VCO_MIN 350000000ULL
+#define K210_PLL_VCO_MAX 1750000000ULL
+
+#define K210_PLL_NR_MIN 1
+#define K210_PLL_NR_MAX 16
+#define K210_PLL_NF_MIN 1
+#define K210_PLL_NF_MAX 64
+#define K210_PLL_OD_MIN 1
+#define K210_PLL_OD_MAX 16
+
+#define K210_PLL_LOCK_TIMEOUT_US 50000
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct k210_pll_params_s
+{
+ uint8_t clkr;
+ uint8_t clkf;
+ uint8_t clkod;
+ uint8_t bwadj;
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: k210_sysctl_pll_get_freq
+ *
+ * Description:
+ * Calculate PLL output frequency from register fields.
+ *
+ * Formula: FOUT = FIN * (CLKF + 1) / (CLKR + 1) / (CLKOD + 1)
+ * Where FIN = 26MHz (external crystal oscillator)
+ *
+ * Input Parameters:
+ * pll_offset - PLL register offset (K210_SYSCTL_PLL0/PLL1/PLL2)
+ *
+ * Returned Value:
+ * PLL output frequency in Hz
+ *
+ ****************************************************************************/
+
+static uint32_t k210_sysctl_pll_get_freq(uint32_t pll_base)
+{
+ uint32_t regval = getreg32(pll_base);
+ uint32_t clkr = (regval >> PLL_CLKR_SHIFT) & 0x0f;
+ uint32_t clkf = (regval >> PLL_CLKF_SHIFT) & 0x3f;
+ uint32_t clkod = (regval >> PLL_CLKOD_SHIFT) & 0x0f;
+
+ return (K210_PLL_INPUT_FREQ * (clkf + 1)) / (clkr + 1) / (clkod + 1);
+}
+
+/****************************************************************************
+ * Name: k210_clock_to_bit
+ *
+ * Description:
+ * Map a clock ID to its corresponding bit position in CLK_EN_PERI
+ * register.
+ *
+ * Input Parameters:
+ * clkid - Clock ID to map
+ * bit - Pointer to store the bit position
+ *
+ * Returned Value:
+ * OK on success, -EINVAL for invalid clock ID
+ *
+ ****************************************************************************/
+
+static int k210_clock_to_bit(k210_clockid_t clkid, uint32_t *bit)
+{
+ switch (clkid)
+ {
+ case K210_CLOCK_ROM:
+ *bit = CLK_EN_PERI_ROM_SHIFT;
+ break;
+ case K210_CLOCK_DMA:
+ *bit = CLK_EN_PERI_DMA_SHIFT;
+ break;
+ case K210_CLOCK_AI:
+ *bit = CLK_EN_PERI_AI_SHIFT;
+ break;
+ case K210_CLOCK_DVP:
+ *bit = CLK_EN_PERI_DVP_SHIFT;
+ break;
+ case K210_CLOCK_FFT:
+ *bit = CLK_EN_PERI_FFT_SHIFT;
+ break;
+ case K210_CLOCK_GPIO:
+ *bit = CLK_EN_PERI_GPIO_SHIFT;
+ break;
+ case K210_CLOCK_SPI0:
+ *bit = CLK_EN_PERI_SPI0_SHIFT;
+ break;
+ case K210_CLOCK_SPI1:
+ *bit = CLK_EN_PERI_SPI1_SHIFT;
+ break;
+ case K210_CLOCK_SPI2:
+ *bit = CLK_EN_PERI_SPI2_SHIFT;
+ break;
+ case K210_CLOCK_SPI3:
+ *bit = CLK_EN_PERI_SPI3_SHIFT;
+ break;
+ case K210_CLOCK_I2S0:
+ *bit = CLK_EN_PERI_I2S0_SHIFT;
+ break;
+ case K210_CLOCK_I2S1:
+ *bit = CLK_EN_PERI_I2S1_SHIFT;
+ break;
+ case K210_CLOCK_I2S2:
+ *bit = CLK_EN_PERI_I2S2_SHIFT;
+ break;
+ case K210_CLOCK_I2C0:
+ *bit = CLK_EN_PERI_I2C0_SHIFT;
+ break;
+ case K210_CLOCK_I2C1:
+ *bit = CLK_EN_PERI_I2C1_SHIFT;
+ break;
+ case K210_CLOCK_I2C2:
+ *bit = CLK_EN_PERI_I2C2_SHIFT;
+ break;
+ case K210_CLOCK_UART1:
+ *bit = CLK_EN_PERI_UART1_SHIFT;
+ break;
+ case K210_CLOCK_UART2:
+ *bit = CLK_EN_PERI_UART2_SHIFT;
+ break;
+ case K210_CLOCK_UART3:
+ *bit = CLK_EN_PERI_UART3_SHIFT;
+ break;
+ case K210_CLOCK_AES:
+ *bit = CLK_EN_PERI_AES_SHIFT;
+ break;
+ case K210_CLOCK_FPIOA:
+ *bit = CLK_EN_PERI_FPIOA_SHIFT;
+ break;
+ case K210_CLOCK_TIMER0:
+ *bit = CLK_EN_PERI_TIMER0_SHIFT;
+ break;
+ case K210_CLOCK_TIMER1:
+ *bit = CLK_EN_PERI_TIMER1_SHIFT;
+ break;
+ case K210_CLOCK_TIMER2:
+ *bit = CLK_EN_PERI_TIMER2_SHIFT;
+ break;
+ case K210_CLOCK_WDT0:
+ *bit = CLK_EN_PERI_WDT0_SHIFT;
+ break;
+ case K210_CLOCK_WDT1:
+ *bit = CLK_EN_PERI_WDT1_SHIFT;
+ break;
+ case K210_CLOCK_SHA:
+ *bit = CLK_EN_PERI_SHA_SHIFT;
+ break;
+ case K210_CLOCK_OTP:
+ *bit = CLK_EN_PERI_OTP_SHIFT;
+ break;
+ case K210_CLOCK_RTC:
+ *bit = CLK_EN_PERI_RTC_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: k210_reset_to_bit
+ *
+ * Description:
+ * Map a reset ID to its corresponding bit position in PERI_RESET register.
+ *
+ * Input Parameters:
+ * rstidx - Reset ID to map
+ * bit - Pointer to store the bit position
+ *
+ * Returned Value:
+ * OK on success, -EINVAL for invalid reset ID
+ *
+ ****************************************************************************/
+
+static int k210_reset_to_bit(k210_rstidx_t rstidx, uint32_t *bit)
+{
+ switch (rstidx)
+ {
+ case K210_RESET_ROM:
+ *bit = PERI_RESET_ROM_SHIFT;
+ break;
+ case K210_RESET_DMA:
+ *bit = PERI_RESET_DMA_SHIFT;
+ break;
+ case K210_RESET_AI:
+ *bit = PERI_RESET_AI_SHIFT;
+ break;
+ case K210_RESET_DVP:
+ *bit = PERI_RESET_DVP_SHIFT;
+ break;
+ case K210_RESET_FFT:
+ *bit = PERI_RESET_FFT_SHIFT;
+ break;
+ case K210_RESET_GPIO:
+ *bit = PERI_RESET_GPIO_SHIFT;
+ break;
+ case K210_RESET_SPI0:
+ *bit = PERI_RESET_SPI0_SHIFT;
+ break;
+ case K210_RESET_SPI1:
+ *bit = PERI_RESET_SPI1_SHIFT;
+ break;
+ case K210_RESET_SPI2:
+ *bit = PERI_RESET_SPI2_SHIFT;
+ break;
+ case K210_RESET_SPI3:
+ *bit = PERI_RESET_SPI3_SHIFT;
+ break;
+ case K210_RESET_I2S0:
+ *bit = PERI_RESET_I2S0_SHIFT;
+ break;
+ case K210_RESET_I2S1:
+ *bit = PERI_RESET_I2S1_SHIFT;
+ break;
+ case K210_RESET_I2S2:
+ *bit = PERI_RESET_I2S2_SHIFT;
+ break;
+ case K210_RESET_I2C0:
+ *bit = PERI_RESET_I2C0_SHIFT;
+ break;
+ case K210_RESET_I2C1:
+ *bit = PERI_RESET_I2C1_SHIFT;
+ break;
+ case K210_RESET_I2C2:
+ *bit = PERI_RESET_I2C2_SHIFT;
+ break;
+ case K210_RESET_UART1:
+ *bit = PERI_RESET_UART1_SHIFT;
+ break;
+ case K210_RESET_UART2:
+ *bit = PERI_RESET_UART2_SHIFT;
+ break;
+ case K210_RESET_UART3:
+ *bit = PERI_RESET_UART3_SHIFT;
+ break;
+ case K210_RESET_AES:
+ *bit = PERI_RESET_AES_SHIFT;
+ break;
+ case K210_RESET_FPIOA:
+ *bit = PERI_RESET_FPIOA_SHIFT;
+ break;
+ case K210_RESET_TIMER0:
+ *bit = PERI_RESET_TIMER0_SHIFT;
+ break;
+ case K210_RESET_TIMER1:
+ *bit = PERI_RESET_TIMER1_SHIFT;
+ break;
+ case K210_RESET_TIMER2:
+ *bit = PERI_RESET_TIMER2_SHIFT;
+ break;
+ case K210_RESET_WDT0:
+ *bit = PERI_RESET_WDT0_SHIFT;
+ break;
+ case K210_RESET_WDT1:
+ *bit = PERI_RESET_WDT1_SHIFT;
+ break;
+ case K210_RESET_SHA:
+ *bit = PERI_RESET_SHA_SHIFT;
+ break;
+ case K210_RESET_RTC:
+ *bit = PERI_RESET_RTC_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: k210_clock_to_apb
+ *
+ * Description:
+ * Map a clock ID to its APB bus number.
+ *
+ * APB0: GPIO, UART1-3, I2S0-2, I2C0-2, FPIOA, TIMER0-2, SHA
+ * APB1: AES, WDT0-1, OTP, RTC
+ * APB2: SPI0, SPI1
+ * Others: ROM, DMA, AI, DVP, FFT, SPI2, SPI3 do not require APB bus clock
+ *
+ * Input Parameters:
+ * clkid - Clock ID to map
+ *
+ * Returned Value:
+ * 0 for APB0, 1 for APB1, 2 for APB2, -1 if not on APB bus
+ *
+ ****************************************************************************/
+
+static int k210_clock_to_apb(k210_clockid_t clkid)
+{
+ switch (clkid)
+ {
+ case K210_CLOCK_GPIO:
+ case K210_CLOCK_UART1:
+ case K210_CLOCK_UART2:
+ case K210_CLOCK_UART3:
+ case K210_CLOCK_I2S0:
+ case K210_CLOCK_I2S1:
+ case K210_CLOCK_I2S2:
+ case K210_CLOCK_I2C0:
+ case K210_CLOCK_I2C1:
+ case K210_CLOCK_I2C2:
+ case K210_CLOCK_FPIOA:
+ case K210_CLOCK_TIMER0:
+ case K210_CLOCK_TIMER1:
+ case K210_CLOCK_TIMER2:
+ case K210_CLOCK_SHA:
+ return 0;
+
+ case K210_CLOCK_AES:
+ case K210_CLOCK_WDT0:
+ case K210_CLOCK_WDT1:
+ case K210_CLOCK_OTP:
+ case K210_CLOCK_RTC:
+ return 1;
+
+ case K210_CLOCK_SPI0:
+ case K210_CLOCK_SPI1:
+ return 2;
+
+ default:
+ return -1;
+ }
+}
+
+/****************************************************************************
+ * Name: k210_clock_is_central
+ *
+ * Description:
+ * Check if a clock ID is a central clock (CPU, SRAM, APB).
+ *
+ * Central clocks are controlled via CLK_EN_CENT register, while
+ * peripheral clocks use CLK_EN_PERI register.
+ *
+ * Input Parameters:
+ * clkid - Clock ID to check
+ *
+ * Returned Value:
+ * true if central clock, false otherwise
+ *
+ ****************************************************************************/
+
+static bool k210_clock_is_central(k210_clockid_t clkid)
+{
+ switch (clkid)
+ {
+ case K210_CLOCK_CPU:
+ case K210_CLOCK_SRAM0:
+ case K210_CLOCK_SRAM1:
+ case K210_CLOCK_APB0:
+ case K210_CLOCK_APB1:
+ case K210_CLOCK_APB2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/****************************************************************************
+ * Name: k210_clock_to_central_bit
+ *
+ * Description:
+ * Map a central clock ID to its corresponding bit mask in CLK_EN_CENT.
+ *
+ * Input Parameters:
+ * clkid - Central clock ID to map
+ * mask - Pointer to store the bit mask
+ *
+ * Returned Value:
+ * OK on success, -EINVAL for invalid clock ID
+ *
+ ****************************************************************************/
+
+static int k210_clock_to_central_bit(k210_clockid_t clkid, uint32_t *mask)
+{
+ switch (clkid)
+ {
+ case K210_CLOCK_CPU:
+ *mask = CLK_EN_CENT_CPU_MASK;
+ break;
+ case K210_CLOCK_SRAM0:
+ *mask = CLK_EN_CENT_SRAM0_MASK;
+ break;
+ case K210_CLOCK_SRAM1:
+ *mask = CLK_EN_CENT_SRAM1_MASK;
+ break;
+ case K210_CLOCK_APB0:
+ *mask = CLK_EN_CENT_APB0_MASK;
+ break;
+ case K210_CLOCK_APB1:
+ *mask = CLK_EN_CENT_APB1_MASK;
+ break;
+ case K210_CLOCK_APB2:
+ *mask = CLK_EN_CENT_APB2_MASK;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: k210_sysctl_enable_apb_clock
+ *
+ * Description:
+ * Enable the APB bus clock for a peripheral.
+ *
+ * K210 peripheral clock enable requires a two-stage process:
+ * 1. First enable the corresponding APB bus clock (CLK_EN_CENT)
+ * 2. Then enable the peripheral clock (CLK_EN_PERI)
+ *
+ * This separation prevents accidental APB clock disable when CPU
+ * manipulates peripheral clock bits.
+ *
+ * Input Parameters:
+ * clkid - Clock ID of the peripheral
+ *
+ * Returned Value:
+ * OK on success
+ *
+ ****************************************************************************/
+
+static int k210_sysctl_enable_apb_clock(k210_clockid_t clkid)
+{
+ int apb = k210_clock_to_apb(clkid);
+ uint32_t regval;
+
+ if (apb < 0)
+ {
+ return OK;
+ }
+
+ regval = getreg32(K210_SYSCTL_CLK_EN_CENT);
+
+ switch (apb)
+ {
+ case 0:
+ regval |= CLK_EN_CENT_APB0_MASK;
+ break;
+ case 1:
+ regval |= CLK_EN_CENT_APB1_MASK;
+ break;
+ case 2:
+ regval |= CLK_EN_CENT_APB2_MASK;
+ break;
+ }
+
+ putreg32(regval, K210_SYSCTL_CLK_EN_CENT);
+ return OK;
+}
+
+/****************************************************************************
+ * Name: k210_sysctl_pll_wait_lock
+ ****************************************************************************/
+
+static void k210_sysctl_pll_clear_slip(uint32_t pll_base)
+{
+ uint32_t regval;
+
+ regval = getreg32(K210_SYSCTL_PLL_LOCK);
+
+ if (pll_base == K210_SYSCTL_PLL0)
+ {
+ regval |= PLL_LOCK_PLL0_SLIP_CLR;
+ }
+ else if (pll_base == K210_SYSCTL_PLL1)
+ {
+ regval |= PLL_LOCK_PLL1_SLIP_CLR;
+ }
+ else if (pll_base == K210_SYSCTL_PLL2)
+ {
+ regval |= PLL_LOCK_PLL2_SLIP_CLR;
+ }
+
+ putreg32(regval, K210_SYSCTL_PLL_LOCK);
+}
+
+static bool k210_sysctl_pll_wait_lock(uint32_t pll_base)
+{
+ uint32_t timeout = K210_PLL_LOCK_TIMEOUT_US;
+
+ while (timeout-- > 0)
+ {
+ if (k210_sysctl_pll_is_locked(pll_base))
+ {
+ return true;
+ }
+
+ k210_sysctl_pll_clear_slip(pll_base);
+ up_udelay(1);
+ }
+
+ return false;
+}
+
+/****************************************************************************
+ * Name: k210_sysctl_pll_calc_params
+ ****************************************************************************/
+
+static bool k210_sysctl_pll_calc_params(uint32_t fin, uint32_t fout,
+ struct k210_pll_params_s *params)
+{
+ uint32_t nr;
+ uint32_t nf;
+ uint32_t od;
+ uint64_t best_err = UINT64_MAX;
+ uint64_t best_vco = 0;
+ uint32_t best_nr = 0;
+ bool found = false;
+
+ if (fout == 0 || params == NULL)
+ {
+ return false;
+ }
+
+ for (nr = K210_PLL_NR_MIN; nr <= K210_PLL_NR_MAX; nr++)
+ {
+ uint64_t ref_hz = fin / nr;
+
+ if (ref_hz < K210_PLL_REF_MIN || ref_hz > K210_PLL_REF_MAX)
+ {
+ continue;
+ }
+
+ for (nf = K210_PLL_NF_MIN; nf <= K210_PLL_NF_MAX; nf++)
+ {
+ uint64_t vco_hz = ((uint64_t)fin * nf) / nr;
+
+ if (vco_hz < K210_PLL_VCO_MIN || vco_hz > K210_PLL_VCO_MAX)
+ {
+ continue;
+ }
+
+ for (od = K210_PLL_OD_MIN; od <= K210_PLL_OD_MAX; od++)
+ {
+ uint64_t out_hz = vco_hz / od;
+ uint64_t err = out_hz > fout ? out_hz - fout : fout - out_hz;
+
+ if (!found ||
+ err < best_err ||
+ (err == best_err && vco_hz > best_vco) ||
+ (err == best_err && vco_hz == best_vco && nr < best_nr))
+ {
+ found = true;
+ best_err = err;
+ best_vco = vco_hz;
+ best_nr = nr;
+
+ params->clkr = nr - 1;
+ params->clkf = nf - 1;
+ params->clkod = od - 1;
+ params->bwadj = nf - 1;
+
+ if (err == 0)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return found;
+}
+
+/****************************************************************************
+ * Name: k210_sysctl_pll_set_freq
+ ****************************************************************************/
+
+static uint32_t k210_sysctl_pll_set_freq(uint32_t pll_base, uint32_t freq)
+{
+ struct k210_pll_params_s params =
+ {
+ 0
+ };
+
+ uint32_t regval;
+ uint32_t clksel0;
+
+ if (freq == 0 || pll_base != K210_SYSCTL_PLL0)
+ {
+ return 0;
+ }
+
+ if (!k210_sysctl_pll_calc_params(K210_PLL_INPUT_FREQ, freq, ¶ms))
+ {
+ return 0;
+ }
+
+ /* 1. Switch CPU clock to IN0 before touching PLL0 */
+
+ clksel0 = getreg32(K210_SYSCTL_CLKSEL0);
+ clksel0 &= ~CLKSEL0_ACLK_SEL_MASK;
+ putreg32(clksel0, K210_SYSCTL_CLKSEL0);
+
+ /* 2. Disable PLL output */
+
+ regval = getreg32(pll_base);
+ regval &= ~PLL_OUT_EN_MASK;
+ putreg32(regval, pll_base);
+
+ /* 3. Power down PLL */
+
+ regval &= ~PLL_PWRD_MASK;
+ putreg32(regval, pll_base);
+
+ /* 4. Program PLL parameters */
+
+ regval &= ~(PLL_CLKR_MASK | PLL_CLKF_MASK | PLL_CLKOD_MASK |
+ PLL_BWADJ_MASK | PLL_BYPASS_MASK);
+ regval |= ((uint32_t)params.clkr << PLL_CLKR_SHIFT);
+ regval |= ((uint32_t)params.clkf << PLL_CLKF_SHIFT);
+ regval |= ((uint32_t)params.clkod << PLL_CLKOD_SHIFT);
+ regval |= ((uint32_t)params.bwadj << PLL_BWADJ_SHIFT);
+ putreg32(regval, pll_base);
+
+ /* 5. Power on PLL */
+
+ regval |= PLL_PWRD_MASK;
+ putreg32(regval, pll_base);
+ up_udelay(1);
+
+ /* 6. Reset PLL and release reset */
+
+ regval &= ~PLL_RESET_MASK;
+ putreg32(regval, pll_base);
+ regval |= PLL_RESET_MASK;
+ putreg32(regval, pll_base);
+ up_udelay(1);
+ regval &= ~PLL_RESET_MASK;
+ putreg32(regval, pll_base);
+
+ /* 7. Wait lock with timeout */
+
+ if (!k210_sysctl_pll_wait_lock(pll_base))
+ {
+ return 0;
+ }
+
+ /* 8. Enable PLL output */
+
+ regval = getreg32(pll_base);
+ regval |= PLL_OUT_EN_MASK;
+ putreg32(regval, pll_base);
+
+ /* 9. Switch CPU clock back to PLL0 */
+
+ clksel0 = getreg32(K210_SYSCTL_CLKSEL0);
+ clksel0 |= CLKSEL0_ACLK_SEL_MASK;
+ putreg32(clksel0, K210_SYSCTL_CLKSEL0);
+
+ return k210_sysctl_pll_get_freq(pll_base);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: k210_sysctl_pll_is_locked
+ *
+ * Description:
+ * Check if a PLL is locked by reading the PLL_LOCK register.
+ *
+ * PLL0 requires both overflow and slip bits to be set (value == 3).
+ * PLL1 and PLL2 only check the lock bit.
+ *
+ * Input Parameters:
+ * pll_offset - PLL register offset (K210_SYSCTL_PLL0/PLL1/PLL2)
+ *
+ * Returned Value:
+ * true if PLL is locked, false otherwise
+ *
+ ****************************************************************************/
+
+bool k210_sysctl_pll_is_locked(uint32_t pll_offset)
+{
+ uint32_t regval = getreg32(K210_SYSCTL_PLL_LOCK);
+
+ if (pll_offset == K210_SYSCTL_PLL0)
+ {
+ return (regval & (1 << PLL_LOCK_PLL0_SHIFT)) != 0;
+ }
+ else if (pll_offset == K210_SYSCTL_PLL1)
+ {
+ return (regval & PLL_LOCK_PLL1_MASK) == PLL_LOCK_PLL1_LOCKED;
+ }
+ else if (pll_offset == K210_SYSCTL_PLL2)
+ {
+ return (regval & PLL_LOCK_PLL2_MASK) == PLL_LOCK_PLL2_LOCKED;
+ }
+
+ return false;
+}
+
+/****************************************************************************
+ * Name: k210_sysctl_init
+ ****************************************************************************/
+
+void k210_sysctl_init(void)
+{
+ uint32_t clksel0;
+
+ /* Default to ACLK divider /2 */
+
+ clksel0 = getreg32(K210_SYSCTL_CLKSEL0);
+ clksel0 &= ~CLKSEL0_ACLK_DIV_MASK;
+ putreg32(clksel0, K210_SYSCTL_CLKSEL0);
+
+ k210_sysctl_cpu_set_freq(CONFIG_K210_CPU_FREQ);
+}
+
+/****************************************************************************
+ * Name: k210_sysctl_cpu_get_freq
+ ****************************************************************************/
+
+uint32_t k210_sysctl_cpu_get_freq(void)
+{
+ return k210_sysctl_clock_get_freq(K210_CLOCK_CPU);
+}
+
+/****************************************************************************
+ * Name: k210_sysctl_cpu_set_freq
+ ****************************************************************************/
+
+uint32_t k210_sysctl_cpu_set_freq(uint32_t freq)
+{
+ uint32_t actual_pll;
+ uint32_t actual;
+ uint32_t clksel0;
+ uint32_t div_sel;
+ uint32_t target_pll;
+ uint64_t target64;
+
+ if (freq == 0)
+ {
+ return 0;
+ }
+
+ clksel0 = getreg32(K210_SYSCTL_CLKSEL0);
+ div_sel = (clksel0 & CLKSEL0_ACLK_DIV_MASK) >> CLKSEL0_ACLK_DIV_SHIFT;
+ target64 = (uint64_t)freq * (uint64_t)(2u << div_sel);
+ if (target64 > UINT32_MAX)
+ {
+ return 0;
+ }
+
+ target_pll = (uint32_t)target64;
+ actual_pll = k210_sysctl_pll_set_freq(K210_SYSCTL_PLL0, target_pll);
+
+ if (actual_pll == 0)
+ {
+ return 0;
+ }
+
+ actual = k210_sysctl_clock_get_freq(K210_CLOCK_CPU);
+ return actual;
+}
+
+/****************************************************************************
+ * Name: k210_sysctl_clock_enable
+ *
+ * Description:
+ * Enable a peripheral or central clock.
+ *
+ * For peripheral clocks, this follows a two-stage process:
+ * 1. Enable the corresponding APB bus clock (CLK_EN_CENT)
+ * 2. Enable the peripheral clock (CLK_EN_PERI)
+ *
+ * For central clocks (CPU, SRAM, APB), only CLK_EN_CENT is used.
+ *
+ * Input Parameters:
+ * clkid - Clock ID to enable
+ *
+ * Returned Value:
+ * OK on success, negative error code on failure
+ *
+ ****************************************************************************/
+
+int k210_sysctl_clock_enable(k210_clockid_t clkid)
+{
+ uint32_t regval;
+ uint32_t bit;
+ int ret;
+
+ if (clkid >= K210_CLOCK_MAX)
+ {
+ return -EINVAL;
+ }
+
+ if (k210_clock_is_central(clkid))
+ {
+ ret = k210_clock_to_central_bit(clkid, &bit);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ regval = getreg32(K210_SYSCTL_CLK_EN_CENT);
+ regval |= bit;
+ putreg32(regval, K210_SYSCTL_CLK_EN_CENT);
+ }
+ else
+ {
+ ret = k210_sysctl_enable_apb_clock(clkid);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ ret = k210_clock_to_bit(clkid, &bit);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ regval = getreg32(K210_SYSCTL_CLK_EN_PERI);
+ regval |= (1 << bit);
+ putreg32(regval, K210_SYSCTL_CLK_EN_PERI);
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: k210_sysctl_clock_disable
+ *
+ * Description:
+ * Disable a peripheral or central clock.
+ *
+ * For peripheral clocks, only the peripheral clock is disabled.
+ * The APB bus clock is kept enabled to protect other peripherals
+ * on the same bus.
+ *
+ * For central clocks (CPU, SRAM, APB), CLK_EN_CENT is used.
+ * WARNING: Disabling central clocks can cause system instability.
+ *
+ * Input Parameters:
+ * clkid - Clock ID to disable
+ *
+ * Returned Value:
+ * OK on success, negative error code on failure
+ *
+ ****************************************************************************/
+
+int k210_sysctl_clock_disable(k210_clockid_t clkid)
+{
+ uint32_t regval;
+ uint32_t bit;
+ int ret;
+
+ if (clkid >= K210_CLOCK_MAX)
+ {
+ return -EINVAL;
+ }
+
+ if (k210_clock_is_central(clkid))
+ {
+ ret = k210_clock_to_central_bit(clkid, &bit);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ regval = getreg32(K210_SYSCTL_CLK_EN_CENT);
+ regval &= ~bit;
+ putreg32(regval, K210_SYSCTL_CLK_EN_CENT);
+ }
+ else
+ {
+ ret = k210_clock_to_bit(clkid, &bit);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ regval = getreg32(K210_SYSCTL_CLK_EN_PERI);
+ regval &= ~(1 << bit);
+ putreg32(regval, K210_SYSCTL_CLK_EN_PERI);
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: k210_sysctl_clock_get_freq
+ *
+ * Description:
+ * Get the frequency of a clock.
+ *
+ * Clock frequency relationships:
+ * - PLL0/1/2: Calculated from PLL registers
+ * - CPU (ACLK): PLL0 / (2^aclk_div) when aclk_sel=1, else IN0
+ * - APB0: ACLK / (apb0_div + 1)
+ * - APB1: ACLK / (apb1_div + 1)
+ * - APB2: ACLK / (apb2_div + 1)
+ * - Peripheral clocks: Equal to corresponding APB bus frequency
+ *
+ * Input Parameters:
+ * clkid - Clock ID to query
+ *
+ * Returned Value:
+ * Clock frequency in Hz, 0 for unknown clocks
+ *
+ ****************************************************************************/
+
+uint32_t k210_sysctl_clock_get_freq(k210_clockid_t clkid)
+{
+ uint32_t regval;
+ uint32_t div;
+ uint32_t source;
+
+ switch (clkid)
+ {
+ case K210_CLOCK_PLL0:
+ return k210_sysctl_pll_get_freq(K210_SYSCTL_PLL0);
+
+ case K210_CLOCK_PLL1:
+ return k210_sysctl_pll_get_freq(K210_SYSCTL_PLL1);
+
+ case K210_CLOCK_PLL2:
+ return k210_sysctl_pll_get_freq(K210_SYSCTL_PLL2);
+
+ case K210_CLOCK_CPU:
+ regval = getreg32(K210_SYSCTL_CLKSEL0);
+ if (regval & CLKSEL0_ACLK_SEL_MASK)
+ {
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL0);
+ div = (regval >> CLKSEL0_ACLK_DIV_SHIFT) & 0x03;
+ return source / (2u << div);
+ }
+ else
+ {
+ return K210_PLL_INPUT_FREQ;
+ }
+
+ case K210_CLOCK_SRAM0:
+ source = k210_sysctl_clock_get_freq(K210_CLOCK_CPU);
+ regval = getreg32(K210_SYSCTL_CLK_TH0);
+ div = (regval >> CLK_TH0_SRAM0_SHIFT) & 0x0f;
+ return source / (div + 1);
+
+ case K210_CLOCK_SRAM1:
+ source = k210_sysctl_clock_get_freq(K210_CLOCK_CPU);
+ regval = getreg32(K210_SYSCTL_CLK_TH0);
+ div = (regval >> CLK_TH0_SRAM1_SHIFT) & 0x0f;
+ return source / (div + 1);
+
+ case K210_CLOCK_ROM:
+ source = k210_sysctl_clock_get_freq(K210_CLOCK_CPU);
+ regval = getreg32(K210_SYSCTL_CLK_TH0);
+ div = (regval >> CLK_TH0_ROM_SHIFT) & 0x0f;
+ return source / (div + 1);
+
+ case K210_CLOCK_DVP:
+ source = k210_sysctl_clock_get_freq(K210_CLOCK_CPU);
+ regval = getreg32(K210_SYSCTL_CLK_TH0);
+ div = (regval >> CLK_TH0_DVP_SHIFT) & 0x0f;
+ return source / (div + 1);
+
+ case K210_CLOCK_APB0:
+ source = k210_sysctl_clock_get_freq(K210_CLOCK_CPU);
+ regval = getreg32(K210_SYSCTL_CLKSEL0);
+ div = (regval >> CLKSEL0_APB0_DIV_SHIFT) & 0x07;
+ return source / (div + 1);
+
+ case K210_CLOCK_APB1:
+ source = k210_sysctl_clock_get_freq(K210_CLOCK_CPU);
+ regval = getreg32(K210_SYSCTL_CLKSEL0);
+ div = (regval >> CLKSEL0_APB1_DIV_SHIFT) & 0x07;
+ return source / (div + 1);
+
+ case K210_CLOCK_APB2:
+ source = k210_sysctl_clock_get_freq(K210_CLOCK_CPU);
+ regval = getreg32(K210_SYSCTL_CLKSEL0);
+ div = (regval >> CLKSEL0_APB2_DIV_SHIFT) & 0x07;
+ return source / (div + 1);
+
+ case K210_CLOCK_AI:
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL1);
+ regval = getreg32(K210_SYSCTL_CLK_TH0);
+ div = (regval >> CLK_TH0_AI_SHIFT) & 0x0f;
+ return source / (div + 1);
+
+ case K210_CLOCK_DMA:
+ case K210_CLOCK_FFT:
+ return k210_sysctl_clock_get_freq(K210_CLOCK_CPU);
+
+ case K210_CLOCK_SPI0:
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL0);
+ regval = getreg32(K210_SYSCTL_CLK_TH1);
+ div = (regval >> CLK_TH1_SPI0_SHIFT) & 0xff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_SPI1:
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL0);
+ regval = getreg32(K210_SYSCTL_CLK_TH1);
+ div = (regval >> CLK_TH1_SPI1_SHIFT) & 0xff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_SPI2:
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL0);
+ regval = getreg32(K210_SYSCTL_CLK_TH1);
+ div = (regval >> CLK_TH1_SPI2_SHIFT) & 0xff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_SPI3:
+ regval = getreg32(K210_SYSCTL_CLKSEL0);
+ if ((regval >> 12) & 0x01)
+ {
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL0);
+ }
+ else
+ {
+ source = K210_PLL_INPUT_FREQ;
+ }
+
+ regval = getreg32(K210_SYSCTL_CLK_TH1);
+ div = (regval >> CLK_TH1_SPI3_SHIFT) & 0xff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_I2S0:
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL2);
+ regval = getreg32(K210_SYSCTL_CLK_TH3);
+ div = (regval >> CLK_TH3_I2S0_SHIFT) & 0xffff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_I2S1:
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL2);
+ regval = getreg32(K210_SYSCTL_CLK_TH3);
+ div = (regval >> CLK_TH3_I2S1_SHIFT) & 0xffff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_I2S2:
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL2);
+ regval = getreg32(K210_SYSCTL_CLK_TH4);
+ div = (regval >> CLK_TH4_I2S2_SHIFT) & 0xffff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_I2C0:
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL0);
+ regval = getreg32(K210_SYSCTL_CLK_TH5);
+ div = (regval >> CLK_TH5_I2C0_SHIFT) & 0xff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_I2C1:
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL0);
+ regval = getreg32(K210_SYSCTL_CLK_TH5);
+ div = (regval >> CLK_TH5_I2C1_SHIFT) & 0xff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_I2C2:
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL0);
+ regval = getreg32(K210_SYSCTL_CLK_TH5);
+ div = (regval >> CLK_TH5_I2C2_SHIFT) & 0xff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_TIMER0:
+ regval = getreg32(K210_SYSCTL_CLKSEL0);
+ if ((regval >> 13) & 0x01)
+ {
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL0);
+ }
+ else
+ {
+ source = K210_PLL_INPUT_FREQ;
+ }
+
+ regval = getreg32(K210_SYSCTL_CLK_TH2);
+ div = (regval >> CLK_TH2_TIMER0_SHIFT) & 0xff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_TIMER1:
+ regval = getreg32(K210_SYSCTL_CLKSEL0);
+ if ((regval >> 14) & 0x01)
+ {
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL0);
+ }
+ else
+ {
+ source = K210_PLL_INPUT_FREQ;
+ }
+
+ regval = getreg32(K210_SYSCTL_CLK_TH2);
+ div = (regval >> CLK_TH2_TIMER1_SHIFT) & 0xff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_TIMER2:
+ regval = getreg32(K210_SYSCTL_CLKSEL0);
+ if ((regval >> 15) & 0x01)
+ {
+ source = k210_sysctl_pll_get_freq(K210_SYSCTL_PLL0);
+ }
+ else
+ {
+ source = K210_PLL_INPUT_FREQ;
+ }
+
+ regval = getreg32(K210_SYSCTL_CLK_TH2);
+ div = (regval >> CLK_TH2_TIMER2_SHIFT) & 0xff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_WDT0:
+ source = K210_PLL_INPUT_FREQ;
+ regval = getreg32(K210_SYSCTL_CLK_TH6);
+ div = (regval >> CLK_TH6_WDT0_SHIFT) & 0xff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_WDT1:
+ source = K210_PLL_INPUT_FREQ;
+ regval = getreg32(K210_SYSCTL_CLK_TH6);
+ div = (regval >> CLK_TH6_WDT1_SHIFT) & 0xff;
+ return source / ((div + 1) * 2);
+
+ case K210_CLOCK_GPIO:
+ case K210_CLOCK_UART1:
+ case K210_CLOCK_UART2:
+ case K210_CLOCK_UART3:
+ case K210_CLOCK_FPIOA:
+ case K210_CLOCK_SHA:
+ return k210_sysctl_clock_get_freq(K210_CLOCK_APB0);
+
+ case K210_CLOCK_AES:
+ case K210_CLOCK_OTP:
+ return k210_sysctl_clock_get_freq(K210_CLOCK_APB1);
+
+ case K210_CLOCK_RTC:
+ return K210_PLL_INPUT_FREQ;
+
+ default:
+ return 0;
+ }
+}
+
+/****************************************************************************
+ * Name: k210_sysctl_reset
+ *
+ * Description:
+ * Reset a peripheral with proper timing sequence.
+ *
+ * Reset sequence:
+ * 1. Assert reset (set bit in PERI_RESET register)
+ * 2. Wait approximately 10us for reset to take effect
+ * 3. Deassert reset (clear bit in PERI_RESET register)
+ *
+ * Input Parameters:
+ * rstidx - Reset ID of the peripheral to reset
+ *
+ * Returned Value:
+ * OK on success, negative error code on failure
+ *
+ ****************************************************************************/
+
+int k210_sysctl_reset(k210_rstidx_t rstidx)
+{
+ uint32_t bit;
+ uint32_t regval;
+ int ret;
+
+ ret = k210_reset_to_bit(rstidx, &bit);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ regval = getreg32(K210_SYSCTL_PERI_RESET);
+
+ regval |= (1 << bit);
+ putreg32(regval, K210_SYSCTL_PERI_RESET);
+
+ up_udelay(10);
+
+ regval &= ~(1 << bit);
+ putreg32(regval, K210_SYSCTL_PERI_RESET);
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: k210_sysctl_init_peripheral
+ *
+ * Description:
+ * Initialize a peripheral by enabling its clock and releasing from reset.
+ *
+ * This is a convenience function that combines clock enable and reset
+ * operations for simplified peripheral initialization.
+ *
+ * Typical usage:
+ * k210_sysctl_init_peripheral(K210_CLOCK_UART1, K210_RESET_UART1);
+ * k210_sysctl_init_peripheral(K210_CLOCK_SPI0, K210_RESET_SPI0);
+ *
+ * Input Parameters:
+ * clkid - Clock ID of the peripheral
+ * rstidx - Reset ID of the peripheral
+ *
+ * Returned Value:
+ * OK on success, negative error code on failure
+ *
+ ****************************************************************************/
+
+int k210_sysctl_init_peripheral(k210_clockid_t clkid, k210_rstidx_t rstidx)
+{
+ int ret;
+
+ ret = k210_sysctl_clock_enable(clkid);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ ret = k210_sysctl_reset(rstidx);
+ if (ret < 0)
+ {
+ k210_sysctl_clock_disable(clkid);
+ return ret;
+ }
+
+ return OK;
+}
diff --git a/arch/risc-v/src/k210/k210_sysctl.h
b/arch/risc-v/src/k210/k210_sysctl.h
new file mode 100644
index 00000000000..ad02ac13247
--- /dev/null
+++ b/arch/risc-v/src/k210/k210_sysctl.h
@@ -0,0 +1,239 @@
+/****************************************************************************
+ * arch/risc-v/src/k210/k210_sysctl.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __ARCH_RISCV_SRC_K210_K210_SYSCTL_H
+#define __ARCH_RISCV_SRC_K210_K210_SYSCTL_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/**
+ * @brief K210 peripheral clock ID enumeration
+ *
+ * This enumeration defines all clock IDs for K210 peripherals.
+ * These IDs are used to enable/disable clocks and get clock frequencies.
+ */
+
+typedef enum
+{
+ K210_CLOCK_PLL0 = 0, /* PLL0 clock */
+ K210_CLOCK_PLL1, /* PLL1 clock */
+ K210_CLOCK_PLL2, /* PLL2 clock */
+ K210_CLOCK_CPU, /* CPU clock */
+ K210_CLOCK_SRAM0, /* SRAM0 clock */
+ K210_CLOCK_SRAM1, /* SRAM1 clock */
+ K210_CLOCK_APB0, /* APB0 bus clock */
+ K210_CLOCK_APB1, /* APB1 bus clock */
+ K210_CLOCK_APB2, /* APB2 bus clock */
+ K210_CLOCK_ROM, /* ROM clock */
+ K210_CLOCK_DMA, /* DMA clock */
+ K210_CLOCK_AI, /* AI accelerator clock */
+ K210_CLOCK_DVP, /* DVP camera interface clock */
+ K210_CLOCK_FFT, /* FFT accelerator clock */
+ K210_CLOCK_GPIO, /* GPIO clock */
+ K210_CLOCK_SPI0, /* SPI0 clock */
+ K210_CLOCK_SPI1, /* SPI1 clock */
+ K210_CLOCK_SPI2, /* SPI2 clock */
+ K210_CLOCK_SPI3, /* SPI3 clock */
+ K210_CLOCK_I2S0, /* I2S0 clock */
+ K210_CLOCK_I2S1, /* I2S1 clock */
+ K210_CLOCK_I2S2, /* I2S2 clock */
+ K210_CLOCK_I2C0, /* I2C0 clock */
+ K210_CLOCK_I2C1, /* I2C1 clock */
+ K210_CLOCK_I2C2, /* I2C2 clock */
+ K210_CLOCK_UART1, /* UART1 clock */
+ K210_CLOCK_UART2, /* UART2 clock */
+ K210_CLOCK_UART3, /* UART3 clock */
+ K210_CLOCK_AES, /* AES accelerator clock */
+ K210_CLOCK_FPIOA, /* FPIOA (GPIO mux) clock */
+ K210_CLOCK_TIMER0, /* TIMER0 clock */
+ K210_CLOCK_TIMER1, /* TIMER1 clock */
+ K210_CLOCK_TIMER2, /* TIMER2 clock */
+ K210_CLOCK_WDT0, /* Watchdog timer 0 clock */
+ K210_CLOCK_WDT1, /* Watchdog timer 1 clock */
+ K210_CLOCK_SHA, /* SHA accelerator clock */
+ K210_CLOCK_OTP, /* OTP (one-time programmable) clock */
+ K210_CLOCK_RTC, /* RTC clock */
+ K210_CLOCK_MAX /* Boundary check value */
+} k210_clockid_t;
+
+/**
+ * @brief K210 peripheral reset ID enumeration
+ *
+ * This enumeration defines all reset IDs for K210 peripherals.
+ * These IDs are used to reset individual peripherals.
+ */
+
+typedef enum
+{
+ K210_RESET_SOC = 0, /* SOC reset */
+ K210_RESET_ROM, /* ROM reset */
+ K210_RESET_DMA, /* DMA reset */
+ K210_RESET_AI, /* AI accelerator reset */
+ K210_RESET_DVP, /* DVP camera interface reset */
+ K210_RESET_FFT, /* FFT accelerator reset */
+ K210_RESET_GPIO, /* GPIO reset */
+ K210_RESET_SPI0, /* SPI0 reset */
+ K210_RESET_SPI1, /* SPI1 reset */
+ K210_RESET_SPI2, /* SPI2 reset */
+ K210_RESET_SPI3, /* SPI3 reset */
+ K210_RESET_I2S0, /* I2S0 reset */
+ K210_RESET_I2S1, /* I2S1 reset */
+ K210_RESET_I2S2, /* I2S2 reset */
+ K210_RESET_I2C0, /* I2C0 reset */
+ K210_RESET_I2C1, /* I2C1 reset */
+ K210_RESET_I2C2, /* I2C2 reset */
+ K210_RESET_UART1, /* UART1 reset */
+ K210_RESET_UART2, /* UART2 reset */
+ K210_RESET_UART3, /* UART3 reset */
+ K210_RESET_AES, /* AES accelerator reset */
+ K210_RESET_FPIOA, /* FPIOA reset */
+ K210_RESET_TIMER0, /* TIMER0 reset */
+ K210_RESET_TIMER1, /* TIMER1 reset */
+ K210_RESET_TIMER2, /* TIMER2 reset */
+ K210_RESET_WDT0, /* Watchdog timer 0 reset */
+ K210_RESET_WDT1, /* Watchdog timer 1 reset */
+ K210_RESET_SHA, /* SHA accelerator reset */
+ K210_RESET_RTC, /* RTC reset */
+ K210_RESET_MAX /* Boundary check value */
+} k210_rstidx_t;
+
+#ifndef __ASSEMBLY__
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/**
+ * @brief Initialize the system controller
+ *
+ * This function initializes the K210 system controller, including
+ * PLL configuration and clock tree setup.
+ */
+
+EXTERN void k210_sysctl_init(void);
+
+/**
+ * @brief Enable a peripheral clock
+ *
+ * @param clkid Clock ID of the peripheral
+ * @return 0 on success, negative error code on failure
+ */
+
+EXTERN int k210_sysctl_clock_enable(k210_clockid_t clkid);
+
+/**
+ * @brief Disable a peripheral clock
+ *
+ * @param clkid Clock ID of the peripheral
+ * @return 0 on success, negative error code on failure
+ */
+
+EXTERN int k210_sysctl_clock_disable(k210_clockid_t clkid);
+
+/**
+ * @brief Get the frequency of a peripheral clock
+ *
+ * @param clkid Clock ID of the peripheral
+ * @return Clock frequency in Hz, 0 on error
+ */
+
+EXTERN uint32_t k210_sysctl_clock_get_freq(k210_clockid_t clkid);
+
+/**
+ * @brief Get current CPU clock frequency
+ *
+ * @return CPU clock frequency in Hz
+ */
+
+EXTERN uint32_t k210_sysctl_cpu_get_freq(void);
+
+/**
+ * @brief Set CPU clock frequency (SDK-like)
+ *
+ * This function adjusts PLL0 and keeps current ACLK divider setting.
+ * The final frequency may be near the target due to integer PLL factors.
+ *
+ * @param freq Target CPU frequency in Hz
+ * @return Actual CPU clock frequency in Hz, 0 on failure
+ */
+
+EXTERN uint32_t k210_sysctl_cpu_set_freq(uint32_t freq);
+
+/**
+ * @brief Reset a peripheral
+ *
+ * @param rstidx Reset ID of the peripheral
+ * @return 0 on success, negative error code on failure
+ */
+
+EXTERN int k210_sysctl_reset(k210_rstidx_t rstidx);
+
+/**
+ * @brief Initialize a peripheral (enable clock and deassert reset)
+ *
+ * This is a convenience function that enables the clock and releases
+ * the peripheral from reset state.
+ *
+ * @param clkid Clock ID of the peripheral
+ * @param rstidx Reset ID of the peripheral
+ * @return 0 on success, negative error code on failure
+ */
+
+EXTERN int k210_sysctl_init_peripheral(k210_clockid_t clkid,
+ k210_rstidx_t rstidx);
+
+/**
+ * @brief Check if a PLL is locked
+ *
+ * @param pll_offset PLL register offset (K210_SYSCTL_PLL0/PLL1/PLL2)
+ * @return true if PLL is locked, false otherwise
+ */
+
+EXTERN bool k210_sysctl_pll_is_locked(uint32_t pll_offset);
+
+#if defined(__cplusplus)
+}
+#endif
+#undef EXTERN
+
+#endif /* __ASSEMBLY__ */
+#endif /* __ARCH_RISCV_SRC_K210_K210_SYSCTL_H */