blob: d6e5a69c59acab89f3b1737494e7397e45b47ea4 [file] [log] [blame]
/*
* Nexus interrupt(s) resolution API
*
* Copyright (C) 2015-2016, Broadcom
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* A copy of the GPL is available at
* http://www.broadcom.com/licenses/GPLv2.php or from the Free Software
* Foundation at https://www.gnu.org/licenses/ .
*/
#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/brcmstb/irq_api.h>
#define BUILD_IRQ_NAME(_name) \
case brcmstb_l2_irq_##_name: \
return __stringify(_name); \
static const char *brcmstb_l2_irq_to_name(brcmstb_l2_irq irq)
{
switch (irq) {
BUILD_IRQ_NAME(gio);
BUILD_IRQ_NAME(gio_aon);
BUILD_IRQ_NAME(iica);
BUILD_IRQ_NAME(iicb);
BUILD_IRQ_NAME(iicc);
BUILD_IRQ_NAME(iicd);
BUILD_IRQ_NAME(iice);
BUILD_IRQ_NAME(iicf);
BUILD_IRQ_NAME(iicg);
BUILD_IRQ_NAME(irb);
BUILD_IRQ_NAME(icap);
BUILD_IRQ_NAME(kbd1);
BUILD_IRQ_NAME(kbd2);
BUILD_IRQ_NAME(kbd3);
BUILD_IRQ_NAME(ldk);
BUILD_IRQ_NAME(spi);
BUILD_IRQ_NAME(ua);
BUILD_IRQ_NAME(ub);
BUILD_IRQ_NAME(uc);
default:
return NULL;
}
return NULL;
}
static int brcmstb_resolve_l2_irq(struct device_node *np, brcmstb_l2_irq irq)
{
int i, num_elems, ret;
const char *int_name, *irq_name;
u32 hwirq;
irq_name = brcmstb_l2_irq_to_name(irq);
if (!irq_name)
return -EINVAL;
/* Special case for AON interrupt names, search the other node for the
* same name
*/
if (strstr(irq_name, "aon") && !strstr(np->name, "aon"))
return -EAGAIN;
pr_debug("%s: resolving on node %s\n", __func__, np->full_name);
num_elems = of_property_count_strings(np, "interrupt-names");
if (num_elems <= 0) {
pr_err("Unable to find an interrupt-names property, check DT\n");
return -EINVAL;
}
for (i = 0; i < num_elems; i++) {
ret = of_property_read_u32_index(np, "interrupts", i,
&hwirq);
if (ret < 0)
return ret;
ret = of_property_read_string_index(np,
"interrupt-names",
i, &int_name);
if (ret < 0)
return ret;
/* We may be requesting to match, eg: "gio" with "gio_aon" */
if (!strncasecmp(int_name, irq_name, strlen(int_name)))
break;
}
if (i == num_elems) {
pr_debug("%s: exceeded search for %s\n", __func__, irq_name);
return -ENOENT;
}
pr_debug("%s IRQ name: %s Node: %s @%d mapped to: %d\n", __func__,
irq_name, np->full_name, i, hwirq);
ret = of_irq_get(np, i);
if (ret < 0) {
#ifndef CONFIG_BCM7120_L2_IRQ
if (ret == -EPROBE_DEFER)
pr_warn("Could't get IRQ. Enable CONFIG_BCM7120_L2_IRQ"
" if you want \"%s\" interrupt support\n",
irq_name);
#endif
}
return ret;
}
static const char *nexus_irq0_node_names[] = { "nexus-irq0", "nexus-irq0_aon",
"nexus-upg_main_irq",
"nexus-upg_main_aon_irq",
"nexus-upg_bsc_irq",
"nexus-upg_bsc_aon_irq",
"nexus-upg_spi_aon_irq" };
int brcmstb_get_l2_irq_id(brcmstb_l2_irq irq)
{
struct device_node *np;
int ret = -ENOENT;
int i = 0;
for (i = 0; i < ARRAY_SIZE(nexus_irq0_node_names); i++) {
np = of_find_node_by_name(NULL, nexus_irq0_node_names[i]);
if (!np)
continue;
ret = brcmstb_resolve_l2_irq(np, irq);
if (ret < 0)
continue;
if (ret == 0)
return -EBUSY;
if (ret > 0)
break;
}
return ret;
}
EXPORT_SYMBOL(brcmstb_get_l2_irq_id);