zephyr/drivers/interrupt_controller/rv32m1_intmux.c
Marti Bolivar 58d8afb476 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>
2019-01-25 11:59:46 -05:00

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);