mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-02 15:10:06 +00:00
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>
196 lines
5.1 KiB
C
196 lines
5.1 KiB
C
/*
|
|
* 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);
|