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_VEXRISCV_LITEX_IRQ intc_vexriscv_litex.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_NUCLEI_ECLIC intc_clic.S)
zephyr_library_sources_ifdef(CONFIG_CLIC intc_clic.c)
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.S)
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>
# 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
bool "Enhanced Core Local Interrupt Controller (ECLIC)"
default y
depends on DT_HAS_NUCLEI_ECLIC_ENABLED
select RISCV_SOC_HAS_CUSTOM_IRQ_HANDLING
select CLIC
select CLIC_SMCLICSHV_EXT if RISCV_VECTORED_MODE
help
Interrupt controller for Nuclei SoC core.
@ -18,6 +27,8 @@ config NRFX_CLIC
help
Interrupt controller for Nordic VPR cores.
if CLIC
config CLIC_SMCLICSHV_EXT
bool
help
@ -25,8 +36,6 @@ config CLIC_SMCLICSHV_EXT
to select the behavior for each interrupt. The CLIC driver needs to
implement the riscv_clic_irq_vector_set() function.
if NUCLEI_ECLIC
config LEGACY_CLIC
bool "Use the legacy clic specification"
depends on RISCV_HAS_CLIC
@ -34,4 +43,4 @@ config LEGACY_CLIC
Enables legacy clic, where smclicshv extension is not supported and
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 "intc_clic.h"
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 non-vectored mode, interrupts are cleared when writing the mnxti register (done in
* __soc_handle_all_irqs).
@ -31,7 +32,7 @@ GTEXT(sys_trace_isr_exit)
#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)
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
* 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. */
irq_loop:
@ -50,7 +51,7 @@ irq_loop:
/* 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. */
csrr t0, 0x307 /* mtvt */
csrr t0, CSR_MTVT
sub a0, a0, t0
la t0, _sw_isr_table
slli a0, a0, (1)
@ -70,7 +71,7 @@ irq_loop:
#endif
/* 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
irq_done:

View file

@ -1,55 +1,47 @@
/*
* Copyright (c) 2021 Tokita, Hiroshi <tokita.hiroshi@gmail.com>
* Copyright (c) 2025 Andes Technology Corporation
*
* 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/arch/cpu.h>
#include <zephyr/sys/util.h>
#include <zephyr/arch/riscv/csr.h>
#include <zephyr/device.h>
#include <zephyr/sw_isr_table.h>
#include <zephyr/drivers/interrupt_controller/riscv_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
#else
#error "Unknown CLIC controller compatible for this configuration"
#endif
/** CLIC INTATTR: TRIG Mask */
#define CLIC_INTATTR_TRIG_Msk 0x3U
struct clic_data {
uint8_t nlbits;
uint8_t intctlbits;
};
#define ECLIC_CFG (*((volatile union CLICCFG *)(DT_REG_ADDR_BY_IDX(DT_NODELABEL(eclic), 0))))
#define ECLIC_INFO (*((volatile union CLICINFO *)(DT_REG_ADDR_BY_IDX(DT_NODELABEL(eclic), 1))))
#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;
}
struct clic_config {
mem_addr_t base;
};
/**
* @brief Enable interrupt
*/
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)
{
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)
{
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)
{
const uint8_t prio = leftalign8(MIN(pri, max_prio), intctlbits);
const uint8_t level = leftalign8(max_level, nlbits);
const uint8_t intctrl = (prio | level) | (~intctrl_mask);
const struct device *dev = DEVICE_DT_INST_GET(0);
const struct clic_config *config = dev->config;
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. */
intattr.b.shv = 0;
intattr.b.trg = (uint8_t)(flags & CLIC_INTATTR_TRIG_Msk);
ECLIC_CTRL[irq].INTATTR = intattr;
/* Set the IRQ operates in machine mode, non-vectoring and the trigger type. */
union CLICINTATTR clicattr = {.b = {.mode = 0x3, .shv = 0x0, .trg = flags & BIT_MASK(3)}};
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)
{
/* Set Selective Hardware Vectoring. */
union CLICINTATTR intattr = ECLIC_CTRL[irq].INTATTR;
const struct device *dev = DEVICE_DT_INST_GET(0);
const struct clic_config *config = dev->config;
union CLICINTATTR clicattr = {.w = sys_read8(config->base + CLIC_INTATTR(irq))};
intattr.b.shv = 1;
ECLIC_CTRL[irq].INTATTR = intattr;
/* Set Selective Hardware Vectoring. */
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)
{
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;
ECLIC_CFG.w = 0;
ECLIC_CFG.b.nlbits = 0;
for (int i = 0; i < ECLIC_CTRL_SIZE; i++) {
ECLIC_CTRL[i] = (struct CLICCTRL) { 0 };
const struct clic_config *config = dev->config;
struct clic_data *data = dev->data;
if (IS_ENABLED(CONFIG_NUCLEI_ECLIC)) {
/* 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;
}
nlbits = ECLIC_CFG.b.nlbits;
intctlbits = ECLIC_INFO.b.intctlbits;
max_prio = mask8(intctlbits - nlbits);
max_level = mask8(nlbits);
intctrl_mask = leftalign8(mask8(intctlbits), 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);
}
/* Reset all interrupt control register */
for (int i = 0; i < CONFIG_NUM_IRQS; i++) {
sys_write32(0, config->base + CLIC_CTRL(i));
}
return 0;
}
DEVICE_DT_INST_DEFINE(0, nuclei_eclic_init, NULL, NULL, NULL,
PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, NULL);
#define CLIC_INTC_DATA_INIT(n) \
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_
#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 {
struct {
#ifdef CONFIG_NUCLEI_ECLIC
uint32_t _reserved0: 1;
#endif /* CONFIG_NUCLEI_ECLIC */
/** number of interrupt level bits */
uint32_t nlbits: 4;
/** number of clicintattr[i].MODE bits */
@ -60,6 +84,7 @@ union CLICCTRL {
uint32_t qw;
};
/* Nuclei ECLIC register structure */
union CLICINFO {
struct {
/** number of max supported interrupts */
@ -82,4 +107,6 @@ union CLICMTH {
uint32_t qw;
};
#endif /*__ASSEMBLER__*/
#endif /* ZEPHYR_DRIVERS_INTERRUPT_CONTROLLER_INTC_CLIC_H_ */

View file

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

View file

@ -164,7 +164,7 @@ static inline void trigger_irq(int irq)
}
#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);
static inline void trigger_irq(int irq)
{