interrupt_controller: RV32M1: add intmux driver / DT bindings

Add a level 2 interrupt controller for the RV32M1 SoC. This uses the
INTMUX peripheral.

As a first customer, convert the timer driver over to using this,
adding nodes for the LPTMR peripherals. This lets users select the
timer instance they want to use, and what intmux channel they want to
route its interrupt to, using DT overlays.

Signed-off-by: Marti Bolivar <marti@foundries.io>
Signed-off-by: Mike Scott <mike@foundries.io>
This commit is contained in:
Marti Bolivar 2018-11-25 02:41:38 -07:00 committed by Anas Nashif
commit 58d8afb476
14 changed files with 758 additions and 54 deletions

View file

@ -9,3 +9,4 @@ zephyr_sources_ifdef(CONFIG_SHARED_IRQ shared_irq.c)
zephyr_sources_ifdef(CONFIG_SOC_FAMILY_STM32 exti_stm32.c)
zephyr_sources_ifdef(CONFIG_CAVS_ICTL cavs_ictl.c)
zephyr_sources_ifdef(CONFIG_DW_ICTL dw_ictl.c)
zephyr_sources_ifdef(CONFIG_RV32M1_INTMUX rv32m1_intmux.c)

View file

@ -162,4 +162,6 @@ source "drivers/interrupt_controller/Kconfig.multilevel"
source "drivers/interrupt_controller/Kconfig.s1000"
source "drivers/interrupt_controller/Kconfig.rv32m1"
endmenu

View file

@ -0,0 +1,66 @@
# Kconfig - RV32M1 INTMUX config
#
# Copyright (c) 2018 Foundries.io
#
# SPDX-License-Identifier: Apache-2.0
config RV32M1_INTMUX
bool "OpenISA RV32M1 INTMUX interrupt controller support"
depends on SOC_OPENISA_RV32M1_RISCV32 && MULTI_LEVEL_INTERRUPTS
help
Select this option to enable support for the RV32M1 INTMUX
driver. This provides a level 2 interrupt controller for the SoC.
The INTMUX peripheral combines level 2 interrupts into
eight channels; each channel has its own level 1 interrupt to
the core.
if RV32M1_INTMUX
config RV32M1_INTMUX_INIT_PRIORITY
int "INTMUX driver initialization priority"
default 60
help
Boot time initialization priority for INTMUX driver.
Don't change the default unless you know what you are doing.
config RV32M1_INTMUX_CHANNEL_0
bool "INTMUX channel 0"
help
Enable support for INTMUX channel 0.
config RV32M1_INTMUX_CHANNEL_1
bool "INTMUX channel 1"
help
Enable support for INTMUX channel 1.
config RV32M1_INTMUX_CHANNEL_2
bool "INTMUX channel 2"
help
Enable support for INTMUX channel 2.
config RV32M1_INTMUX_CHANNEL_3
bool "INTMUX channel 3"
help
Enable support for INTMUX channel 3.
config RV32M1_INTMUX_CHANNEL_4
bool "INTMUX channel 4"
help
Enable support for INTMUX channel 4.
config RV32M1_INTMUX_CHANNEL_5
bool "INTMUX channel 5"
help
Enable support for INTMUX channel 5.
config RV32M1_INTMUX_CHANNEL_6
bool "INTMUX channel 6"
help
Enable support for INTMUX channel 6.
config RV32M1_INTMUX_CHANNEL_7
bool "INTMUX channel 7"
help
Enable support for INTMUX channel 7.
endif # RV32M1_INTMUX

View file

@ -0,0 +1,195 @@
/*
* Copyright (c) 2018 Foundries.io
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief RV32M1 INTMUX (interrupt multiplexer) driver
*
* This driver provides support for level 2 interrupts on the RV32M1
* SoC using the INTMUX peripheral.
*
* Each of the RI5CY and ZERO-RISCY cores has an INTMUX peripheral;
* INTMUX0 is wired to the RI5CY event unit interrupt table, while
* INTMUX1 is used with ZERO-RISCY.
*
* For this reason, only a single intmux device is declared here. The
* dtsi for each core needs to set up the intmux device and any
* associated IRQ numbers to work with this driver.
*/
#include <kernel.h>
#include <clock_control.h>
#include <init.h>
#include <irq.h>
#include <irq_nextlevel.h>
#include <sw_isr_table.h>
#include <soc.h>
#include <dt-bindings/interrupt-controller/openisa-intmux.h>
/*
* CHn_VEC registers are offset by a value that is convenient if
* you're dealing with a Cortex-M NVIC vector table; we're not, so it
* needs to be subtracted out to get a useful value.
*/
#define VECN_OFFSET 48U
struct rv32m1_intmux_config {
INTMUX_Type *regs;
char *clock_name;
clock_control_subsys_t clock_subsys;
struct _isr_table_entry *isr_base;
};
#define DEV_CFG(dev) \
((struct rv32m1_intmux_config *)(dev->config->config_info))
#define DEV_REGS(dev) (DEV_CFG(dev)->regs)
DEVICE_DECLARE(intmux);
/*
* <irq_nextlevel.h> API
*/
static void rv32m1_intmux_irq_enable(struct device *dev, u32_t irq)
{
INTMUX_Type *regs = DEV_REGS(dev);
u32_t channel = rv32m1_intmux_channel(irq);
u32_t line = rv32m1_intmux_line(irq);
regs->CHANNEL[channel].CHn_IER_31_0 |= BIT(line);
}
static void rv32m1_intmux_irq_disable(struct device *dev, u32_t irq)
{
INTMUX_Type *regs = DEV_REGS(dev);
u32_t channel = rv32m1_intmux_channel(irq);
u32_t line = rv32m1_intmux_line(irq);
regs->CHANNEL[channel].CHn_IER_31_0 &= ~BIT(line);
}
static u32_t rv32m1_intmux_get_state(struct device *dev)
{
INTMUX_Type *regs = DEV_REGS(dev);
size_t i;
for (i = 0; i < INTMUX_CHn_IER_31_0_COUNT; i++) {
if (regs->CHANNEL[i].CHn_IER_31_0) {
return 1;
}
}
return 0;
}
/*
* IRQ handling.
*/
#define ISR_ENTRY(channel, line) \
((channel) * CONFIG_MAX_IRQ_PER_AGGREGATOR + line)
static void rv32m1_intmux_isr(void *arg)
{
struct device *dev = DEVICE_GET(intmux);
INTMUX_Type *regs = DEV_REGS(dev);
u32_t channel = POINTER_TO_UINT(arg);
u32_t line = (regs->CHANNEL[channel].CHn_VEC >> 2) - VECN_OFFSET;
struct _isr_table_entry *isr_base = DEV_CFG(dev)->isr_base;
struct _isr_table_entry *entry = &isr_base[ISR_ENTRY(channel, line)];
entry->isr(entry->arg);
}
/*
* Instance and initialization
*/
static const struct irq_next_level_api rv32m1_intmux_apis = {
.intr_enable = rv32m1_intmux_irq_enable,
.intr_disable = rv32m1_intmux_irq_disable,
.intr_get_state = rv32m1_intmux_get_state,
};
static const struct rv32m1_intmux_config rv32m1_intmux_cfg = {
.regs = (INTMUX_Type *)INTMUX_BASE_ADDRESS,
.clock_name = INTMUX_CLOCK_CONTROLLER,
.clock_subsys = UINT_TO_POINTER(INTMUX_CLOCK_NAME),
.isr_base = &_sw_isr_table[CONFIG_2ND_LVL_ISR_TBL_OFFSET],
};
static int rv32m1_intmux_init(struct device *dev)
{
const struct rv32m1_intmux_config *config = DEV_CFG(dev);
INTMUX_Type *regs = DEV_REGS(dev);
struct device *clock_dev = device_get_binding(config->clock_name);
size_t i;
if (!clock_dev) {
return -ENODEV;
}
/* Enable INTMUX clock. */
clock_control_on(clock_dev, config->clock_subsys);
/*
* Reset all channels, not just the ones we're configured to
* support. We don't want to continue to take level 2 IRQs
* enabled by bootloaders, for example.
*/
for (i = 0; i < INTMUX_CHn_CSR_COUNT; i++) {
regs->CHANNEL[i].CHn_CSR |= INTMUX_CHn_CSR_RST_MASK;
}
/* Connect and enable level 1 (channel) interrupts. */
#ifdef CONFIG_RV32M1_INTMUX_CHANNEL_0
IRQ_CONNECT(INTMUX_CH0_IRQ, 0, rv32m1_intmux_isr,
UINT_TO_POINTER(0), 0);
irq_enable(INTMUX_CH0_IRQ);
#endif
#ifdef CONFIG_RV32M1_INTMUX_CHANNEL_1
IRQ_CONNECT(INTMUX_CH1_IRQ, 0, rv32m1_intmux_isr,
UINT_TO_POINTER(1), 0);
irq_enable(INTMUX_CH1_IRQ);
#endif
#ifdef CONFIG_RV32M1_INTMUX_CHANNEL_2
IRQ_CONNECT(INTMUX_CH2_IRQ, 0, rv32m1_intmux_isr,
UINT_TO_POINTER(2), 0);
irq_enable(INTMUX_CH2_IRQ);
#endif
#ifdef CONFIG_RV32M1_INTMUX_CHANNEL_3
IRQ_CONNECT(INTMUX_CH3_IRQ, 0, rv32m1_intmux_isr,
UINT_TO_POINTER(3), 0);
irq_enable(INTMUX_CH3_IRQ);
#endif
#ifdef CONFIG_RV32M1_INTMUX_CHANNEL_4
IRQ_CONNECT(INTMUX_CH4_IRQ, 0, rv32m1_intmux_isr,
UINT_TO_POINTER(4), 0);
irq_enable(INTMUX_CH4_IRQ);
#endif
#ifdef CONFIG_RV32M1_INTMUX_CHANNEL_5
IRQ_CONNECT(INTMUX_CH5_IRQ, 0, rv32m1_intmux_isr,
UINT_TO_POINTER(5), 0);
irq_enable(INTMUX_CH5_IRQ);
#endif
#ifdef CONFIG_RV32M1_INTMUX_CHANNEL_6
IRQ_CONNECT(INTMUX_CH6_IRQ, 0, rv32m1_intmux_isr,
UINT_TO_POINTER(6), 0);
irq_enable(INTMUX_CH6_IRQ);
#endif
#ifdef CONFIG_RV32M1_INTMUX_CHANNEL_7
IRQ_CONNECT(INTMUX_CH7_IRQ, 0, rv32m1_intmux_isr,
UINT_TO_POINTER(7), 0);
irq_enable(INTMUX_CH7_IRQ);
#endif
return 0;
}
DEVICE_AND_API_INIT(intmux, INTMUX_LABEL, &rv32m1_intmux_init, NULL,
&rv32m1_intmux_cfg, PRE_KERNEL_1,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &rv32m1_intmux_apis);

View file

@ -139,6 +139,7 @@ config RV32M1_LPTMR_TIMER
default y
depends on SOC_OPENISA_RV32M1_RISCV32
depends on !TICKLESS_IDLE
depends on RV32M1_INTMUX
help
This module implements a kernel device driver for using the LPTMR
peripheral as the system clock. It provides the standard "system clock

View file

@ -14,29 +14,24 @@
*
* Assumptions and limitations:
*
* - LPTMR0 clocked by SIRC output SIRCDIV3 divide-by-1, SIRC at 8MHz
* - system clock based on an LPTMR instance, clocked by SIRC output
* SIRCDIV3, prescaler divide-by-1, SIRC at 8MHz
* - no tickless
* - direct control of INTMUX0 channel 0 (bypasses intmux driver)
*
* This should be rewritten as follows:
*
* - use RTC instead of LPTMR
* - support tickless operation
*/
#define CYCLES_PER_TICK \
(CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
#define CYCLES_PER_SEC CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC
#define CYCLES_PER_TICK (CYCLES_PER_SEC / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
/* Sanity check the 8MHz clock assumption. */
#if CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC != 8000000
#error "timer driver misconfiguration"
/*
* As a simplifying assumption, we only support a clock ticking at the
* SIRC reset rate of 8MHz.
*/
#if MHZ(8) != CYCLES_PER_SEC
#error "system timer misconfiguration; unsupported clock rate"
#endif
#define LPTMR_INSTANCE LPTMR0
#define LPTMR_LEVEL0_IRQ 24 /* INTMUX channel 0 */
#define LPTMR_LEVEL0_IRQ_PRIO 0
#define LPTMR_LEVEL1_IRQ 7
#define LPTMR_LEVEL1_IRQ_EN (1U << LPTMR_LEVEL1_IRQ)
#define SYSTEM_TIMER_INSTANCE ((LPTMR_Type *)(SYSTEM_LPTMR_BASE_ADDRESS))
#define SYSTEM_TIMER_IRQ_PRIO 0
#define SIRC_RANGE_8MHZ SCG_SIRCCFG_RANGE(1)
#define SIRCDIV3_DIVIDE_BY_1 1
@ -50,32 +45,18 @@ static void lptmr_irq_handler(struct device *unused)
{
ARG_UNUSED(unused);
LPTMR_INSTANCE->CSR |= LPTMR_CSR_TCF(1); /* Rearm timer. */
SYSTEM_TIMER_INSTANCE->CSR |= LPTMR_CSR_TCF(1); /* Rearm timer. */
cycle_count += CYCLES_PER_TICK; /* Track cycles. */
z_clock_announce(1); /* Poke the scheduler. */
}
static void enable_intmux0_pcc(void)
{
u32_t ier;
/*
* The reference manual doesn't say this exists, but it's in
* the peripheral registers.
*/
*(u32_t*)(PCC0_BASE + 0x13c) |= PCC_CLKCFG_CGC_MASK;
ier = INTMUX0->CHANNEL[0].CHn_IER_31_0;
ier |= LPTMR_LEVEL1_IRQ_EN;
INTMUX0->CHANNEL[0].CHn_IER_31_0 = ier;
}
int z_clock_driver_init(struct device *unused)
{
u32_t csr, psr, sircdiv; /* LPTMR registers */
ARG_UNUSED(unused);
IRQ_CONNECT(LPTMR_LEVEL0_IRQ, LPTMR_LEVEL0_IRQ_PRIO, lptmr_irq_handler, NULL, 0);
IRQ_CONNECT(SYSTEM_LPTMR_IRQ, SYSTEM_TIMER_IRQ_PRIO, lptmr_irq_handler,
NULL, 0);
if ((SCG->SIRCCSR & SCG_SIRCCSR_SIRCEN_MASK) == SCG_SIRCCSR_SIRCEN(0)) {
/*
@ -87,10 +68,10 @@ int z_clock_driver_init(struct device *unused)
}
/* Disable the timer and clear any pending IRQ. */
csr = LPTMR_INSTANCE->CSR;
csr = SYSTEM_TIMER_INSTANCE->CSR;
csr &= ~LPTMR_CSR_TEN(0);
csr |= LPTMR_CSR_TFC(1);
LPTMR_INSTANCE->CSR = csr;
SYSTEM_TIMER_INSTANCE->CSR = csr;
/*
* Set up the timer clock source and configure the timer.
@ -114,15 +95,15 @@ int z_clock_driver_init(struct device *unused)
* TIE = 1: enable interrupt
*/
csr |= LPTMR_CSR_TIE(1);
LPTMR_INSTANCE->CSR = csr;
SYSTEM_TIMER_INSTANCE->CSR = csr;
/*
* PCS = 0: clock source is SIRCDIV3 (SoC dependent)
* PBYP = 1: bypass the prescaler
*/
psr = LPTMR_INSTANCE->PSR;
psr = SYSTEM_TIMER_INSTANCE->PSR;
psr &= ~LPTMR_PSR_PCS_MASK;
psr |= (LPTMR_PSR_PBYP(1) | LPTMR_PSR_PCS(PCS_SOURCE_SIRCDIV3));
LPTMR_INSTANCE->PSR = psr;
SYSTEM_TIMER_INSTANCE->PSR = psr;
/*
* Set compare register to the proper tick count. The check
@ -134,29 +115,26 @@ int z_clock_driver_init(struct device *unused)
if ((SCG->SIRCCFG & SCG_SIRCCFG_RANGE_MASK) != SIRC_RANGE_8MHZ) {
return -EINVAL;
}
LPTMR_INSTANCE->CMR = CYCLES_PER_TICK;
SYSTEM_TIMER_INSTANCE->CMR = CYCLES_PER_TICK;
/*
* Enable interrupts and the timer. There's no need to clear the
* TFC bit in the csr variable, as it's already clear.
*/
enable_intmux0_pcc();
irq_enable(LPTMR_LEVEL0_IRQ);
csr = LPTMR_INSTANCE->CSR;
irq_enable(SYSTEM_LPTMR_IRQ);
csr = SYSTEM_TIMER_INSTANCE->CSR;
csr |= LPTMR_CSR_TEN(1);
LPTMR_INSTANCE->CSR = csr;
SYSTEM_TIMER_INSTANCE->CSR = csr;
return 0;
}
u32_t _timer_cycle_get_32(void)
{
return cycle_count + LPTMR_INSTANCE->CNR;
return cycle_count + SYSTEM_TIMER_INSTANCE->CNR;
}
/*
* Since we're tickless, this is identically zero unless the timer
* interrupt is getting locked out due to other higher priority work.
* Since we're not tickless, this is identically zero.
*/
u32_t z_clock_elapsed(void)
{

View file

@ -0,0 +1,32 @@
#
# Copyright (c) 2018, Linaro Inc.
# Copyright (c) 2018, Foundries.io
#
# SPDX-License-Identifier: Apache-2.0
#
---
title: RV32M1 Event Unit
version: 0.1
description: >
This binding describes the RV32M1 Event Unit
properties:
compatible:
category: required
type: string
description: compatible strings
constraint: "openisa,rv32m1-event-unit"
generation: define
reg:
category: required
type: int
description: mmio register space
generation: define
base_label: RV32M1_EVENT_UNIT
"#cells":
- irq
...

View file

@ -0,0 +1,48 @@
#
# Copyright (c) 2018 Foundries.io
#
# SPDX-License-Identifier: Apache-2.0
#
---
title: RV32M1 INTMUX
version: 0.1
description: >
This binding describes the RV32M1 INTMUX IP
properties:
compatible:
category: required
type: string
description: compatible strings
constraint: "openisa,rv32m1-intmux"
generation: define
reg:
category: required
type: int
description: mmio register space
generation: define
clocks:
type: array
category: optional
description: Clock gate information
generation: define
label:
type: string
category: required
description: Human readable string naming the device (used by Zephyr for API name)
generation: define
interrupts:
type: array
category: required
description: required interrupts
generation: define
"#cells":
- irq
- pri
...

View file

@ -0,0 +1,34 @@
---
title: OpenISA RV32M1 LPTMR
version: 0.1
description: >
This binding represents the OpenISA RV32M1 LPTMR peripheral.
properties:
compatible:
type: string
category: required
description: compatible strings
constraint: "openisa,rv32m1-lptmr"
generation: define
reg:
type: array
description: MMIO register space
generation: define
category: required
interrupts:
type: array
category: required
description: interrupt configuration
generation: define
label:
type: string
category: required
description: Human readable string describing the device (used by Zephyr for API name)
generation: define
...

View file

@ -3,6 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <dt-bindings/interrupt-controller/openisa-intmux.h>
/ {
#address-cells = <1>;
#size-cells = <1>;
@ -10,6 +12,8 @@
aliases {
pcc-0 = &pcc0;
pcc-1 = &pcc1;
intmux = &intmux;
system-lptmr = &lptmr0;
};
cpus {
@ -49,5 +53,60 @@
label = "PCC1";
#clock-cells = <1>;
};
event: interrupt-controller@e0041000 {
compatible = "openisa,rv32m1-event-unit";
#interrupt-cells = <1>;
interrupt-controller;
reg = <0xe0041000 0x88>;
};
intmux: interrupt-controller@4004f000 {
compatible = "openisa,rv32m1-intmux";
#interrupt-cells = <1>;
interrupt-controller;
interrupt-parent = <&event>;
interrupts = <INTMUX_CH0_IRQ>, <INTMUX_CH1_IRQ>, <INTMUX_CH2_IRQ>, <INTMUX_CH3_IRQ>, <INTMUX_CH4_IRQ>, <INTMUX_CH5_IRQ>, <INTMUX_CH6_IRQ>, <INTMUX_CH7_IRQ>;
reg = <0x4004f000 0x20>;
clocks = <&pcc0 0x13c>;
label = "INTMUX0";
};
/*
* INTMUX channels below are somewhat arbitrary.
*
* The system timer (assumed at LPTMR0) is placed on channel 0,
* and peripherals are in channel 1. This can be overridden with
* overlays, e.g. to manage IRQ priorities, and it'll will Just
* Work, but using fewer channels here allows disabling unused
* ones in Kconfig, making the binary smaller.
*
* Each enabled channel requires 256 bytes in _sw_isr_table,
* so the savings for disabling channels can add up.
*/
lptmr0: timer@40032000 {
compatible = "openisa,rv32m1-lptmr";
reg = <0x40032000 0x10>;
interrupt-parent = <&intmux>;
interrupts = <INTMUX_LEVEL2_IRQ(INTMUX_CH0, 7)>;
label = "LPTMR_0";
};
lptmr1: timer@40033000 {
compatible = "openisa,rv32m1-lptmr";
reg = <0x40033000 0x10>;
interrupt-parent = <&intmux>;
interrupts = <INTMUX_LEVEL2_IRQ(INTMUX_CH1, 8)>;
label = "LPTMR_1";
};
lptmr2: timer@4102b000 {
compatible = "openisa,rv32m1-lptmr";
reg = <0x4102b000 0x10>;
interrupt-parent = <&intmux>;
interrupts = <INTMUX_LEVEL2_IRQ(INTMUX_CH1, 22)>;
label = "LPTMR_2";
};
};
};

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2018 Foundries.io Ltd
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_INTERRUPT_CONTROLLER_OPENISA_INTMUX_H_
#define ZEPHYR_INCLUDE_DT_BINDINGS_INTERRUPT_CONTROLLER_OPENISA_INTMUX_H_
/*
* Symbols for intmux channels, for DT readability when using
* INTMUX_LEVEL2_IRQ().
*/
#define INTMUX_CH0 0
#define INTMUX_CH1 1
#define INTMUX_CH2 2
#define INTMUX_CH3 3
#define INTMUX_CH4 4
#define INTMUX_CH5 5
#define INTMUX_CH6 6
#define INTMUX_CH7 7
/*
* Level 1 IRQ offsets for each INTMUX channel.
*/
#define INTMUX_CH0_IRQ 24
#define INTMUX_CH1_IRQ 25
#define INTMUX_CH2_IRQ 26
#define INTMUX_CH3_IRQ 27
#define INTMUX_CH4_IRQ 28
#define INTMUX_CH5_IRQ 29
#define INTMUX_CH6_IRQ 30
#define INTMUX_CH7_IRQ 31
/*
* Multi-level IRQ number for a INTMUX channel/line interrupt.
*
* See gen_isr_tables.py for details.
*/
#define INTMUX_LEVEL2_IRQ(channel, line) \
((((line) + 1) << 8) | ((channel) + INTMUX_CH0_IRQ))
#endif

View file

@ -10,8 +10,17 @@ config SOC
string
default "openisa_rv32m1"
# 32 from event unit + 32 * (1 + max enabled INTMUX channel)
config NUM_IRQS
int
default 288 if RV32M1_INTMUX_CHANNEL_7
default 256 if RV32M1_INTMUX_CHANNEL_6
default 224 if RV32M1_INTMUX_CHANNEL_5
default 192 if RV32M1_INTMUX_CHANNEL_4
default 160 if RV32M1_INTMUX_CHANNEL_3
default 128 if RV32M1_INTMUX_CHANNEL_2
default 96 if RV32M1_INTMUX_CHANNEL_1
default 64 if RV32M1_INTMUX_CHANNEL_0
default 32
config XIP
@ -73,4 +82,89 @@ config SYS_CLOCK_HW_CYCLES_PER_SEC
int
default 8000000 if SOC_OPENISA_RV32M1_RI5CY # SIRC at 8MHz
if MULTI_LEVEL_INTERRUPTS
config MAX_IRQ_PER_AGGREGATOR
int
default 32
config 2ND_LEVEL_INTERRUPTS
def_bool y
config 2ND_LVL_ISR_TBL_OFFSET
int
default 32
config NUM_2ND_LEVEL_AGGREGATORS
int
default 8 if RV32M1_INTMUX_CHANNEL_7
default 7 if RV32M1_INTMUX_CHANNEL_6
default 6 if RV32M1_INTMUX_CHANNEL_5
default 5 if RV32M1_INTMUX_CHANNEL_4
default 4 if RV32M1_INTMUX_CHANNEL_3
default 3 if RV32M1_INTMUX_CHANNEL_2
default 2 if RV32M1_INTMUX_CHANNEL_1
default 1 # just channel 0
config 2ND_LVL_INTR_00_OFFSET
int
default 24
config 2ND_LVL_INTR_01_OFFSET
int
default 25
config 2ND_LVL_INTR_02_OFFSET
int
default 26
config 2ND_LVL_INTR_03_OFFSET
int
default 27
config 2ND_LVL_INTR_04_OFFSET
int
default 28
config 2ND_LVL_INTR_05_OFFSET
int
default 29
config 2ND_LVL_INTR_06_OFFSET
int
default 30
config 2ND_LVL_INTR_07_OFFSET
int
default 31
config RV32M1_INTMUX
def_bool y
config RV32M1_INTMUX_CHANNEL_0
def_bool y
config RV32M1_INTMUX_CHANNEL_1
def_bool y
config RV32M1_INTMUX_CHANNEL_2
def_bool y
config RV32M1_INTMUX_CHANNEL_3
def_bool y
config RV32M1_INTMUX_CHANNEL_4
def_bool y
config RV32M1_INTMUX_CHANNEL_5
def_bool y
config RV32M1_INTMUX_CHANNEL_6
def_bool y
config RV32M1_INTMUX_CHANNEL_7
def_bool y
endif # MULTI_LEVEL_INTERRUPTS
endif # SOC_OPENISA_RV32M1_RISCV32

View file

@ -9,9 +9,21 @@
#include <device.h>
#include <init.h>
#include <fsl_clock.h>
#include <misc/util.h>
#if defined(CONFIG_MULTI_LEVEL_INTERRUPTS)
#include <errno.h>
#include <irq_nextlevel.h>
#endif
#define LOG_LEVEL CONFIG_SOC_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(soc);
#define SCG_LPFLL_DISABLE 0U
static struct device *dev_intmux;
/*
* Run-mode configuration for the fast internal reference clock (FIRC).
*/
@ -49,24 +61,75 @@ static const scg_lpfll_config_t rv32m1_lpfll_cfg = {
void _arch_irq_enable(unsigned int irq)
{
EVENT_UNIT->INTPTEN |= (1U << irq);
(void)(EVENT_UNIT->INTPTEN); /* Ensures write has finished. */
if (IS_ENABLED(CONFIG_MULTI_LEVEL_INTERRUPTS)) {
unsigned int level = rv32m1_irq_level(irq);
if (level == 1) {
EVENT_UNIT->INTPTEN |= BIT(rv32m1_level1_irq(irq));
/* Ensures write has finished: */
(void)(EVENT_UNIT->INTPTEN);
} else {
irq_enable_next_level(dev_intmux, irq);
}
} else {
EVENT_UNIT->INTPTEN |= BIT(rv32m1_level1_irq(irq));
(void)(EVENT_UNIT->INTPTEN);
}
}
void _arch_irq_disable(unsigned int irq)
{
EVENT_UNIT->INTPTEN &= ~(1U << irq);
(void)(EVENT_UNIT->INTPTEN); /* Ensures write has finished. */
if (IS_ENABLED(CONFIG_MULTI_LEVEL_INTERRUPTS)) {
unsigned int level = rv32m1_irq_level(irq);
if (level == 1) {
EVENT_UNIT->INTPTEN &= ~BIT(rv32m1_level1_irq(irq));
/* Ensures write has finished: */
(void)(EVENT_UNIT->INTPTEN);
} else {
irq_disable_next_level(dev_intmux, irq);
}
} else {
EVENT_UNIT->INTPTEN &= ~BIT(rv32m1_level1_irq(irq));
(void)(EVENT_UNIT->INTPTEN);
}
}
int _arch_irq_is_enabled(unsigned int irq)
{
return (EVENT_UNIT->INTPTEN & (1U << irq)) != 0;
if (IS_ENABLED(CONFIG_MULTI_LEVEL_INTERRUPTS)) {
unsigned int level = rv32m1_irq_level(irq);
if (level == 1) {
return (EVENT_UNIT->INTPTEN &
BIT(rv32m1_level1_irq(irq))) != 0;
} else {
u32_t channel, line, ier;
/*
* Here we break the abstraction and look
* directly at the INTMUX registers. We can't
* use the irq_nextlevel.h API, as that only
* tells us whether some IRQ at the next level
* is enabled or not.
*/
channel = rv32m1_intmux_channel(irq);
line = rv32m1_intmux_line(irq);
ier = INTMUX->CHANNEL[channel].CHn_IER_31_0 & BIT(line);
return ier != 0;
}
} else {
return (EVENT_UNIT->INTPTEN & BIT(rv32m1_level1_irq(irq))) != 0;
}
}
/*
* SoC-level interrupt initialization. Clear any pending interrupts or
* events.
* events, and find the INTMUX device if necessary.
*
* This gets called as almost the first thing _Cstart() does, so it
* will happen before any calls to the _arch_irq_xxx() routines above.
*/
void soc_interrupt_init(void)
{
@ -74,6 +137,11 @@ void soc_interrupt_init(void)
(void)(EVENT_UNIT->INTPTPENDCLEAR); /* Ensures write has finished. */
EVENT_UNIT->EVTPENDCLEAR = 0xFFFFFFFF;
(void)(EVENT_UNIT->EVTPENDCLEAR); /* Ensures write has finished. */
if (IS_ENABLED(CONFIG_MULTI_LEVEL_INTERRUPTS)) {
dev_intmux = device_get_binding(INTMUX_LABEL);
__ASSERT(dev_intmux, "no INTMUX device found");
}
}
/**

View file

@ -10,6 +10,89 @@
#ifndef _ASMLANGUAGE
#include "fsl_device_registers.h"
#include <zephyr/types.h>
/*
* Helpers related to interrupt handling. This SoC has two levels of
* interrupts.
*
* Level 1 interrupts go straight to the SoC. Level 2 interrupts must
* go through one of the 8 channels in the the INTMUX
* peripheral. There are 32 level 1 interrupts, including 8 INTMUX
* interrupts. Each INTMUX interrupt can mux at most
* CONFIG_MAX_IRQ_PER_AGGREGATOR (which happens to be 32) interrupts
* to its level 1 interrupt.
*
* See gen_isr_tables.py for details on the Zephyr multi-level IRQ
* number encoding, which determines how these helpers work.
*/
/**
* @brief Get an IRQ's level
* @param irq The IRQ number in the Zephyr irq.h numbering system
* @return IRQ level, either 1 or 2
*/
static inline unsigned int rv32m1_irq_level(unsigned int irq)
{
return ((irq >> 8) & 0xff) == 0 ? 1 : 2;
}
/**
* @brief Level 1 interrupt line associated with an IRQ
*
* Results are undefined if rv32m1_irq_level(irq) is not 1.
*
* @param The IRQ number in the Zephyr <irq.h> numbering system
* @return Level 1 (i.e. event unit) IRQ number associated with irq
*/
static inline u32_t rv32m1_level1_irq(unsigned int irq)
{
/*
* There's no need to do any math; the precondition is that
* it's a level 1 IRQ.
*/
return irq;
}
/**
* @brief INTMUX channel (i.e. level 2 aggregator number) for an IRQ
*
* Results are undefined if rv32m1_irq_level(irq) is not 2.
*
* @param irq The IRQ number whose INTMUX channel / level 2 aggregator
* to get, in the Zephyr <irq.h> numbering system
* @return INTMUX channel number associated with the IRQ
*/
static inline u32_t rv32m1_intmux_channel(unsigned int irq)
{
/*
* Here we make use of these facts:
*
* - the INTMUX output IRQ numbers are arranged consecutively
* by channel in the event unit IRQ numbering assignment,
* starting from channel 0.
*
* - CONFIG_2ND_LVL_INTR_00_OFFSET is defined to
* be the offset of the first level 2 aggregator in the parent
* interrupt controller's IRQ numbers, i.e. channel 0's
* IRQ number in the event unit.
*/
return (irq & 0xff) - CONFIG_2ND_LVL_INTR_00_OFFSET;
}
/**
* @brief INTMUX interrupt ID number for an IRQ
*
* Results are undefined if rv32m1_irq_level(irq) is not 2.
*
* @param The IRQ number whose INTMUX interrupt ID to get, in the Zephyr
* <irq.h> numbering system
* @return The INTMUX interrupt ID, in the inclusive range 0 to 31
*/
static inline u32_t rv32m1_intmux_line(unsigned int irq)
{
return ((irq >> 8) & 0xff) - 1;
}
void soc_interrupt_init(void);