driver: interrupt_controller: intc_clic: rework to standard CLIC driver

Rework intc_clic to standard CLIC driver with Nuclei ECLIC extention.

Signed-off-by: Jimmy Zheng <jimmyzhe@andestech.com>
This commit is contained in:
Jimmy Zheng 2025-02-11 14:42:53 +08:00 committed by Benjamin Cabé
commit 9349d54074
7 changed files with 166 additions and 75 deletions

View file

@ -31,8 +31,8 @@ zephyr_library_sources_ifdef(CONFIG_INTC_ESP32 intc_esp32.c)
zephyr_library_sources_ifdef(CONFIG_SWERV_PIC intc_swerv_pic.c) zephyr_library_sources_ifdef(CONFIG_SWERV_PIC intc_swerv_pic.c)
zephyr_library_sources_ifdef(CONFIG_VEXRISCV_LITEX_IRQ intc_vexriscv_litex.c) zephyr_library_sources_ifdef(CONFIG_VEXRISCV_LITEX_IRQ intc_vexriscv_litex.c)
zephyr_library_sources_ifdef(CONFIG_VIM intc_vim.c) zephyr_library_sources_ifdef(CONFIG_VIM intc_vim.c)
zephyr_library_sources_ifdef(CONFIG_NUCLEI_ECLIC intc_clic.c) zephyr_library_sources_ifdef(CONFIG_CLIC intc_clic.c)
zephyr_library_sources_ifdef(CONFIG_NUCLEI_ECLIC intc_clic.S) zephyr_library_sources_ifdef(CONFIG_CLIC intc_clic.S)
zephyr_library_sources_ifdef(CONFIG_NRFX_CLIC intc_nrfx_clic.c) zephyr_library_sources_ifdef(CONFIG_NRFX_CLIC intc_nrfx_clic.c)
zephyr_library_sources_ifdef(CONFIG_NRFX_CLIC intc_nrfx_clic.S) zephyr_library_sources_ifdef(CONFIG_NRFX_CLIC intc_nrfx_clic.S)
zephyr_library_sources_ifdef(CONFIG_NXP_S32_EIRQ intc_eirq_nxp_s32.c) zephyr_library_sources_ifdef(CONFIG_NXP_S32_EIRQ intc_eirq_nxp_s32.c)

View file

@ -1,11 +1,20 @@
# Copyright (c) 2021 Tokita, Hiroshi <tokita.hiroshi@gmail.com> # Copyright (c) 2021 Tokita, Hiroshi <tokita.hiroshi@gmail.com>
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
config CLIC
bool "RISC-V Core Local Interrupt Controller (CLIC)"
default y
depends on DT_HAS_NUCLEI_ECLIC_ENABLED || DT_HAS_RISCV_CLIC_ENABLED
select RISCV_SOC_HAS_CUSTOM_IRQ_HANDLING
help
Core Local Interrupt Controller provide low-latency, vectored,
preemptive interrupts for RISC-V systems.
config NUCLEI_ECLIC config NUCLEI_ECLIC
bool "Enhanced Core Local Interrupt Controller (ECLIC)" bool "Enhanced Core Local Interrupt Controller (ECLIC)"
default y default y
depends on DT_HAS_NUCLEI_ECLIC_ENABLED depends on DT_HAS_NUCLEI_ECLIC_ENABLED
select RISCV_SOC_HAS_CUSTOM_IRQ_HANDLING select CLIC
select CLIC_SMCLICSHV_EXT if RISCV_VECTORED_MODE select CLIC_SMCLICSHV_EXT if RISCV_VECTORED_MODE
help help
Interrupt controller for Nuclei SoC core. Interrupt controller for Nuclei SoC core.
@ -18,6 +27,8 @@ config NRFX_CLIC
help help
Interrupt controller for Nordic VPR cores. Interrupt controller for Nordic VPR cores.
if CLIC
config CLIC_SMCLICSHV_EXT config CLIC_SMCLICSHV_EXT
bool bool
help help
@ -25,8 +36,6 @@ config CLIC_SMCLICSHV_EXT
to select the behavior for each interrupt. The CLIC driver needs to to select the behavior for each interrupt. The CLIC driver needs to
implement the riscv_clic_irq_vector_set() function. implement the riscv_clic_irq_vector_set() function.
if NUCLEI_ECLIC
config LEGACY_CLIC config LEGACY_CLIC
bool "Use the legacy clic specification" bool "Use the legacy clic specification"
depends on RISCV_HAS_CLIC depends on RISCV_HAS_CLIC
@ -34,4 +43,4 @@ config LEGACY_CLIC
Enables legacy clic, where smclicshv extension is not supported and Enables legacy clic, where smclicshv extension is not supported and
hardware vectoring is set via mode bits of mtvec. hardware vectoring is set via mode bits of mtvec.
endif # NUCLEI_ECLIC endif # CLIC

View file

@ -5,15 +5,16 @@
*/ */
/** /**
* @brief Assembler-hooks specific to Nuclei's Extended Core Interrupt Controller * @brief Assembler-hooks specific to RISC-V Core Local Interrupt Controller
*/ */
#include <zephyr/arch/cpu.h> #include <zephyr/arch/cpu.h>
#include "intc_clic.h"
GTEXT(__soc_handle_irq) GTEXT(__soc_handle_irq)
/* /*
* In an ECLIC, pending interrupts don't have to be cleared by hand. * In an CLIC, pending interrupts don't have to be cleared by hand.
* In vectored mode, interrupts are cleared automatically. * In vectored mode, interrupts are cleared automatically.
* In non-vectored mode, interrupts are cleared when writing the mnxti register (done in * In non-vectored mode, interrupts are cleared when writing the mnxti register (done in
* __soc_handle_all_irqs). * __soc_handle_all_irqs).
@ -31,7 +32,7 @@ GTEXT(sys_trace_isr_exit)
#endif #endif
/* /*
* This function services and clears all pending interrupts for an ECLIC in non-vectored mode. * This function services and clears all pending interrupts for an CLIC in non-vectored mode.
*/ */
SECTION_FUNC(exception.other, __soc_handle_all_irqs) SECTION_FUNC(exception.other, __soc_handle_all_irqs)
addi sp, sp, -16 addi sp, sp, -16
@ -39,7 +40,7 @@ SECTION_FUNC(exception.other, __soc_handle_all_irqs)
/* Read and clear mnxti to get highest current interrupt and enable interrupts. Will return /* Read and clear mnxti to get highest current interrupt and enable interrupts. Will return
* original interrupt if no others appear. */ * original interrupt if no others appear. */
csrrci a0, 0x345, MSTATUS_IEN csrrci a0, CSR_MNXTI, MSTATUS_IEN
beqz a0, irq_done /* Check if original interrupt vanished. */ beqz a0, irq_done /* Check if original interrupt vanished. */
irq_loop: irq_loop:
@ -50,7 +51,7 @@ irq_loop:
/* Call corresponding registered function in _sw_isr_table. a0 is offset in pointer with /* Call corresponding registered function in _sw_isr_table. a0 is offset in pointer with
* the mtvt, sw irq table is 2-pointer wide -> shift by one. */ * the mtvt, sw irq table is 2-pointer wide -> shift by one. */
csrr t0, 0x307 /* mtvt */ csrr t0, CSR_MTVT
sub a0, a0, t0 sub a0, a0, t0
la t0, _sw_isr_table la t0, _sw_isr_table
slli a0, a0, (1) slli a0, a0, (1)
@ -70,7 +71,7 @@ irq_loop:
#endif #endif
/* Read and clear mnxti to get highest current interrupt and enable interrupts. */ /* Read and clear mnxti to get highest current interrupt and enable interrupts. */
csrrci a0, 0x345, MSTATUS_IEN csrrci a0, CSR_MNXTI, MSTATUS_IEN
bnez a0, irq_loop bnez a0, irq_loop
irq_done: irq_done:

View file

@ -1,55 +1,47 @@
/* /*
* Copyright (c) 2021 Tokita, Hiroshi <tokita.hiroshi@gmail.com> * Copyright (c) 2021 Tokita, Hiroshi <tokita.hiroshi@gmail.com>
* Copyright (c) 2025 Andes Technology Corporation
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
/** /**
* @brief Driver for Nuclie's Extended Core Interrupt Controller * @brief Driver for Core Local Interrupt Controller
*/ */
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
#include <zephyr/arch/cpu.h> #include <zephyr/arch/riscv/csr.h>
#include <zephyr/sys/util.h>
#include <zephyr/device.h> #include <zephyr/device.h>
#include <zephyr/sw_isr_table.h>
#include <zephyr/drivers/interrupt_controller/riscv_clic.h> #include <zephyr/drivers/interrupt_controller/riscv_clic.h>
#include "intc_clic.h" #include "intc_clic.h"
#if DT_HAS_COMPAT_STATUS_OKAY(riscv_clic)
#define DT_DRV_COMPAT riscv_clic
#elif DT_HAS_COMPAT_STATUS_OKAY(nuclei_eclic)
#define DT_DRV_COMPAT nuclei_eclic #define DT_DRV_COMPAT nuclei_eclic
#else
#error "Unknown CLIC controller compatible for this configuration"
#endif
/** CLIC INTATTR: TRIG Mask */ struct clic_data {
#define CLIC_INTATTR_TRIG_Msk 0x3U uint8_t nlbits;
uint8_t intctlbits;
};
#define ECLIC_CFG (*((volatile union CLICCFG *)(DT_REG_ADDR_BY_IDX(DT_NODELABEL(eclic), 0)))) struct clic_config {
#define ECLIC_INFO (*((volatile union CLICINFO *)(DT_REG_ADDR_BY_IDX(DT_NODELABEL(eclic), 1)))) mem_addr_t base;
#define ECLIC_MTH (*((volatile union CLICMTH *)(DT_REG_ADDR_BY_IDX(DT_NODELABEL(eclic), 2)))) };
#define ECLIC_CTRL ((volatile struct CLICCTRL *)(DT_REG_ADDR_BY_IDX(DT_NODELABEL(eclic), 3)))
#define ECLIC_CTRL_SIZE (DT_REG_SIZE_BY_IDX(DT_NODELABEL(eclic), 3))
static uint8_t nlbits;
static uint8_t intctlbits;
static uint8_t max_prio;
static uint8_t max_level;
static uint8_t intctrl_mask;
static inline uint8_t leftalign8(uint8_t val, uint8_t shift)
{
return (val << (8U - shift));
}
static inline uint8_t mask8(uint8_t len)
{
return ((1 << len) - 1) & 0xFFFFU;
}
/** /**
* @brief Enable interrupt * @brief Enable interrupt
*/ */
void riscv_clic_irq_enable(uint32_t irq) void riscv_clic_irq_enable(uint32_t irq)
{ {
ECLIC_CTRL[irq].INTIE.b.IE = 1; const struct device *dev = DEVICE_DT_INST_GET(0);
const struct clic_config *config = dev->config;
union CLICINTIE clicintie = {.b = {.IE = 0x1}};
sys_write8(clicintie.w, config->base + CLIC_INTIE(irq));
} }
/** /**
@ -57,7 +49,11 @@ void riscv_clic_irq_enable(uint32_t irq)
*/ */
void riscv_clic_irq_disable(uint32_t irq) void riscv_clic_irq_disable(uint32_t irq)
{ {
ECLIC_CTRL[irq].INTIE.b.IE = 0; const struct device *dev = DEVICE_DT_INST_GET(0);
const struct clic_config *config = dev->config;
union CLICINTIE clicintie = {.b = {.IE = 0x0}};
sys_write8(clicintie.w, config->base + CLIC_INTIE(irq));
} }
/** /**
@ -65,7 +61,11 @@ void riscv_clic_irq_disable(uint32_t irq)
*/ */
int riscv_clic_irq_is_enabled(uint32_t irq) int riscv_clic_irq_is_enabled(uint32_t irq)
{ {
return ECLIC_CTRL[irq].INTIE.b.IE; const struct device *dev = DEVICE_DT_INST_GET(0);
const struct clic_config *config = dev->config;
union CLICINTIE clicintie = {.w = sys_read8(config->base + CLIC_INTIE(irq))};
return clicintie.b.IE;
} }
/** /**
@ -73,18 +73,35 @@ int riscv_clic_irq_is_enabled(uint32_t irq)
*/ */
void riscv_clic_irq_priority_set(uint32_t irq, uint32_t pri, uint32_t flags) void riscv_clic_irq_priority_set(uint32_t irq, uint32_t pri, uint32_t flags)
{ {
const uint8_t prio = leftalign8(MIN(pri, max_prio), intctlbits); const struct device *dev = DEVICE_DT_INST_GET(0);
const uint8_t level = leftalign8(max_level, nlbits); const struct clic_config *config = dev->config;
const uint8_t intctrl = (prio | level) | (~intctrl_mask); const struct clic_data *data = dev->data;
ECLIC_CTRL[irq].INTCTRL = intctrl; /*
* Set the interrupt level and the interrupt priority.
* Examples of mcliccfg settings:
* CLICINTCTLBITS mnlbits clicintctl[i] interrupt levels
* 0 2 ........ 255
* 1 2 l....... 127,255
* 2 2 ll...... 63,127,191,255
* 3 3 lll..... 31,63,95,127,159,191,223,255
* 4 1 lppp.... 127,255
* "." bits are non-existent bits for level encoding, assumed to be 1
* "l" bits are available variable bits in level specification
* "p" bits are available variable bits in priority specification
*/
const uint8_t max_level = BIT_MASK(data->nlbits);
const uint8_t max_prio = BIT_MASK(data->intctlbits - data->nlbits);
uint8_t intctrl = (MIN(pri, max_prio) << (8U - data->intctlbits)) |
(MIN(pri, max_level) << (8U - data->nlbits)) |
BIT_MASK(8U - data->intctlbits);
union CLICINTATTR intattr = {.w = 0}; sys_write8(intctrl, config->base + CLIC_INTCTRL(irq));
/* Set non-vectoring as default. */ /* Set the IRQ operates in machine mode, non-vectoring and the trigger type. */
intattr.b.shv = 0; union CLICINTATTR clicattr = {.b = {.mode = 0x3, .shv = 0x0, .trg = flags & BIT_MASK(3)}};
intattr.b.trg = (uint8_t)(flags & CLIC_INTATTR_TRIG_Msk);
ECLIC_CTRL[irq].INTATTR = intattr; sys_write8(clicattr.w, config->base + CLIC_INTATTR(irq));
} }
/** /**
@ -92,11 +109,13 @@ void riscv_clic_irq_priority_set(uint32_t irq, uint32_t pri, uint32_t flags)
*/ */
void riscv_clic_irq_vector_set(uint32_t irq) void riscv_clic_irq_vector_set(uint32_t irq)
{ {
/* Set Selective Hardware Vectoring. */ const struct device *dev = DEVICE_DT_INST_GET(0);
union CLICINTATTR intattr = ECLIC_CTRL[irq].INTATTR; const struct clic_config *config = dev->config;
union CLICINTATTR clicattr = {.w = sys_read8(config->base + CLIC_INTATTR(irq))};
intattr.b.shv = 1; /* Set Selective Hardware Vectoring. */
ECLIC_CTRL[irq].INTATTR = intattr; clicattr.b.shv = 1;
sys_write8(clicattr.w, config->base + CLIC_INTATTR(irq));
} }
/** /**
@ -104,26 +123,64 @@ void riscv_clic_irq_vector_set(uint32_t irq)
*/ */
void riscv_clic_irq_set_pending(uint32_t irq) void riscv_clic_irq_set_pending(uint32_t irq)
{ {
ECLIC_CTRL[irq].INTIP.b.IP = 1; const struct device *dev = DEVICE_DT_INST_GET(0);
const struct clic_config *config = dev->config;
union CLICINTIP clicintip = {.b = {.IP = 0x1}};
sys_write8(clicintip.w, config->base + CLIC_INTIP(irq));
} }
static int nuclei_eclic_init(const struct device *dev) static int clic_init(const struct device *dev)
{ {
ECLIC_MTH.w = 0; const struct clic_config *config = dev->config;
ECLIC_CFG.w = 0; struct clic_data *data = dev->data;
ECLIC_CFG.b.nlbits = 0;
for (int i = 0; i < ECLIC_CTRL_SIZE; i++) { if (IS_ENABLED(CONFIG_NUCLEI_ECLIC)) {
ECLIC_CTRL[i] = (struct CLICCTRL) { 0 }; /* Configure the interrupt level threshold. */
union CLICMTH clicmth = {.b = {.mth = 0x0}};
sys_write32(clicmth.qw, config->base + CLIC_MTH);
/* Detect the number of bits for the clicintctl register. */
union CLICINFO clicinfo = {.qw = sys_read32(config->base + CLIC_INFO)};
data->intctlbits = clicinfo.b.intctlbits;
if (data->nlbits > data->intctlbits) {
data->nlbits = data->intctlbits;
}
/* Configure the number of bits assigned to interrupt levels. */
union CLICCFG cliccfg = {.qw = sys_read32(config->base + CLIC_CFG)};
cliccfg.w.nlbits = data->nlbits;
sys_write32(cliccfg.qw, config->base + CLIC_CFG);
} else {
/* Configure the interrupt level threshold by CSR mintthresh. */
csr_write(CSR_MINTTHRESH, 0x0);
} }
nlbits = ECLIC_CFG.b.nlbits; /* Reset all interrupt control register */
intctlbits = ECLIC_INFO.b.intctlbits; for (int i = 0; i < CONFIG_NUM_IRQS; i++) {
max_prio = mask8(intctlbits - nlbits); sys_write32(0, config->base + CLIC_CTRL(i));
max_level = mask8(nlbits); }
intctrl_mask = leftalign8(mask8(intctlbits), intctlbits);
return 0; return 0;
} }
DEVICE_DT_INST_DEFINE(0, nuclei_eclic_init, NULL, NULL, NULL, #define CLIC_INTC_DATA_INIT(n) \
PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, NULL); static struct clic_data clic_data_##n = { \
.nlbits = 0, \
.intctlbits = 8, \
};
#define CLIC_INTC_CONFIG_INIT(n) \
const static struct clic_config clic_config_##n = { \
.base = DT_REG_ADDR(DT_DRV_INST(n)), \
};
#define CLIC_INTC_DEVICE_INIT(n) \
CLIC_INTC_DATA_INIT(n) \
CLIC_INTC_CONFIG_INIT(n) \
DEVICE_DT_INST_DEFINE(n, &clic_init, NULL, &clic_data_##n, &clic_config_##n, PRE_KERNEL_1, \
CONFIG_INTC_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(CLIC_INTC_DEVICE_INIT)

View file

@ -8,9 +8,33 @@
#ifndef ZEPHYR_DRIVERS_INTERRUPT_CONTROLLER_INTC_CLIC_H_ #ifndef ZEPHYR_DRIVERS_INTERRUPT_CONTROLLER_INTC_CLIC_H_
#define ZEPHYR_DRIVERS_INTERRUPT_CONTROLLER_INTC_CLIC_H_ #define ZEPHYR_DRIVERS_INTERRUPT_CONTROLLER_INTC_CLIC_H_
/* CLIC relative CSR number */
#define CSR_MTVT (0x307)
#define CSR_MNXTI (0x345)
#define CSR_MINTTHRESH (0x347)
#ifndef __ASSEMBLER__
#include <stddef.h>
/* CLIC Memory mapped register offset */
#define CLIC_CFG (0x0)
#define CLIC_CTRL(irq) (0x1000 + 4 * (irq))
#define CLIC_INTIP(irq) (CLIC_CTRL(irq) + offsetof(union CLICCTRL, w.INTIP))
#define CLIC_INTIE(irq) (CLIC_CTRL(irq) + offsetof(union CLICCTRL, w.INTIE))
#define CLIC_INTATTR(irq) (CLIC_CTRL(irq) + offsetof(union CLICCTRL, w.INTATTR))
#define CLIC_INTCTRL(irq) (CLIC_CTRL(irq) + offsetof(union CLICCTRL, w.INTCTRL))
/* Nuclei ECLIC memory mapped register offset */
#define CLIC_INFO (0x4)
#define CLIC_MTH (0x8)
/* CLIC register structure */
union CLICCFG { union CLICCFG {
struct { struct {
#ifdef CONFIG_NUCLEI_ECLIC
uint32_t _reserved0: 1; uint32_t _reserved0: 1;
#endif /* CONFIG_NUCLEI_ECLIC */
/** number of interrupt level bits */ /** number of interrupt level bits */
uint32_t nlbits: 4; uint32_t nlbits: 4;
/** number of clicintattr[i].MODE bits */ /** number of clicintattr[i].MODE bits */
@ -60,6 +84,7 @@ union CLICCTRL {
uint32_t qw; uint32_t qw;
}; };
/* Nuclei ECLIC register structure */
union CLICINFO { union CLICINFO {
struct { struct {
/** number of max supported interrupts */ /** number of max supported interrupts */
@ -82,4 +107,6 @@ union CLICMTH {
uint32_t qw; uint32_t qw;
}; };
#endif /*__ASSEMBLER__*/
#endif /* ZEPHYR_DRIVERS_INTERRUPT_CONTROLLER_INTC_CLIC_H_ */ #endif /* ZEPHYR_DRIVERS_INTERRUPT_CONTROLLER_INTC_CLIC_H_ */

View file

@ -52,10 +52,7 @@
#address-cells = <0>; #address-cells = <0>;
#interrupt-cells = <2>; #interrupt-cells = <2>;
interrupt-controller; interrupt-controller;
reg = <0xd2000000 0x0001 reg = <0xd2000000 0x2000>;
0xd2000004 0x0004
0xd200000b 0x0001
0xd2001000 0x1000>;
}; };
rcu: reset-clock-controller@40021000 { rcu: reset-clock-controller@40021000 {

View file

@ -164,7 +164,7 @@ static inline void trigger_irq(int irq)
} }
#elif defined(CONFIG_RISCV) #elif defined(CONFIG_RISCV)
#if defined(CONFIG_NUCLEI_ECLIC) || defined(CONFIG_NRFX_CLIC) #if defined(CONFIG_CLIC) || defined(CONFIG_NRFX_CLIC)
void riscv_clic_irq_set_pending(uint32_t irq); void riscv_clic_irq_set_pending(uint32_t irq);
static inline void trigger_irq(int irq) static inline void trigger_irq(int irq)
{ {