From 58d8afb476756b219960f0c3f19d1f807f5d0502 Mon Sep 17 00:00:00 2001 From: Marti Bolivar Date: Sun, 25 Nov 2018 02:41:38 -0700 Subject: [PATCH] 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 Signed-off-by: Mike Scott --- drivers/interrupt_controller/CMakeLists.txt | 1 + drivers/interrupt_controller/Kconfig | 2 + drivers/interrupt_controller/Kconfig.rv32m1 | 66 ++++++ drivers/interrupt_controller/rv32m1_intmux.c | 195 ++++++++++++++++++ drivers/timer/Kconfig | 1 + drivers/timer/rv32m1_lptmr_timer.c | 74 +++---- .../openisa,rv32m1-event-unit.yaml | 32 +++ .../openisa,rv32m1-intmux.yaml | 48 +++++ dts/bindings/timer/openisa,rv32m1-lptmr.yaml | 34 +++ dts/riscv32/rv32m1_ri5cy.dtsi | 59 ++++++ .../interrupt-controller/openisa-intmux.h | 43 ++++ soc/riscv32/openisa_rv32m1/Kconfig.defconfig | 94 +++++++++ soc/riscv32/openisa_rv32m1/soc.c | 80 ++++++- soc/riscv32/openisa_rv32m1/soc.h | 83 ++++++++ 14 files changed, 758 insertions(+), 54 deletions(-) create mode 100644 drivers/interrupt_controller/Kconfig.rv32m1 create mode 100644 drivers/interrupt_controller/rv32m1_intmux.c create mode 100644 dts/bindings/interrupt-controller/openisa,rv32m1-event-unit.yaml create mode 100644 dts/bindings/interrupt-controller/openisa,rv32m1-intmux.yaml create mode 100644 dts/bindings/timer/openisa,rv32m1-lptmr.yaml create mode 100644 include/dt-bindings/interrupt-controller/openisa-intmux.h 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);