diff --git a/drivers/interrupt_controller/CMakeLists.txt b/drivers/interrupt_controller/CMakeLists.txt index 82fb5e7941e..b8edafff5ff 100644 --- a/drivers/interrupt_controller/CMakeLists.txt +++ b/drivers/interrupt_controller/CMakeLists.txt @@ -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) diff --git a/drivers/interrupt_controller/Kconfig b/drivers/interrupt_controller/Kconfig index a8daf6213ca..391493e3846 100644 --- a/drivers/interrupt_controller/Kconfig +++ b/drivers/interrupt_controller/Kconfig @@ -162,4 +162,6 @@ source "drivers/interrupt_controller/Kconfig.multilevel" source "drivers/interrupt_controller/Kconfig.s1000" +source "drivers/interrupt_controller/Kconfig.rv32m1" + endmenu diff --git a/drivers/interrupt_controller/Kconfig.rv32m1 b/drivers/interrupt_controller/Kconfig.rv32m1 new file mode 100644 index 00000000000..e0803165d2a --- /dev/null +++ b/drivers/interrupt_controller/Kconfig.rv32m1 @@ -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 diff --git a/drivers/interrupt_controller/rv32m1_intmux.c b/drivers/interrupt_controller/rv32m1_intmux.c new file mode 100644 index 00000000000..68ac917dadc --- /dev/null +++ b/drivers/interrupt_controller/rv32m1_intmux.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +/* + * 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); + +/* + * 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); diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig index 078a07460f4..2bc5d565a06 100644 --- a/drivers/timer/Kconfig +++ b/drivers/timer/Kconfig @@ -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 diff --git a/drivers/timer/rv32m1_lptmr_timer.c b/drivers/timer/rv32m1_lptmr_timer.c index 3eb38aac100..022a651f5c8 100644 --- a/drivers/timer/rv32m1_lptmr_timer.c +++ b/drivers/timer/rv32m1_lptmr_timer.c @@ -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) { diff --git a/dts/bindings/interrupt-controller/openisa,rv32m1-event-unit.yaml b/dts/bindings/interrupt-controller/openisa,rv32m1-event-unit.yaml new file mode 100644 index 00000000000..7bdb59f7be0 --- /dev/null +++ b/dts/bindings/interrupt-controller/openisa,rv32m1-event-unit.yaml @@ -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 +... diff --git a/dts/bindings/interrupt-controller/openisa,rv32m1-intmux.yaml b/dts/bindings/interrupt-controller/openisa,rv32m1-intmux.yaml new file mode 100644 index 00000000000..932f5c0402b --- /dev/null +++ b/dts/bindings/interrupt-controller/openisa,rv32m1-intmux.yaml @@ -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 +... diff --git a/dts/bindings/timer/openisa,rv32m1-lptmr.yaml b/dts/bindings/timer/openisa,rv32m1-lptmr.yaml new file mode 100644 index 00000000000..927f3bf280e --- /dev/null +++ b/dts/bindings/timer/openisa,rv32m1-lptmr.yaml @@ -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 + +... diff --git a/dts/riscv32/rv32m1_ri5cy.dtsi b/dts/riscv32/rv32m1_ri5cy.dtsi index e17697f0317..67e67fbf4de 100644 --- a/dts/riscv32/rv32m1_ri5cy.dtsi +++ b/dts/riscv32/rv32m1_ri5cy.dtsi @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include + / { #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 = , , , , , , , ; + 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 = ; + label = "LPTMR_0"; + }; + + lptmr1: timer@40033000 { + compatible = "openisa,rv32m1-lptmr"; + reg = <0x40033000 0x10>; + interrupt-parent = <&intmux>; + interrupts = ; + label = "LPTMR_1"; + }; + + lptmr2: timer@4102b000 { + compatible = "openisa,rv32m1-lptmr"; + reg = <0x4102b000 0x10>; + interrupt-parent = <&intmux>; + interrupts = ; + label = "LPTMR_2"; + }; }; }; diff --git a/include/dt-bindings/interrupt-controller/openisa-intmux.h b/include/dt-bindings/interrupt-controller/openisa-intmux.h new file mode 100644 index 00000000000..7ac9957c923 --- /dev/null +++ b/include/dt-bindings/interrupt-controller/openisa-intmux.h @@ -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 diff --git a/soc/riscv32/openisa_rv32m1/Kconfig.defconfig b/soc/riscv32/openisa_rv32m1/Kconfig.defconfig index 4d4d2d80df4..4529e00f040 100644 --- a/soc/riscv32/openisa_rv32m1/Kconfig.defconfig +++ b/soc/riscv32/openisa_rv32m1/Kconfig.defconfig @@ -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 diff --git a/soc/riscv32/openisa_rv32m1/soc.c b/soc/riscv32/openisa_rv32m1/soc.c index dbf60cb6ed1..7fbac461b82 100644 --- a/soc/riscv32/openisa_rv32m1/soc.c +++ b/soc/riscv32/openisa_rv32m1/soc.c @@ -9,9 +9,21 @@ #include #include #include +#include + +#if defined(CONFIG_MULTI_LEVEL_INTERRUPTS) +#include +#include +#endif + +#define LOG_LEVEL CONFIG_SOC_LOG_LEVEL +#include +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"); + } } /** diff --git a/soc/riscv32/openisa_rv32m1/soc.h b/soc/riscv32/openisa_rv32m1/soc.h index cc017c014b1..2604fd0f763 100644 --- a/soc/riscv32/openisa_rv32m1/soc.h +++ b/soc/riscv32/openisa_rv32m1/soc.h @@ -10,6 +10,89 @@ #ifndef _ASMLANGUAGE #include "fsl_device_registers.h" +#include + +/* + * 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 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 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 + * 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);