/* * 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 #include #include #include #include #include #include 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); }