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:
parent
521f4778a1
commit
58d8afb476
14 changed files with 758 additions and 54 deletions
|
@ -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)
|
||||
|
|
|
@ -162,4 +162,6 @@ source "drivers/interrupt_controller/Kconfig.multilevel"
|
|||
|
||||
source "drivers/interrupt_controller/Kconfig.s1000"
|
||||
|
||||
source "drivers/interrupt_controller/Kconfig.rv32m1"
|
||||
|
||||
endmenu
|
||||
|
|
66
drivers/interrupt_controller/Kconfig.rv32m1
Normal file
66
drivers/interrupt_controller/Kconfig.rv32m1
Normal 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
|
195
drivers/interrupt_controller/rv32m1_intmux.c
Normal file
195
drivers/interrupt_controller/rv32m1_intmux.c
Normal 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);
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
...
|
48
dts/bindings/interrupt-controller/openisa,rv32m1-intmux.yaml
Normal file
48
dts/bindings/interrupt-controller/openisa,rv32m1-intmux.yaml
Normal 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
|
||||
...
|
34
dts/bindings/timer/openisa,rv32m1-lptmr.yaml
Normal file
34
dts/bindings/timer/openisa,rv32m1-lptmr.yaml
Normal 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
|
||||
|
||||
...
|
|
@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
43
include/dt-bindings/interrupt-controller/openisa-intmux.h
Normal file
43
include/dt-bindings/interrupt-controller/openisa-intmux.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue