Skip to content

WCH: ch32v interrupt/timer fixes #91966

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions drivers/clock_control/clock_control_wch_rcc.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ static int clock_control_wch_rcc_init(const struct device *dev)
RCC->CFGR0 |= RCC_PLLSRC;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A suggestion for the subject line: "drivers: clock_control: disable the HSI PLL prescaler"

That describes what the change does, and the body can describe why such as "For consistency with other SoCs in the family, ..."

Note that this PR does change behaviour. Do a quick grep of the current boards and see if anyone is using the hsi.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one using it seems to be ch32v003f4p6_dev_board, which as you noted, doesn't have this prescalar. I've added that preprocessor conditional to catch that.

} else if (IS_ENABLED(WCH_RCC_PLL_SRC_IS_HSI)) {
RCC->CFGR0 &= ~RCC_PLLSRC;
#if defined(EXTEN_PLL_HSI_PRE)
EXTEN->EXTEN_CTR |= EXTEN_PLL_HSI_PRE;
#endif
}
RCC->CFGR0 |= (config->mul == 18 ? 0xF : (config->mul - 2)) << 0x12;
RCC->CTLR |= RCC_PLLON;
Expand Down
4 changes: 0 additions & 4 deletions drivers/interrupt_controller/intc_wch_pfic.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ int arch_irq_is_enabled(unsigned int irq)

static int pfic_init(void)
{
/* `wfi` is called with interrupts disabled. Configure the PFIC to wake up on any event,
* including any interrupt.
*/
PFIC->SCTLR = SEVONPEND | WFITOWFE;
return 0;
}

Expand Down
1 change: 1 addition & 0 deletions drivers/timer/Kconfig.wch_ch32v00x
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ config CH32V00X_SYSTICK
depends on SOC_SERIES_QINGKE_V2A || SOC_SERIES_QINGKE_V4C || SOC_SERIES_CH32V00X || SOC_SERIES_QINGKE_V4B || SOC_SERIES_QINGKE_V4F
default y
depends on DT_HAS_WCH_SYSTICK_ENABLED
select TIMER_HAS_64BIT_CYCLE_COUNTER
61 changes: 54 additions & 7 deletions drivers/timer/wch_systick_ch32v00x.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

#include <hal_ch32fun.h>

#define STK_SWIE BIT(31)
#define STK_STRE BIT(3)
#define STK_STCLK BIT(2)
#define STK_STIE BIT(1)
#define STK_STE BIT(0)
Expand All @@ -27,20 +25,68 @@

#define SYSTICK ((SysTick_Type *)(DT_INST_REG_ADDR(0)))

static volatile uint32_t ch32v00x_systick_count;
static uint64_t last_cycles_announced;

static inline bool cycles_close_to_next_cmp(uint32_t cycles)
{
return (cycles % CYCLES_PER_TICK) > (9 * CYCLES_PER_TICK / 10);
}

static void ch32v00x_systick_irq(const void *unused)
{
uint64_t elapsed_cycles;
uint32_t ticks = 0;
uint64_t cnt = SYSTICK->CNT;

ARG_UNUSED(unused);

if (cnt < last_cycles_announced) {
elapsed_cycles = (UINT64_MAX - last_cycles_announced) + cnt;
ticks = elapsed_cycles / CYCLES_PER_TICK;

/* If we're too close to the next tick, announce that tick early now rather than
* miss it
*/
if (cycles_close_to_next_cmp(elapsed_cycles % CYCLES_PER_TICK)) {
ticks++;
last_cycles_announced = (cnt % CYCLES_PER_TICK) + CYCLES_PER_TICK;
} else {
last_cycles_announced = cnt % CYCLES_PER_TICK;
}
} else {
ticks = (cnt - last_cycles_announced) / CYCLES_PER_TICK;

/* If we're too close to the next tick, announce that tick early now rather than
* miss it
*/
if (cycles_close_to_next_cmp(cnt - last_cycles_announced)) {
ticks++;
}

last_cycles_announced += ticks * CYCLES_PER_TICK;
}


/* Ensure we trigger when CNT resets to zero */
if (UINT64_MAX - SYSTICK->CMP < CYCLES_PER_TICK) {
SYSTICK->CMP = SYSTICK->CMP % CYCLES_PER_TICK;
} else {
SYSTICK->CMP = (last_cycles_announced + CYCLES_PER_TICK);
}

SYSTICK->SR = 0;
ch32v00x_systick_count += CYCLES_PER_TICK; /* Track cycles. */
sys_clock_announce(1); /* Poke the scheduler. */

sys_clock_announce(ticks);
}

uint32_t sys_clock_cycle_get_32(void)
{
return ch32v00x_systick_count + SYSTICK->CNT;
return (uint32_t)SYSTICK->CNT;
}

uint64_t sys_clock_cycle_get_64(void)
{
return SYSTICK->CNT;
}

uint32_t sys_clock_elapsed(void)
Expand All @@ -55,10 +101,11 @@ static int ch32v00x_systick_init(void)
SYSTICK->SR = 0;
SYSTICK->CMP = CYCLES_PER_TICK;
SYSTICK->CNT = 0;
SYSTICK->CTLR = STK_STRE | STK_STCLK | STK_STIE | STK_STE;

irq_enable(DT_INST_IRQN(0));

SYSTICK->CTLR = STK_STE | STK_STCLK | STK_STIE;

return 0;
}

Expand Down
1 change: 1 addition & 0 deletions soc/wch/ch32v/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) 2024 Michael Hope
# SPDX-License-Identifier: Apache-2.0

add_subdirectory(common)
add_subdirectory(${SOC_SERIES})

set(SOC_LINKER_SCRIPT ${ZEPHYR_BASE}/include/zephyr/arch/riscv/common/linker.ld CACHE INTERNAL "")
6 changes: 6 additions & 0 deletions soc/wch/ch32v/common/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) 2025 Pete Johanson
# SPDX-License-Identifier: Apache-2.0

zephyr_sources(
soc_idle.c
)
28 changes: 28 additions & 0 deletions soc/wch/ch32v/common/soc_idle.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2025 Michael Hope <michaelh@juju.nz>
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/irq.h>
#include <zephyr/tracing/tracing.h>

void arch_cpu_idle(void)
{
/*
* The RISC-V Machine-Level ISA section 3.3.3 says that `wfi` will complete even if
* interrupts are masked, but the QingKe V2A does not do this. Work-around by enabling
* interrupts first.
*/
sys_trace_idle();
irq_unlock(MSTATUS_IEN);
__asm__ volatile("wfi");
sys_trace_idle_exit();
}

void arch_cpu_atomic_idle(unsigned int key)
{
sys_trace_idle();
irq_unlock(key);
__asm__ volatile("wfi");
sys_trace_idle_exit();
}
3 changes: 3 additions & 0 deletions soc/wch/ch32v/qingke_v2a/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ config SOC_SERIES_QINGKE_V2A
select RISCV_ISA_EXT_ZICSR
select RISCV_ISA_EXT_ZIFENCEI
select RISCV_ISA_EXT_C
select RISCV_ALWAYS_SWITCH_THROUGH_ECALL
select ARCH_HAS_CUSTOM_CPU_IDLE
select ARCH_HAS_CUSTOM_CPU_ATOMIC_IDLE
3 changes: 3 additions & 0 deletions soc/wch/ch32v/qingke_v4b/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ config SOC_SERIES_QINGKE_V4B
select RISCV_ISA_EXT_C
select RISCV_ISA_EXT_ZICSR
select RISCV_ISA_EXT_ZIFENCEI
select RISCV_ALWAYS_SWITCH_THROUGH_ECALL
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed on the v2a, but we didn't see the same lockup on the v2c or v4f. Could you confirm that this is needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've verified on v4f (CH32V305), but don't have v2c on hand to verify. Would appreciate help testing, otherwise I can remove this from there for now.

select ARCH_HAS_CUSTOM_CPU_IDLE
select ARCH_HAS_CUSTOM_CPU_ATOMIC_IDLE
3 changes: 3 additions & 0 deletions soc/wch/ch32v/qingke_v4c/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ config SOC_SERIES_QINGKE_V4C
select RISCV_ISA_EXT_C
select RISCV_ISA_EXT_ZICSR
select RISCV_ISA_EXT_ZIFENCEI
select RISCV_ALWAYS_SWITCH_THROUGH_ECALL
select ARCH_HAS_CUSTOM_CPU_IDLE
select ARCH_HAS_CUSTOM_CPU_ATOMIC_IDLE
3 changes: 3 additions & 0 deletions soc/wch/ch32v/qingke_v4f/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ config SOC_SERIES_QINGKE_V4F
select RISCV_ISA_EXT_F
select RISCV_ISA_EXT_ZICSR
select RISCV_ISA_EXT_ZIFENCEI
select RISCV_ALWAYS_SWITCH_THROUGH_ECALL
select ARCH_HAS_CUSTOM_CPU_IDLE
select ARCH_HAS_CUSTOM_CPU_ATOMIC_IDLE
3 changes: 3 additions & 0 deletions soc/wch/ch32v/qingke_v4f/Kconfig.defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ config SYS_CLOCK_HW_CYCLES_PER_SEC
config CLOCK_CONTROL
default y

config ISR_TABLES_LOCAL_DECLARATION_SUPPORTED
default n

rsource "Kconfig.defconfig.*"

endif # SOC_SERIES_QINGKE_V4F
7 changes: 6 additions & 1 deletion soc/wch/ch32v/qingke_v4f/vector.S
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ SECTION_FUNC(vectors, ivt)
lui x5, 0x8000
jr 0x8(x5)
j __start
_irq_vector_table:
.rept CONFIG_VECTOR_TABLE_SIZE
.word _isr_wrapper
.endr

SECTION_FUNC(vectors, __start)
li a0, 0xf
li a0, 0x1f
csrw 0xbc0, a0
li a0, 0x1e
csrw 0x804, a0
li a0, 0xf
csrw mtvec, a0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to double check: on the v2a and v2c, mtvec must be aligned to a 1 KiB boundrary else it will halt. Does the v4f support looser alignments?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am actually going to revert this particular bit. I plan to work on support for flashing with different offsets to support using tinyuf2, but don't need to wrap that work into this PR.

j __initialize