blob: e059c6c2bfc609bf49262237303d3bcb8b6e926d [file] [log] [blame]
/*
* Copyright (c) 2017, Intel Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Intel Corporation nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* Author: Keyon Jie <yang.jie@linux.intel.com>
* Liam Girdwood <liam.r.girdwood@linux.intel.com>
*
*/
#include <sof/interrupt.h>
#include <sof/interrupt-map.h>
#include <sof/alloc.h>
#include <arch/interrupt.h>
#include <platform/interrupt.h>
#include <stdint.h>
#include <stdlib.h>
static int irq_register_child(struct irq_desc *parent, int irq,
void (*handler)(void *arg), void *arg);
static void irq_unregister_child(struct irq_desc *parent, int irq);
static uint32_t irq_enable_child(struct irq_desc *parent, int irq);
static uint32_t irq_disable_child(struct irq_desc *parent, int irq);
static int irq_register_child(struct irq_desc *parent, int irq,
void (*handler)(void *arg), void *arg)
{
int ret = 0;
struct irq_desc *child;
if (parent == NULL)
return -EINVAL;
spin_lock(&parent->lock);
/* init child */
child = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM,
sizeof(struct irq_desc));
if (!child) {
ret = -ENOMEM;
goto finish;
}
child->enabled_count = 0;
child->handler = handler;
child->handler_arg = arg;
child->id = SOF_IRQ_ID(irq);
list_item_append(&child->irq_list, &parent->child[SOF_IRQ_BIT(irq)]);
/* do we need to register parent ? */
if (parent->num_children == 0) {
ret = arch_interrupt_register(parent->irq,
parent->handler, parent);
}
/* increment number of children */
parent->num_children++;
finish:
spin_unlock(&parent->lock);
return ret;
}
static void irq_unregister_child(struct irq_desc *parent, int irq)
{
spin_lock(&parent->lock);
struct irq_desc *child;
struct list_item *clist;
/* does child already exist ? */
if (list_is_empty(&parent->child[SOF_IRQ_BIT(irq)]))
goto finish;
list_for_item(clist, &parent->child[SOF_IRQ_BIT(irq)]) {
child = container_of(clist, struct irq_desc, irq_list);
if (SOF_IRQ_ID(irq) == child->id) {
list_item_del(&child->irq_list);
rfree(child);
parent->num_children--;
}
}
/*
* unregister the root interrupt if the this l2 is
* the last registered one.
*/
if (parent->num_children == 0)
arch_interrupt_unregister(parent->irq);
finish:
spin_unlock(&parent->lock);
}
static uint32_t irq_enable_child(struct irq_desc *parent, int irq)
{
struct irq_desc *child;
struct list_item *clist;
spin_lock(&parent->lock);
/* enable the parent interrupt */
if (parent->enabled_count == 0)
arch_interrupt_enable_mask(1 << SOF_IRQ_NUMBER(irq));
list_for_item(clist, &parent->child[SOF_IRQ_BIT(irq)]) {
child = container_of(clist, struct irq_desc, irq_list);
if ((SOF_IRQ_ID(irq) == child->id) &&
!child->enabled_count) {
child->enabled_count = 1;
parent->enabled_count++;
/* enable the child interrupt */
platform_interrupt_unmask(irq, 0);
}
}
spin_unlock(&parent->lock);
return 0;
}
static uint32_t irq_disable_child(struct irq_desc *parent, int irq)
{
struct irq_desc *child;
struct list_item *clist;
spin_lock(&parent->lock);
list_for_item(clist, &parent->child[SOF_IRQ_BIT(irq)]) {
child = container_of(clist, struct irq_desc, irq_list);
if ((SOF_IRQ_ID(irq) == child->id) &&
child->enabled_count) {
child->enabled_count = 0;
parent->enabled_count--;
}
}
if (parent->enabled_count == 0) {
/* disable the child interrupt */
platform_interrupt_mask(irq, 0);
arch_interrupt_disable_mask(1 << SOF_IRQ_NUMBER(irq));
}
spin_unlock(&parent->lock);
return 0;
}
int interrupt_register(uint32_t irq,
void (*handler)(void *arg), void *arg)
{
struct irq_desc *parent;
/* no parent means we are registering DSP internal IRQ */
parent = platform_irq_get_parent(irq);
if (parent == NULL)
return arch_interrupt_register(irq, handler, arg);
else
return irq_register_child(parent, irq, handler, arg);
}
void interrupt_unregister(uint32_t irq)
{
struct irq_desc *parent;
/* no parent means we are unregistering DSP internal IRQ */
parent = platform_irq_get_parent(irq);
if (parent == NULL)
arch_interrupt_unregister(irq);
else
irq_unregister_child(parent, irq);
}
uint32_t interrupt_enable(uint32_t irq)
{
struct irq_desc *parent;
/* no parent means we are enabling DSP internal IRQ */
parent = platform_irq_get_parent(irq);
if (parent == NULL)
return arch_interrupt_enable_mask(1 << irq);
else
return irq_enable_child(parent, irq);
}
uint32_t interrupt_disable(uint32_t irq)
{
struct irq_desc *parent;
/* no parent means we are disabling DSP internal IRQ */
parent = platform_irq_get_parent(irq);
if (parent == NULL)
return arch_interrupt_disable_mask(1 << irq);
else
return irq_disable_child(parent, irq);
}