zephyr/drivers/interrupt_controller/mvic.c

236 lines
6.2 KiB
C

/*
* Copyright (c) 2015 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* @brief Quark D2000 Interrupt Controller (MVIC)
*
* This module is based on the standard Local APIC and IO APIC source modules.
* This modules combines these modules into one source module that exports the
* same APIs defined by the Local APIC and IO APIC header modules. These
* routine have been adapted for the Quark D2000 Interrupt Controller which has
* a cutdown implementation of the Local APIC & IO APIC register sets.
*
* The MVIC (Quark D2000 Interrupt Controller) is configured by default
* to support 32 external interrupt lines.
* Unlike the traditional IA LAPIC/IOAPIC, the interrupt vectors in MVIC are fixed
* and not programmable.
* The larger the vector number, the higher the priority of the interrupt.
* Higher priority interrupts preempt lower priority interrupts.
* Lower priority interrupts do not preempt higher priority interrupts.
* The MVIC holds the lower priority interrupts pending until the interrupt
* service routine for the higher priority interrupt writes to the End of
* Interrupt (EOI) register.
* After an EOI write, the MVIC asserts the next highest pending interrupt.
*
* INCLUDE FILES: ioapic.h loapic.h
*
*/
/* includes */
#include <nanokernel.h>
#include <arch/cpu.h>
#include <misc/__assert.h>
#include <misc/util.h>
#include <init.h>
#include <arch/x86/irq_controller.h>
#include <inttypes.h>
static inline uint32_t compute_ioregsel(unsigned int irq)
{
unsigned int low_nibble;
unsigned int high_nibble;
__ASSERT(irq < MVIC_NUM_RTES, "invalid irq line %d", irq);
low_nibble = ((irq & MVIC_LOW_NIBBLE_MASK) << 0x1);
high_nibble = ((irq & MVIC_HIGH_NIBBLE_MASK) << 0x2);
return low_nibble | high_nibble;
}
/**
*
* @brief write to 32 bit MVIC IO APIC register
*
* @param irq INTIN number
* @param value value to be written
*
* @returns N/A
*/
static void _mvic_rte_set(unsigned int irq, uint32_t value)
{
int key; /* interrupt lock level */
uint32_t regsel;
__ASSERT(!(value & ~MVIC_IOWIN_SUPPORTED_BITS_MASK),
"invalid IRQ flags %" PRIx32 " for irq %d", value, irq);
regsel = compute_ioregsel(irq);
/* lock interrupts to ensure indirect addressing works "atomically" */
key = irq_lock();
sys_write32(regsel, MVIC_IOREGSEL);
sys_write32(value, MVIC_IOWIN);
irq_unlock(key);
}
/**
*
* @brief modify interrupt line register.
*
* @param irq INTIN number
* @param value value to be written
* @param mask of bits to be modified
*
* @returns N/A
*/
static void _mvic_rte_update(unsigned int irq, uint32_t value, uint32_t mask)
{
int key;
uint32_t regsel, old_value, updated_value;
__ASSERT(!(value & ~MVIC_IOWIN_SUPPORTED_BITS_MASK),
"invalid IRQ flags %" PRIx32 " for irq %d", value, irq);
regsel = compute_ioregsel(irq);
key = irq_lock();
sys_write32(regsel, MVIC_IOREGSEL);
old_value = sys_read32(MVIC_IOWIN);
updated_value = (old_value & ~mask) | (value & mask);
sys_write32(updated_value, MVIC_IOWIN);
irq_unlock(key);
}
/**
*
* @brief initialize the MVIC IO APIC and local APIC register sets.
*
* This routine initializes the Quark D2000 Interrupt Controller (MVIC).
* This routine replaces the standard Local APIC / IO APIC init routines.
*
* @returns: N/A
*/
static int _mvic_init(struct device *unused)
{
ARG_UNUSED(unused);
int i;
/* By default mask all interrupt lines */
for (i = 0; i < MVIC_NUM_RTES; i++) {
_mvic_rte_set(i, MVIC_IOWIN_MASK);
}
/* reset the task priority and timer initial count registers */
sys_write32(0, MVIC_TPR);
sys_write32(0, MVIC_ICR);
/* Initialize and mask the timer interrupt.
* Bits 0-3 program the interrupt line number we will use
* for the timer interrupt.
*/
__ASSERT(CONFIG_MVIC_TIMER_IRQ < 16,
"Bad irq line %d chosen for timer irq", CONFIG_MVIC_TIMER_IRQ);
sys_write32(MVIC_LVTTIMER_MASK | CONFIG_MVIC_TIMER_IRQ, MVIC_LVTTIMER);
/* discard a pending interrupt if any */
sys_write32(0, MVIC_EOI);
return 0;
}
SYS_INIT(_mvic_init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
void _arch_irq_enable(unsigned int irq)
{
if (irq == CONFIG_MVIC_TIMER_IRQ) {
sys_write32(sys_read32(MVIC_LVTTIMER) & ~MVIC_LVTTIMER_MASK,
MVIC_LVTTIMER);
} else {
_mvic_rte_update(irq, 0, MVIC_IOWIN_MASK);
}
}
void _arch_irq_disable(unsigned int irq)
{
if (irq == CONFIG_MVIC_TIMER_IRQ) {
sys_write32(sys_read32(MVIC_LVTTIMER) | MVIC_LVTTIMER_MASK,
MVIC_LVTTIMER);
} else {
_mvic_rte_update(irq, MVIC_IOWIN_MASK, MVIC_IOWIN_MASK);
}
}
void __irq_controller_irq_config(unsigned int vector, unsigned int irq,
uint32_t flags)
{
ARG_UNUSED(vector);
/* Vector argument always ignored. There are no triggering options
* for the timer, so nothing to do at all for that case. Other I/O
* interrupts need their triggering set
*/
if (irq != CONFIG_MVIC_TIMER_IRQ) {
_mvic_rte_set(irq, MVIC_IOWIN_MASK | flags);
} else {
__ASSERT(flags == 0,
"Timer interrupt cannot have triggering flags set");
}
}
/**
* @brief Find the currently executing interrupt vector, if any
*
* This routine finds the vector of the interrupt that is being processed.
* The ISR (In-Service Register) register contain the vectors of the interrupts
* in service. And the higher vector is the indentification of the interrupt
* being currently processed.
*
* MVIC ISR registers' offsets:
* --------------------
* | Offset | bits |
* --------------------
* | 0110H | 32:63 |
* --------------------
*
* @return The vector of the interrupt that is currently being processed, or
* -1 if this can't be determined
*/
int __irq_controller_isr_vector_get(void)
{
/* In-service register value */
int isr;
isr = sys_read32(MVIC_ISR);
if (unlikely(!isr)) {
return -1;
}
return 32 + (find_msb_set(isr) - 1);
}